From c455ad13860f1e65790537bed5537335a4ab3e92 Mon Sep 17 00:00:00 2001 From: alex-ds13 <145657253+alex-ds13@users.noreply.github.com> Date: Wed, 19 Feb 2025 18:43:10 +0000 Subject: [PATCH] feat(wm): register more monitor reconcilator events This commits adds a few more events that can trigger a `DisplayConnectionChange` event. Some of these events are redundant and after a display is disconnected/reconnected it emits multiple `DisplayConnectionChange` events. However, when trying to remove them to have just one it stopped behaving as it should, as if it was missing the update, while having them duplicated it works properly. Therefore it appears to be better to keep them for now, since the duplicated events will exit early as soon as they see that the monitor counts match (on the first event the counts don't match so it adds/removes the monitor and the following events see that the counts match). --- Cargo.toml | 2 + komorebi/src/monitor_reconciliator/hidden.rs | 101 ++++++++++++++++++- komorebi/src/monitor_reconciliator/mod.rs | 47 ++++++++- komorebi/src/windows_api.rs | 26 +++++ 4 files changed, 169 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0e81b6d..28ba6917 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ version = "0.58" features = [ "implement", "Foundation_Numerics", + "Win32_Devices", "Win32_System_Com", "Win32_UI_Shell_Common", # for IObjectArray "Win32_Foundation", @@ -55,6 +56,7 @@ features = [ "Win32_Graphics_Direct2D_Common", "Win32_Graphics_Dxgi_Common", "Win32_System_LibraryLoader", + "Win32_System_Power", "Win32_System_RemoteDesktop", "Win32_System_Threading", "Win32_UI_Accessibility", diff --git a/komorebi/src/monitor_reconciliator/hidden.rs b/komorebi/src/monitor_reconciliator/hidden.rs index 225d5e75..91954894 100644 --- a/komorebi/src/monitor_reconciliator/hidden.rs +++ b/komorebi/src/monitor_reconciliator/hidden.rs @@ -2,21 +2,33 @@ use std::sync::mpsc; use std::time::Duration; use windows::core::PCWSTR; +use windows::Win32::Devices::Display::GUID_DEVINTERFACE_DISPLAY_ADAPTER; +use windows::Win32::Devices::Display::GUID_DEVINTERFACE_MONITOR; +use windows::Win32::Devices::Display::GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL; use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::LPARAM; use windows::Win32::Foundation::LRESULT; use windows::Win32::Foundation::WPARAM; +use windows::Win32::System::Power::POWERBROADCAST_SETTING; +use windows::Win32::System::SystemServices::GUID_LIDSWITCH_STATE_CHANGE; use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW; use windows::Win32::UI::WindowsAndMessaging::GetMessageW; use windows::Win32::UI::WindowsAndMessaging::TranslateMessage; use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW; use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW; +use windows::Win32::UI::WindowsAndMessaging::DBT_CONFIGCHANGED; +use windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEARRIVAL; +use windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEREMOVECOMPLETE; use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED; +use windows::Win32::UI::WindowsAndMessaging::DBT_DEVTYP_DEVICEINTERFACE; +use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W; use windows::Win32::UI::WindowsAndMessaging::MSG; use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC; use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMESUSPEND; use windows::Win32::UI::WindowsAndMessaging::PBT_APMSUSPEND; +use windows::Win32::UI::WindowsAndMessaging::PBT_POWERSETTINGCHANGE; +use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS; use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA; use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE; use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE; @@ -92,8 +104,57 @@ impl Hidden { let hwnd = hwnd_receiver.recv()?; + // Register Session Lock/Unlock events WindowsApi::wts_register_session_notification(hwnd)?; + // Register Laptop lid open/close events + WindowsApi::register_power_setting_notification( + hwnd, + &GUID_LIDSWITCH_STATE_CHANGE, + REGISTER_NOTIFICATION_FLAGS(0), + )?; + + // Register device interface events for multiple display related devices. Some of this + // device interfaces might not be needed but it doesn't hurt to have them in case some user + // uses some output device as monitor that falls into one of these device interface class + // GUID. + let monitor_filter = DEV_BROADCAST_DEVICEINTERFACE_W { + dbcc_size: std::mem::size_of::() as u32, + dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0, + dbcc_reserved: 0, + dbcc_classguid: GUID_DEVINTERFACE_MONITOR, + dbcc_name: [0; 1], + }; + let display_adapter_filter = DEV_BROADCAST_DEVICEINTERFACE_W { + dbcc_size: std::mem::size_of::() as u32, + dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0, + dbcc_reserved: 0, + dbcc_classguid: GUID_DEVINTERFACE_DISPLAY_ADAPTER, + dbcc_name: [0; 1], + }; + let video_output_filter = DEV_BROADCAST_DEVICEINTERFACE_W { + dbcc_size: std::mem::size_of::() as u32, + dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0, + dbcc_reserved: 0, + dbcc_classguid: GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL, + dbcc_name: [0; 1], + }; + WindowsApi::register_device_notification( + hwnd, + monitor_filter, + REGISTER_NOTIFICATION_FLAGS(0), + )?; + WindowsApi::register_device_notification( + hwnd, + display_adapter_filter, + REGISTER_NOTIFICATION_FLAGS(0), + )?; + WindowsApi::register_device_notification( + hwnd, + video_output_filter, + REGISTER_NOTIFICATION_FLAGS(0), + )?; + Ok(Self { hwnd }) } @@ -128,6 +189,35 @@ impl Hidden { ); LRESULT(0) } + // Monitor change power status + PBT_POWERSETTINGCHANGE => { + if let POWERBROADCAST_SETTING { + PowerSetting: GUID_LIDSWITCH_STATE_CHANGE, + DataLength: _, + Data: [0], + } = *(lparam.0 as *const POWERBROADCAST_SETTING) + { + tracing::debug!( + "WM_POWERBROADCAST event received - laptop lid closed" + ); + monitor_reconciliator::send_notification( + monitor_reconciliator::MonitorNotification::DisplayConnectionChange, + ); + } else if let POWERBROADCAST_SETTING { + PowerSetting: GUID_LIDSWITCH_STATE_CHANGE, + DataLength: _, + Data: [1], + } = *(lparam.0 as *const POWERBROADCAST_SETTING) + { + tracing::debug!( + "WM_POWERBROADCAST event received - laptop lid opened" + ); + monitor_reconciliator::send_notification( + monitor_reconciliator::MonitorNotification::DisplayConnectionChange, + ); + } + LRESULT(0) + } _ => LRESULT(0), } } @@ -188,10 +278,15 @@ impl Hidden { // Original idea from https://stackoverflow.com/a/33762334 WM_DEVICECHANGE => { #[allow(clippy::cast_possible_truncation)] - if wparam.0 as u32 == DBT_DEVNODES_CHANGED { + let event = wparam.0 as u32; + if event == DBT_DEVNODES_CHANGED + || event == DBT_CONFIGCHANGED + || event == DBT_DEVICEARRIVAL + || event == DBT_DEVICEREMOVECOMPLETE + { tracing::debug!( - "WM_DEVICECHANGE event received with DBT_DEVNODES_CHANGED - display added or removed" - ); + "WM_DEVICECHANGE event received with one of [DBT_DEVNODES_CHANGED, DBT_CONFIGCHANGED, DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE] - display added or removed" + ); monitor_reconciliator::send_notification( monitor_reconciliator::MonitorNotification::DisplayConnectionChange, ); diff --git a/komorebi/src/monitor_reconciliator/mod.rs b/komorebi/src/monitor_reconciliator/mod.rs index 2f28f141..4c760f4c 100644 --- a/komorebi/src/monitor_reconciliator/mod.rs +++ b/komorebi/src/monitor_reconciliator/mod.rs @@ -275,6 +275,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result if initial_monitor_count == attached_devices.len() { tracing::debug!("monitor counts match, reconciliation not required"); + drop(wm); continue 'receiver; } @@ -282,6 +283,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result tracing::debug!( "no devices found, skipping reconciliation to avoid breaking state" ); + drop(wm); continue 'receiver; } @@ -310,25 +312,58 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result newly_removed_displays.push(id.clone()); - for workspace in m.workspaces() { - for container in workspace.containers() { - for window in container.windows() { + let focused_workspace_idx = m.focused_workspace_idx(); + + for (idx, workspace) in m.workspaces().iter().enumerate() { + let is_focused_workspace = idx == focused_workspace_idx; + let focused_container_idx = workspace.focused_container_idx(); + for (c_idx, container) in workspace.containers().iter().enumerate() + { + let focused_window_idx = container.focused_window_idx(); + for (w_idx, window) in container.windows().iter().enumerate() { windows_to_remove.push(window.hwnd); + if is_focused_workspace + && c_idx == focused_container_idx + && w_idx == focused_window_idx + { + // Minimize the focused window since Windows might try + // to move it to another monitor if it was focused. + if window.is_focused() { + window.minimize(); + } + } } } if let Some(maximized) = workspace.maximized_window() { windows_to_remove.push(maximized.hwnd); + // Minimize the focused window since Windows might try + // to move it to another monitor if it was focused. + if maximized.is_focused() { + maximized.minimize(); + } } if let Some(container) = workspace.monocle_container() { for window in container.windows() { windows_to_remove.push(window.hwnd); } + if let Some(window) = container.focused_window() { + // Minimize the focused window since Windows might try + // to move it to another monitor if it was focused. + if window.is_focused() { + window.minimize(); + } + } } for window in workspace.floating_windows() { windows_to_remove.push(window.hwnd); + // Minimize the focused window since Windows might try + // to move it to another monitor if it was focused. + if window.is_focused() { + window.minimize(); + } } } @@ -453,8 +488,12 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result empty_containers.push(idx); } - if is_focused_workspace && idx == focused_container_idx { + if is_focused_workspace { if let Some(window) = container.focused_window() { + tracing::debug!( + "restoring window: {}", + window.hwnd + ); WindowsApi::restore_window(window.hwnd); } else { // If the focused window was moved or removed by diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 044ad1c6..729c2c72 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -49,6 +49,8 @@ use windows::Win32::Graphics::Gdi::MONITORENUMPROC; use windows::Win32::Graphics::Gdi::MONITORINFOEXW; use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST; use windows::Win32::System::LibraryLoader::GetModuleHandleW; +use windows::Win32::System::Power::RegisterPowerSettingNotification; +use windows::Win32::System::Power::HPOWERNOTIFY; use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId; use windows::Win32::System::RemoteDesktop::WTSRegisterSessionNotification; use windows::Win32::System::Threading::GetCurrentProcessId; @@ -93,6 +95,7 @@ use windows::Win32::UI::WindowsAndMessaging::MoveWindow; use windows::Win32::UI::WindowsAndMessaging::PostMessageW; use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW; use windows::Win32::UI::WindowsAndMessaging::RegisterClassW; +use windows::Win32::UI::WindowsAndMessaging::RegisterDeviceNotificationW; use windows::Win32::UI::WindowsAndMessaging::SetCursorPos; use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow; use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes; @@ -102,11 +105,14 @@ use windows::Win32::UI::WindowsAndMessaging::ShowWindow; use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW; use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint; use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT; +use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W; use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE; use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT; +use windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY; use windows::Win32::UI::WindowsAndMessaging::HWND_TOP; use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA; +use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS; use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS; use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD; use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE; @@ -1210,6 +1216,26 @@ impl WindowsApi { .process() } + pub fn register_power_setting_notification( + hwnd: isize, + guid: &windows_core::GUID, + flags: REGISTER_NOTIFICATION_FLAGS, + ) -> WindowsCrateResult { + unsafe { RegisterPowerSettingNotification(HWND(as_ptr!(hwnd)), guid, flags) } + } + + pub fn register_device_notification( + hwnd: isize, + mut filter: DEV_BROADCAST_DEVICEINTERFACE_W, + flags: REGISTER_NOTIFICATION_FLAGS, + ) -> WindowsCrateResult { + unsafe { + let state_ptr: *const core::ffi::c_void = + &mut filter as *mut _ as *const core::ffi::c_void; + RegisterDeviceNotificationW(HWND(as_ptr!(hwnd)), state_ptr, flags) + } + } + pub fn invalidate_rect(hwnd: isize, rect: Option<&Rect>, erase: bool) -> bool { let rect = rect.map(|rect| &rect.rect() as *const RECT); unsafe { InvalidateRect(HWND(as_ptr!(hwnd)), rect, erase) }.as_bool()