Wayland fractional scaling (#7961)

Roman created

This PR adds support for fractional scaling on Wayland.

Release Notes:

- N/A

Change summary

crates/gpui/Cargo.toml                           |  2 
crates/gpui/src/platform/linux/text_system.rs    |  4 
crates/gpui/src/platform/linux/wayland/client.rs | 57 ++++++++++++++++++
crates/gpui/src/platform/linux/wayland/window.rs | 49 +++++++++-----
4 files changed, 91 insertions(+), 21 deletions(-)

Detailed changes

crates/gpui/Cargo.toml 🔗

@@ -105,7 +105,7 @@ ashpd = "0.7.0"
 # todo!(linux) - Technically do not use `randr`, but it doesn't compile otherwise
 xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr", "xkb"] }
 wayland-client= { version = "0.31.2" }
-wayland-protocols = { version = "0.31.2", features = ["client"] }
+wayland-protocols = { version = "0.31.2", features = ["client", "staging"] }
 wayland-backend = { version = "0.3.3", features = ["client_system"] }
 xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
 as-raw-xcb-connection = "1"

crates/gpui/src/platform/linux/text_system.rs 🔗

@@ -276,7 +276,7 @@ impl LinuxTextSystemState {
                 CacheKey::new(
                     font.id(),
                     params.glyph_id.0 as u16,
-                    params.font_size.into(),
+                    (params.font_size * params.scale_factor).into(),
                     (0.0, 0.0),
                 )
                 .0,
@@ -308,7 +308,7 @@ impl LinuxTextSystemState {
                     CacheKey::new(
                         font.id(),
                         params.glyph_id.0 as u16,
-                        params.font_size.into(),
+                        (params.font_size * params.scale_factor).into(),
                         (0.0, 0.0),
                     )
                     .0,

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

@@ -2,6 +2,7 @@ use std::rc::Rc;
 use std::sync::Arc;
 
 use parking_lot::Mutex;
+use wayland_backend::client::ObjectId;
 use wayland_backend::protocol::WEnum;
 use wayland_client::protocol::wl_callback::WlCallback;
 use wayland_client::protocol::wl_pointer::AxisRelativeDirection;
@@ -14,6 +15,10 @@ use wayland_client::{
     },
     Connection, Dispatch, EventQueue, Proxy, QueueHandle,
 };
+use wayland_protocols::wp::fractional_scale::v1::client::{
+    wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
+};
+use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter};
 use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
 use xkbcommon::xkb;
 use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
@@ -36,6 +41,8 @@ pub(crate) struct WaylandClientState {
     compositor: Option<wl_compositor::WlCompositor>,
     buffer: Option<wl_buffer::WlBuffer>,
     wm_base: Option<xdg_wm_base::XdgWmBase>,
+    viewporter: Option<wp_viewporter::WpViewporter>,
+    fractional_scale_manager: Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
     windows: Vec<(xdg_surface::XdgSurface, Rc<WaylandWindowState>)>,
     platform_inner: Rc<LinuxPlatformInner>,
     wl_seat: Option<wl_seat::WlSeat>,
@@ -62,6 +69,8 @@ impl WaylandClient {
             compositor: None,
             buffer: None,
             wm_base: None,
+            viewporter: None,
+            fractional_scale_manager: None,
             windows: Vec::new(),
             platform_inner: Rc::clone(&linux_platform_inner),
             wl_seat: None,
@@ -135,16 +144,26 @@ impl Client for WaylandClient {
         let toplevel = xdg_surface.get_toplevel(&self.qh, ());
         let wl_surface = Arc::new(wl_surface);
 
+        let viewport = state
+            .viewporter
+            .as_ref()
+            .map(|viewporter| viewporter.get_viewport(&wl_surface, &self.qh, ()));
+
         wl_surface.frame(&self.qh, wl_surface.clone());
         wl_surface.commit();
 
         let window_state = Rc::new(WaylandWindowState::new(
             &self.conn,
             wl_surface.clone(),
+            viewport,
             Arc::new(toplevel),
             options,
         ));
 
+        if let Some(fractional_scale_manager) = state.fractional_scale_manager.as_ref() {
+            fractional_scale_manager.get_fractional_scale(&wl_surface, &self.qh, xdg_surface.id());
+        }
+
         state.windows.push((xdg_surface, Rc::clone(&window_state)));
         Box::new(WaylandWindow(window_state))
     }
@@ -177,6 +196,21 @@ impl Dispatch<wl_registry::WlRegistry, ()> for WaylandClientState {
                     let seat = registry.bind::<wl_seat::WlSeat, _, _>(name, 1, qh, ());
                     state.wl_seat = Some(seat);
                 }
+                "wp_fractional_scale_manager_v1" => {
+                    let manager = registry
+                        .bind::<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, _, _>(
+                        name,
+                        1,
+                        qh,
+                        (),
+                    );
+                    state.fractional_scale_manager = Some(manager);
+                }
+                "wp_viewporter" => {
+                    let view_porter =
+                        registry.bind::<wp_viewporter::WpViewporter, _, _>(name, 1, qh, ());
+                    state.viewporter = Some(view_porter);
+                }
                 _ => {}
             };
         }
@@ -188,6 +222,9 @@ delegate_noop!(WaylandClientState: ignore wl_surface::WlSurface);
 delegate_noop!(WaylandClientState: ignore wl_shm::WlShm);
 delegate_noop!(WaylandClientState: ignore wl_shm_pool::WlShmPool);
 delegate_noop!(WaylandClientState: ignore wl_buffer::WlBuffer);
+delegate_noop!(WaylandClientState: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
+delegate_noop!(WaylandClientState: ignore wp_viewporter::WpViewporter);
+delegate_noop!(WaylandClientState: ignore wp_viewport::WpViewport);
 
 impl Dispatch<WlCallback, Arc<WlSurface>> for WaylandClientState {
     fn event(
@@ -599,3 +636,23 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
         }
     }
 }
+
+impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for WaylandClientState {
+    fn event(
+        state: &mut Self,
+        _: &wp_fractional_scale_v1::WpFractionalScaleV1,
+        event: <wp_fractional_scale_v1::WpFractionalScaleV1 as Proxy>::Event,
+        id: &ObjectId,
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+        if let wp_fractional_scale_v1::Event::PreferredScale { scale, .. } = event {
+            for window in &state.windows {
+                if window.0.id() == *id {
+                    window.1.rescale(scale as f32 / 120.0);
+                    return;
+                }
+            }
+        }
+    }
+}

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

@@ -11,6 +11,7 @@ use raw_window_handle::{
     DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
 };
 use wayland_client::{protocol::wl_surface, Proxy};
+use wayland_protocols::wp::viewporter::client::wp_viewport;
 use wayland_protocols::xdg::shell::client::xdg_toplevel;
 
 use crate::platform::blade::BladeRenderer;
@@ -38,6 +39,7 @@ pub(crate) struct Callbacks {
 struct WaylandWindowInner {
     renderer: BladeRenderer,
     bounds: Bounds<i32>,
+    scale: f32,
     input_handler: Option<PlatformInputHandler>,
 }
 
@@ -92,6 +94,7 @@ impl WaylandWindowInner {
         Self {
             renderer: BladeRenderer::new(gpu, extent),
             bounds,
+            scale: 1.0,
             input_handler: None,
         }
     }
@@ -103,12 +106,14 @@ pub(crate) struct WaylandWindowState {
     pub(crate) callbacks: Mutex<Callbacks>,
     pub(crate) surface: Arc<wl_surface::WlSurface>,
     pub(crate) toplevel: Arc<xdg_toplevel::XdgToplevel>,
+    viewport: Option<wp_viewport::WpViewport>,
 }
 
 impl WaylandWindowState {
     pub(crate) fn new(
         conn: &Arc<wayland_client::Connection>,
         wl_surf: Arc<wl_surface::WlSurface>,
+        viewport: Option<wp_viewport::WpViewport>,
         toplevel: Arc<xdg_toplevel::XdgToplevel>,
         options: WindowOptions,
     ) -> Self {
@@ -135,6 +140,7 @@ impl WaylandWindowState {
             inner: Mutex::new(WaylandWindowInner::new(&Arc::clone(conn), &wl_surf, bounds)),
             callbacks: Mutex::new(Callbacks::default()),
             toplevel,
+            viewport,
         }
     }
 
@@ -147,30 +153,40 @@ impl WaylandWindowState {
         }
     }
 
-    pub fn resize(&self, width: i32, height: i32) {
-        {
-            let mut inner = self.inner.lock();
-            inner.bounds.size.width = width;
-            inner.bounds.size.height = height;
-            inner
-                .renderer
-                .update_drawable_size(size(width as f64, height as f64));
-        }
-        let mut callbacks = self.callbacks.lock();
-        if let Some(ref mut fun) = callbacks.resize {
+    pub fn set_size_and_scale(&self, width: i32, height: i32, scale: f32) {
+        self.inner.lock().scale = scale;
+        self.inner.lock().bounds.size.width = width;
+        self.inner.lock().bounds.size.height = height;
+        self.inner.lock().renderer.update_drawable_size(size(
+            width as f64 * scale as f64,
+            height as f64 * scale as f64,
+        ));
+
+        if let Some(ref mut fun) = self.callbacks.lock().resize {
             fun(
                 Size {
                     width: px(width as f32),
                     height: px(height as f32),
                 },
-                1.0,
+                scale,
             );
         }
-        if let Some(ref mut fun) = callbacks.moved {
-            fun()
+
+        if let Some(viewport) = &self.viewport {
+            viewport.set_destination(width, height);
         }
     }
 
+    pub fn resize(&self, width: i32, height: i32) {
+        let scale = self.inner.lock().scale;
+        self.set_size_and_scale(width, height, scale);
+    }
+
+    pub fn rescale(&self, scale: f32) {
+        let bounds = self.inner.lock().bounds;
+        self.set_size_and_scale(bounds.size.width, bounds.size.height, scale)
+    }
+
     pub fn close(&self) {
         let mut callbacks = self.callbacks.lock();
         if let Some(fun) = callbacks.close.take() {
@@ -215,7 +231,6 @@ impl PlatformWindow for WaylandWindow {
         WindowBounds::Maximized
     }
 
-    // todo!(linux)
     fn content_size(&self) -> Size<Pixels> {
         let inner = self.0.inner.lock();
         Size {
@@ -224,9 +239,8 @@ impl PlatformWindow for WaylandWindow {
         }
     }
 
-    // todo!(linux)
     fn scale_factor(&self) -> f32 {
-        1f32
+        self.0.inner.lock().scale
     }
 
     //todo!(linux)
@@ -263,7 +277,6 @@ impl PlatformWindow for WaylandWindow {
         self.0.inner.lock().input_handler = Some(input_handler);
     }
 
-    //todo!(linux)
     fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
         self.0.inner.lock().input_handler.take()
     }