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()