diff --git a/komorebi/src/focus_manager.rs b/komorebi/src/focus_manager.rs new file mode 100644 index 000000000..1aa435612 --- /dev/null +++ b/komorebi/src/focus_manager.rs @@ -0,0 +1,70 @@ +#![deny(clippy::unwrap_used, clippy::expect_used)] + +use crossbeam_channel::Receiver; +use crossbeam_channel::Sender; +use parking_lot::Mutex; +use std::ops::Deref; +use std::sync::Arc; +use std::sync::OnceLock; + +use crate::Window; +use crate::WindowManager; + +pub struct Notification(isize); + +impl Deref for Notification { + type Target = isize; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +static CHANNEL: OnceLock<(Sender, Receiver)> = OnceLock::new(); + +pub fn channel() -> &'static (Sender, Receiver) { + CHANNEL.get_or_init(|| crossbeam_channel::bounded(20)) +} + +fn event_tx() -> Sender { + channel().0.clone() +} + +fn event_rx() -> Receiver { + channel().1.clone() +} + +// Currently this should only be used for async focus updates, such as +// when an animation finishes and we need to focus to set the cursor +// position if the user has mouse follows focus enabled +pub fn send_notification(hwnd: isize) { + if event_tx().try_send(Notification(hwnd)).is_err() { + tracing::warn!("channel is full; dropping notification") + } +} + +pub fn listen_for_notifications(wm: Arc>) { + std::thread::spawn(move || loop { + match handle_notifications(wm.clone()) { + Ok(()) => { + tracing::warn!("restarting finished thread"); + } + Err(error) => { + tracing::warn!("restarting failed thread: {}", error); + } + } + }); +} + +pub fn handle_notifications(wm: Arc>) -> color_eyre::Result<()> { + tracing::info!("listening"); + + let receiver = event_rx(); + + for notification in receiver { + let mouse_follows_focus = wm.lock().mouse_follows_focus; + let _ = Window::from(*notification).focus(mouse_follows_focus); + } + + Ok(()) +} diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index 6eeb7d9d1..70d5601a5 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -8,6 +8,7 @@ pub mod com; pub mod ring; pub mod colour; pub mod container; +pub mod focus_manager; pub mod monitor; pub mod monitor_reconciliator; pub mod process_command; diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 5b0b3286a..13d4f288f 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -25,6 +25,7 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::EnvFilter; use komorebi::border_manager; +use komorebi::focus_manager; use komorebi::load_configuration; use komorebi::monitor_reconciliator; use komorebi::process_command::listen_for_commands; @@ -265,6 +266,7 @@ fn main() -> Result<()> { workspace_reconciliator::listen_for_notifications(wm.clone()); monitor_reconciliator::listen_for_notifications(wm.clone())?; reaper::watch_for_orphans(wm.clone()); + focus_manager::listen_for_notifications(wm.clone()); let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1); ctrlc::set_handler(move || { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index c2df6624f..c8f56e35a 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -1,5 +1,6 @@ use crate::border_manager; use crate::com::SetCloak; +use crate::focus_manager; use crate::stackbar_manager; use crate::ANIMATIONS_IN_PROGRESS; use crate::ANIMATION_DURATION; @@ -189,6 +190,9 @@ impl Window { if progress == 1.0 { WindowsApi::position_window(hwnd, &new_rect, top)?; + if WindowsApi::foreground_window().unwrap_or_default() == hwnd.0 { + focus_manager::send_notification(hwnd.0) + } if ANIMATIONS_IN_PROGRESS.load(Ordering::Acquire) == 0 { border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);