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