diff --git a/examples/session_lock.rs b/examples/session_lock.rs new file mode 100644 index 000000000..ed49c040a --- /dev/null +++ b/examples/session_lock.rs @@ -0,0 +1,229 @@ +use smithay_client_toolkit::{ + compositor::{CompositorHandler, CompositorState}, + output::{OutputHandler, OutputState}, + reexports::{ + calloop::{ + timer::{TimeoutAction, Timer}, + EventLoop, LoopHandle, + }, + calloop_wayland_source::WaylandSource, + }, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, + session_lock::{ + SessionLock, SessionLockHandler, SessionLockState, SessionLockSurface, + SessionLockSurfaceConfigure, + }, + shm::{raw::RawPool, Shm, ShmHandler}, +}; +use std::time::Duration; +use wayland_client::{ + globals::registry_queue_init, + protocol::{wl_buffer, wl_output, wl_shm, wl_surface}, + Connection, QueueHandle, +}; + +struct AppData { + loop_handle: LoopHandle<'static, Self>, + conn: Connection, + compositor_state: CompositorState, + output_state: OutputState, + registry_state: RegistryState, + shm: Shm, + session_lock_state: SessionLockState, + session_lock: Option, + lock_surfaces: Vec, + exit: bool, +} + +fn main() { + env_logger::init(); + + let conn = Connection::connect_to_env().unwrap(); + + let (globals, event_queue) = registry_queue_init(&conn).unwrap(); + let qh: QueueHandle = event_queue.handle(); + let mut event_loop: EventLoop = + EventLoop::try_new().expect("Failed to initialize the event loop!"); + + let mut app_data = AppData { + loop_handle: event_loop.handle(), + conn: conn.clone(), + compositor_state: CompositorState::bind(&globals, &qh).unwrap(), + output_state: OutputState::new(&globals, &qh), + registry_state: RegistryState::new(&globals), + shm: Shm::bind(&globals, &qh).unwrap(), + session_lock_state: SessionLockState::new(&globals, &qh), + session_lock: None, + lock_surfaces: Vec::new(), + exit: false, + }; + + app_data.session_lock = + Some(app_data.session_lock_state.lock(&qh).expect("ext-session-lock not supported")); + + WaylandSource::new(conn.clone(), event_queue).insert(event_loop.handle()).unwrap(); + + loop { + event_loop.dispatch(Duration::from_millis(16), &mut app_data).unwrap(); + + if app_data.exit { + break; + } + } +} + +impl SessionLockHandler for AppData { + fn locked(&mut self, _conn: &Connection, qh: &QueueHandle, session_lock: SessionLock) { + println!("Locked"); + + for output in self.output_state.outputs() { + let surface = self.compositor_state.create_surface(&qh); + let lock_surface = session_lock.create_lock_surface(surface, &output, qh); + self.lock_surfaces.push(lock_surface); + } + + // After 5 seconds, destroy lock + self.loop_handle + .insert_source(Timer::from_duration(Duration::from_secs(5)), |_, _, app_data| { + // Destroy the session lock + app_data.session_lock.take(); + // Sync connection to make sure compostor receives destroy + app_data.conn.roundtrip().unwrap(); + // Then we can exit + app_data.exit = true; + TimeoutAction::Drop + }) + .unwrap(); + } + + fn finished( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _session_lock: SessionLock, + ) { + println!("Finished"); + self.exit = true; + } + + fn configure( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + session_lock_surface: SessionLockSurface, + configure: SessionLockSurfaceConfigure, + _serial: u32, + ) { + let (width, height) = configure.new_size; + + let mut pool = RawPool::new(width as usize * height as usize * 4, &self.shm).unwrap(); + let canvas = pool.mmap(); + canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| { + let x = (index % width as usize) as u32; + let y = (index / width as usize) as u32; + + let a = 0xFF; + let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); + let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); + let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); + let color = (a << 24) + (r << 16) + (g << 8) + b; + + let array: &mut [u8; 4] = chunk.try_into().unwrap(); + *array = color.to_le_bytes(); + }); + let buffer = pool.create_buffer( + 0, + width as i32, + height as i32, + width as i32 * 4, + wl_shm::Format::Argb8888, + (), + qh, + ); + + session_lock_surface.wl_surface().attach(Some(&buffer), 0, 0); + session_lock_surface.wl_surface().commit(); + + buffer.destroy(); + } +} + +impl CompositorHandler for AppData { + fn scale_factor_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _new_factor: i32, + ) { + } + + fn transform_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _new_transform: wl_output::Transform, + ) { + } + + fn frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _time: u32, + ) { + } +} + +impl OutputHandler for AppData { + fn output_state(&mut self) -> &mut OutputState { + &mut self.output_state + } + + fn new_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } + + fn update_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } + + fn output_destroyed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } +} + +impl ProvidesRegistryState for AppData { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![OutputState,]; +} + +impl ShmHandler for AppData { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm + } +} + +smithay_client_toolkit::delegate_compositor!(AppData); +smithay_client_toolkit::delegate_output!(AppData); +smithay_client_toolkit::delegate_session_lock!(AppData); +smithay_client_toolkit::delegate_shm!(AppData); +smithay_client_toolkit::delegate_registry!(AppData); +wayland_client::delegate_noop!(AppData: ignore wl_buffer::WlBuffer); diff --git a/src/lib.rs b/src/lib.rs index 154c46236..5040c4b63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub mod output; pub mod primary_selection; pub mod registry; pub mod seat; +pub mod session_lock; pub mod shell; pub mod shm; pub mod subcompositor; diff --git a/src/session_lock/dispatch.rs b/src/session_lock/dispatch.rs new file mode 100644 index 000000000..aea9b355c --- /dev/null +++ b/src/session_lock/dispatch.rs @@ -0,0 +1,88 @@ +use crate::globals::GlobalData; +use std::sync::atomic::Ordering; +use wayland_client::{Connection, Dispatch, QueueHandle}; +use wayland_protocols::ext::session_lock::v1::client::{ + ext_session_lock_manager_v1, ext_session_lock_surface_v1, ext_session_lock_v1, +}; + +use super::{ + SessionLock, SessionLockData, SessionLockHandler, SessionLockState, SessionLockSurface, + SessionLockSurfaceConfigure, SessionLockSurfaceData, +}; + +impl Dispatch + for SessionLockState +where + D: Dispatch, +{ + fn event( + _state: &mut D, + _proxy: &ext_session_lock_manager_v1::ExtSessionLockManagerV1, + _event: ext_session_lock_manager_v1::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + unreachable!() + } +} + +impl Dispatch for SessionLockState +where + D: Dispatch + SessionLockHandler, +{ + fn event( + state: &mut D, + proxy: &ext_session_lock_v1::ExtSessionLockV1, + event: ext_session_lock_v1::Event, + _: &SessionLockData, + conn: &Connection, + qh: &QueueHandle, + ) { + if let Some(session_lock) = SessionLock::from_ext_session_lock(proxy) { + match event { + ext_session_lock_v1::Event::Locked => { + session_lock.0.locked.store(true, Ordering::SeqCst); + state.locked(conn, qh, session_lock); + } + ext_session_lock_v1::Event::Finished => { + state.finished(conn, qh, session_lock); + } + _ => unreachable!(), + } + } + } +} + +impl Dispatch + for SessionLockState +where + D: Dispatch + + SessionLockHandler, +{ + fn event( + state: &mut D, + proxy: &ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, + event: ext_session_lock_surface_v1::Event, + _: &SessionLockSurfaceData, + conn: &Connection, + qh: &QueueHandle, + ) { + if let Some(session_lock_surface) = SessionLockSurface::from_ext_session_lock_surface(proxy) + { + match event { + ext_session_lock_surface_v1::Event::Configure { serial, width, height } => { + proxy.ack_configure(serial); + state.configure( + conn, + qh, + session_lock_surface, + SessionLockSurfaceConfigure { new_size: (width, height) }, + serial, + ); + } + _ => unreachable!(), + } + } + } +} diff --git a/src/session_lock/mod.rs b/src/session_lock/mod.rs new file mode 100644 index 000000000..fe60690dc --- /dev/null +++ b/src/session_lock/mod.rs @@ -0,0 +1,211 @@ +use crate::{compositor::Surface, error::GlobalError, globals::GlobalData, registry::GlobalProxy}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Weak, +}; +use wayland_client::{ + globals::GlobalList, + protocol::{wl_output, wl_surface}, + Connection, Dispatch, Proxy, QueueHandle, +}; +use wayland_protocols::ext::session_lock::v1::client::{ + ext_session_lock_manager_v1, ext_session_lock_surface_v1, ext_session_lock_v1, +}; + +mod dispatch; + +/// Handler trait for session lock protocol. +pub trait SessionLockHandler: Sized { + /// The session lock is active, and the client may create lock surfaces. + fn locked(&mut self, conn: &Connection, qh: &QueueHandle, session_lock: SessionLock); + + /// Session lock is not active and should be destroyed. + /// + /// This may be sent immediately if the compositor denys the requires to create a lock, + /// or may be sent some time after `lock`. + fn finished(&mut self, conn: &Connection, qh: &QueueHandle, session_lock: SessionLock); + + /// Compositor has requested size for surface. + fn configure( + &mut self, + conn: &Connection, + qh: &QueueHandle, + surface: SessionLockSurface, + configure: SessionLockSurfaceConfigure, + serial: u32, + ); +} + +#[non_exhaustive] +#[derive(Debug, Clone)] +pub struct SessionLockSurfaceConfigure { + pub new_size: (u32, u32), +} + +#[derive(Debug)] +struct SessionLockSurfaceInner { + surface: Surface, + session_lock_surface: ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, +} + +impl Drop for SessionLockSurfaceInner { + fn drop(&mut self) { + self.session_lock_surface.destroy(); + } +} + +#[must_use] +#[derive(Debug, Clone)] +pub struct SessionLockSurface(Arc); + +impl SessionLockSurface { + pub fn from_ext_session_lock_surface( + surface: &ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, + ) -> Option { + surface.data::().and_then(|data| data.inner.upgrade()).map(Self) + } + + pub fn wl_surface(&self) -> &wl_surface::WlSurface { + self.0.surface.wl_surface() + } +} + +#[derive(Debug)] +pub struct SessionLockSurfaceData { + inner: Weak, +} + +impl SessionLockSurfaceData { + pub fn session_lock_surface(&self) -> Option { + self.inner.upgrade().map(SessionLockSurface) + } +} + +#[derive(Debug)] +pub struct SessionLockInner { + session_lock: ext_session_lock_v1::ExtSessionLockV1, + locked: AtomicBool, +} + +impl Drop for SessionLockInner { + fn drop(&mut self) { + if self.locked.load(Ordering::SeqCst) { + self.session_lock.unlock_and_destroy(); + } else { + self.session_lock.destroy(); + } + } +} + +/// A session lock +/// +/// The lock is destroyed on drop, which must be done after `locked` or `finished` +/// is received. +#[must_use] +#[derive(Debug, Clone)] +pub struct SessionLock(Arc); + +impl SessionLock { + pub fn from_ext_session_lock(surface: &ext_session_lock_v1::ExtSessionLockV1) -> Option { + surface.data::().and_then(|data| data.inner.upgrade()).map(Self) + } + + pub fn is_locked(&self) -> bool { + self.0.locked.load(Ordering::SeqCst) + } +} + +#[derive(Debug)] +pub struct SessionLockData { + inner: Weak, +} + +impl SessionLock { + pub fn create_lock_surface( + &self, + surface: impl Into, + output: &wl_output::WlOutput, + qh: &QueueHandle, + ) -> SessionLockSurface + where + D: Dispatch + + 'static, + { + // Freeze the queue during the creation of the Arc to avoid a race between events on the + // new objects being processed and the Weak in the SessionLockSurfaceData becoming usable. + let freeze = qh.freeze(); + let surface = surface.into(); + + let inner = Arc::new_cyclic(|weak| { + let session_lock_surface = self.0.session_lock.get_lock_surface( + surface.wl_surface(), + output, + qh, + SessionLockSurfaceData { inner: weak.clone() }, + ); + + SessionLockSurfaceInner { surface, session_lock_surface } + }); + drop(freeze); + + SessionLockSurface(inner) + } +} + +/// A handler for [`ext_session_lock_manager_v1::ExtSessionLockManagerV1`] +#[derive(Debug)] +pub struct SessionLockState { + session_lock_manager: GlobalProxy, +} + +impl SessionLockState { + pub fn new(globals: &GlobalList, qh: &QueueHandle) -> Self + where + D: Dispatch + 'static, + { + let session_lock_manager = GlobalProxy::from(globals.bind(qh, 1..=1, GlobalData)); + Self { session_lock_manager } + } + + pub fn lock(&self, qh: &QueueHandle) -> Result + where + D: Dispatch + 'static, + { + let session_lock_manager = self.session_lock_manager.get()?; + + // Freeze the queue during the creation of the Arc to avoid a race between events on the + // new objects being processed and the Weak in the SessionLockData becoming usable. + let freeze = qh.freeze(); + + let inner = Arc::new_cyclic(|weak| { + let session_lock = + session_lock_manager.lock(qh, SessionLockData { inner: weak.clone() }); + + SessionLockInner { session_lock, locked: AtomicBool::new(false) } + }); + drop(freeze); + + Ok(SessionLock(inner)) + } +} + +#[macro_export] +macro_rules! delegate_session_lock { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::ext::session_lock::v1::client::ext_session_lock_manager_v1::ExtSessionLockManagerV1: $crate::globals::GlobalData + ] => $crate::session_lock::SessionLockState + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::ext::session_lock::v1::client::ext_session_lock_v1::ExtSessionLockV1: $crate::session_lock::SessionLockData + ] => $crate::session_lock::SessionLockState + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::ext::session_lock::v1::client::ext_session_lock_surface_v1::ExtSessionLockSurfaceV1: $crate::session_lock::SessionLockSurfaceData + ] => $crate::session_lock::SessionLockState + ); + }; +} diff --git a/src/shell/wlr_layer/mod.rs b/src/shell/wlr_layer/mod.rs index ea8c4b235..c287962f3 100644 --- a/src/shell/wlr_layer/mod.rs +++ b/src/shell/wlr_layer/mod.rs @@ -55,7 +55,7 @@ impl LayerShell { State: Dispatch + 'static, { // Freeze the queue during the creation of the Arc to avoid a race between events on the - // new objects being processed and the Weak in the PopupData becoming usable. + // new objects being processed and the Weak in the LayerSurfaceData becoming usable. let freeze = qh.freeze(); let surface = surface.into(); diff --git a/src/shell/xdg/mod.rs b/src/shell/xdg/mod.rs index 115b14a57..c1a442e63 100644 --- a/src/shell/xdg/mod.rs +++ b/src/shell/xdg/mod.rs @@ -90,7 +90,7 @@ impl XdgShell { let surface = surface.into(); // Freeze the queue during the creation of the Arc to avoid a race between events on the - // new objects being processed and the Weak in the PopupData becoming usable. + // new objects being processed and the Weak in the WindowData becoming usable. let freeze = qh.freeze(); let inner = Arc::new_cyclic(|weak| {