mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-25 10:08:33 +02:00
feat(wm): add alt-tab heuristics to wsr
This commit adds some rough heuristics to workspace_reconciliator which should help with having the correct window focused after reconciliation in the majority of, but probably not all, cases. EnumWindows generally returns HWNDs according to z order, and a window selected by alt-tab will almost always be put on the top of the z order. Before sending a workspace_reconciliator::Notification, we store this HWND along with an Instant and an AtomicBool telling us that we have a candidate to focus after the workspace switch.
This commit is contained in:
2
justfile
2
justfile
@@ -28,7 +28,6 @@ install:
|
|||||||
just install-target komorebi
|
just install-target komorebi
|
||||||
|
|
||||||
run:
|
run:
|
||||||
just install-target komorebic
|
|
||||||
cargo +stable run --bin komorebi --locked
|
cargo +stable run --bin komorebi --locked
|
||||||
|
|
||||||
warn $RUST_LOG="warn":
|
warn $RUST_LOG="warn":
|
||||||
@@ -44,7 +43,6 @@ trace $RUST_LOG="trace":
|
|||||||
just run
|
just run
|
||||||
|
|
||||||
deadlock $RUST_LOG="trace":
|
deadlock $RUST_LOG="trace":
|
||||||
just install-komorebic
|
|
||||||
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
||||||
|
|
||||||
docgen:
|
docgen:
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ use std::sync::atomic::AtomicI32;
|
|||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
|
|
||||||
|
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||||
use crate::Colour;
|
use crate::Colour;
|
||||||
use crate::Rect;
|
use crate::Rect;
|
||||||
use crate::Rgb;
|
use crate::Rgb;
|
||||||
@@ -121,23 +124,37 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
tracing::info!("listening");
|
tracing::info!("listening");
|
||||||
|
|
||||||
let receiver = event_rx();
|
let receiver = event_rx();
|
||||||
|
let mut instant: Option<Instant> = None;
|
||||||
|
event_tx().send(Notification)?;
|
||||||
|
|
||||||
'receiver: for _ in receiver {
|
'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 = BORDER_STATE.lock();
|
||||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
let mut borders_monitors = BORDERS_MONITORS.lock();
|
||||||
|
|
||||||
// Check the wm state every time we receive a notification
|
// Check the wm state every time we receive a notification
|
||||||
let state = wm.lock();
|
let state = wm.lock();
|
||||||
|
|
||||||
if !BORDER_ENABLED.load_consume() || state.is_paused {
|
// If borders are disabled
|
||||||
if !borders.is_empty() {
|
if !BORDER_ENABLED.load_consume()
|
||||||
for (_, border) in borders.iter() {
|
// Or if the wm is paused
|
||||||
border.destroy()?;
|
|| state.is_paused
|
||||||
}
|
// Or if we are handling an alt-tab across workspaces
|
||||||
|
|| ALT_TAB_HWND.load().is_some()
|
||||||
borders.clear();
|
{
|
||||||
|
// Destroy the borders we know about
|
||||||
|
for (_, border) in borders.iter() {
|
||||||
|
border.destroy()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
borders.clear();
|
||||||
continue 'receiver;
|
continue 'receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use color_eyre::eyre::anyhow;
|
use color_eyre::eyre::anyhow;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -23,6 +25,8 @@ use crate::window_manager_event::WindowManagerEvent;
|
|||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::winevent::WinEvent;
|
use crate::winevent::WinEvent;
|
||||||
use crate::workspace_reconciliator;
|
use crate::workspace_reconciliator;
|
||||||
|
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||||
|
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
|
||||||
use crate::Notification;
|
use crate::Notification;
|
||||||
use crate::NotificationEvent;
|
use crate::NotificationEvent;
|
||||||
use crate::DATA_DIR;
|
use crate::DATA_DIR;
|
||||||
@@ -271,6 +275,24 @@ impl WindowManager {
|
|||||||
for (i, monitors) in self.monitors().iter().enumerate() {
|
for (i, monitors) in self.monitors().iter().enumerate() {
|
||||||
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
||||||
if workspace.contains_window(window.hwnd) && focused_pair != (i, j) {
|
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::event_tx().send(
|
||||||
workspace_reconciliator::Notification {
|
workspace_reconciliator::Notification {
|
||||||
monitor_idx: i,
|
monitor_idx: i,
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ use crate::monitor::Monitor;
|
|||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::set_window_position::SetWindowPosition;
|
use crate::set_window_position::SetWindowPosition;
|
||||||
use crate::windows_callbacks;
|
use crate::windows_callbacks;
|
||||||
|
use crate::Window;
|
||||||
|
|
||||||
pub enum WindowsResult<T, E> {
|
pub enum WindowsResult<T, E> {
|
||||||
Err(E),
|
Err(E),
|
||||||
@@ -493,6 +494,16 @@ impl WindowsApi {
|
|||||||
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
|
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn alt_tab_windows() -> Result<Vec<Window>> {
|
||||||
|
let mut hwnds = vec![];
|
||||||
|
Self::enum_windows(
|
||||||
|
Some(windows_callbacks::alt_tab_windows),
|
||||||
|
&mut hwnds as *mut Vec<Window> as isize,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(hwnds)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn top_visible_window() -> Result<isize> {
|
pub fn top_visible_window() -> Result<isize> {
|
||||||
let hwnd = Self::top_window()?;
|
let hwnd = Self::top_window()?;
|
||||||
|
|||||||
@@ -152,6 +152,26 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|||||||
true.into()
|
true.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub extern "system" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||||
|
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
|
||||||
|
|
||||||
|
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(
|
pub extern "system" fn win_event_hook(
|
||||||
_h_win_event_hook: HWINEVENTHOOK,
|
_h_win_event_hook: HWINEVENTHOOK,
|
||||||
event: u32,
|
event: u32,
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||||
|
|
||||||
|
use crate::border_manager;
|
||||||
use crate::WindowManager;
|
use crate::WindowManager;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
|
use crossbeam_utils::atomic::AtomicCell;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Notification {
|
pub struct Notification {
|
||||||
@@ -13,6 +18,12 @@ pub struct Notification {
|
|||||||
pub workspace_idx: usize,
|
pub workspace_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static ALT_TAB_HWND: AtomicCell<Option<isize>> = AtomicCell::new(None);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref ALT_TAB_HWND_INSTANT: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
||||||
|
}
|
||||||
|
|
||||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||||
|
|
||||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||||
@@ -34,7 +45,11 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
|||||||
tracing::warn!("restarting finished thread");
|
tracing::warn!("restarting finished thread");
|
||||||
}
|
}
|
||||||
Err(error) => {
|
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<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
tracing::info!("listening");
|
tracing::info!("listening");
|
||||||
|
|
||||||
let receiver = event_rx();
|
let receiver = event_rx();
|
||||||
|
let arc = wm.clone();
|
||||||
|
|
||||||
for notification in receiver {
|
for notification in receiver {
|
||||||
tracing::info!("running reconciliation");
|
tracing::info!("running reconciliation");
|
||||||
|
|
||||||
let mut wm = wm.lock();
|
let mut wm = wm.lock();
|
||||||
let focused_monitor_idx = wm.focused_monitor_idx();
|
let focused_monitor_idx = wm.focused_monitor_idx();
|
||||||
let focused_workspace_idx =
|
let focused_workspace_idx =
|
||||||
@@ -62,6 +79,36 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
monitor.focus_workspace(notification.workspace_idx)?;
|
monitor.focus_workspace(notification.workspace_idx)?;
|
||||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
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)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user