From de782504abc3cccccd144521dc2ae75c0d0d4546 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 20 Jan 2023 00:02:16 +0300 Subject: [PATCH] On Wayland, add support for fractional scaling This adds support for the fractional scaling on Wayland via the wp-fractional-scale protocol. Co-authored-by: Julian Orth --- CHANGELOG.md | 1 + Cargo.toml | 10 +- build.rs | 41 +++++- src/platform_impl/linux/mod.rs | 2 +- src/platform_impl/linux/wayland/env.rs | 17 +++ .../linux/wayland/event_loop/mod.rs | 19 ++- src/platform_impl/linux/wayland/mod.rs | 1 + src/platform_impl/linux/wayland/protocols.rs | 13 ++ .../linux/wayland/seat/pointer/handlers.rs | 20 ++- .../linux/wayland/seat/touch/handlers.rs | 32 +++-- src/platform_impl/linux/wayland/window/mod.rs | 122 ++++++++++++------ .../linux/wayland/window/shim.rs | 46 ++++++- src/window.rs | 1 + wayland_protocols/fractional-scale-v1.xml | 102 +++++++++++++++ 14 files changed, 361 insertions(+), 66 deletions(-) create mode 100644 src/platform_impl/linux/wayland/protocols.rs create mode 100644 wayland_protocols/fractional-scale-v1.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c04112a..79ad4d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On X11, added `drag_resize_window` method. - Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS. - On macOS, fix the mouse buttons other than left/right/middle being reported as middle. +- On Wayland, support fractional scaling via the wp-fractional-scale protocol. # 0.27.5 diff --git a/Cargo.toml b/Cargo.toml index 874ff842..2c223d97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] x11 = ["x11-dl", "mio", "percent-encoding"] -wayland = ["wayland-client", "wayland-protocols", "sctk"] +wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"] wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] @@ -109,14 +109,18 @@ mio = { version = "0.8", features = ["os-ext"], optional = true } percent-encoding = { version = "2.0", optional = true } sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true } sctk-adwaita = { version = "0.5.1", default_features = false, optional = true } -wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true } -wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true } +wayland-client = { version = "0.29.5", default_features = false, features = ["use_system_lib"], optional = true } +wayland-protocols = { version = "0.29.5", features = [ "staging_protocols"], optional = true } +wayland-commons = { version = "0.29.5", optional = true } x11-dl = { version = "2.18.5", optional = true } [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } redox_syscall = "0.3" +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.build-dependencies] +wayland-scanner = "0.29.5" + [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] package = "web-sys" version = "0.3.22" diff --git a/build.rs b/build.rs index b35823ab..1a3f8f9e 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,34 @@ use cfg_aliases::cfg_aliases; +#[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + ), + feature = "wayland", +))] +mod wayland { + use std::env; + use std::path::PathBuf; + use wayland_scanner::Side; + + pub fn main() { + let mut path = PathBuf::from(env::var("OUT_DIR").unwrap()); + path.push("fractional_scale_v1.rs"); + wayland_scanner::generate_code( + "wayland_protocols/fractional-scale-v1.xml", + &path, + Side::Client, + ); + } +} + fn main() { // The script doesn't depend on our code - println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=build.rs:wayland_protocols"); // Setup cfg aliases cfg_aliases! { // Systems. @@ -20,4 +46,17 @@ fn main() { wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) }, orbital_platform: { redox }, } + + // XXX aliases are not available for the build script itself. + #[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + ), + feature = "wayland", + ))] + wayland::main(); } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 964ca06c..44d4bf14 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -441,7 +441,7 @@ impl Window { #[inline] pub fn scale_factor(&self) -> f64 { - x11_or_wayland!(match self; Window(w) => w.scale_factor() as _) + x11_or_wayland!(match self; Window(w) => w.scale_factor()) } #[inline] diff --git a/src/platform_impl/linux/wayland/env.rs b/src/platform_impl/linux/wayland/env.rs index f3e9b240..e88b1bad 100644 --- a/src/platform_impl/linux/wayland/env.rs +++ b/src/platform_impl/linux/wayland/env.rs @@ -14,6 +14,7 @@ use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_rela use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1; +use sctk::reexports::protocols::viewporter::client::wp_viewporter::WpViewporter; use sctk::environment::{Environment, SimpleGlobal}; use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener}; @@ -21,6 +22,8 @@ use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener}; use sctk::shell::{Shell, ShellHandler, ShellHandling}; use sctk::shm::ShmHandler; +use crate::platform_impl::wayland::protocols::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; + /// Set of extra features that are supported by the compositor. #[derive(Debug, Clone, Copy)] pub struct WindowingFeatures { @@ -61,6 +64,8 @@ sctk::environment!(WinitEnv, ZwpPointerConstraintsV1 => pointer_constraints, ZwpTextInputManagerV3 => text_input_manager, XdgActivationV1 => xdg_activation, + WpFractionalScaleManagerV1 => fractional_scale_manager, + WpViewporter => viewporter, ], multis = [ WlSeat => seats, @@ -91,6 +96,10 @@ pub struct WinitEnv { decoration_manager: SimpleGlobal, xdg_activation: SimpleGlobal, + + fractional_scale_manager: SimpleGlobal, + + viewporter: SimpleGlobal, } impl WinitEnv { @@ -125,6 +134,12 @@ impl WinitEnv { // Surface activation. let xdg_activation = SimpleGlobal::new(); + // Fractional surface scaling. + let fractional_scale_manager = SimpleGlobal::new(); + + // Surface resizing (used for fractional scaling). + let viewporter = SimpleGlobal::new(); + Self { seats, outputs, @@ -137,6 +152,8 @@ impl WinitEnv { pointer_constraints, text_input_manager, xdg_activation, + fractional_scale_manager, + viewporter, } } } diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 4421b463..5a4c7e2c 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -374,10 +374,11 @@ impl EventLoop { }); for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() { - if let Some(scale_factor) = window_compositor_update.scale_factor.map(|f| f as f64) - { + if let Some(scale_factor) = window_compositor_update.scale_factor { let mut physical_size = self.with_state(|state| { let window_handle = state.window_map.get(window_id).unwrap(); + *window_handle.scale_factor.lock().unwrap() = scale_factor; + let mut size = window_handle.size.lock().unwrap(); // Update the new logical size if it was changed. @@ -409,6 +410,15 @@ impl EventLoop { if let Some(size) = window_compositor_update.size.take() { let physical_size = self.with_state(|state| { let window_handle = state.window_map.get_mut(window_id).unwrap(); + + if let Some(fs_state) = window_handle.fractional_scaling_state.as_ref() { + // If we have a viewport then we support fractional scaling. As per the + // protocol, we have to set the viewport size of the size prior scaling. + fs_state + .viewport + .set_destination(size.width as _, size.height as _); + } + let mut window_size = window_handle.size.lock().unwrap(); // Always issue resize event on scale factor change. @@ -419,9 +429,8 @@ impl EventLoop { None } else { *window_size = size; - let scale_factor = - sctk::get_surface_scale_factor(window_handle.window.surface()); - let physical_size = size.to_physical(scale_factor as f64); + let scale_factor = window_handle.scale_factor(); + let physical_size = size.to_physical(scale_factor); Some(physical_size) }; diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 19bf6c92..fce6d8ec 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -10,6 +10,7 @@ pub use window::Window; mod env; mod event_loop; mod output; +mod protocols; mod seat; mod window; diff --git a/src/platform_impl/linux/wayland/protocols.rs b/src/platform_impl/linux/wayland/protocols.rs new file mode 100644 index 00000000..7612ff67 --- /dev/null +++ b/src/platform_impl/linux/wayland/protocols.rs @@ -0,0 +1,13 @@ +#![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)] +#![allow(non_upper_case_globals, non_snake_case, unused_imports)] +#![allow(missing_docs, clippy::all)] + +use wayland_client::protocol::wl_surface; +use wayland_client::sys; +use wayland_client::{AnonymousObject, Attached, Main, Proxy, ProxyMap}; +use wayland_commons::map::{Object, ObjectMetadata}; +use wayland_commons::smallvec; +use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc}; +use wayland_commons::{Interface, MessageGroup}; + +include!(concat!(env!("OUT_DIR"), "/fractional_scale_v1.rs")); diff --git a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs index 7348cc66..50c24d64 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs @@ -53,7 +53,7 @@ pub(super) fn handle_pointer( None => return, }; - let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let scale_factor = window_handle.scale_factor(); pointer_data.surface = Some(surface); // Notify window that pointer entered the surface. @@ -133,8 +133,12 @@ pub(super) fn handle_pointer( }; let window_id = wayland::make_wid(surface); + let window_handle = match winit_state.window_map.get(&window_id) { + Some(w) => w, + _ => return, + }; - let scale_factor = sctk::get_surface_scale_factor(surface) as f64; + let scale_factor = window_handle.scale_factor(); let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor); event_sink.push_window_event( @@ -192,6 +196,10 @@ pub(super) fn handle_pointer( }; let window_id = wayland::make_wid(surface); + let window_handle = match winit_state.window_map.get(&window_id) { + Some(w) => w, + _ => return, + }; if pointer.as_ref().version() < 5 { let (mut x, mut y) = (0.0, 0.0); @@ -204,7 +212,7 @@ pub(super) fn handle_pointer( _ => unreachable!(), } - let scale_factor = sctk::get_surface_scale_factor(surface) as f64; + let scale_factor = window_handle.scale_factor(); let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor); event_sink.push_window_event( @@ -268,6 +276,10 @@ pub(super) fn handle_pointer( None => return, }; let window_id = wayland::make_wid(surface); + let window_handle = match winit_state.window_map.get(&window_id) { + Some(w) => w, + _ => return, + }; let window_event = if let Some((x, y)) = axis_discrete_buffer { WindowEvent::MouseWheel { @@ -279,7 +291,7 @@ pub(super) fn handle_pointer( modifiers: *pointer_data.modifiers_state.borrow(), } } else if let Some((x, y)) = axis_buffer { - let scale_factor = sctk::get_surface_scale_factor(surface) as f64; + let scale_factor = window_handle.scale_factor(); let delta = LogicalPosition::new(x, y).to_physical(scale_factor); WindowEvent::MouseWheel { diff --git a/src/platform_impl/linux/wayland/seat/touch/handlers.rs b/src/platform_impl/linux/wayland/seat/touch/handlers.rs index d8790944..190b5770 100644 --- a/src/platform_impl/linux/wayland/seat/touch/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/touch/handlers.rs @@ -24,11 +24,12 @@ pub(super) fn handle_touch( surface, id, x, y, .. } => { let window_id = wayland::make_wid(&surface); - if !winit_state.window_map.contains_key(&window_id) { - return; - } + let window_handle = match winit_state.window_map.get(&window_id) { + Some(w) => w, + _ => return, + }; - let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let scale_factor = window_handle.scale_factor(); let position = LogicalPosition::new(x, y); event_sink.push_window_event( @@ -60,7 +61,12 @@ pub(super) fn handle_touch( None => return, }; - let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let window_id = wayland::make_wid(&touch_point.surface); + let window_handle = match winit_state.window_map.get(&window_id) { + Some(w) => w, + _ => return, + }; + let scale_factor = window_handle.scale_factor(); let location = touch_point.position.to_physical(scale_factor); let window_id = wayland::make_wid(&touch_point.surface); @@ -82,10 +88,15 @@ pub(super) fn handle_touch( Some(touch_point) => touch_point, None => return, }; + let window_id = wayland::make_wid(&touch_point.surface); + let window_handle = match winit_state.window_map.get(&window_id) { + Some(w) => w, + _ => return, + }; touch_point.position = LogicalPosition::new(x, y); - let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let scale_factor = window_handle.scale_factor(); let location = touch_point.position.to_physical(scale_factor); let window_id = wayland::make_wid(&touch_point.surface); @@ -105,9 +116,14 @@ pub(super) fn handle_touch( TouchEvent::Frame => (), TouchEvent::Cancel => { for touch_point in inner.touch_points.drain(..) { - let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; - let location = touch_point.position.to_physical(scale_factor); let window_id = wayland::make_wid(&touch_point.surface); + let window_handle = match winit_state.window_map.get(&window_id) { + Some(w) => w, + _ => return, + }; + + let scale_factor = window_handle.scale_factor(); + let location = touch_point.position.to_physical(scale_factor); event_sink.push_window_event( WindowEvent::Touch(crate::event::Touch { diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index d51f5df9..e6f6b9f1 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -11,12 +11,14 @@ use raw_window_handle::{ RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, }; use sctk::window::Decorations; +use wayland_protocols::viewporter::client::wp_viewporter::WpViewporter; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::platform_impl::{ - Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, - PlatformSpecificWindowBuilderAttributes as PlatformAttributes, + wayland::protocols::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, + wayland::protocols::wp_fractional_scale_v1, Fullscreen, MonitorHandle as PlatformMonitorHandle, + OsError, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; use crate::window::{ CursorGrabMode, CursorIcon, ResizeDirection, Theme, UserAttentionType, WindowAttributes, @@ -30,7 +32,9 @@ use super::{EventLoopWindowTarget, WindowId}; pub mod shim; -use shim::{WindowCompositorUpdate, WindowHandle, WindowRequest, WindowUserRequest}; +use shim::{ + FractionalScalingState, WindowCompositorUpdate, WindowHandle, WindowRequest, WindowUserRequest, +}; #[cfg(feature = "sctk-adwaita")] pub type WinitFrame = sctk_adwaita::AdwaitaFrame; @@ -50,6 +54,9 @@ pub struct Window { /// The underlying wl_surface. surface: WlSurface, + /// The scale factor. + scale_factor: Arc>, + /// The current window size. size: Arc>>, @@ -90,33 +97,50 @@ impl Window { attributes: WindowAttributes, platform_attributes: PlatformAttributes, ) -> Result { - let surface = event_loop_window_target + let viewporter = event_loop_window_target.env.get_global::(); + let fractional_scale_manager = event_loop_window_target .env - .create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| { - let winit_state = dispatch_data.get::().unwrap(); + .get_global::(); + + // Create surface and register callback for the scale factor changes. + let mut scale_factor = 1.; + let (surface, fractional_scaling_state) = + if let (Some(viewporter), Some(fractional_scale_manager)) = + (viewporter, fractional_scale_manager) + { + let surface = event_loop_window_target.env.create_surface().detach(); + let fractional_scale = fractional_scale_manager.get_fractional_scale(&surface); - // Get the window that received the event. let window_id = super::make_wid(&surface); - let mut window_compositor_update = winit_state - .window_compositor_updates - .get_mut(&window_id) - .unwrap(); + fractional_scale.quick_assign(move |_, event, mut dispatch_data| { + let wp_fractional_scale_v1::Event::PreferredScale { scale } = event; + let winit_state = dispatch_data.get::().unwrap(); + apply_scale(window_id, scale as f64 / 120., winit_state); + }); - // Mark that we need a frame refresh on the DPI change. - winit_state - .window_user_requests - .get_mut(&window_id) - .unwrap() - .refresh_frame = true; + let fractional_scale = fractional_scale.detach(); + let viewport = viewporter.get_viewport(&surface).detach(); + let fractional_scaling_state = + FractionalScalingState::new(viewport, fractional_scale); - // Set pending scale factor. - window_compositor_update.scale_factor = Some(scale); + (surface, Some(fractional_scaling_state)) + } else { + let surface = event_loop_window_target + .env + .create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); - surface.set_buffer_scale(scale); - }) - .detach(); + // Get the window that received the event. + let window_id = super::make_wid(&surface); + apply_scale(window_id, scale as f64, winit_state); + surface.set_buffer_scale(scale); + }) + .detach(); - let scale_factor = sctk::get_surface_scale_factor(&surface); + scale_factor = sctk::get_surface_scale_factor(&surface) as _; + + (surface, None) + }; let window_id = super::make_wid(&surface); let maximized = Arc::new(AtomicBool::new(false)); @@ -126,7 +150,7 @@ impl Window { let (width, height) = attributes .inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()) + .map(|size| size.to_logical::(scale_factor).into()) .unwrap_or((800, 600)); let theme_manager = event_loop_window_target.theme_manager.clone(); @@ -194,13 +218,13 @@ impl Window { // Min dimensions. let min_size = attributes .min_inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()); + .map(|size| size.to_logical::(scale_factor).into()); window.set_min_size(min_size); // Max dimensions. let max_size = attributes .max_inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()); + .map(|size| size.to_logical::(scale_factor).into()); window.set_max_size(max_size); // Set Wayland specific window attributes. @@ -263,6 +287,8 @@ impl Window { window, size.clone(), has_focus.clone(), + fractional_scaling_state, + scale_factor, window_requests.clone(), ); @@ -324,6 +350,7 @@ impl Window { decorated: AtomicBool::new(attributes.decorations), cursor_grab_mode: Mutex::new(CursorGrabMode::None), has_focus, + scale_factor: window_handle.scale_factor.clone(), }; Ok(window) @@ -372,10 +399,7 @@ impl Window { } pub fn inner_size(&self) -> PhysicalSize { - self.size - .lock() - .unwrap() - .to_physical(self.scale_factor() as f64) + self.size.lock().unwrap().to_physical(self.scale_factor()) } #[inline] @@ -385,15 +409,12 @@ impl Window { #[inline] pub fn outer_size(&self) -> PhysicalSize { - self.size - .lock() - .unwrap() - .to_physical(self.scale_factor() as f64) + self.size.lock().unwrap().to_physical(self.scale_factor()) } #[inline] pub fn set_inner_size(&self, size: Size) { - let scale_factor = self.scale_factor() as f64; + let scale_factor = self.scale_factor(); let size = size.to_logical::(scale_factor); *self.size.lock().unwrap() = size; @@ -403,7 +424,7 @@ impl Window { #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - let scale_factor = self.scale_factor() as f64; + let scale_factor = self.scale_factor(); let size = dimensions.map(|size| size.to_logical::(scale_factor)); self.send_request(WindowRequest::MinSize(size)); @@ -411,7 +432,7 @@ impl Window { #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - let scale_factor = self.scale_factor() as f64; + let scale_factor = self.scale_factor(); let size = dimensions.map(|size| size.to_logical::(scale_factor)); self.send_request(WindowRequest::MaxSize(size)); @@ -447,10 +468,8 @@ impl Window { } #[inline] - pub fn scale_factor(&self) -> u32 { - // The scale factor from `get_surface_scale_factor` is always greater than zero, so - // u32 conversion is safe. - sctk::get_surface_scale_factor(&self.surface) as u32 + pub fn scale_factor(&self) -> f64 { + *self.scale_factor.lock().unwrap() } #[inline] @@ -561,7 +580,7 @@ impl Window { )))); } - let scale_factor = self.scale_factor() as f64; + let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.send_request(WindowRequest::SetLockedCursorPosition(position)); @@ -589,7 +608,7 @@ impl Window { #[inline] pub fn set_ime_position(&self, position: Position) { - let scale_factor = self.scale_factor() as f64; + let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.send_request(WindowRequest::ImePosition(position)); } @@ -700,3 +719,20 @@ impl TryFrom<&str> for Theme { } } } + +/// Set pending scale for the provided `window_id`. +fn apply_scale(window_id: WindowId, scale: f64, winit_state: &mut WinitState) { + // Set pending scale factor. + winit_state + .window_compositor_updates + .get_mut(&window_id) + .unwrap() + .scale_factor = Some(scale); + + // Mark that we need a frame refresh on the DPI change. + winit_state + .window_user_requests + .get_mut(&window_id) + .unwrap() + .refresh_frame = true; +} diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs index 0560c44c..dd280bc9 100644 --- a/src/platform_impl/linux/wayland/window/shim.rs +++ b/src/platform_impl/linux/wayland/window/shim.rs @@ -11,6 +11,7 @@ use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activat use sctk::environment::Environment; use sctk::window::{Decorations, Window}; +use wayland_protocols::viewporter::client::wp_viewport::WpViewport; use crate::dpi::{LogicalPosition, LogicalSize}; @@ -18,6 +19,7 @@ use crate::event::{Ime, WindowEvent}; use crate::platform_impl::wayland; use crate::platform_impl::wayland::env::WinitEnv; use crate::platform_impl::wayland::event_loop::{EventSink, WinitState}; +use crate::platform_impl::wayland::protocols::wp_fractional_scale_v1::WpFractionalScaleV1; use crate::platform_impl::wayland::seat::pointer::WinitPointer; use crate::platform_impl::wayland::seat::text_input::TextInputHandler; use crate::platform_impl::wayland::WindowId; @@ -109,7 +111,7 @@ pub struct WindowCompositorUpdate { pub size: Option>, /// New scale factor. - pub scale_factor: Option, + pub scale_factor: Option, /// Close the window. pub close_window: bool, @@ -143,6 +145,12 @@ pub struct WindowHandle { /// An actual window. pub window: ManuallyDrop>, + /// The state of the fractional scaling handlers for the window. + pub fractional_scaling_state: Option, + + /// The scale factor of the window. + pub scale_factor: Arc>, + /// The current size of the window. pub size: Arc>>, @@ -192,6 +200,8 @@ impl WindowHandle { window: Window, size: Arc>>, has_focus: Arc, + fractional_scaling_state: Option, + scale_factor: f64, pending_window_requests: Arc>>, ) -> Self { let xdg_activation = env.get_global::(); @@ -201,6 +211,8 @@ impl WindowHandle { Self { window: ManuallyDrop::new(window), + fractional_scaling_state, + scale_factor: Arc::new(Mutex::new(scale_factor)), size, pending_window_requests, cursor_icon: Cell::new(CursorIcon::Default), @@ -218,6 +230,10 @@ impl WindowHandle { } } + pub fn scale_factor(&self) -> f64 { + *self.scale_factor.lock().unwrap() + } + pub fn set_cursor_grab(&self, mode: CursorGrabMode) { // The new requested state matches the current confine status, return. let old_mode = self.cursor_grab_mode.replace(mode); @@ -587,6 +603,9 @@ pub fn handle_window_requests(winit_state: &mut WinitState) { impl Drop for WindowHandle { fn drop(&mut self) { + // Drop the fractional scaling before the surface. + let _ = self.fractional_scaling_state.take(); + unsafe { let surface = self.window.surface().clone(); // The window must be destroyed before wl_surface. @@ -595,3 +614,28 @@ impl Drop for WindowHandle { } } } + +/// Fractional scaling objects. +pub struct FractionalScalingState { + /// The wp-viewport of the window. + pub viewport: WpViewport, + + /// The wp-fractional-scale of the window surface. + pub fractional_scale: WpFractionalScaleV1, +} + +impl FractionalScalingState { + pub fn new(viewport: WpViewport, fractional_scale: WpFractionalScaleV1) -> Self { + Self { + viewport, + fractional_scale, + } + } +} + +impl Drop for FractionalScalingState { + fn drop(&mut self) { + self.viewport.destroy(); + self.fractional_scale.destroy(); + } +} diff --git a/src/window.rs b/src/window.rs index ca2d4ed2..3b8431a3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -486,6 +486,7 @@ impl Window { /// ## Platform-specific /// /// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. + /// - **Wayland:** Uses the wp-fractional-scale protocol if available. Falls back to integer-scale factors otherwise. /// - **Android:** Always returns 1.0. /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s /// [`contentScaleFactor`]. diff --git a/wayland_protocols/fractional-scale-v1.xml b/wayland_protocols/fractional-scale-v1.xml new file mode 100644 index 00000000..350bfc01 --- /dev/null +++ b/wayland_protocols/fractional-scale-v1.xml @@ -0,0 +1,102 @@ + + + + Copyright © 2022 Kenny Levinsen + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows a compositor to suggest for surfaces to render at + fractional scales. + + A client can submit scaled content by utilizing wp_viewport. This is done by + creating a wp_viewport object for the surface and setting the destination + rectangle to the surface size before the scale factor is applied. + + The buffer size is calculated by multiplying the surface size by the + intended scale. + + The wl_surface buffer scale should remain set to 1. + + If a surface has a surface-local size of 100 px by 50 px and wishes to + submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should + be used and the wp_viewport destination rectangle should be 100 px by 50 px. + + For toplevel surfaces, the size is rounded halfway away from zero. The + rounding algorithm for subsurface position and size is not defined. + + + + + A global interface for requesting surfaces to use fractional scales. + + + + + Informs the server that the client will not be using this protocol + object anymore. This does not affect any other objects, + wp_fractional_scale_v1 objects included. + + + + + + + + + + Create an add-on object for the the wl_surface to let the compositor + request fractional scales. If the given wl_surface already has a + wp_fractional_scale_v1 object associated, the fractional_scale_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + + + + + Destroy the fractional scale object. When this object is destroyed, + preferred_scale events will no longer be sent. + + + + + + Notification of a new preferred scale for this surface that the + compositor suggests that the client should use. + + The sent scale is the numerator of a fraction with a denominator of 120. + + + + +