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 <ju.orth@gmail.com>
This commit is contained in:
Kirill Chibisov 2023-01-20 00:02:16 +03:00 committed by GitHub
parent 1886949efe
commit de782504ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 361 additions and 66 deletions

View file

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

View file

@ -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<ZxdgDecorationManagerV1>,
xdg_activation: SimpleGlobal<XdgActivationV1>,
fractional_scale_manager: SimpleGlobal<WpFractionalScaleManagerV1>,
viewporter: SimpleGlobal<WpViewporter>,
}
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,
}
}
}

View file

@ -374,10 +374,11 @@ impl<T: 'static> EventLoop<T> {
});
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<T: 'static> EventLoop<T> {
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<T: 'static> EventLoop<T> {
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)
};

View file

@ -10,6 +10,7 @@ pub use window::Window;
mod env;
mod event_loop;
mod output;
mod protocols;
mod seat;
mod window;

View file

@ -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"));

View file

@ -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 {

View file

@ -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 {

View file

@ -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<Mutex<f64>>,
/// The current window size.
size: Arc<Mutex<LogicalSize<u32>>>,
@ -90,33 +97,50 @@ impl Window {
attributes: WindowAttributes,
platform_attributes: PlatformAttributes,
) -> Result<Self, RootOsError> {
let surface = event_loop_window_target
let viewporter = event_loop_window_target.env.get_global::<WpViewporter>();
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::<WinitState>().unwrap();
.get_global::<WpFractionalScaleManagerV1>();
// 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::<WinitState>().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::<WinitState>().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::<f64>(scale_factor as f64).into())
.map(|size| size.to_logical::<f64>(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::<f64>(scale_factor as f64).into());
.map(|size| size.to_logical::<f64>(scale_factor).into());
window.set_min_size(min_size);
// Max dimensions.
let max_size = attributes
.max_inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
.map(|size| size.to_logical::<f64>(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<u32> {
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<u32> {
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::<u32>(scale_factor);
*self.size.lock().unwrap() = size;
@ -403,7 +424,7 @@ impl Window {
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
let scale_factor = self.scale_factor() as f64;
let scale_factor = self.scale_factor();
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
self.send_request(WindowRequest::MinSize(size));
@ -411,7 +432,7 @@ impl Window {
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let scale_factor = self.scale_factor() as f64;
let scale_factor = self.scale_factor();
let size = dimensions.map(|size| size.to_logical::<u32>(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;
}

View file

@ -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<LogicalSize<u32>>,
/// New scale factor.
pub scale_factor: Option<i32>,
pub scale_factor: Option<f64>,
/// Close the window.
pub close_window: bool,
@ -143,6 +145,12 @@ pub struct WindowHandle {
/// An actual window.
pub window: ManuallyDrop<Window<WinitFrame>>,
/// The state of the fractional scaling handlers for the window.
pub fractional_scaling_state: Option<FractionalScalingState>,
/// The scale factor of the window.
pub scale_factor: Arc<Mutex<f64>>,
/// The current size of the window.
pub size: Arc<Mutex<LogicalSize<u32>>>,
@ -192,6 +200,8 @@ impl WindowHandle {
window: Window<WinitFrame>,
size: Arc<Mutex<LogicalSize<u32>>>,
has_focus: Arc<AtomicBool>,
fractional_scaling_state: Option<FractionalScalingState>,
scale_factor: f64,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self {
let xdg_activation = env.get_global::<XdgActivationV1>();
@ -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();
}
}