fix(wm): match display name / avoid id volatility

This commit replaces all usages of MONITORINFO with MONITORINFOEX in
order to retrieve a name for each connected display device.

This display device name makes for easier deduping during monitor
reconciliation, so that matching display monitor names can simply have
their hmonitor id updated instead of trying to figure out which id
corresponds to which monitor by looking at the windows currently visible
on each.

fix #267
This commit is contained in:
LGUG2Z
2022-10-24 16:19:10 -07:00
committed by جاد
parent 91c532d9b1
commit 37f1a163cc
4 changed files with 92 additions and 60 deletions

View File

@@ -21,6 +21,8 @@ pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")] #[getset(get_copy = "pub", set = "pub")]
id: isize, id: isize,
#[getset(get = "pub", set = "pub")] #[getset(get = "pub", set = "pub")]
name: String,
#[getset(get = "pub", set = "pub")]
size: Rect, size: Rect,
#[getset(get = "pub", set = "pub")] #[getset(get = "pub", set = "pub")]
work_area_size: Rect, work_area_size: Rect,
@@ -32,12 +34,13 @@ pub struct Monitor {
impl_ring_elements!(Monitor, Workspace); impl_ring_elements!(Monitor, Workspace);
pub fn new(id: isize, size: Rect, work_area_size: Rect) -> Monitor { pub fn new(id: isize, size: Rect, work_area_size: Rect, name: String) -> Monitor {
let mut workspaces = Ring::default(); let mut workspaces = Ring::default();
workspaces.elements_mut().push_back(Workspace::default()); workspaces.elements_mut().push_back(Workspace::default());
Monitor { Monitor {
id, id,
name,
size, size,
work_area_size, work_area_size,
workspaces, workspaces,

View File

@@ -327,65 +327,63 @@ impl WindowManager {
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn reconcile_monitors(&mut self) -> Result<()> { pub fn reconcile_monitors(&mut self) -> Result<()> {
let valid_hmonitors = WindowsApi::valid_hmonitors()?; let valid_hmonitors = WindowsApi::valid_hmonitors()?;
let mut invalid = vec![]; let mut valid_names = vec![];
let mut updated = vec![]; let before_count = self.monitors().len();
let mut overlapping = vec![];
for monitor in self.monitors_mut() { for monitor in self.monitors_mut() {
if !valid_hmonitors.contains(&monitor.id()) { for (valid_name, valid_id) in &valid_hmonitors {
let mut mark_as_invalid = true; let actual_name = monitor.name().clone();
if actual_name == *valid_name {
// If an invalid hmonitor has at least one window in the window manager state, monitor.set_id(*valid_id);
// we can attempt to update its hmonitor id in-place so that it doesn't get reaped valid_names.push(actual_name);
//
// This needs to be done because when monitors are attached and detached, even
// monitors that remained connected get assigned new HMONITOR values
if let Some(workspace) = monitor.focused_workspace() {
if let Some(container) = workspace.focused_container() {
if let Some(window) = container.focused_window() {
let actual_hmonitor = WindowsApi::monitor_from_window(window.hwnd());
if actual_hmonitor != monitor.id() {
monitor.set_id(actual_hmonitor);
if updated.contains(&actual_hmonitor) {
overlapping.push(monitor.clone());
} else {
mark_as_invalid = false;
updated.push(actual_hmonitor);
}
}
}
}
} }
}
}
if mark_as_invalid { let mut orphaned_containers = vec![];
invalid.push(monitor.id());
for invalid in self
.monitors()
.iter()
.filter(|m| !valid_names.contains(m.name()))
{
for workspace in invalid.workspaces() {
for container in workspace.containers() {
// Save the orphaned containers from an invalid monitor
// (which has most likely been disconnected)
orphaned_containers.push(container.clone());
} }
} }
} }
// Remove any invalid monitors from our state // Remove any invalid monitors from our state
self.monitors_mut().retain(|m| !invalid.contains(&m.id())); self.monitors_mut()
.retain(|m| valid_names.contains(m.name()));
// If monitor IDs are overlapping we are fucked and need to load let after_count = self.monitors().len();
// monitor and workspace state again, followed by the configuration
if !overlapping.is_empty() {
WindowsApi::load_monitor_information(&mut self.monitors)?;
WindowsApi::load_workspace_information(&mut self.monitors)?;
std::thread::spawn(|| { if let Some(primary) = self.monitors_mut().front_mut() {
load_configuration().expect("could not load configuration"); if let Some(focused_ws) = primary.focused_workspace_mut() {
}); let focused_container_idx = focused_ws.focused_container_idx();
// Put the orphaned containers somewhere visible
for container in orphaned_containers {
focused_ws.add_container(container);
}
// Gotta reset the focus or the movement will feel "off"
focused_ws.focus_container(focused_container_idx);
}
} }
let invisible_borders = self.invisible_borders; let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset; let offset = self.work_area_offset;
for monitor in self.monitors_mut() { for monitor in self.monitors_mut() {
let mut should_update = false; // If we have lost a monitor, update everything to filter out any jank
let reference = WindowsApi::monitor(monitor.id())?; let mut should_update = before_count != after_count;
// TODO: If this is different, force a redraw
let reference = WindowsApi::monitor(monitor.id())?;
if reference.work_area_size() != monitor.work_area_size() { if reference.work_area_size() != monitor.work_area_size() {
monitor.set_work_area_size(Rect { monitor.set_work_area_size(Rect {
left: reference.work_area_size().left, left: reference.work_area_size().left,
@@ -416,6 +414,16 @@ impl WindowManager {
// Check for and add any new monitors that may have been plugged in // Check for and add any new monitors that may have been plugged in
WindowsApi::load_monitor_information(&mut self.monitors)?; WindowsApi::load_monitor_information(&mut self.monitors)?;
let final_count = self.monitors().len();
if after_count != final_count {
self.retile_all(true)?;
// Second retile to fix DPI/resolution related jank when a window
// moves between monitors with different resolutions - this doesn't
// really get seen by the user since the screens are flickering anyway
// as a result of the display connections / disconnections
self.retile_all(true)?;
}
Ok(()) Ok(())
} }

View File

@@ -1,6 +1,8 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ffi::c_void; use std::ffi::c_void;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow; use color_eyre::eyre::anyhow;
@@ -36,7 +38,7 @@ use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC; use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR; use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::MONITORENUMPROC; use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFO; use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST; use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::System::LibraryLoader::GetModuleHandleW; use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId; use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
@@ -202,12 +204,12 @@ impl WindowsApi {
.process() .process()
} }
pub fn valid_hmonitors() -> Result<Vec<isize>> { pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
let mut monitors: Vec<isize> = vec![]; let mut monitors: Vec<(String, isize)> = vec![];
let monitors_ref: &mut Vec<isize> = monitors.as_mut(); let monitors_ref: &mut Vec<(String, isize)> = monitors.as_mut();
Self::enum_display_monitors( Self::enum_display_monitors(
Option::Some(windows_callbacks::valid_display_monitors), Option::Some(windows_callbacks::valid_display_monitors),
monitors_ref as *mut Vec<isize> as isize, monitors_ref as *mut Vec<(String, isize)> as isize,
)?; )?;
Ok(monitors) Ok(monitors)
@@ -228,7 +230,7 @@ impl WindowsApi {
pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> { pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> {
for monitor in monitors.elements_mut() { for monitor in monitors.elements_mut() {
let monitor_id = monitor.id(); let monitor_name = monitor.name().clone();
if let Some(workspace) = monitor.workspaces_mut().front_mut() { if let Some(workspace) = monitor.workspaces_mut().front_mut() {
// EnumWindows will enumerate through windows on all monitors // EnumWindows will enumerate through windows on all monitors
Self::enum_windows( Self::enum_windows(
@@ -246,7 +248,7 @@ impl WindowsApi {
for container in workspace.containers_mut() { for container in workspace.containers_mut() {
for window in container.windows() { for window in container.windows() {
if Self::monitor_from_window(window.hwnd()) != monitor_id { if Self::monitor_name_from_window(window.hwnd())? != monitor_name {
windows_on_other_monitors.push(window.hwnd().0); windows_on_other_monitors.push(window.hwnd().0);
} }
} }
@@ -273,6 +275,16 @@ impl WindowsApi {
unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0 unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0
} }
pub fn monitor_name_from_window(hwnd: HWND) -> Result<String> {
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
Ok(
Self::monitor(unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0)?
.name()
.to_string(),
)
}
pub fn monitor_from_point(point: POINT) -> isize { pub fn monitor_from_point(point: POINT) -> isize {
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL // MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
@@ -612,24 +624,30 @@ impl WindowsApi {
unsafe { IsIconic(hwnd) }.into() unsafe { IsIconic(hwnd) }.into()
} }
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFO> { pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFOEXW> {
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() }; let mut ex_info = MONITORINFOEXW::default();
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?; ex_info.monitorInfo.cbSize = u32::try_from(std::mem::size_of::<MONITORINFOEXW>())?;
unsafe { GetMonitorInfoW(hmonitor, &mut ex_info.monitorInfo) }
unsafe { GetMonitorInfoW(hmonitor, &mut monitor_info) }
.ok() .ok()
.process()?; .process()?;
Ok(monitor_info) Ok(ex_info)
} }
pub fn monitor(hmonitor: isize) -> Result<Monitor> { pub fn monitor(hmonitor: isize) -> Result<Monitor> {
let monitor_info = Self::monitor_info_w(HMONITOR(hmonitor))?; let ex_info = Self::monitor_info_w(HMONITOR(hmonitor))?;
let name = OsString::from_wide(&ex_info.szDevice);
let name = name
.to_string_lossy()
.replace('\u{0000}', "")
.trim_start_matches(r"\\.\")
.to_string();
Ok(monitor::new( Ok(monitor::new(
hmonitor, hmonitor,
monitor_info.rcMonitor.into(), ex_info.monitorInfo.rcMonitor.into(),
monitor_info.rcWork.into(), ex_info.monitorInfo.rcWork.into(),
name,
)) ))
} }

View File

@@ -47,8 +47,11 @@ pub extern "system" fn valid_display_monitors(
_: *mut RECT, _: *mut RECT,
lparam: LPARAM, lparam: LPARAM,
) -> BOOL { ) -> BOOL {
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<isize>) }; let monitors = unsafe { &mut *(lparam.0 as *mut Vec<(String, isize)>) };
monitors.push(hmonitor.0); if let Ok(m) = WindowsApi::monitor(hmonitor.0) {
monitors.push((m.name().to_string(), hmonitor.0));
}
true.into() true.into()
} }