From c8e03ce42a2d64cad7622a5778801767479faae8 Mon Sep 17 00:00:00 2001 From: bbb651 <53972231+bbb651@users.noreply.github.com> Date: Thu, 7 Mar 2024 04:13:23 +0200 Subject: [PATCH] Wayland: Support integer scaling without wp_fractional_scale (#8886) Release Notes: - N/A `DoubleBuffered` is not currently very necessary because we only care about a single field `OutputState::scale` but I think it can be useful for other objects as it's a fairly common pattern in wayland. --- .../gpui/src/platform/linux/wayland/client.rs | 171 ++++++++++++++++-- .../gpui/src/platform/linux/wayland/window.rs | 4 + 2 files changed, 162 insertions(+), 13 deletions(-) diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index e8f20ebc0e..2ab63033ba 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::num::NonZeroU32; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; @@ -12,6 +13,7 @@ use wayland_backend::client::ObjectId; use wayland_backend::protocol::WEnum; use wayland_client::globals::{registry_queue_init, GlobalListContents}; use wayland_client::protocol::wl_callback::WlCallback; +use wayland_client::protocol::wl_output; use wayland_client::protocol::wl_pointer::AxisRelativeDirection; use wayland_client::{ delegate_noop, @@ -55,6 +57,7 @@ pub(crate) struct WaylandClientStateInner { fractional_scale_manager: Option, decoration_manager: Option, windows: Vec<(xdg_surface::XdgSurface, Rc)>, + outputs: Vec<(wl_output::WlOutput, Rc>)>, platform_inner: Rc, keymap_state: Option, repeat: KeyRepeat, @@ -94,6 +97,7 @@ pub(crate) struct WaylandClient { } const WL_SEAT_VERSION: u32 = 4; +const WL_OUTPUT_VERSION: u32 = 2; impl WaylandClient { pub(crate) fn new(linux_platform_inner: Rc) -> Self { @@ -101,16 +105,29 @@ impl WaylandClient { let (globals, mut event_queue) = registry_queue_init::(&conn).unwrap(); let qh = event_queue.handle(); + let mut outputs = Vec::new(); globals.contents().with_list(|list| { for global in list { - if global.interface == "wl_seat" { - globals.registry().bind::( - global.name, - WL_SEAT_VERSION, - &qh, - (), - ); + match &global.interface[..] { + "wl_seat" => { + globals.registry().bind::( + global.name, + WL_SEAT_VERSION, + &qh, + (), + ); + } + "wl_output" => outputs.push(( + globals.registry().bind::( + global.name, + WL_OUTPUT_VERSION, + &qh, + (), + ), + Rc::new(RefCell::new(OutputState::default())), + )), + _ => {} } } }); @@ -119,9 +136,12 @@ impl WaylandClient { let (primary, clipboard) = unsafe { create_clipboards_from_external(display) }; let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner { - compositor: globals.bind(&qh, 1..=1, ()).unwrap(), + compositor: globals + .bind(&qh, 1..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE, ()) + .unwrap(), wm_base: globals.bind(&qh, 1..=1, ()).unwrap(), shm: globals.bind(&qh, 1..=1, ()).unwrap(), + outputs, viewporter: globals.bind(&qh, 1..=1, ()).ok(), fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(), decoration_manager: globals.bind(&qh, 1..=1, ()).ok(), @@ -291,16 +311,24 @@ impl Dispatch for WaylandClientStat _: &Connection, qh: &QueueHandle, ) { + let mut state = state.client_state_inner.borrow_mut(); match event { wl_registry::Event::Global { name, interface, version: _, - } => { - if interface.as_str() == "wl_seat" { - registry.bind::(name, 4, qh, ()); + } => match &interface[..] { + "wl_seat" => { + registry.bind::(name, WL_SEAT_VERSION, qh, ()); } - } + "wl_output" => { + state.outputs.push(( + registry.bind::(name, WL_OUTPUT_VERSION, qh, ()), + Rc::new(RefCell::new(OutputState::default())), + )); + } + _ => {} + }, wl_registry::Event::GlobalRemove { name: _ } => {} _ => {} } @@ -308,7 +336,6 @@ impl Dispatch for WaylandClientStat } delegate_noop!(WaylandClientState: ignore wl_compositor::WlCompositor); -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); @@ -339,6 +366,124 @@ impl Dispatch> for WaylandClientState { } } +impl Dispatch for WaylandClientState { + fn event( + state: &mut Self, + surface: &wl_surface::WlSurface, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + let mut state = state.client_state_inner.borrow_mut(); + + // We use `WpFractionalScale` instead to set the scale if it's available + // or give up on scaling if `WlSurface::set_buffer_scale` isn't available + if state.fractional_scale_manager.is_some() + || state.compositor.version() < wl_surface::REQ_SET_BUFFER_SCALE_SINCE + { + return; + } + + let Some(window) = state + .windows + .iter() + .map(|(_, state)| state) + .find(|state| &*state.surface == surface) + else { + return; + }; + + let mut outputs = window.outputs.borrow_mut(); + + match event { + wl_surface::Event::Enter { output } => { + // We use `PreferredBufferScale` instead to set the scale if it's available + if surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE { + return; + } + let mut scale = 1; + for global_output in &state.outputs { + if output == global_output.0 { + outputs.insert(output.id()); + scale = scale.max(global_output.1.borrow().scale.get()); + } else if outputs.contains(&global_output.0.id()) { + scale = scale.max(global_output.1.borrow().scale.get()); + } + } + window.rescale(scale as f32); + window.surface.set_buffer_scale(scale as i32); + window.surface.commit(); + } + wl_surface::Event::Leave { output } => { + // We use `PreferredBufferScale` instead to set the scale if it's available + if surface.version() >= 6 { + return; + } + + outputs.remove(&output.id()); + + let mut scale = 1; + for global_output in &state.outputs { + if outputs.contains(&global_output.0.id()) { + scale = scale.max(global_output.1.borrow().scale.get()); + } + } + window.rescale(scale as f32); + window.surface.set_buffer_scale(scale as i32); + window.surface.commit(); + } + wl_surface::Event::PreferredBufferScale { factor } => { + window.rescale(factor as f32); + surface.set_buffer_scale(factor); + window.surface.commit(); + } + _ => {} + } + } +} + +#[derive(Debug, Clone)] +struct OutputState { + scale: NonZeroU32, +} + +impl Default for OutputState { + fn default() -> Self { + Self { + scale: NonZeroU32::new(1).unwrap(), + } + } +} + +impl Dispatch for WaylandClientState { + fn event( + state: &mut Self, + output: &wl_output::WlOutput, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + let mut state = state.client_state_inner.borrow_mut(); + let mut output_state = state + .outputs + .iter_mut() + .find(|(o, _)| o == output) + .map(|(_, state)| state) + .unwrap() + .borrow_mut(); + match event { + wl_output::Event::Scale { factor } => { + if factor > 0 { + output_state.scale = NonZeroU32::new(factor as u32).unwrap(); + } + } + _ => {} + } + } +} + impl Dispatch for WaylandClientState { fn event( state: &mut Self, diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 9af7aa1ed8..5114a11364 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -6,10 +6,12 @@ use std::sync::Arc; use blade_graphics as gpu; use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle}; +use collections::HashSet; use futures::channel::oneshot::Receiver; use raw_window_handle::{ DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle, }; +use wayland_backend::client::ObjectId; use wayland_client::{protocol::wl_surface, Proxy}; use wayland_protocols::wp::viewporter::client::wp_viewport; use wayland_protocols::xdg::shell::client::xdg_toplevel; @@ -111,6 +113,7 @@ pub(crate) struct WaylandWindowState { pub(crate) callbacks: RefCell, pub(crate) surface: Arc, pub(crate) toplevel: Arc, + pub(crate) outputs: RefCell>, viewport: Option, } @@ -142,6 +145,7 @@ impl WaylandWindowState { surface: Arc::clone(&wl_surf), inner: RefCell::new(WaylandWindowInner::new(&wl_surf, bounds)), callbacks: RefCell::new(Callbacks::default()), + outputs: RefCell::new(HashSet::default()), toplevel, viewport, }