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 {