From 2c8f25ef82b2a2af1dd364f37f90a25497a6965a Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Thu, 11 Jul 2024 18:29:44 -0700 Subject: [PATCH] fix(animation): add async focus manager for mff This commit adds a new focus manager module to be used to trigger async focus changes with mouse follows focus updates. Currently this should only need to be used with animations as all other focus calls are synchronous. fix #910 --- komorebi/src/focus_manager.rs | 70 +++++++++++++++++++++++++++++++++++ komorebi/src/lib.rs | 1 + komorebi/src/main.rs | 2 + komorebi/src/window.rs | 4 ++ 4 files changed, 77 insertions(+) create mode 100644 komorebi/src/focus_manager.rs diff --git a/komorebi/src/focus_manager.rs b/komorebi/src/focus_manager.rs new file mode 100644 index 00000000..1aa43561 --- /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 6eeb7d9d..70d5601a 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 5b0b3286..13d4f288 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 c2df6624..c8f56e35 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);