From 925f3bd87bf4e7b2100d83b6c0644769ad476d2d Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Sat, 21 Jan 2023 13:29:06 -0800 Subject: [PATCH] feat(wm): add optional alt focus hack This commit adds an optional focusing hack using simulated ALT key presses to ensure that focus changes always succeed. As noted in the documentation for LockSetForegroundWindow, the system automatically enables calls to SetForegroundWindow if the user presses the ALT key. --- komorebi-core/src/lib.rs | 1 + komorebi/src/main.rs | 1 + komorebi/src/process_command.rs | 4 ++++ komorebi/src/process_event.rs | 4 +++- komorebi/src/window.rs | 15 +++++++++++++++ komorebi/src/window_manager.rs | 4 +++- komorebi/src/windows_api.rs | 9 +++++++++ komorebic.lib.ahk | 4 ++++ komorebic/src/main.rs | 12 ++++++++++++ 9 files changed, 52 insertions(+), 2 deletions(-) diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index f7e13a8c..a3878ee8 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -104,6 +104,7 @@ pub enum SocketMessage { ReloadConfiguration, WatchConfiguration(bool), CompleteConfiguration, + AltFocusHack(bool), ActiveWindowBorder(bool), ActiveWindowBorderColour(WindowKind, u32, u32, u32), ActiveWindowBorderWidth(i32), diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index e3a4a915..f60f2e4e 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -168,6 +168,7 @@ lazy_static! { pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false); pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false); pub static SESSION_ID: AtomicU32 = AtomicU32::new(0); +pub static ALT_FOCUS_HACK: AtomicBool = AtomicBool::new(false); pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false); pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0); pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false); diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 70da5f2b..af2a464a 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -42,6 +42,7 @@ use crate::window_manager::WindowManager; use crate::windows_api::WindowsApi; use crate::Notification; use crate::NotificationEvent; +use crate::ALT_FOCUS_HACK; use crate::BORDER_COLOUR_CURRENT; use crate::BORDER_COLOUR_SINGLE; use crate::BORDER_COLOUR_STACK; @@ -907,6 +908,9 @@ impl WindowManager { WindowsApi::invalidate_border_rect()?; } + SocketMessage::AltFocusHack(enable) => { + ALT_FOCUS_HACK.store(enable, Ordering::SeqCst); + } SocketMessage::NotificationSchema => { let notification = schema_for!(Notification); let schema = serde_json::to_string_pretty(¬ification)?; diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 7ce0197d..c41fdd2b 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -97,7 +97,9 @@ impl WindowManager { // // This check ensures that we only update the focused monitor when the window // triggering monitor reconciliation is known to not be tied to a specific monitor. - if window.class()? != "OleMainThreadWndClass" { + if window.class()? != "OleMainThreadWndClass" + && self.focused_monitor_idx() != monitor_idx + { self.focus_monitor(monitor_idx)?; } } diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 03d5a900..fe575ecd 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -2,6 +2,7 @@ use std::convert::TryFrom; use std::fmt::Display; use std::fmt::Formatter; use std::fmt::Write as _; +use std::sync::atomic::Ordering; use color_eyre::eyre::anyhow; use color_eyre::Result; @@ -11,6 +12,9 @@ use serde::ser::SerializeStruct; use serde::Serialize; use serde::Serializer; use windows::Win32::Foundation::HWND; +use winput::press; +use winput::release; +use winput::Vk; use komorebi_core::ApplicationIdentifier; use komorebi_core::HidingBehaviour; @@ -20,6 +24,7 @@ use crate::styles::ExtendedWindowStyle; use crate::styles::WindowStyle; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; +use crate::ALT_FOCUS_HACK; use crate::BORDER_OVERFLOW_IDENTIFIERS; use crate::FLOAT_IDENTIFIERS; use crate::HIDDEN_HWNDS; @@ -268,7 +273,13 @@ impl Window { let mut foregrounded = false; let mut tried_resetting_foreground_access = false; let mut max_attempts = 10; + + let hotkey_uses_alt = WindowsApi::alt_is_pressed(); while !foregrounded && max_attempts > 0 { + if ALT_FOCUS_HACK.load(Ordering::SeqCst) { + press(Vk::Alt); + } + match WindowsApi::set_foreground_window(self.hwnd()) { Ok(_) => { foregrounded = true; @@ -289,6 +300,10 @@ impl Window { } } }; + + if ALT_FOCUS_HACK.load(Ordering::SeqCst) && !hotkey_uses_alt { + release(Vk::Alt); + } } // Center cursor in Window diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 9cce1071..84934d33 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -388,7 +388,9 @@ impl WindowManager { } // Gotta reset the focus or the movement will feel "off" - focused_ws.focus_container(focused_container_idx); + if before_count != after_count { + focused_ws.focus_container(focused_container_idx); + } } } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 51a5812d..2df64f1c 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -52,6 +52,7 @@ use windows::Win32::System::Threading::PROCESS_NAME_WIN32; use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION; use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext; use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2; +use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState; use windows::Win32::UI::Input::KeyboardAndMouse::SendInput; use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus; use windows::Win32::UI::Input::KeyboardAndMouse::INPUT; @@ -60,6 +61,7 @@ use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE; use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN; use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP; use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT; +use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU; use windows::Win32::UI::Shell::Common::DEVICE_SCALE_FACTOR; use windows::Win32::UI::Shell::GetScaleFactorForMonitor; use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; @@ -797,6 +799,13 @@ impl WindowsApi { .process() } + pub fn alt_is_pressed() -> bool { + let state = unsafe { GetKeyState(i32::from(VK_MENU.0)) }; + #[allow(clippy::cast_sign_loss)] + let actual = (state as u16) & 0x8000; + actual != 0 + } + pub fn left_click() -> u32 { let inputs = [ INPUT { diff --git a/komorebic.lib.ahk b/komorebic.lib.ahk index 68db878a..5d76415c 100644 --- a/komorebic.lib.ahk +++ b/komorebic.lib.ahk @@ -288,6 +288,10 @@ CompleteConfiguration() { RunWait, komorebic.exe complete-configuration, , Hide } +AltFocusHack(boolean_state) { + RunWait, komorebic.exe alt-focus-hack %boolean_state%, , Hide +} + WindowHidingBehaviour(hiding_behaviour) { RunWait, komorebic.exe window-hiding-behaviour %hiding_behaviour%, , Hide } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index e5bcce2e..eac1572f 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -513,6 +513,12 @@ struct FormatAppSpecificConfiguration { path: String, } +#[derive(Parser, AhkFunction)] +struct AltFocusHack { + #[clap(value_enum)] + boolean_state: BooleanState, +} + #[derive(Parser)] #[clap(author, about, version)] struct Opts { @@ -719,6 +725,9 @@ enum SubCommand { WatchConfiguration(WatchConfiguration), /// Signal that the final configuration option has been sent CompleteConfiguration, + /// Enable or disable a hack simulating ALT key presses to ensure focus changes succeed + #[clap(arg_required_else_help = true)] + AltFocusHack(AltFocusHack), /// Set the window behaviour when switching workspaces / cycling stacks #[clap(arg_required_else_help = true)] WindowHidingBehaviour(WindowHidingBehaviour), @@ -1319,6 +1328,9 @@ fn main() -> Result<()> { SubCommand::CompleteConfiguration => { send_message(&SocketMessage::CompleteConfiguration.as_bytes()?)?; } + SubCommand::AltFocusHack(arg) => { + send_message(&SocketMessage::AltFocusHack(arg.boolean_state.into()).as_bytes()?)?; + } SubCommand::IdentifyObjectNameChangeApplication(target) => { send_message( &SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id)