refactor: better fractional scaling management

This commit is contained in:
drendog 2025-10-25 11:44:09 +02:00
parent 238bd150ad
commit 2cd5df6ee1
Signed by: dwenya
GPG key ID: 8DD77074645332D0
6 changed files with 281 additions and 35 deletions

4
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -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<WindowState>,
) -> Result<(WlCompositor, WlOutput, ZwlrLayerShellV1, WlSeat), LayerShikaError> {
) -> Result<
(
WlCompositor,
WlOutput,
ZwlrLayerShellV1,
WlSeat,
Option<WpFractionalScaleManagerV1>,
Option<WpViewporter>,
),
LayerShikaError,
> {
let global_list = registry_queue_init::<WindowState>(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::<WpFractionalScaleManagerV1, _, _>(queue_handle, 1..=1, ())
.ok();
let viewporter = global_list
.bind::<WpViewporter, _, _>(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<WindowState>,
config: &WindowConfig,
) -> (Rc<WlSurface>, Rc<ZwlrLayerSurfaceV1>) {
) -> (
Rc<WlSurface>,
Rc<ZwlrLayerSurfaceV1>,
Option<Rc<WpFractionalScaleV1>>,
Option<Rc<WpViewport>>,
) {
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(

View file

@ -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<ComponentDefinition>,
pub surface: Option<Rc<WlSurface>>,
pub layer_surface: Option<Rc<ZwlrLayerSurfaceV1>>,
pub fractional_scale: Option<Rc<WpFractionalScaleV1>>,
pub viewport: Option<Rc<WpViewport>>,
pub size: Option<PhysicalSize>,
pub output_size: Option<PhysicalSize>,
pub pointer: Option<Rc<WlPointer>>,
@ -86,6 +90,18 @@ impl WindowStateBuilder {
self
}
#[must_use]
pub fn with_fractional_scale(mut self, fractional_scale: Rc<WpFractionalScaleV1>) -> Self {
self.fractional_scale = Some(fractional_scale);
self
}
#[must_use]
pub fn with_viewport(mut self, viewport: Rc<WpViewport>) -> Self {
self.viewport = Some(viewport);
self
}
pub fn build(self) -> Result<WindowState, LayerShikaError> {
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,

View file

@ -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<ZwlrLayerSurfaceV1, ()> 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<ZwlrLayerSurfaceV1, ()> 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<WlPointer, ()> for WindowState {
}
}
impl Dispatch<WpFractionalScaleV1, ()> for WindowState {
fn event(
state: &mut Self,
_proxy: &WpFractionalScaleV1,
event: wp_fractional_scale_v1::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
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, ())
);

View file

@ -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<WlSurface>,
layer_surface: Rc<ZwlrLayerSurfaceV1>,
fractional_scale: Option<Rc<WpFractionalScaleV1>>,
viewport: Option<Rc<WpViewport>>,
size: PhysicalSize,
logical_size: PhysicalSize,
output_size: PhysicalSize,
window: Rc<FemtoVGWindow>,
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
}
}