From 5da72e10df1598c3f5a6af51f2f4ff44f6fed7ff Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Fri, 11 Oct 2024 10:32:01 -0700 Subject: [PATCH] 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. --- komorebi-bar/src/main.rs | 5 +++- komorebi-client/src/lib.rs | 27 +++++++++++++++++++ komorebi/src/container.rs | 8 +----- komorebi/src/core/mod.rs | 40 ++++++++++++++++++++++++--- komorebi/src/lib.rs | 34 +++++++++++++++++------ komorebi/src/process_command.rs | 26 ++++++++++++++---- komorebi/src/process_event.rs | 17 ++++++++---- komorebi/src/window_manager.rs | 48 +++++++++++++++++++++++++++++++++ 8 files changed, 176 insertions(+), 29 deletions(-) diff --git a/komorebi-bar/src/main.rs b/komorebi-bar/src/main.rs index da2b87ea..f32c5937 100644 --- a/komorebi-bar/src/main.rs +++ b/komorebi-bar/src/main.rs @@ -22,6 +22,7 @@ use font_loader::system_fonts; use hotwatch::EventKind; use hotwatch::Hotwatch; use komorebi_client::SocketMessage; +use komorebi_client::SubscribeOptions; use schemars::gen::SchemaSettings; use std::io::BufReader; use std::io::Read; @@ -328,7 +329,9 @@ fn main() -> color_eyre::Result<()> { std::thread::spawn(move || { 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"); tracing::info!("subscribed to komorebi notifications: \"{}\"", subscriber_name); diff --git a/komorebi-client/src/lib.rs b/komorebi-client/src/lib.rs index 4344266e..5870a7d2 100644 --- a/komorebi-client/src/lib.rs +++ b/komorebi-client/src/lib.rs @@ -44,6 +44,7 @@ pub use komorebi::RuleDebug; pub use komorebi::StackbarConfig; pub use komorebi::State; pub use komorebi::StaticConfig; +pub use komorebi::SubscribeOptions; pub use komorebi::TabsConfig; use komorebi::DATA_DIR; @@ -96,3 +97,29 @@ pub fn subscribe(name: &str) -> std::io::Result { Ok(listener) } + +pub fn subscribe_with_options( + name: &str, + options: SubscribeOptions, +) -> std::io::Result { + 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) +} diff --git a/komorebi/src/container.rs b/komorebi/src/container.rs index 20283a25..6e9f07ef 100644 --- a/komorebi/src/container.rs +++ b/komorebi/src/container.rs @@ -9,7 +9,7 @@ use serde::Serialize; use crate::ring::Ring; use crate::window::Window; -#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters, JsonSchema)] pub struct Container { #[getset(get = "pub")] 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 { pub fn hide(&self, omit: Option) { for window in self.windows().iter().rev() { diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 93f785bd..ee992901 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -196,6 +196,7 @@ pub enum SocketMessage { RemoveTitleBar(ApplicationIdentifier, String), ToggleTitleBars, AddSubscriberSocket(String), + AddSubscriberSocketWithOptions(String, SubscribeOptions), RemoveSubscriberSocket(String), AddSubscriberPipe(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)] pub enum StackbarMode { Always, @@ -336,7 +343,16 @@ pub enum ApplicationIdentifier { } #[derive( - Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, + Copy, + Clone, + Debug, + PartialEq, + Serialize, + Deserialize, + Display, + EnumString, + ValueEnum, + JsonSchema, )] pub enum FocusFollowsMouseImplementation { /// A custom FFM implementation (slightly more CPU-intensive) @@ -377,7 +393,16 @@ pub enum WindowContainerBehaviour { } #[derive( - Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, + Clone, + Copy, + Debug, + PartialEq, + Serialize, + Deserialize, + Display, + EnumString, + ValueEnum, + JsonSchema, )] pub enum MoveBehaviour { /// Swap the window container with the window container at the edge of the adjacent monitor @@ -411,7 +436,16 @@ pub enum HidingBehaviour { } #[derive( - Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, + Clone, + Copy, + Debug, + PartialEq, + Serialize, + Deserialize, + Display, + EnumString, + ValueEnum, + JsonSchema, )] pub enum OperationBehaviour { /// Process komorebic commands on temporarily unmanaged/floated windows diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index b001e577..b7d318c9 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -177,6 +177,8 @@ lazy_static! { Arc::new(Mutex::new(HashMap::new())); pub static ref SUBSCRIPTION_SOCKETS: Arc>> = Arc::new(Mutex::new(HashMap::new())); + pub static ref SUBSCRIPTION_SOCKET_OPTIONS: Arc>> = + Arc::new(Mutex::new(HashMap::new())); static ref TCP_CONNECTIONS: Arc>> = Arc::new(Mutex::new(HashMap::new())); static ref HIDING_BEHAVIOUR: Arc> = @@ -297,18 +299,34 @@ pub struct Notification { 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 sockets = SUBSCRIPTION_SOCKETS.lock(); + let options = SUBSCRIPTION_SOCKET_OPTIONS.lock(); for (socket, path) in &mut *sockets { - 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()); + let apply_state_filter = (*options) + .get(socket) + .copied() + .unwrap_or_default() + .filter_state_changes; + + 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()); + } } } } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index b3525c0b..0e79ee8c 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -61,6 +61,7 @@ use crate::winevent_listener; use crate::GlobalState; use crate::Notification; use crate::NotificationEvent; +use crate::State; use crate::ANIMATION_DURATION; use crate::ANIMATION_ENABLED; use crate::ANIMATION_FPS; @@ -79,6 +80,7 @@ use crate::OBJECT_NAME_CHANGE_ON_LAUNCH; use crate::REMOVE_TITLEBARS; use crate::SUBSCRIPTION_PIPES; use crate::SUBSCRIPTION_SOCKETS; +use crate::SUBSCRIPTION_SOCKET_OPTIONS; use crate::TCP_CONNECTIONS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; 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 { SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => { if let Some(monitor) = self.focused_monitor_mut() { @@ -1319,6 +1325,14 @@ impl WindowManager { let socket_path = DATA_DIR.join(socket); 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) => { let mut sockets = SUBSCRIPTION_SOCKETS.lock(); sockets.remove(socket); @@ -1574,12 +1588,14 @@ impl WindowManager { | SocketMessage::IdentifyBorderOverflowApplication(_, _) => {} }; - let notification = Notification { - event: NotificationEvent::Socket(message.clone()), - state: self.as_ref().into(), - }; + notify_subscribers( + Notification { + 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); transparency_manager::send_notification(); stackbar_manager::send_notification(); diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 71d6e090..8b05fd97 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -32,6 +32,7 @@ use crate::workspace_reconciliator::ALT_TAB_HWND; use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT; use crate::Notification; use crate::NotificationEvent; +use crate::State; use crate::DATA_DIR; use crate::FLOATING_APPLICATIONS; 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 match event { WindowManagerEvent::FocusChange(_, window) @@ -670,12 +675,14 @@ impl WindowManager { serde_json::to_writer_pretty(&file, &known_hwnds)?; - let notification = Notification { - event: NotificationEvent::WindowManager(event), - state: self.as_ref().into(), - }; + notify_subscribers( + Notification { + 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())); transparency_manager::send_notification(); stackbar_manager::send_notification(); diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 2d3c7da3..b44c0e9a 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -122,6 +122,54 @@ pub struct State { 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)] #[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct GlobalState {