From b3c87caa7c040111d8dcae764fe903f54df77426 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 24 Nov 2023 00:14:06 -0800 Subject: [PATCH] On X11, reload DPI on PropertyChange Signed-off-by: John Nunley Fixes #1228. --- CHANGELOG.md | 1 + .../linux/x11/event_processor.rs | 114 ++++++++---------- src/platform_impl/linux/x11/window.rs | 49 +++++++- 3 files changed, 97 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7993a0003f..df94dffedc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Unreleased` header. - Fix crash when running iOS app on macOS. - On X11, check common alternative cursor names when loading cursor. +- On X11, reload the DPI after a property change event. - On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread. - On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account. - On macOS, send a `Resized` event after each `ScaleFactorChanged` event. diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 3e3c0f5e91..6395d35828 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -570,6 +570,14 @@ impl EventProcessor { event: WindowEvent::Destroyed, }); } + ffi::PropertyNotify => { + let xev: &ffi::XPropertyEvent = xev.as_ref(); + let atom = xev.atom as xproto::Atom; + + if atom == xproto::Atom::from(xproto::AtomEnum::RESOURCE_MANAGER) { + self.process_dpi_change(&mut callback); + } + } ffi::VisibilityNotify => { let xev: &ffi::XVisibilityEvent = xev.as_ref(); @@ -1306,72 +1314,7 @@ impl EventProcessor { } } if event_type == self.randr_event_offset as c_int { - // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = wt.xconn.invalidate_cached_monitor_list(); - if let Some(prev_list) = prev_list { - let new_list = wt - .xconn - .available_monitors() - .expect("Failed to get monitor list"); - for new_monitor in new_list { - // Previous list may be empty, in case of disconnecting and - // reconnecting the only one monitor. We still need to emit events in - // this case. - let maybe_prev_scale_factor = prev_list - .iter() - .find(|prev_monitor| prev_monitor.name == new_monitor.name) - .map(|prev_monitor| prev_monitor.scale_factor); - if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { - for (window_id, window) in wt.windows.borrow().iter() { - if let Some(window) = window.upgrade() { - // Check if the window is on this monitor - let monitor = - window.shared_state_lock().last_monitor.clone(); - if monitor.name == new_monitor.name { - let (width, height) = window.inner_size_physical(); - let (new_width, new_height) = window.adjust_for_dpi( - // If we couldn't determine the previous scale - // factor (e.g., because all monitors were closed - // before), just pick whatever the current monitor - // has set as a baseline. - maybe_prev_scale_factor - .unwrap_or(monitor.scale_factor), - new_monitor.scale_factor, - width, - height, - &window.shared_state_lock(), - ); - - let window_id = crate::window::WindowId(*window_id); - let old_inner_size = PhysicalSize::new(width, height); - let inner_size = Arc::new(Mutex::new( - PhysicalSize::new(new_width, new_height), - )); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_monitor.scale_factor, - inner_size_writer: InnerSizeWriter::new( - Arc::downgrade(&inner_size), - ), - }, - }); - - let new_inner_size = *inner_size.lock().unwrap(); - drop(inner_size); - - if new_inner_size != old_inner_size { - let (new_width, new_height) = new_inner_size.into(); - window.request_inner_size_physical( - new_width, new_height, - ); - } - } - } - } - } - } - } + self.process_dpi_change(&mut callback); } } } @@ -1464,6 +1407,45 @@ impl EventProcessor { }); } } + + fn process_dpi_change(&self, callback: &mut F) + where + F: FnMut(Event), + { + let wt = get_xtarget(&self.target); + + // In the future, it would be quite easy to emit monitor hotplug events. + let prev_list = { + let prev_list = wt.xconn.invalidate_cached_monitor_list(); + match prev_list { + Some(prev_list) => prev_list, + None => return, + } + }; + + let new_list = wt + .xconn + .available_monitors() + .expect("Failed to get monitor list"); + for new_monitor in new_list { + // Previous list may be empty, in case of disconnecting and + // reconnecting the only one monitor. We still need to emit events in + // this case. + let maybe_prev_scale_factor = prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| prev_monitor.scale_factor); + if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { + for window in wt.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) { + window.refresh_dpi_for_monitor( + &new_monitor, + maybe_prev_scale_factor, + &mut *callback, + ) + } + } + } + } } fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchPhase) -> bool { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 1b08869f5f..b2e45bd91c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -22,6 +22,7 @@ use x11rb::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, + event::{Event, InnerSizeWriter, WindowEvent}, event_loop::AsyncRequestSerial, platform_impl::{ x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error}, @@ -276,7 +277,8 @@ impl UnownedWindow { | EventMask::KEYMAP_STATE | EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE - | EventMask::POINTER_MOTION; + | EventMask::POINTER_MOTION + | EventMask::PROPERTY_CHANGE; aux = aux.event_mask(event_mask).border_pixel(0); @@ -923,6 +925,51 @@ impl UnownedWindow { }) } + /// Refresh the API for the given monitor. + #[inline] + pub(super) fn refresh_dpi_for_monitor( + &self, + new_monitor: &X11MonitorHandle, + maybe_prev_scale_factor: Option, + mut callback: impl FnMut(Event), + ) { + // Check if the self is on this monitor + let monitor = self.shared_state_lock().last_monitor.clone(); + if monitor.name == new_monitor.name { + let (width, height) = self.inner_size_physical(); + let (new_width, new_height) = self.adjust_for_dpi( + // If we couldn't determine the previous scale + // factor (e.g., because all monitors were closed + // before), just pick whatever the current monitor + // has set as a baseline. + maybe_prev_scale_factor.unwrap_or(monitor.scale_factor), + new_monitor.scale_factor, + width, + height, + &self.shared_state_lock(), + ); + + let window_id = crate::window::WindowId(self.id()); + let old_inner_size = PhysicalSize::new(width, height); + let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height))); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_monitor.scale_factor, + inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)), + }, + }); + + let new_inner_size = *inner_size.lock().unwrap(); + drop(inner_size); + + if new_inner_size != old_inner_size { + let (new_width, new_height) = new_inner_size.into(); + self.request_inner_size_physical(new_width, new_height); + } + } + } + fn set_minimized_inner(&self, minimized: bool) -> Result, X11Error> { let atoms = self.xconn.atoms();