diff --git a/justfile b/justfile index c718a82c..37d85ca7 100644 --- a/justfile +++ b/justfile @@ -28,7 +28,6 @@ install: just install-target komorebi run: - just install-target komorebic cargo +stable run --bin komorebi --locked warn $RUST_LOG="warn": @@ -44,7 +43,6 @@ trace $RUST_LOG="trace": just run deadlock $RUST_LOG="trace": - just install-komorebic cargo +stable run --bin komorebi --locked --features deadlock_detection docgen: diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 32572b91..b8896b2a 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -18,8 +18,11 @@ use std::sync::atomic::AtomicI32; use std::sync::atomic::AtomicU32; use std::sync::Arc; use std::sync::OnceLock; +use std::time::Duration; +use std::time::Instant; use windows::Win32::Foundation::HWND; +use crate::workspace_reconciliator::ALT_TAB_HWND; use crate::Colour; use crate::Rect; use crate::Rgb; @@ -121,23 +124,37 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result tracing::info!("listening"); let receiver = event_rx(); + let mut instant: Option = None; + event_tx().send(Notification)?; 'receiver: for _ in receiver { + if let Some(instant) = instant { + if instant.elapsed().lt(&Duration::from_millis(50)) { + continue 'receiver; + } + } + + instant = Some(Instant::now()); + let mut borders = BORDER_STATE.lock(); let mut borders_monitors = BORDERS_MONITORS.lock(); // Check the wm state every time we receive a notification let state = wm.lock(); - if !BORDER_ENABLED.load_consume() || state.is_paused { - if !borders.is_empty() { - for (_, border) in borders.iter() { - border.destroy()?; - } - - borders.clear(); + // If borders are disabled + if !BORDER_ENABLED.load_consume() + // Or if the wm is paused + || state.is_paused + // Or if we are handling an alt-tab across workspaces + || ALT_TAB_HWND.load().is_some() + { + // Destroy the borders we know about + for (_, border) in borders.iter() { + border.destroy()?; } + borders.clear(); continue 'receiver; } diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index ec45c466..2abe4c53 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -1,6 +1,8 @@ use std::fs::OpenOptions; use std::sync::atomic::Ordering; use std::sync::Arc; +use std::time::Duration; +use std::time::Instant; use color_eyre::eyre::anyhow; use color_eyre::Result; @@ -23,6 +25,8 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent::WinEvent; use crate::workspace_reconciliator; +use crate::workspace_reconciliator::ALT_TAB_HWND; +use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT; use crate::Notification; use crate::NotificationEvent; use crate::DATA_DIR; @@ -271,6 +275,24 @@ impl WindowManager { for (i, monitors) in self.monitors().iter().enumerate() { for (j, workspace) in monitors.workspaces().iter().enumerate() { if workspace.contains_window(window.hwnd) && focused_pair != (i, j) { + // At this point we know we are going to send a notification to the workspace reconciliator + // So we get the topmost window returned by EnumWindows, which is almost always the window + // that has been selected by alt-tab + if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() { + if let Some(first) = + alt_tab_windows.iter().find(|w| w.title().is_ok()) + { + // If our record of this HWND hasn't been updated in over a minute + let mut instant = ALT_TAB_HWND_INSTANT.lock(); + if instant.elapsed().gt(&Duration::from_secs(1)) { + // Update our record with the HWND we just found + ALT_TAB_HWND.store(Some(first.hwnd)); + // Update the timestamp of our record + *instant = Instant::now(); + } + } + } + workspace_reconciliator::event_tx().send( workspace_reconciliator::Notification { monitor_idx: i, diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 6689493a..01cfca92 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -139,6 +139,7 @@ use crate::monitor::Monitor; use crate::ring::Ring; use crate::set_window_position::SetWindowPosition; use crate::windows_callbacks; +use crate::Window; pub enum WindowsResult { Err(E), @@ -493,6 +494,16 @@ impl WindowsApi { unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process() } + pub fn alt_tab_windows() -> Result> { + let mut hwnds = vec![]; + Self::enum_windows( + Some(windows_callbacks::alt_tab_windows), + &mut hwnds as *mut Vec as isize, + )?; + + Ok(hwnds) + } + #[allow(dead_code)] pub fn top_visible_window() -> Result { let hwnd = Self::top_window()?; diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index 42ce5862..89ddd5ee 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -152,6 +152,26 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL { true.into() } +pub extern "system" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL { + let windows = unsafe { &mut *(lparam.0 as *mut Vec) }; + + let is_visible = WindowsApi::is_window_visible(hwnd); + let is_window = WindowsApi::is_window(hwnd); + let is_minimized = WindowsApi::is_iconic(hwnd); + + if is_visible && is_window && !is_minimized { + let window = Window { hwnd: hwnd.0 }; + + if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default()) { + if should_manage { + windows.push(window); + } + } + } + + true.into() +} + pub extern "system" fn win_event_hook( _h_win_event_hook: HWINEVENTHOOK, event: u32, diff --git a/komorebi/src/workspace_reconciliator.rs b/komorebi/src/workspace_reconciliator.rs index 2a3612fd..973e4b2e 100644 --- a/komorebi/src/workspace_reconciliator.rs +++ b/komorebi/src/workspace_reconciliator.rs @@ -1,11 +1,16 @@ #![deny(clippy::unwrap_used, clippy::expect_used)] +use crate::border_manager; use crate::WindowManager; use crossbeam_channel::Receiver; use crossbeam_channel::Sender; +use crossbeam_utils::atomic::AtomicCell; +use lazy_static::lazy_static; use parking_lot::Mutex; use std::sync::Arc; use std::sync::OnceLock; +use std::time::Duration; +use std::time::Instant; #[derive(Copy, Clone)] pub struct Notification { @@ -13,6 +18,12 @@ pub struct Notification { pub workspace_idx: usize, } +pub static ALT_TAB_HWND: AtomicCell> = AtomicCell::new(None); + +lazy_static! { + pub static ref ALT_TAB_HWND_INSTANT: Arc> = Arc::new(Mutex::new(Instant::now())); +} + static CHANNEL: OnceLock<(Sender, Receiver)> = OnceLock::new(); pub fn channel() -> &'static (Sender, Receiver) { @@ -34,7 +45,11 @@ pub fn listen_for_notifications(wm: Arc>) { tracing::warn!("restarting finished thread"); } Err(error) => { - tracing::warn!("restarting failed thread: {}", error); + if cfg!(debug_assertions) { + tracing::error!("restarting failed thread: {:?}", error) + } else { + tracing::error!("restarting failed thread: {}", error) + } } } }); @@ -43,9 +58,11 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result tracing::info!("listening"); let receiver = event_rx(); + let arc = wm.clone(); 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 = @@ -62,6 +79,36 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result monitor.focus_workspace(notification.workspace_idx)?; monitor.load_focused_workspace(mouse_follows_focus)?; } + + // Drop our lock on the window manager state here to not slow down updates + drop(wm); + + // Check if there was an alt-tab across workspaces in the last second + if let Some(hwnd) = ALT_TAB_HWND.load() { + if ALT_TAB_HWND_INSTANT + .lock() + .elapsed() + .lt(&Duration::from_secs(1)) + { + // Sleep for 100 millis to let other events pass + std::thread::sleep(Duration::from_millis(100)); + tracing::info!("focusing alt-tabbed window"); + + // Take a new lock on the wm and try to focus the container with + // the recorded HWND from the alt-tab + let mut wm = arc.lock(); + if let Ok(workspace) = wm.focused_workspace_mut() { + // Regardless of if this fails, we need to get past this part + // to unblock the border manager below + let _ = workspace.focus_container_by_window(hwnd); + } + + // Unblock the border manager + ALT_TAB_HWND.store(None); + // Send a notification to the border manager to update the borders + border_manager::event_tx().send(border_manager::Notification)?; + } + } } }