From c42b218bb124d8e7892c16a203d7abd757c68731 Mon Sep 17 00:00:00 2001 From: Charles Saracco Date: Sun, 14 Jun 2020 00:14:01 -0400 Subject: [PATCH] Better DPI scale factor strategy for X11 --- Cargo.toml | 1 + README.md | 15 +++--- src/x11/window.rs | 113 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 95 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c1cd457..2ec317a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ gl = "0.14.0" [target.'cfg(target_os="linux")'.dependencies] xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } x11 = { version = "2.3", features = ["xlib", "glx"]} +libc = "0.2" [target.'cfg(target_os="windows")'.dependencies] winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi"] } diff --git a/README.md b/README.md index 430c603..d09951b 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ A low-level windowing system geared towards making audio plugin UIs. Below is a proposed list of milestones (roughly in-order) and their status. Subject to change at any time. -| Feature | Windows | Mac OS | Linux | -| -------------------------------------- | ------------------ | ------------------ | ------------------ | -| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Window uses an OpenGL surface | :heavy_check_mark: | | :heavy_check_mark: | -| Basic DPI scaling support | | | :question: | +| Feature | Windows | Mac OS | Linux | +| ----------------------------------------------- | ------------------ | ------------------ | ------------------ | +| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Window uses an OpenGL surface | :heavy_check_mark: | | :heavy_check_mark: | +| Can find DPI scale factor | | | :heavy_check_mark: | +| Basic event handling (mouse, keyboard) | | | | +| Parent window support | | | | +| *(Converge on a common API for all platforms?)* | | | | diff --git a/src/x11/window.rs b/src/x11/window.rs index 999f2bf..0dd697d 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -19,6 +19,7 @@ use crate::WindowOpenOptions; pub struct Window { xcb_connection: XcbConnection, + scaling: Option, // DPI scale, 96.0 is "default". } impl Window { @@ -229,8 +230,15 @@ impl Window { xlib::XSync(xcb_connection.conn.get_raw_dpy(), xlib::False); } - let x11_window = Self { xcb_connection }; + let mut x11_window = Self { + xcb_connection, + scaling: None, + }; + x11_window.scaling = x11_window + .get_scaling_xft() + .or(x11_window.get_scaling_screen_dimensions()); + println!("Scale factor: {:?}", x11_window.scaling); x11_window.handle_events(window_id, ctx); return x11_window; @@ -259,34 +267,83 @@ impl Window { } } } -} -// Figure out the DPI scaling by opening a new temporary connection and asking XCB -// TODO: currently returning (96, 96) on my system, even though I have 4k screens. Problem with my setup perhaps? -#[allow(dead_code)] -pub fn get_scaling() -> (u32, u32) { - // Connect to the X server - let xcb_connection = XcbConnection::new(); + // Try to get the scaling with this function first. + // If this gives you `None`, fall back to `get_scaling_screen_dimensions`. + // If neither work, I guess just assume 96.0 and don't do any scaling. + fn get_scaling_xft(&self) -> Option { + use std::ffi::CString; + use x11::xlib::{ + XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, + XrmValue, + }; - // Figure out screen information - let setup = xcb_connection.conn.get_setup(); - let screen = setup - .roots() - .nth(xcb_connection.xlib_display as usize) - .unwrap(); + let display = self.xcb_connection.conn.get_raw_dpy(); + unsafe { + let rms = XResourceManagerString(display); + if !rms.is_null() { + let db = XrmGetStringDatabase(rms); + if !db.is_null() { + let mut value = XrmValue { + size: 0, + addr: std::ptr::null_mut(), + }; - // Get the DPI from the screen struct - // - // there are 2.54 centimeters to an inch; so there are 25.4 millimeters. - // dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch)) - // = N pixels / (M inch / 25.4) - // = N * 25.4 pixels / M inch - let width_px = screen.width_in_pixels() as f64; - let width_mm = screen.width_in_millimeters() as f64; - let height_px = screen.height_in_pixels() as f64; - let height_mm = screen.height_in_millimeters() as f64; - let xres = width_px * 25.4 / width_mm; - let yres = height_px * 25.4 / height_mm; + let mut value_type: *mut libc::c_char = std::ptr::null_mut(); + let name_c_str = CString::new("Xft.dpi").unwrap(); + let c_str = CString::new("Xft.Dpi").unwrap(); - ((xres + 0.5) as u32, (yres + 0.5) as u32) -} + let dpi = if XrmGetResource( + db, + name_c_str.as_ptr(), + c_str.as_ptr(), + &mut value_type, + &mut value, + ) != 0 + && !value.addr.is_null() + { + let value_addr: &CStr = CStr::from_ptr(value.addr); + value_addr.to_str().ok(); + let value_str = value_addr.to_str().ok()?; + let value_f64 = value_str.parse().ok()?; + Some(value_f64) + } else { + None + }; + XrmDestroyDatabase(db); + + return dpi; + } + } + } + None + } + + // Try to get the scaling with `get_scaling_xft` first. + // Only use this function as a fallback. + // If neither work, I guess just assume 96.0 and don't do any scaling. + fn get_scaling_screen_dimensions(&self) -> Option { + // Figure out screen information + let setup = self.xcb_connection.conn.get_setup(); + let screen = setup + .roots() + .nth(self.xcb_connection.xlib_display as usize) + .unwrap(); + + // Get the DPI from the screen struct + // + // there are 2.54 centimeters to an inch; so there are 25.4 millimeters. + // dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch)) + // = N pixels / (M inch / 25.4) + // = N * 25.4 pixels / M inch + let width_px = screen.width_in_pixels() as f64; + let width_mm = screen.width_in_millimeters() as f64; + let height_px = screen.height_in_pixels() as f64; + let height_mm = screen.height_in_millimeters() as f64; + let _xres = width_px * 25.4 / width_mm; + let yres = height_px * 25.4 / height_mm; + + // TODO: choose between `xres` and `yres`? (probably both are the same?) + Some(yres) + } +} \ No newline at end of file