x11: HiDPI support (#11140)

apricotbucket28 created

Fixes https://github.com/zed-industries/zed/issues/11121

Release Notes:

- N/A

Change summary

crates/gpui/Cargo.toml                       |  8 ++
crates/gpui/src/platform/linux/x11/client.rs | 75 +++++++++++++++++----
crates/gpui/src/platform/linux/x11/window.rs | 20 ++++-
3 files changed, 83 insertions(+), 20 deletions(-)

Detailed changes

crates/gpui/Cargo.toml 🔗

@@ -114,7 +114,13 @@ wayland-protocols = { version = "0.31.2", features = [
 oo7 = "0.3.0"
 open = "5.1.2"
 filedescriptor = "0.8.2"
-x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr", "xinput"] }
+x11rb = { version = "0.13.0", features = [
+    "allow-unsafe-code",
+    "xkb",
+    "randr",
+    "xinput",
+    "resource_manager",
+] }
 xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
 
 [target.'cfg(windows)'.dependencies]

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -16,6 +16,7 @@ use x11rb::protocol::xinput::{ConnectionExt, ScrollClass};
 use x11rb::protocol::xkb::ConnectionExt as _;
 use x11rb::protocol::xproto::ConnectionExt as _;
 use x11rb::protocol::{randr, xinput, xkb, xproto, Event};
+use x11rb::resource_manager::Database;
 use x11rb::xcb_ffi::XCBConnection;
 use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
 use xkbcommon::xkb as xkbc;
@@ -23,9 +24,9 @@ use xkbcommon::xkb as xkbc;
 use crate::platform::linux::LinuxClient;
 use crate::platform::{LinuxCommon, PlatformWindow};
 use crate::{
-    modifiers_from_xinput_info, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId, Modifiers,
-    ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size,
-    TouchPhase, WindowParams, X11Window,
+    modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
+    Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta,
+    Size, TouchPhase, WindowParams, X11Window,
 };
 
 use super::{super::SCROLL_LINES, X11Display, X11WindowStatePtr, XcbAtoms};
@@ -58,8 +59,11 @@ pub struct X11ClientState {
     pub(crate) last_location: Point<Pixels>,
     pub(crate) current_count: usize,
 
+    pub(crate) scale_factor: f32,
+
     pub(crate) xcb_connection: Rc<XCBConnection>,
     pub(crate) x_root_index: usize,
+    pub(crate) resource_database: Database,
     pub(crate) atoms: XcbAtoms,
     pub(crate) windows: HashMap<xproto::Window, WindowRef>,
     pub(crate) focused_window: Option<xproto::Window>,
@@ -178,6 +182,31 @@ impl X11Client {
             xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
         };
 
+        let screen = xcb_connection.setup().roots.get(x_root_index).unwrap();
+
+        // Values from `Database::GET_RESOURCE_DATABASE`
+        let resource_manager = xcb_connection
+            .get_property(
+                false,
+                screen.root,
+                xproto::AtomEnum::RESOURCE_MANAGER,
+                xproto::AtomEnum::STRING,
+                0,
+                100_000_000,
+            )
+            .unwrap();
+        let resource_manager = resource_manager.reply().unwrap();
+
+        // todo(linux): read hostname
+        let resource_database = Database::new_from_default(&resource_manager, "HOSTNAME".into());
+
+        let scale_factor = resource_database
+            .get_value("Xft.dpi", "Xft.dpi")
+            .ok()
+            .flatten()
+            .map(|dpi: f32| dpi / 96.0)
+            .unwrap_or(1.0);
+
         let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap();
         let primary = X11ClipboardContext::<Primary>::new().unwrap();
 
@@ -212,9 +241,11 @@ impl X11Client {
             last_click: Instant::now(),
             last_location: Point::new(px(0.0), px(0.0)),
             current_count: 0,
+            scale_factor,
 
             xcb_connection,
             x_root_index,
+            resource_database,
             atoms,
             windows: HashMap::default(),
             focused_window: None,
@@ -345,8 +376,10 @@ impl X11Client {
                 let mut state = self.0.borrow_mut();
 
                 let modifiers = modifiers_from_state(event.state);
-                let position =
-                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                let position = point(
+                    px(event.event_x as f32 / state.scale_factor),
+                    px(event.event_y as f32 / state.scale_factor),
+                );
                 if let Some(button) = button_of_key(event.detail) {
                     let click_elapsed = state.last_click.elapsed();
 
@@ -378,8 +411,10 @@ impl X11Client {
                 let window = self.get_window(event.event)?;
                 let state = self.0.borrow();
                 let modifiers = modifiers_from_state(event.state);
-                let position =
-                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                let position = point(
+                    px(event.event_x as f32 / state.scale_factor),
+                    px(event.event_y as f32 / state.scale_factor),
+                );
                 if let Some(button) = button_of_key(event.detail) {
                     let click_count = state.current_count;
                     drop(state);
@@ -393,11 +428,12 @@ impl X11Client {
             }
             Event::XinputMotion(event) => {
                 let window = self.get_window(event.event)?;
-
-                let position = Point::new(
-                    (event.event_x as f32 / u16::MAX as f32).into(),
-                    (event.event_y as f32 / u16::MAX as f32).into(),
+                let state = self.0.borrow();
+                let position = point(
+                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
+                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
                 );
+                drop(state);
                 let modifiers = modifiers_from_xinput_info(event.mods);
 
                 let axisvalues = event
@@ -471,10 +507,14 @@ impl X11Client {
             }
             Event::MotionNotify(event) => {
                 let window = self.get_window(event.event)?;
+                let state = self.0.borrow();
                 let pressed_button = super::button_from_state(event.state);
-                let position =
-                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                let position = point(
+                    px(event.event_x as f32 / state.scale_factor),
+                    px(event.event_y as f32 / state.scale_factor),
+                );
                 let modifiers = modifiers_from_state(event.state);
+                drop(state);
                 window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
                     pressed_button,
                     position,
@@ -483,10 +523,14 @@ impl X11Client {
             }
             Event::LeaveNotify(event) => {
                 let window = self.get_window(event.event)?;
+                let state = self.0.borrow();
                 let pressed_button = super::button_from_state(event.state);
-                let position =
-                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                let position = point(
+                    px(event.event_x as f32 / state.scale_factor),
+                    px(event.event_y as f32 / state.scale_factor),
+                );
                 let modifiers = modifiers_from_state(event.state);
+                drop(state);
                 window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
                     pressed_button,
                     position,
@@ -553,6 +597,7 @@ impl LinuxClient for X11Client {
             state.x_root_index,
             x_window,
             &state.atoms,
+            state.scale_factor,
         );
 
         let screen_resources = state

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -17,6 +17,7 @@ use x11rb::{
         xinput,
         xproto::{self, ConnectionExt as _, CreateWindowAux},
     },
+    resource_manager::Database,
     wrapper::ConnectionExt,
     xcb_ffi::XCBConnection,
 };
@@ -27,6 +28,7 @@ use std::{
     iter::Zip,
     mem,
     num::NonZeroU32,
+    ops::Div,
     ptr::NonNull,
     rc::Rc,
     sync::{self, Arc},
@@ -128,6 +130,7 @@ impl rwh::HasDisplayHandle for X11Window {
 }
 
 impl X11WindowState {
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         client: X11ClientStatePtr,
         executor: ForegroundExecutor,
@@ -136,6 +139,7 @@ impl X11WindowState {
         x_main_screen_index: usize,
         x_window: xproto::Window,
         atoms: &XcbAtoms,
+        scale_factor: f32,
     ) -> Self {
         let x_screen_index = params
             .display_id
@@ -246,7 +250,7 @@ impl X11WindowState {
             display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
             raw,
             bounds: params.bounds.map(|v| v.0),
-            scale_factor: 1.0,
+            scale_factor,
             renderer: BladeRenderer::new(gpu, gpu_extent),
             atoms: *atoms,
 
@@ -291,6 +295,7 @@ impl Drop for X11Window {
 }
 
 impl X11Window {
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         client: X11ClientStatePtr,
         executor: ForegroundExecutor,
@@ -299,6 +304,7 @@ impl X11Window {
         x_main_screen_index: usize,
         x_window: xproto::Window,
         atoms: &XcbAtoms,
+        scale_factor: f32,
     ) -> Self {
         Self(X11WindowStatePtr {
             state: Rc::new(RefCell::new(X11WindowState::new(
@@ -309,6 +315,7 @@ impl X11Window {
                 x_main_screen_index,
                 x_window,
                 atoms,
+                scale_factor,
             ))),
             callbacks: Rc::new(RefCell::new(Callbacks::default())),
             xcb_connection: xcb_connection.clone(),
@@ -402,7 +409,7 @@ impl X11WindowStatePtr {
 
 impl PlatformWindow for X11Window {
     fn bounds(&self) -> Bounds<DevicePixels> {
-        self.0.state.borrow_mut().bounds.map(|v| v.into())
+        self.0.state.borrow().bounds.map(|v| v.into())
     }
 
     // todo(linux)
@@ -416,11 +423,16 @@ impl PlatformWindow for X11Window {
     }
 
     fn content_size(&self) -> Size<Pixels> {
-        self.0.state.borrow_mut().content_size()
+        // We divide by the scale factor here because this value is queried to determine how much to draw,
+        // but it will be multiplied later by the scale to adjust for scaling.
+        let state = self.0.state.borrow();
+        state
+            .content_size()
+            .map(|size| size.div(state.scale_factor))
     }
 
     fn scale_factor(&self) -> f32 {
-        self.0.state.borrow_mut().scale_factor
+        self.0.state.borrow().scale_factor
     }
 
     // todo(linux)