diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index 3877bf7c1..e53431ebb 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -21,6 +21,7 @@ pub mod windows_callbacks; pub mod winevent; pub mod winevent_listener; pub mod workspace; +pub mod workspace_reconciliator; use lazy_static::lazy_static; use std::collections::HashMap; diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index c7734b334..fb5d6b018 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -34,6 +34,7 @@ use komorebi::static_config::StaticConfig; use komorebi::window_manager::WindowManager; use komorebi::windows_api::WindowsApi; use komorebi::winevent_listener; +use komorebi::workspace_reconciliator; use komorebi::CUSTOM_FFM; use komorebi::DATA_DIR; use komorebi::HOME_DIR; @@ -255,6 +256,7 @@ fn main() -> Result<()> { } border_manager::listen_for_notifications(wm.clone()); + workspace_reconciliator::listen_for_notifications(wm.clone()); let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1); ctrlc::set_handler(move || { diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index c81f497f8..2bad9f32b 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -22,6 +22,7 @@ use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent::WinEvent; +use crate::workspace_reconciliator; use crate::Notification; use crate::NotificationEvent; use crate::DATA_DIR; @@ -259,74 +260,70 @@ impl WindowManager { WindowManagerEvent::Show(_, window) | WindowManagerEvent::Manage(window) | WindowManagerEvent::Uncloak(_, window) => { - let mut switch_to = None; + let focused_monitor_idx = self.focused_monitor_idx(); + let focused_workspace_idx = + self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?; + + let focused_pair = (focused_monitor_idx, focused_workspace_idx); + + let mut needs_reconciliation = false; + for (i, monitors) in self.monitors().iter().enumerate() { for (j, workspace) in monitors.workspaces().iter().enumerate() { if workspace.contains_window(window.hwnd) { - switch_to = Some((i, j)); - } - } - } + if focused_pair != (i, j) { + workspace_reconciliator::event_tx().send( + workspace_reconciliator::Notification { + monitor_idx: i, + workspace_idx: j, + }, + )?; - match switch_to { - Some((known_monitor_idx, known_workspace_idx)) => { - if !matches!(event, WindowManagerEvent::Uncloak(_, _)) { - if self.focused_monitor_idx() != known_monitor_idx - || self - .focused_monitor() - .ok_or_else(|| anyhow!("there is no monitor"))? - .focused_workspace_idx() - != known_workspace_idx - { - self.focus_monitor(known_monitor_idx)?; - self.focus_workspace(known_workspace_idx)?; + needs_reconciliation = true; } } } - None => { - // There are some applications such as Firefox where, if they are focused when a - // workspace switch takes place, it will fire an additional Show event, which will - // result in them being associated with both the original workspace and the workspace - // being switched to. This loop is to try to ensure that we don't end up with - // duplicates across multiple workspaces, as it results in ghost layout tiles. - let mut proceed = true; - - for (i, monitor) in self.monitors().iter().enumerate() { - for (j, workspace) in monitor.workspaces().iter().enumerate() { - if workspace.container_for_window(window.hwnd).is_some() - && i != self.focused_monitor_idx() - && j != monitor.focused_workspace_idx() - { - tracing::debug!( + } + + // There are some applications such as Firefox where, if they are focused when a + // workspace switch takes place, it will fire an additional Show event, which will + // result in them being associated with both the original workspace and the workspace + // being switched to. This loop is to try to ensure that we don't end up with + // duplicates across multiple workspaces, as it results in ghost layout tiles. + let mut proceed = true; + + for (i, monitor) in self.monitors().iter().enumerate() { + for (j, workspace) in monitor.workspaces().iter().enumerate() { + if workspace.container_for_window(window.hwnd).is_some() + && i != self.focused_monitor_idx() + && j != monitor.focused_workspace_idx() + { + tracing::debug!( "ignoring show event for window already associated with another workspace" ); - window.hide(); - proceed = false; - } - } + window.hide(); + proceed = false; } + } + } - if proceed { - let behaviour = self.window_container_behaviour; - let workspace = self.focused_workspace_mut()?; + if proceed { + let behaviour = self.window_container_behaviour; + let workspace = self.focused_workspace_mut()?; - if !workspace.contains_window(window.hwnd) && switch_to.is_none() { - match behaviour { - WindowContainerBehaviour::Create => { - workspace.new_container_for_window(window); - self.update_focused_workspace(false, false)?; - } - WindowContainerBehaviour::Append => { - workspace - .focused_container_mut() - .ok_or_else(|| { - anyhow!("there is no focused container") - })? - .add_window(window); - self.update_focused_workspace(true, false)?; - } - } + if !workspace.contains_window(window.hwnd) && !needs_reconciliation { + match behaviour { + WindowContainerBehaviour::Create => { + workspace.new_container_for_window(window); + self.update_focused_workspace(false, false)?; + } + WindowContainerBehaviour::Append => { + workspace + .focused_container_mut() + .ok_or_else(|| anyhow!("there is no focused container"))? + .add_window(window); + self.update_focused_workspace(true, false)?; } } } diff --git a/komorebi/src/workspace_reconciliator.rs b/komorebi/src/workspace_reconciliator.rs new file mode 100644 index 000000000..ef4b7dc2a --- /dev/null +++ b/komorebi/src/workspace_reconciliator.rs @@ -0,0 +1,56 @@ +use crate::WindowManager; +use crossbeam_channel::Receiver; +use crossbeam_channel::Sender; +use parking_lot::Mutex; +use std::sync::Arc; +use std::sync::OnceLock; + +#[derive(Copy, Clone)] +pub struct Notification { + pub monitor_idx: usize, + pub workspace_idx: usize, +} + +static CHANNEL: OnceLock<(Sender, Receiver)> = OnceLock::new(); + +pub fn channel() -> &'static (Sender, Receiver) { + CHANNEL.get_or_init(|| crossbeam_channel::bounded(1)) +} + +pub fn event_tx() -> Sender { + channel().0.clone() +} + +pub fn event_rx() -> Receiver { + channel().1.clone() +} + +pub fn listen_for_notifications(wm: Arc>) { + tracing::info!("listening"); + let receiver = event_rx(); + + std::thread::spawn(move || -> color_eyre::Result<()> { + for notification in receiver { + tracing::info!("running reconciliation"); + let mut wm = wm.lock(); + let focused_monitor_idx = wm.focused_monitor_idx(); + let focused_workspace_idx = + wm.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?; + + let focused_pair = (focused_monitor_idx, focused_workspace_idx); + let updated_pair = (notification.monitor_idx, notification.workspace_idx); + + if focused_pair != updated_pair { + wm.focus_monitor(notification.monitor_idx)?; + let mouse_follows_focus = wm.mouse_follows_focus; + + if let Some(monitor) = wm.focused_monitor_mut() { + monitor.focus_workspace(notification.workspace_idx)?; + monitor.load_focused_workspace(mouse_follows_focus)?; + } + } + } + + Ok(()) + }); +}