From 2cd5df6ee1dc1b3cf27c10fcbcc0eaed195c1b3c Mon Sep 17 00:00:00 2001 From: drendog Date: Sat, 25 Oct 2025 11:44:09 +0200 Subject: [PATCH] refactor: better fractional scaling management --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/windowing/mod.rs | 95 +++++++++++++++++++++--- src/windowing/state/builder.rs | 18 +++++ src/windowing/state/dispatches.rs | 80 ++++++++++++++++++-- src/windowing/state/mod.rs | 117 ++++++++++++++++++++++++++---- 6 files changed, 281 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fd75ba..500242d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4012,9 +4012,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ "bitflags 2.10.0", "wayland-backend", diff --git a/Cargo.toml b/Cargo.toml index 9b73f22..7784206 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,4 +38,4 @@ slint-interpreter = { path = "../slint/internal/interpreter", default-features = smithay-client-toolkit = "0.20.0" thiserror = "2.0.17" wayland-client = "0.31.11" -wayland-protocols = { version = "0.32.6", features = ["client", "staging"] } +wayland-protocols = { version = "0.32.9", features = ["client", "staging"] } diff --git a/src/windowing/mod.rs b/src/windowing/mod.rs index 30918d7..06e888c 100644 --- a/src/windowing/mod.rs +++ b/src/windowing/mod.rs @@ -24,6 +24,13 @@ use wayland_client::{ }, Connection, EventQueue, Proxy, QueueHandle, }; +use wayland_protocols::wp::fractional_scale::v1::client::{ + wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, + wp_fractional_scale_v1::WpFractionalScaleV1, +}; +use wayland_protocols::wp::viewporter::client::{ + wp_viewport::WpViewport, wp_viewporter::WpViewporter, +}; pub mod builder; mod config; @@ -44,14 +51,16 @@ impl WindowingSystem { Rc::new(Connection::connect_to_env().map_err(LayerShikaError::WaylandConnection)?); let event_queue = connection.new_event_queue(); - let (compositor, output, layer_shell, seat) = + let (compositor, output, layer_shell, seat, fractional_scale_manager, viewporter) = Self::initialize_globals(&connection, &event_queue.handle()) .map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?; - let (surface, layer_surface) = Self::setup_surface( + let (surface, layer_surface, fractional_scale, viewport) = Self::setup_surface( &compositor, &output, &layer_shell, + fractional_scale_manager.as_ref(), + viewporter.as_ref(), &event_queue.handle(), config, ); @@ -63,7 +72,7 @@ impl WindowingSystem { LayerShikaError::WindowConfiguration("Component definition is required".to_string()) })?; - let state = WindowStateBuilder::new() + let mut builder = WindowStateBuilder::new() .with_component_definition(component_definition) .with_surface(Rc::clone(&surface)) .with_layer_surface(Rc::clone(&layer_surface)) @@ -71,7 +80,17 @@ impl WindowingSystem { .with_scale_factor(config.scale_factor) .with_height(config.height) .with_exclusive_zone(config.exclusive_zone) - .with_window(window) + .with_window(window); + + if let Some(fs) = fractional_scale { + builder = builder.with_fractional_scale(fs); + } + + if let Some(vp) = viewport { + builder = builder.with_viewport(vp); + } + + let state = builder .build() .map_err(|e| LayerShikaError::WindowConfiguration(e.to_string()))?; @@ -89,7 +108,17 @@ impl WindowingSystem { fn initialize_globals( connection: &Connection, queue_handle: &QueueHandle, - ) -> Result<(WlCompositor, WlOutput, ZwlrLayerShellV1, WlSeat), LayerShikaError> { + ) -> Result< + ( + WlCompositor, + WlOutput, + ZwlrLayerShellV1, + WlSeat, + Option, + Option, + ), + LayerShikaError, + > { let global_list = registry_queue_init::(connection) .map(|(global_list, _)| global_list) .map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?; @@ -97,22 +126,52 @@ impl WindowingSystem { let (compositor, output, layer_shell, seat) = bind_globals!( &global_list, queue_handle, - (WlCompositor, compositor, 1..=1), - (WlOutput, output, 1..=1), - (ZwlrLayerShellV1, layer_shell, 1..=1), - (WlSeat, seat, 1..=1) + (WlCompositor, compositor, 3..=6), + (WlOutput, output, 1..=4), + (ZwlrLayerShellV1, layer_shell, 1..=5), + (WlSeat, seat, 1..=9) )?; - Ok((compositor, output, layer_shell, seat)) + let fractional_scale_manager = global_list + .bind::(queue_handle, 1..=1, ()) + .ok(); + + let viewporter = global_list + .bind::(queue_handle, 1..=1, ()) + .ok(); + + if fractional_scale_manager.is_none() { + info!("Fractional scale protocol not available, using integer scaling"); + } + + if viewporter.is_none() { + info!("Viewporter protocol not available"); + } + + Ok(( + compositor, + output, + layer_shell, + seat, + fractional_scale_manager, + viewporter, + )) } fn setup_surface( compositor: &WlCompositor, output: &WlOutput, layer_shell: &ZwlrLayerShellV1, + fractional_scale_manager: Option<&WpFractionalScaleManagerV1>, + viewporter: Option<&WpViewporter>, queue_handle: &QueueHandle, config: &WindowConfig, - ) -> (Rc, Rc) { + ) -> ( + Rc, + Rc, + Option>, + Option>, + ) { let surface = Rc::new(compositor.create_surface(queue_handle, ())); let layer_surface = Rc::new(layer_shell.get_layer_surface( &surface, @@ -123,9 +182,21 @@ impl WindowingSystem { (), )); + let fractional_scale = fractional_scale_manager.map(|manager| { + info!("Creating fractional scale object for surface"); + Rc::new(manager.get_fractional_scale(&surface, queue_handle, ())) + }); + + let viewport = viewporter.map(|vp| { + info!("Creating viewport for surface"); + Rc::new(vp.get_viewport(&surface, queue_handle, ())) + }); + Self::configure_layer_surface(&layer_surface, &surface, config); - (surface, layer_surface) + surface.set_buffer_scale(1); + + (surface, layer_surface, fractional_scale, viewport) } fn configure_layer_surface( diff --git a/src/windowing/state/builder.rs b/src/windowing/state/builder.rs index 0a0ff8c..ba04eb6 100644 --- a/src/windowing/state/builder.rs +++ b/src/windowing/state/builder.rs @@ -3,6 +3,8 @@ use slint::PhysicalSize; use slint_interpreter::ComponentDefinition; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use wayland_client::protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}; +use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; +use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; use crate::{errors::LayerShikaError, rendering::{femtovg_window::FemtoVGWindow, slint_platform::CustomSlintPlatform}}; use super::WindowState; @@ -11,6 +13,8 @@ pub struct WindowStateBuilder { pub component_definition: Option, pub surface: Option>, pub layer_surface: Option>, + pub fractional_scale: Option>, + pub viewport: Option>, pub size: Option, pub output_size: Option, pub pointer: Option>, @@ -86,6 +90,18 @@ impl WindowStateBuilder { self } + #[must_use] + pub fn with_fractional_scale(mut self, fractional_scale: Rc) -> Self { + self.fractional_scale = Some(fractional_scale); + self + } + + #[must_use] + pub fn with_viewport(mut self, viewport: Rc) -> Self { + self.viewport = Some(viewport); + self + } + pub fn build(self) -> Result { let platform = CustomSlintPlatform::new(Rc::clone( self.window @@ -106,6 +122,8 @@ impl Default for WindowStateBuilder { component_definition: None, surface: None, layer_surface: None, + fractional_scale: None, + viewport: None, size: None, output_size: None, pointer: None, diff --git a/src/windowing/state/dispatches.rs b/src/windowing/state/dispatches.rs index 1842f0d..61fac1a 100644 --- a/src/windowing/state/dispatches.rs +++ b/src/windowing/state/dispatches.rs @@ -21,10 +21,20 @@ use wayland_client::{ }, Connection, Dispatch, Proxy, QueueHandle, }; +use wayland_protocols::wp::fractional_scale::v1::client::{ + wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, + wp_fractional_scale_v1::{self, WpFractionalScaleV1}, +}; +use wayland_protocols::wp::viewporter::client::{ + wp_viewport::WpViewport, wp_viewporter::WpViewporter, +}; use super::WindowState; impl Dispatch for WindowState { + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_precision_loss)] fn event( state: &mut Self, layer_surface: &ZwlrLayerSurfaceV1, @@ -39,14 +49,49 @@ impl Dispatch for WindowState { width, height, } => { - info!("Layer surface configured with size: {}x{}", width, height); + info!( + "Layer surface configured with compositor size: {}x{}", + width, height + ); layer_surface.ack_configure(serial); - if width > 0 && height > 0 { - state.update_size(state.output_size().width, state.height()); + + let output_width = state.output_size().width; + let scale_factor = state.scale_factor(); + + let target_width = if width == 0 || (width == 1 && output_width > 1) { + if scale_factor > 1.0 { + (output_width as f32 / scale_factor).round() as u32 + } else { + output_width + } } else { - let current_size = state.output_size(); - state.update_size(current_size.width, current_size.height); - } + width + }; + + let target_height = if height > 0 { + height + } else { + let h = state.height(); + if scale_factor > 1.0 { + (h as f32 / scale_factor).round() as u32 + } else { + h + } + }; + + let clamped_width = target_width.min(output_width); + + info!( + "Using dimensions: {}x{} (clamped from {}x{}, output: {}x{})", + clamped_width, + target_height, + target_width, + target_height, + output_width, + state.output_size().height + ); + + state.update_size(clamped_width, target_height); } zwlr_layer_surface_v1::Event::Closed => { info!("Layer surface closed"); @@ -153,10 +198,31 @@ impl Dispatch for WindowState { } } +impl Dispatch for WindowState { + fn event( + state: &mut Self, + _proxy: &WpFractionalScaleV1, + event: wp_fractional_scale_v1::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + if let wp_fractional_scale_v1::Event::PreferredScale { scale } = event { + #[allow(clippy::cast_precision_loss)] + let scale_float = scale as f32 / 120.0; + info!("Fractional scale received: {scale_float} ({scale}x)"); + state.update_scale_factor(scale); + } + } +} + impl_empty_dispatch!( (WlRegistry, GlobalListContents), (WlCompositor, ()), (WlSurface, ()), (ZwlrLayerShellV1, ()), - (WlSeat, ()) + (WlSeat, ()), + (WpFractionalScaleManagerV1, ()), + (WpViewporter, ()), + (WpViewport, ()) ); diff --git a/src/windowing/state/mod.rs b/src/windowing/state/mod.rs index 768a56d..de73417 100644 --- a/src/windowing/state/mod.rs +++ b/src/windowing/state/mod.rs @@ -5,6 +5,8 @@ use slint::{LogicalPosition, PhysicalSize, ComponentHandle}; use slint_interpreter::ComponentInstance; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use wayland_client::protocol::wl_surface::WlSurface; +use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; +use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; use crate::rendering::femtovg_window::FemtoVGWindow; use crate::errors::LayerShikaError; @@ -15,7 +17,10 @@ pub struct WindowState { component_instance: ComponentInstance, surface: Rc, layer_surface: Rc, + fractional_scale: Option>, + viewport: Option>, size: PhysicalSize, + logical_size: PhysicalSize, output_size: PhysicalSize, window: Rc, current_pointer_position: LogicalPosition, @@ -39,7 +44,6 @@ impl WindowState { .show() .map_err(|e| LayerShikaError::SlintComponentCreation(e.to_string()))?; - // Request initial redraw to ensure the first frame is rendered window.request_redraw(); Ok(Self { @@ -50,7 +54,10 @@ impl WindowState { layer_surface: builder .layer_surface .ok_or_else(|| LayerShikaError::InvalidInput("Layer surface is required".into()))?, + fractional_scale: builder.fractional_scale, + viewport: builder.viewport, size: builder.size.unwrap_or_default(), + logical_size: PhysicalSize::default(), output_size: builder.output_size.unwrap_or_default(), window, current_pointer_position: LogicalPosition::default(), @@ -61,27 +68,92 @@ impl WindowState { } pub fn update_size(&mut self, width: u32, height: u32) { - let new_size = PhysicalSize::new(width, height); - info!("Updating window size to {}x{}", width, height); - self.window.set_size(slint::WindowSize::Physical(new_size)); - self.window.set_scale_factor(self.scale_factor); + if width == 0 || height == 0 { + info!( + "Skipping update_size with zero dimension: {}x{}", + width, height + ); + return; + } + + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_precision_loss)] + let physical_width = (width as f32 * self.scale_factor).round() as u32; + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_precision_loss)] + let physical_height = (height as f32 * self.scale_factor).round() as u32; + + let new_physical_size = PhysicalSize::new(physical_width, physical_height); + let new_logical_size = PhysicalSize::new(width, height); + + info!( + "Updating window size: buffer {}x{}, physical {}x{}, scale {}, has_viewport: {}", + width, + height, + physical_width, + physical_height, + self.scale_factor, + self.viewport.is_some() + ); + + if self.fractional_scale.is_some() && self.viewport.is_some() { + self.surface.set_buffer_scale(1); + self.window + .set_size(slint::WindowSize::Logical(slint::LogicalSize::new( + width as f32, + height as f32, + ))); + self.window.set_scale_factor(self.scale_factor); + + if let Some(viewport) = &self.viewport { + viewport.set_destination(width as i32, height as i32); + } + } else if self.fractional_scale.is_some() { + #[allow(clippy::cast_possible_truncation)] + let buffer_scale = self.scale_factor.round() as i32; + self.surface.set_buffer_scale(buffer_scale); + + self.window + .set_size(slint::WindowSize::Logical(slint::LogicalSize::new( + width as f32, + height as f32, + ))); + #[allow(clippy::cast_precision_loss)] + self.window.set_scale_factor(buffer_scale as f32); + } else { + #[allow(clippy::cast_possible_truncation)] + let buffer_scale = self.scale_factor.round() as i32; + self.surface.set_buffer_scale(buffer_scale); + + self.window + .set_size(slint::WindowSize::Physical(new_physical_size)); + self.window.set_scale_factor(self.scale_factor); + } + + info!("Window physical size: {:?}", self.window.size()); - info!("Updating layer surface size to {}x{}", width, height); self.layer_surface.set_size(width, height); self.layer_surface.set_exclusive_zone(self.exclusive_zone); - self.surface.commit(); - self.size = new_size; + + self.size = new_physical_size; + self.logical_size = new_logical_size; self.window.request_redraw(); } #[allow(clippy::cast_possible_truncation)] pub fn set_current_pointer_position(&mut self, physical_x: f64, physical_y: f64) { - let scale_factor = self.scale_factor; - let logical_position = LogicalPosition::new( - physical_x as f32 / scale_factor, - physical_y as f32 / scale_factor, - ); + let logical_position = if self.fractional_scale.is_some() { + LogicalPosition::new(physical_x as f32, physical_y as f32) + } else { + let scale_factor = self.scale_factor; + LogicalPosition::new( + physical_x as f32 / scale_factor, + physical_y as f32 / scale_factor, + ) + }; self.current_pointer_position = logical_position; } @@ -120,4 +192,23 @@ impl WindowState { pub const fn component_instance(&self) -> &ComponentInstance { &self.component_instance } + + pub fn update_scale_factor(&mut self, scale_120ths: u32) { + #[allow(clippy::cast_precision_loss)] + let new_scale_factor = scale_120ths as f32 / 120.0; + info!( + "Updating scale factor from {} to {} ({}x)", + self.scale_factor, new_scale_factor, scale_120ths + ); + self.scale_factor = new_scale_factor; + + let current_logical_size = self.logical_size; + if current_logical_size.width > 0 && current_logical_size.height > 0 { + self.update_size(current_logical_size.width, current_logical_size.height); + } + } + + pub const fn scale_factor(&self) -> f32 { + self.scale_factor + } }