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.
This commit is contained in:
LGUG2Z
2023-01-21 13:29:06 -08:00
parent f8120f6b11
commit 925f3bd87b
9 changed files with 52 additions and 2 deletions

View File

@@ -104,6 +104,7 @@ pub enum SocketMessage {
ReloadConfiguration, ReloadConfiguration,
WatchConfiguration(bool), WatchConfiguration(bool),
CompleteConfiguration, CompleteConfiguration,
AltFocusHack(bool),
ActiveWindowBorder(bool), ActiveWindowBorder(bool),
ActiveWindowBorderColour(WindowKind, u32, u32, u32), ActiveWindowBorderColour(WindowKind, u32, u32, u32),
ActiveWindowBorderWidth(i32), ActiveWindowBorderWidth(i32),

View File

@@ -168,6 +168,7 @@ lazy_static! {
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false); pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false); pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0); 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_ENABLED: AtomicBool = AtomicBool::new(false);
pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0); pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false); pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);

View File

@@ -42,6 +42,7 @@ use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi; use crate::windows_api::WindowsApi;
use crate::Notification; use crate::Notification;
use crate::NotificationEvent; use crate::NotificationEvent;
use crate::ALT_FOCUS_HACK;
use crate::BORDER_COLOUR_CURRENT; use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_COLOUR_SINGLE; use crate::BORDER_COLOUR_SINGLE;
use crate::BORDER_COLOUR_STACK; use crate::BORDER_COLOUR_STACK;
@@ -907,6 +908,9 @@ impl WindowManager {
WindowsApi::invalidate_border_rect()?; WindowsApi::invalidate_border_rect()?;
} }
SocketMessage::AltFocusHack(enable) => {
ALT_FOCUS_HACK.store(enable, Ordering::SeqCst);
}
SocketMessage::NotificationSchema => { SocketMessage::NotificationSchema => {
let notification = schema_for!(Notification); let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?; let schema = serde_json::to_string_pretty(&notification)?;

View File

@@ -97,7 +97,9 @@ impl WindowManager {
// //
// This check ensures that we only update the focused monitor when the window // 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. // 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)?; self.focus_monitor(monitor_idx)?;
} }
} }

View File

@@ -2,6 +2,7 @@ use std::convert::TryFrom;
use std::fmt::Display; use std::fmt::Display;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::fmt::Write as _; use std::fmt::Write as _;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow; use color_eyre::eyre::anyhow;
use color_eyre::Result; use color_eyre::Result;
@@ -11,6 +12,9 @@ use serde::ser::SerializeStruct;
use serde::Serialize; use serde::Serialize;
use serde::Serializer; use serde::Serializer;
use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::HWND;
use winput::press;
use winput::release;
use winput::Vk;
use komorebi_core::ApplicationIdentifier; use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour; use komorebi_core::HidingBehaviour;
@@ -20,6 +24,7 @@ use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle; use crate::styles::WindowStyle;
use crate::window_manager_event::WindowManagerEvent; use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi; use crate::windows_api::WindowsApi;
use crate::ALT_FOCUS_HACK;
use crate::BORDER_OVERFLOW_IDENTIFIERS; use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::FLOAT_IDENTIFIERS; use crate::FLOAT_IDENTIFIERS;
use crate::HIDDEN_HWNDS; use crate::HIDDEN_HWNDS;
@@ -268,7 +273,13 @@ impl Window {
let mut foregrounded = false; let mut foregrounded = false;
let mut tried_resetting_foreground_access = false; let mut tried_resetting_foreground_access = false;
let mut max_attempts = 10; let mut max_attempts = 10;
let hotkey_uses_alt = WindowsApi::alt_is_pressed();
while !foregrounded && max_attempts > 0 { while !foregrounded && max_attempts > 0 {
if ALT_FOCUS_HACK.load(Ordering::SeqCst) {
press(Vk::Alt);
}
match WindowsApi::set_foreground_window(self.hwnd()) { match WindowsApi::set_foreground_window(self.hwnd()) {
Ok(_) => { Ok(_) => {
foregrounded = true; 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 // Center cursor in Window

View File

@@ -388,7 +388,9 @@ impl WindowManager {
} }
// Gotta reset the focus or the movement will feel "off" // 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);
}
} }
} }

View File

@@ -52,6 +52,7 @@ use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION; use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext; use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2; 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::SendInput;
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus; use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT; 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_LEFTDOWN;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP; use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT; 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::Common::DEVICE_SCALE_FACTOR;
use windows::Win32::UI::Shell::GetScaleFactorForMonitor; use windows::Win32::UI::Shell::GetScaleFactorForMonitor;
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
@@ -797,6 +799,13 @@ impl WindowsApi {
.process() .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 { pub fn left_click() -> u32 {
let inputs = [ let inputs = [
INPUT { INPUT {

View File

@@ -288,6 +288,10 @@ CompleteConfiguration() {
RunWait, komorebic.exe complete-configuration, , Hide RunWait, komorebic.exe complete-configuration, , Hide
} }
AltFocusHack(boolean_state) {
RunWait, komorebic.exe alt-focus-hack %boolean_state%, , Hide
}
WindowHidingBehaviour(hiding_behaviour) { WindowHidingBehaviour(hiding_behaviour) {
RunWait, komorebic.exe window-hiding-behaviour %hiding_behaviour%, , Hide RunWait, komorebic.exe window-hiding-behaviour %hiding_behaviour%, , Hide
} }

View File

@@ -513,6 +513,12 @@ struct FormatAppSpecificConfiguration {
path: String, path: String,
} }
#[derive(Parser, AhkFunction)]
struct AltFocusHack {
#[clap(value_enum)]
boolean_state: BooleanState,
}
#[derive(Parser)] #[derive(Parser)]
#[clap(author, about, version)] #[clap(author, about, version)]
struct Opts { struct Opts {
@@ -719,6 +725,9 @@ enum SubCommand {
WatchConfiguration(WatchConfiguration), WatchConfiguration(WatchConfiguration),
/// Signal that the final configuration option has been sent /// Signal that the final configuration option has been sent
CompleteConfiguration, 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 /// Set the window behaviour when switching workspaces / cycling stacks
#[clap(arg_required_else_help = true)] #[clap(arg_required_else_help = true)]
WindowHidingBehaviour(WindowHidingBehaviour), WindowHidingBehaviour(WindowHidingBehaviour),
@@ -1319,6 +1328,9 @@ fn main() -> Result<()> {
SubCommand::CompleteConfiguration => { SubCommand::CompleteConfiguration => {
send_message(&SocketMessage::CompleteConfiguration.as_bytes()?)?; send_message(&SocketMessage::CompleteConfiguration.as_bytes()?)?;
} }
SubCommand::AltFocusHack(arg) => {
send_message(&SocketMessage::AltFocusHack(arg.boolean_state.into()).as_bytes()?)?;
}
SubCommand::IdentifyObjectNameChangeApplication(target) => { SubCommand::IdentifyObjectNameChangeApplication(target) => {
send_message( send_message(
&SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id) &SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id)