diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 6192c91ba8c6..21541b3c5dce 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -70,6 +70,7 @@ pub mod shell; pub mod shm; pub mod socket; pub mod tablet_manager; +pub mod tearing_control; pub mod text_input; pub mod viewporter; pub mod virtual_keyboard; diff --git a/src/wayland/tearing_control/dispatch.rs b/src/wayland/tearing_control/dispatch.rs new file mode 100644 index 000000000000..9eab1cd7d4a9 --- /dev/null +++ b/src/wayland/tearing_control/dispatch.rs @@ -0,0 +1,141 @@ +use wayland_protocols::wp::tearing_control::v1::server::{ + wp_tearing_control_manager_v1::{self, WpTearingControlManagerV1}, + wp_tearing_control_v1::{self, WpTearingControlV1}, +}; +use wayland_server::{ + backend::ClientId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, +}; + +use super::{ + TearingControlState, TearingControlSurfaceCachedState, TearingControlSurfaceData, TearingControlUserData, +}; +use crate::wayland::compositor; + +impl GlobalDispatch for TearingControlState +where + D: GlobalDispatch, + D: Dispatch, + D: Dispatch, + D: 'static, +{ + fn bind( + _state: &mut D, + _: &DisplayHandle, + _: &Client, + resource: New, + _: &(), + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } +} + +impl Dispatch for TearingControlState +where + D: Dispatch, + D: Dispatch, + D: 'static, +{ + fn request( + _state: &mut D, + _: &Client, + manager: &WpTearingControlManagerV1, + request: wp_tearing_control_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + wp_tearing_control_manager_v1::Request::GetTearingControl { id, surface } => { + let already_taken = compositor::with_states(&surface, |states| { + states + .data_map + .insert_if_missing_threadsafe(TearingControlSurfaceData::new); + let data = states.data_map.get::().unwrap(); + + let already_taken = data.is_resource_attached(); + + if !already_taken { + data.set_is_resource_attached(true); + } + + already_taken + }); + + if already_taken { + manager.post_error( + wp_tearing_control_manager_v1::Error::TearingControlExists, + "WlSurface already has WpTearingControlV1 attached", + ) + } else { + data_init.init(id, TearingControlUserData::new(surface)); + } + } + + wp_tearing_control_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +impl Dispatch for TearingControlState +where + D: Dispatch, +{ + fn request( + _state: &mut D, + _: &Client, + _: &WpTearingControlV1, + request: wp_tearing_control_v1::Request, + data: &TearingControlUserData, + _dh: &DisplayHandle, + _: &mut DataInit<'_, D>, + ) { + match request { + wp_tearing_control_v1::Request::SetPresentationHint { hint } => { + let wayland_server::WEnum::Value(hint) = hint else { + return; + }; + let surface = data.wl_surface(); + + compositor::with_states(&surface, |states| { + states + .cached_state + .pending::() + .presentation_hint = hint; + }) + } + // Switch back to default PresentationHint. + // This is equivalent to setting the hint to Vsync, + // including double buffering semantics. + wp_tearing_control_v1::Request::Destroy => { + let surface = data.wl_surface(); + + compositor::with_states(&surface, |states| { + states + .data_map + .get::() + .unwrap() + .set_is_resource_attached(false); + + states + .cached_state + .pending::() + .presentation_hint = wp_tearing_control_v1::PresentationHint::Vsync; + }); + } + _ => unreachable!(), + } + } + + fn destroyed( + _state: &mut D, + _client: ClientId, + _object: &WpTearingControlV1, + _data: &TearingControlUserData, + ) { + // Nothing to do here, graceful Destroy is already handled with double buffering + // and in case of client close WlSurface destroyed handler will clean up the data anyway, + // so there is no point in queuing new update + } +} diff --git a/src/wayland/tearing_control/mod.rs b/src/wayland/tearing_control/mod.rs new file mode 100644 index 000000000000..7f6a4ac86f32 --- /dev/null +++ b/src/wayland/tearing_control/mod.rs @@ -0,0 +1,200 @@ +//! Implementation of wp_tearing_control protocol +//! +//! ### Example +//! +//! ```no_run +//! # extern crate wayland_server; +//! # +//! use wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}; +//! use smithay::{ +//! delegate_tearing_control, delegate_compositor, +//! wayland::compositor::{self, CompositorState, CompositorClientState, CompositorHandler}, +//! wayland::tearing_control::{TearingControlSurfaceCachedState, TearingControlState}, +//! }; +//! +//! pub struct State { +//! compositor_state: CompositorState, +//! }; +//! struct ClientState { compositor_state: CompositorClientState } +//! impl wayland_server::backend::ClientData for ClientState {} +//! +//! delegate_tearing_control!(State); +//! delegate_compositor!(State); +//! +//! impl CompositorHandler for State { +//! fn compositor_state(&mut self) -> &mut CompositorState { +//! &mut self.compositor_state +//! } +//! +//! fn client_compositor_state<'a>(&self, client: &'a wayland_server::Client) -> &'a CompositorClientState { +//! &client.get_data::().unwrap().compositor_state +//! } +//! +//! fn commit(&mut self, surface: &WlSurface) { +//! compositor::with_states(&surface, |states| { +//! let current = states.cached_state.current::(); +//! dbg!(current.presentation_hint()); +//! }); +//! } +//! } +//! +//! let mut display = wayland_server::Display::::new().unwrap(); +//! +//! let compositor_state = CompositorState::new::(&display.handle()); +//! TearingControlState::new::(&display.handle()); +//! +//! let state = State { +//! compositor_state, +//! }; +//! ``` + +use std::sync::{ + atomic::{self, AtomicBool}, + Mutex, +}; + +use wayland_protocols::wp::tearing_control::v1::server::{ + wp_tearing_control_manager_v1::WpTearingControlManagerV1, + wp_tearing_control_v1::{self, WpTearingControlV1}, +}; +use wayland_server::{ + backend::GlobalId, protocol::wl_surface::WlSurface, Dispatch, DisplayHandle, GlobalDispatch, +}; + +use super::compositor::Cacheable; + +mod dispatch; + +/// Data associated with WlSurface +/// Represents the client pending state +/// +/// ```no_run +/// use smithay::wayland::compositor; +/// use smithay::wayland::tearing_control::TearingControlSurfaceCachedState; +/// +/// # let wl_surface = todo!(); +/// compositor::with_states(&wl_surface, |states| { +/// let current = states.cached_state.current::(); +/// dbg!(current.presentation_hint()); +/// }); +/// ``` +#[derive(Debug, Clone, Copy)] +pub struct TearingControlSurfaceCachedState { + presentation_hint: wp_tearing_control_v1::PresentationHint, +} + +impl TearingControlSurfaceCachedState { + /// Provides information for if submitted frames from the client may be presented with tearing. + pub fn presentation_hint(&self) -> &wp_tearing_control_v1::PresentationHint { + &self.presentation_hint + } +} + +impl Default for TearingControlSurfaceCachedState { + fn default() -> Self { + Self { + presentation_hint: wp_tearing_control_v1::PresentationHint::Vsync, + } + } +} + +impl Cacheable for TearingControlSurfaceCachedState { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + *self + } + + fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) { + *into = self; + } +} + +#[derive(Debug)] +struct TearingControlSurfaceData { + is_resource_attached: AtomicBool, +} + +impl TearingControlSurfaceData { + fn new() -> Self { + Self { + is_resource_attached: AtomicBool::new(false), + } + } + + fn set_is_resource_attached(&self, is_attached: bool) { + self.is_resource_attached + .store(is_attached, atomic::Ordering::Release) + } + + fn is_resource_attached(&self) -> bool { + self.is_resource_attached.load(atomic::Ordering::Acquire) + } +} + +/// User data of [WpTearingControlV1] object +#[derive(Debug)] +pub struct TearingControlUserData(Mutex); + +impl TearingControlUserData { + fn new(surface: WlSurface) -> Self { + Self(Mutex::new(surface)) + } + + fn wl_surface(&self) -> WlSurface { + self.0.lock().unwrap().clone() + } +} + +/// Delegate type for [WpTearingControlManagerV1] global. +#[derive(Debug)] +pub struct TearingControlState { + global: GlobalId, +} + +impl TearingControlState { + /// Regiseter new [WpTearingControlManagerV1] global + pub fn new(display: &DisplayHandle) -> TearingControlState + where + D: GlobalDispatch + + Dispatch + + Dispatch + + 'static, + { + let global = display.create_global::(1, ()); + + TearingControlState { global } + } + + /// Returns the [WpTearingControlManagerV1] global id + pub fn global(&self) -> GlobalId { + self.global.clone() + } +} + +/// Macro to delegate implementation of the wp tearing control protocol +#[macro_export] +macro_rules! delegate_tearing_control { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + type __WpTearingControlManagerV1 = + $crate::reexports::wayland_protocols::wp::tearing_control::v1::server::wp_tearing_control_manager_v1::WpTearingControlManagerV1; + type __WpTearingControlV1 = + $crate::reexports::wayland_protocols::wp::tearing_control::v1::server::wp_tearing_control_v1::WpTearingControlV1; + + $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + __WpTearingControlManagerV1: () + ] => $crate::wayland::tearing_control::TearingControlState + ); + + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + __WpTearingControlManagerV1: () + ] => $crate::wayland::tearing_control::TearingControlState + ); + + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + __WpTearingControlV1: $crate::wayland::tearing_control::TearingControlUserData + ] => $crate::wayland::tearing_control::TearingControlState + ); + }; +}