Skip to content

Commit

Permalink
x11: reload dpi on PropertyChangeEvent
Browse files Browse the repository at this point in the history
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 (?)

Signed-off-by: Joe Groocock <[email protected]>
  • Loading branch information
frebib committed Aug 20, 2022
1 parent a4a156f commit ec4af7d
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 61 deletions.
134 changes: 78 additions & 56 deletions src/platform_impl/linux/x11/event_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1184,64 +1184,20 @@ impl<T: 'static> EventProcessor<T> {
_ => {}
}
}

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.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)
}
}
}
Expand Down Expand Up @@ -1352,6 +1308,72 @@ impl<T: 'static> EventProcessor<T> {
});
}
}

fn process_dpi_change<F>(
&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<u64>, num: &mut u32, id: u64, phase: TouchPhase) -> bool {
Expand Down
24 changes: 19 additions & 5 deletions src/platform_impl/linux/x11/util/randr.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -39,11 +41,23 @@ impl XConnection {
// Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
(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 pair.starts_with(&name) {
Expand Down
8 changes: 8 additions & 0 deletions src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,14 @@ impl UnownedWindow {
)
};

unsafe {
(xconn.xlib.XSelectInput)(
xconn.display,
root,
ffi::PropertyChangeMask,
);
}

let mut window = UnownedWindow {
xconn: Arc::clone(xconn),
xwindow,
Expand Down

0 comments on commit ec4af7d

Please sign in to comment.