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).
This commit is contained in:
alex-ds13
2025-02-19 18:43:10 +00:00
committed by LGUG2Z
parent ce99290027
commit c455ad1386
4 changed files with 169 additions and 7 deletions

View File

@@ -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",

View File

@@ -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::<DEV_BROADCAST_DEVICEINTERFACE_W>() 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::<DEV_BROADCAST_DEVICEINTERFACE_W>() 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::<DEV_BROADCAST_DEVICEINTERFACE_W>() 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,
);

View File

@@ -275,6 +275,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> 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<Mutex<WindowManager>>) -> 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<Mutex<WindowManager>>) -> 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<Mutex<WindowManager>>) -> 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

View File

@@ -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<HPOWERNOTIFY> {
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<HDEVNOTIFY> {
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()