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)