From 7f5df6f4f4ff9aa8914d267a25559b1f50ef3c5a Mon Sep 17 00:00:00 2001 From: Joe Groocock Date: Sun, 11 Jun 2023 21:47:07 +0100 Subject: [PATCH] x11: reload dpi on PropertyChangeEvent This change allows X11 windows to receive ScaleFactorChanged events when using Xft.dpi via the root window PropertyChange event. Subscribe to PropertyChange events on the root window, and reload all available monitors when an XA_RESOURCE_MANAGER change is received. This is a very heavy handed approach to this problem, but it was the least intrustive way I could find to make it work with my limited skill. Load XResources directly via XGetTextProperty on every get_xft_dpi call as XResourceManagerString returns stale cached data and does not reload for the lifetime of the window (?) Fixes: https://github.com/rust-windowing/winit/issues/1228 Signed-off-by: Joe Groocock --- .../linux/x11/event_processor.rs | 134 ++++++++++-------- src/platform_impl/linux/x11/util/randr.rs | 24 +++- src/platform_impl/linux/x11/window.rs | 8 ++ 3 files changed, 105 insertions(+), 61 deletions(-) diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index d8f076be012..8c156535b7c 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1102,6 +1102,17 @@ impl EventProcessor { _ => {} } } + + ffi::PropertyNotify => { + let xev: &ffi::XPropertyEvent = xev.as_ref(); + + // XA_RESOURCE_MANAGER received from the root window indicates that xresources + // have been changed. Check and update Xft.dpi if the dpi value was changed. + if xev.atom == ffi::XA_RESOURCE_MANAGER { + self.process_dpi_change(&mut callback) + } + } + _ => { if event_type == self.xkbext.first_event_id { let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) }; @@ -1153,62 +1164,7 @@ impl EventProcessor { } } if event_type == self.randr_event_offset { - // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = monitor::invalidate_cached_monitor_list(); - if let Some(prev_list) = prev_list { - let new_list = wt.xconn.available_monitors(); - 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.current_monitor(); - 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 mut new_inner_size = - PhysicalSize::new(new_width, new_height); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_monitor.scale_factor, - new_inner_size: &mut new_inner_size, - }, - }); - - if new_inner_size != old_inner_size { - let (new_width, new_height) = new_inner_size.into(); - window - .set_inner_size_physical(new_width, new_height); - } - } - } - } - } - } - } + self.process_dpi_change(&mut callback) } } } @@ -1301,6 +1257,72 @@ impl EventProcessor { }); } } + + fn process_dpi_change( + &self, + callback: &mut F, + ) where + F: FnMut(Event<'_, T>) + { + let wt = get_xtarget(&self.target); + + // In the future, it would be quite easy to emit monitor hotplug events. + let prev_list = monitor::invalidate_cached_monitor_list(); + if let Some(prev_list) = prev_list { + let new_list = wt.xconn.available_monitors(); + 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.current_monitor(); + 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 mut new_inner_size = + PhysicalSize::new(new_width, new_height); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_monitor.scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + + if new_inner_size != old_inner_size { + let (new_width, new_height) = new_inner_size.into(); + window + .set_inner_size_physical(new_width, new_height); + } + } + } + } + } + } + } + } } fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchPhase) -> bool { diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index a076157b148..c271facf0df 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,11 +1,13 @@ use std::{env, slice, str::FromStr}; + use super::{ ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, *, }; use crate::platform_impl::platform::x11::monitor; use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; +use x11_dl::xlib::XTextProperty; /// Represents values of `WINIT_HIDPI_FACTOR`. pub enum EnvVarDPI { @@ -39,11 +41,23 @@ impl XConnection { // Retrieve DPI from Xft.dpi property pub unsafe fn get_xft_dpi(&self) -> Option { (self.xlib.XrmInitialize)(); - let resource_manager_str = (self.xlib.XResourceManagerString)(self.display); - if resource_manager_str.is_null() { - return None; - } - if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() { + + // Logic inspired by dunst + // https://github.com/dunst-project/dunst/commit/812d5a3b84c093adfbdab9939ee57545e442090c + (self.xlib.XFlush)(self.display); + + let default_screen = (self.xlib.XDefaultScreen)(self.display); + let root = (self.xlib.XRootWindow)(self.display, default_screen); + + let prop = &mut XTextProperty { value: ptr::null_mut(), encoding: 0, format: 0, nitems: 0 }; + + (self.xlib.XLockDisplay)(self.display); + (self.xlib.XGetTextProperty)(self.display, root, &mut *prop, ffi::XA_RESOURCE_MANAGER); + + (self.xlib.XFlush)(self.display); + (self.xlib.XSync)(self.display, 0); + + if let Ok(res) = ::std::ffi::CStr::from_ptr(prop.value as *const i8).to_str() { let name: &str = "Xft.dpi:\t"; for pair in res.split('\n') { if let Some(stripped) = pair.strip_prefix(name) { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index e64d41789c8..1f5fa47d9b3 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -279,6 +279,14 @@ impl UnownedWindow { ) }; + unsafe { + (xconn.xlib.XSelectInput)( + xconn.display, + root, + ffi::PropertyChangeMask, + ); + } + #[allow(clippy::mutex_atomic)] let mut window = UnownedWindow { xconn: Arc::clone(xconn),