mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-24 09:38:32 +02:00
feat(client): add subscribe_with_options
This commit adds a new method, subscribe_with_options to komorebi-client. The first option introduced is to tell komorebi to only send notifications when the window manager state has been changed during the processing of an event. This new subscription option is now used with komorebi-bar to improve rendering and update performance.
This commit is contained in:
@@ -22,6 +22,7 @@ use font_loader::system_fonts;
|
|||||||
use hotwatch::EventKind;
|
use hotwatch::EventKind;
|
||||||
use hotwatch::Hotwatch;
|
use hotwatch::Hotwatch;
|
||||||
use komorebi_client::SocketMessage;
|
use komorebi_client::SocketMessage;
|
||||||
|
use komorebi_client::SubscribeOptions;
|
||||||
use schemars::gen::SchemaSettings;
|
use schemars::gen::SchemaSettings;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
@@ -328,7 +329,9 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
|
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
|
||||||
|
|
||||||
let listener = komorebi_client::subscribe(&subscriber_name)
|
let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
|
||||||
|
filter_state_changes: true,
|
||||||
|
})
|
||||||
.expect("could not subscribe to komorebi notifications");
|
.expect("could not subscribe to komorebi notifications");
|
||||||
|
|
||||||
tracing::info!("subscribed to komorebi notifications: \"{}\"", subscriber_name);
|
tracing::info!("subscribed to komorebi notifications: \"{}\"", subscriber_name);
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ pub use komorebi::RuleDebug;
|
|||||||
pub use komorebi::StackbarConfig;
|
pub use komorebi::StackbarConfig;
|
||||||
pub use komorebi::State;
|
pub use komorebi::State;
|
||||||
pub use komorebi::StaticConfig;
|
pub use komorebi::StaticConfig;
|
||||||
|
pub use komorebi::SubscribeOptions;
|
||||||
pub use komorebi::TabsConfig;
|
pub use komorebi::TabsConfig;
|
||||||
|
|
||||||
use komorebi::DATA_DIR;
|
use komorebi::DATA_DIR;
|
||||||
@@ -96,3 +97,29 @@ pub fn subscribe(name: &str) -> std::io::Result<UnixListener> {
|
|||||||
|
|
||||||
Ok(listener)
|
Ok(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe_with_options(
|
||||||
|
name: &str,
|
||||||
|
options: SubscribeOptions,
|
||||||
|
) -> std::io::Result<UnixListener> {
|
||||||
|
let socket = DATA_DIR.join(name);
|
||||||
|
|
||||||
|
match std::fs::remove_file(&socket) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::NotFound => {}
|
||||||
|
_ => {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let listener = UnixListener::bind(&socket)?;
|
||||||
|
|
||||||
|
send_message(&SocketMessage::AddSubscriberSocketWithOptions(
|
||||||
|
name.to_string(),
|
||||||
|
options,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok(listener)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use serde::Serialize;
|
|||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters, JsonSchema)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
#[getset(get = "pub")]
|
#[getset(get = "pub")]
|
||||||
id: String,
|
id: String,
|
||||||
@@ -27,12 +27,6 @@ impl Default for Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Container {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.id == other.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
pub fn hide(&self, omit: Option<isize>) {
|
pub fn hide(&self, omit: Option<isize>) {
|
||||||
for window in self.windows().iter().rev() {
|
for window in self.windows().iter().rev() {
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ pub enum SocketMessage {
|
|||||||
RemoveTitleBar(ApplicationIdentifier, String),
|
RemoveTitleBar(ApplicationIdentifier, String),
|
||||||
ToggleTitleBars,
|
ToggleTitleBars,
|
||||||
AddSubscriberSocket(String),
|
AddSubscriberSocket(String),
|
||||||
|
AddSubscriberSocketWithOptions(String, SubscribeOptions),
|
||||||
RemoveSubscriberSocket(String),
|
RemoveSubscriberSocket(String),
|
||||||
AddSubscriberPipe(String),
|
AddSubscriberPipe(String),
|
||||||
RemoveSubscriberPipe(String),
|
RemoveSubscriberPipe(String),
|
||||||
@@ -221,6 +222,12 @@ impl FromStr for SocketMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct SubscribeOptions {
|
||||||
|
/// Only emit notifications when the window manager state has changed
|
||||||
|
pub filter_state_changes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema)]
|
||||||
pub enum StackbarMode {
|
pub enum StackbarMode {
|
||||||
Always,
|
Always,
|
||||||
@@ -336,7 +343,16 @@ pub enum ApplicationIdentifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Display,
|
||||||
|
EnumString,
|
||||||
|
ValueEnum,
|
||||||
|
JsonSchema,
|
||||||
)]
|
)]
|
||||||
pub enum FocusFollowsMouseImplementation {
|
pub enum FocusFollowsMouseImplementation {
|
||||||
/// A custom FFM implementation (slightly more CPU-intensive)
|
/// A custom FFM implementation (slightly more CPU-intensive)
|
||||||
@@ -377,7 +393,16 @@ pub enum WindowContainerBehaviour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Display,
|
||||||
|
EnumString,
|
||||||
|
ValueEnum,
|
||||||
|
JsonSchema,
|
||||||
)]
|
)]
|
||||||
pub enum MoveBehaviour {
|
pub enum MoveBehaviour {
|
||||||
/// Swap the window container with the window container at the edge of the adjacent monitor
|
/// Swap the window container with the window container at the edge of the adjacent monitor
|
||||||
@@ -411,7 +436,16 @@ pub enum HidingBehaviour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Display,
|
||||||
|
EnumString,
|
||||||
|
ValueEnum,
|
||||||
|
JsonSchema,
|
||||||
)]
|
)]
|
||||||
pub enum OperationBehaviour {
|
pub enum OperationBehaviour {
|
||||||
/// Process komorebic commands on temporarily unmanaged/floated windows
|
/// Process komorebic commands on temporarily unmanaged/floated windows
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ lazy_static! {
|
|||||||
Arc::new(Mutex::new(HashMap::new()));
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =
|
pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
pub static ref SUBSCRIPTION_SOCKET_OPTIONS: Arc<Mutex<HashMap<String, SubscribeOptions>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
|
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
|
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
|
||||||
@@ -297,18 +299,34 @@ pub struct Notification {
|
|||||||
pub state: State,
|
pub state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify_subscribers(notification: &str) -> Result<()> {
|
pub fn notify_subscribers(notification: Notification, state_has_been_modified: bool) -> Result<()> {
|
||||||
|
let is_subscription_event = matches!(
|
||||||
|
notification.event,
|
||||||
|
NotificationEvent::Socket(SocketMessage::AddSubscriberSocket(_))
|
||||||
|
| NotificationEvent::Socket(SocketMessage::AddSubscriberSocketWithOptions(_, _))
|
||||||
|
);
|
||||||
|
|
||||||
|
let notification = &serde_json::to_string(¬ification)?;
|
||||||
let mut stale_sockets = vec![];
|
let mut stale_sockets = vec![];
|
||||||
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
||||||
|
let options = SUBSCRIPTION_SOCKET_OPTIONS.lock();
|
||||||
|
|
||||||
for (socket, path) in &mut *sockets {
|
for (socket, path) in &mut *sockets {
|
||||||
match UnixStream::connect(path) {
|
let apply_state_filter = (*options)
|
||||||
Ok(mut stream) => {
|
.get(socket)
|
||||||
tracing::debug!("pushed notification to subscriber: {socket}");
|
.copied()
|
||||||
stream.write_all(notification.as_bytes())?;
|
.unwrap_or_default()
|
||||||
}
|
.filter_state_changes;
|
||||||
Err(_) => {
|
|
||||||
stale_sockets.push(socket.clone());
|
if !apply_state_filter || state_has_been_modified || is_subscription_event {
|
||||||
|
match UnixStream::connect(path) {
|
||||||
|
Ok(mut stream) => {
|
||||||
|
tracing::debug!("pushed notification to subscriber: {socket}");
|
||||||
|
stream.write_all(notification.as_bytes())?;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
stale_sockets.push(socket.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ use crate::winevent_listener;
|
|||||||
use crate::GlobalState;
|
use crate::GlobalState;
|
||||||
use crate::Notification;
|
use crate::Notification;
|
||||||
use crate::NotificationEvent;
|
use crate::NotificationEvent;
|
||||||
|
use crate::State;
|
||||||
use crate::ANIMATION_DURATION;
|
use crate::ANIMATION_DURATION;
|
||||||
use crate::ANIMATION_ENABLED;
|
use crate::ANIMATION_ENABLED;
|
||||||
use crate::ANIMATION_FPS;
|
use crate::ANIMATION_FPS;
|
||||||
@@ -79,6 +80,7 @@ use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
|||||||
use crate::REMOVE_TITLEBARS;
|
use crate::REMOVE_TITLEBARS;
|
||||||
use crate::SUBSCRIPTION_PIPES;
|
use crate::SUBSCRIPTION_PIPES;
|
||||||
use crate::SUBSCRIPTION_SOCKETS;
|
use crate::SUBSCRIPTION_SOCKETS;
|
||||||
|
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
|
||||||
use crate::TCP_CONNECTIONS;
|
use crate::TCP_CONNECTIONS;
|
||||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||||
use crate::WINDOWS_11;
|
use crate::WINDOWS_11;
|
||||||
@@ -187,6 +189,10 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::useless_asref)]
|
||||||
|
// We don't have From implemented for &mut WindowManager
|
||||||
|
let initial_state = State::from(self.as_ref());
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
|
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
|
||||||
if let Some(monitor) = self.focused_monitor_mut() {
|
if let Some(monitor) = self.focused_monitor_mut() {
|
||||||
@@ -1319,6 +1325,14 @@ impl WindowManager {
|
|||||||
let socket_path = DATA_DIR.join(socket);
|
let socket_path = DATA_DIR.join(socket);
|
||||||
sockets.insert(socket.clone(), socket_path);
|
sockets.insert(socket.clone(), socket_path);
|
||||||
}
|
}
|
||||||
|
SocketMessage::AddSubscriberSocketWithOptions(ref socket, options) => {
|
||||||
|
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
||||||
|
let socket_path = DATA_DIR.join(socket);
|
||||||
|
sockets.insert(socket.clone(), socket_path);
|
||||||
|
|
||||||
|
let mut socket_options = SUBSCRIPTION_SOCKET_OPTIONS.lock();
|
||||||
|
socket_options.insert(socket.clone(), options);
|
||||||
|
}
|
||||||
SocketMessage::RemoveSubscriberSocket(ref socket) => {
|
SocketMessage::RemoveSubscriberSocket(ref socket) => {
|
||||||
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
||||||
sockets.remove(socket);
|
sockets.remove(socket);
|
||||||
@@ -1574,12 +1588,14 @@ impl WindowManager {
|
|||||||
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
|
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
let notification = Notification {
|
notify_subscribers(
|
||||||
event: NotificationEvent::Socket(message.clone()),
|
Notification {
|
||||||
state: self.as_ref().into(),
|
event: NotificationEvent::Socket(message.clone()),
|
||||||
};
|
state: self.as_ref().into(),
|
||||||
|
},
|
||||||
|
initial_state.has_been_modified(self.as_ref()),
|
||||||
|
)?;
|
||||||
|
|
||||||
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
|
||||||
border_manager::send_notification(None);
|
border_manager::send_notification(None);
|
||||||
transparency_manager::send_notification();
|
transparency_manager::send_notification();
|
||||||
stackbar_manager::send_notification();
|
stackbar_manager::send_notification();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use crate::workspace_reconciliator::ALT_TAB_HWND;
|
|||||||
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
|
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
|
||||||
use crate::Notification;
|
use crate::Notification;
|
||||||
use crate::NotificationEvent;
|
use crate::NotificationEvent;
|
||||||
|
use crate::State;
|
||||||
use crate::DATA_DIR;
|
use crate::DATA_DIR;
|
||||||
use crate::FLOATING_APPLICATIONS;
|
use crate::FLOATING_APPLICATIONS;
|
||||||
use crate::HIDDEN_HWNDS;
|
use crate::HIDDEN_HWNDS;
|
||||||
@@ -122,6 +123,10 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::useless_asref)]
|
||||||
|
// We don't have From implemented for &mut WindowManager
|
||||||
|
let initial_state = State::from(self.as_ref());
|
||||||
|
|
||||||
// Make sure we have the most recently focused monitor from any event
|
// Make sure we have the most recently focused monitor from any event
|
||||||
match event {
|
match event {
|
||||||
WindowManagerEvent::FocusChange(_, window)
|
WindowManagerEvent::FocusChange(_, window)
|
||||||
@@ -670,12 +675,14 @@ impl WindowManager {
|
|||||||
|
|
||||||
serde_json::to_writer_pretty(&file, &known_hwnds)?;
|
serde_json::to_writer_pretty(&file, &known_hwnds)?;
|
||||||
|
|
||||||
let notification = Notification {
|
notify_subscribers(
|
||||||
event: NotificationEvent::WindowManager(event),
|
Notification {
|
||||||
state: self.as_ref().into(),
|
event: NotificationEvent::WindowManager(event),
|
||||||
};
|
state: self.as_ref().into(),
|
||||||
|
},
|
||||||
|
initial_state.has_been_modified(self.as_ref()),
|
||||||
|
)?;
|
||||||
|
|
||||||
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
|
||||||
border_manager::send_notification(Some(event.hwnd()));
|
border_manager::send_notification(Some(event.hwnd()));
|
||||||
transparency_manager::send_notification();
|
transparency_manager::send_notification();
|
||||||
stackbar_manager::send_notification();
|
stackbar_manager::send_notification();
|
||||||
|
|||||||
@@ -122,6 +122,54 @@ pub struct State {
|
|||||||
pub has_pending_raise_op: bool,
|
pub has_pending_raise_op: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn has_been_modified(&self, wm: &WindowManager) -> bool {
|
||||||
|
let new = Self::from(wm);
|
||||||
|
|
||||||
|
if self.monitors != new.monitors {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_paused != new.is_paused {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.new_window_behaviour != new.new_window_behaviour {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.float_override != new.float_override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.cross_monitor_move_behaviour != new.cross_monitor_move_behaviour {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.unmanaged_window_operation_behaviour != new.unmanaged_window_operation_behaviour {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.work_area_offset != new.work_area_offset {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.focus_follows_mouse != new.focus_follows_mouse {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.mouse_follows_focus != new.mouse_follows_focus {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.has_pending_raise_op != new.has_pending_raise_op {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct GlobalState {
|
pub struct GlobalState {
|
||||||
|
|||||||
Reference in New Issue
Block a user