use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use calloop::generic::Generic; use calloop::{Interest, LoopHandle, Mode, PostAction}; use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::allocator::{Buffer, Fourcc}; use smithay::backend::renderer::damage::OutputDamageTracker; use smithay::backend::renderer::sync::SyncPoint; use smithay::output::Output; use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_frame_v1::{ Flags, ZwlrScreencopyFrameV1, }; use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::{ zwlr_screencopy_frame_v1, zwlr_screencopy_manager_v1, }; use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; use smithay::reexports::wayland_server::protocol::wl_shm::Format; use smithay::reexports::wayland_server::{ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, }; use smithay::utils::{Physical, Point, Rectangle, Size, Transform}; use smithay::wayland::{dmabuf, shm}; use crate::utils::get_monotonic_time; const VERSION: u32 = 3; pub struct ScreencopyQueue { damage_tracker: OutputDamageTracker, screencopies: Vec, } impl Default for ScreencopyQueue { fn default() -> Self { Self::new() } } impl ScreencopyQueue { pub fn new() -> Self { Self { damage_tracker: OutputDamageTracker::new((0, 0), 1.0, Transform::Normal), screencopies: Vec::new(), } } pub fn split(&mut self) -> (&mut OutputDamageTracker, Option<&Screencopy>) { let ScreencopyQueue { damage_tracker, screencopies, } = self; (damage_tracker, screencopies.first()) } pub fn push(&mut self, screencopy: Screencopy) { self.screencopies.push(screencopy); } pub fn pop(&mut self) -> Screencopy { self.screencopies.pop().unwrap() } pub fn remove_output(&mut self, output: &Output) { self.screencopies .retain(|screencopy| screencopy.output() != output); } } #[derive(Default)] pub struct ScreencopyManagerState { queues: HashMap, } pub struct ScreencopyManagerGlobalData { filter: Box Fn(&'c Client) -> bool + Send + Sync>, } impl ScreencopyManagerState { pub fn new(display: &DisplayHandle, filter: F) -> Self where D: GlobalDispatch, D: Dispatch, D: Dispatch, D: ScreencopyHandler, D: 'static, F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static, { let global_data = ScreencopyManagerGlobalData { filter: Box::new(filter), }; display.create_global::(VERSION, global_data); Self { queues: HashMap::new(), } } pub fn bind(&mut self, manager: &ZwlrScreencopyManagerV1) { // Clean up all entries if its manager is dead and its queue is empty. self.queues .retain(|k, v| k.is_alive() || !v.screencopies.is_empty()); self.queues.insert(manager.clone(), ScreencopyQueue::new()); } pub fn get_queue_mut( &mut self, manager: &ZwlrScreencopyManagerV1, ) -> Option<&mut ScreencopyQueue> { self.queues.get_mut(manager) } pub fn queues_mut(&mut self) -> impl Iterator { self.queues.values_mut() } } impl GlobalDispatch for ScreencopyManagerState where D: GlobalDispatch, D: Dispatch, D: Dispatch, D: ScreencopyHandler, D: 'static, { fn bind( state: &mut D, _display: &DisplayHandle, _client: &Client, manager: New, _manager_state: &ScreencopyManagerGlobalData, data_init: &mut DataInit<'_, D>, ) { let manager = data_init.init(manager, ()); state.screencopy_state().bind(&manager); } fn can_view(client: Client, global_data: &ScreencopyManagerGlobalData) -> bool { (global_data.filter)(&client) } } impl Dispatch for ScreencopyManagerState where D: GlobalDispatch, D: Dispatch, D: Dispatch, D: ScreencopyHandler, D: 'static, { fn request( _state: &mut D, _client: &Client, manager: &ZwlrScreencopyManagerV1, request: zwlr_screencopy_manager_v1::Request, _data: &(), _display: &DisplayHandle, data_init: &mut DataInit<'_, D>, ) { let (frame, overlay_cursor, buffer_size, region_loc, output) = match request { zwlr_screencopy_manager_v1::Request::CaptureOutput { frame, overlay_cursor, output, } => { let Some(output) = Output::from_resource(&output) else { trace!("screencopy client requested non-existent output"); let frame = data_init.init(frame, ScreencopyFrameState::Failed); frame.failed(); return; }; let buffer_size = output.current_mode().unwrap().size; let region_loc = Point::from((0, 0)); (frame, overlay_cursor, buffer_size, region_loc, output) } zwlr_screencopy_manager_v1::Request::CaptureOutputRegion { frame, overlay_cursor, x, y, width, height, output, } => { if width <= 0 || height <= 0 { trace!("screencopy client requested invalid sized region"); let frame = data_init.init(frame, ScreencopyFrameState::Failed); frame.failed(); return; } let Some(output) = Output::from_resource(&output) else { trace!("screencopy client requested non-existent output"); let frame = data_init.init(frame, ScreencopyFrameState::Failed); frame.failed(); return; }; let output_transform = output.current_transform(); let output_physical_size = output_transform.transform_size(output.current_mode().unwrap().size); let output_rect = Rectangle::from_size(output_physical_size); let rect = Rectangle::new(Point::from((x, y)), Size::from((width, height))); let output_scale = output.current_scale().fractional_scale(); let physical_rect = rect.to_physical_precise_round(output_scale); // Clamp captured region to the output. let Some(clamped_rect) = physical_rect.intersection(output_rect) else { trace!("screencopy client requested region outside of output"); let frame = data_init.init(frame, ScreencopyFrameState::Failed); frame.failed(); return; }; let untransformed_rect = output_transform .invert() .transform_rect_in(clamped_rect, &output_physical_size); ( frame, overlay_cursor, untransformed_rect.size, clamped_rect.loc, output, ) } zwlr_screencopy_manager_v1::Request::Destroy => return, _ => unreachable!(), }; // Create the frame. let overlay_cursor = overlay_cursor != 0; let info = ScreencopyFrameInfo { output, overlay_cursor, buffer_size, region_loc, }; let frame = data_init.init( frame, ScreencopyFrameState::Pending { manager: manager.clone(), info, copied: Arc::new(AtomicBool::new(false)), }, ); // Send desired SHM buffer parameters. frame.buffer( Format::Xrgb8888, buffer_size.w as u32, buffer_size.h as u32, buffer_size.w as u32 * 4, ); if frame.version() >= 3 { // Send desired DMA buffer parameters. frame.linux_dmabuf( Fourcc::Xrgb8888 as u32, buffer_size.w as u32, buffer_size.h as u32, ); // Notify client that all supported buffers were enumerated. frame.buffer_done(); } } } /// Handler trait for wlr-screencopy. pub trait ScreencopyHandler { /// Handle new screencopy request. fn frame(&mut self, manager: &ZwlrScreencopyManagerV1, screencopy: Screencopy); fn screencopy_state(&mut self) -> &mut ScreencopyManagerState; } #[allow(missing_docs)] #[macro_export] macro_rules! delegate_screencopy { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1: $crate::protocols::screencopy::ScreencopyManagerGlobalData ] => $crate::protocols::screencopy::ScreencopyManagerState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1: () ] => $crate::protocols::screencopy::ScreencopyManagerState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1: $crate::protocols::screencopy::ScreencopyFrameState ] => $crate::protocols::screencopy::ScreencopyManagerState); }; } #[derive(Clone)] pub struct ScreencopyFrameInfo { output: Output, buffer_size: Size, region_loc: Point, overlay_cursor: bool, } pub enum ScreencopyFrameState { Failed, Pending { manager: ZwlrScreencopyManagerV1, info: ScreencopyFrameInfo, copied: Arc, }, } impl Dispatch for ScreencopyManagerState where D: Dispatch, D: ScreencopyHandler, D: 'static, { fn request( state: &mut D, _client: &Client, frame: &ZwlrScreencopyFrameV1, request: zwlr_screencopy_frame_v1::Request, data: &ScreencopyFrameState, _display: &DisplayHandle, _data_init: &mut DataInit<'_, D>, ) { if matches!(request, zwlr_screencopy_frame_v1::Request::Destroy) { return; } let ScreencopyFrameState::Pending { manager, info, copied, } = data else { return; }; if copied.load(Ordering::SeqCst) { frame.post_error( zwlr_screencopy_frame_v1::Error::AlreadyUsed, "copy was already requested", ); return; } let (buffer, with_damage) = match request { zwlr_screencopy_frame_v1::Request::Copy { buffer } => (buffer, false), zwlr_screencopy_frame_v1::Request::CopyWithDamage { buffer } => (buffer, true), _ => unreachable!(), }; let size = info.buffer_size; let buffer = if let Ok(dmabuf) = dmabuf::get_dmabuf(&buffer) { if dmabuf.format().code == Fourcc::Xrgb8888 && dmabuf.width() == size.w as u32 && dmabuf.height() == size.h as u32 { ScreencopyBuffer::Dmabuf(dmabuf.clone()) } else { frame.post_error( zwlr_screencopy_frame_v1::Error::InvalidBuffer, "invalid dmabuf parameters", ); return; } } else if shm::with_buffer_contents(&buffer, |_, shm_len, buffer_data| { buffer_data.format == Format::Xrgb8888 && buffer_data.width == size.w && buffer_data.height == size.h && buffer_data.stride == size.w * 4 && shm_len == buffer_data.stride as usize * buffer_data.height as usize }) .unwrap_or(false) { ScreencopyBuffer::Shm(buffer) } else { frame.post_error( zwlr_screencopy_frame_v1::Error::InvalidBuffer, "invalid buffer", ); return; }; copied.store(true, Ordering::SeqCst); state.frame( manager, Screencopy { buffer, frame: frame.clone(), info: info.clone(), with_damage, submitted: false, }, ); } } /// Screencopy buffer. #[derive(Clone)] pub enum ScreencopyBuffer { Dmabuf(Dmabuf), Shm(WlBuffer), } /// Screencopy frame. pub struct Screencopy { info: ScreencopyFrameInfo, frame: ZwlrScreencopyFrameV1, buffer: ScreencopyBuffer, with_damage: bool, submitted: bool, } impl Drop for Screencopy { fn drop(&mut self) { if !self.submitted { self.frame.failed(); } } } impl Screencopy { /// Get the target buffer to copy to. pub fn buffer(&self) -> &ScreencopyBuffer { &self.buffer } pub fn region_loc(&self) -> Point { self.info.region_loc } pub fn buffer_size(&self) -> Size { self.info.buffer_size } pub fn output(&self) -> &Output { &self.info.output } pub fn overlay_cursor(&self) -> bool { self.info.overlay_cursor } pub fn with_damage(&self) -> bool { self.with_damage } pub fn damage(&self, damages: impl Iterator>) { for Rectangle { loc, size } in damages { self.frame .damage(loc.x as u32, loc.y as u32, size.w as u32, size.h as u32); } } /// Submit the copied content. fn submit(mut self, y_invert: bool, timestamp: Duration) { // Notify client that buffer is ordinary. self.frame.flags(if y_invert { Flags::YInvert } else { Flags::empty() }); // Notify client about successful copy. let tv_sec_hi = (timestamp.as_secs() >> 32) as u32; let tv_sec_lo = (timestamp.as_secs() & 0xFFFFFFFF) as u32; let tv_nsec = timestamp.subsec_nanos(); self.frame.ready(tv_sec_hi, tv_sec_lo, tv_nsec); // Mark frame as submitted to ensure destructor isn't run. self.submitted = true; } pub fn submit_after_sync( self, y_invert: bool, sync_point: Option, event_loop: &LoopHandle<'_, T>, ) { let timestamp = get_monotonic_time(); match sync_point.and_then(|s| s.export()) { None => self.submit(y_invert, timestamp), Some(sync_fd) => { let source = Generic::new(sync_fd, Interest::READ, Mode::OneShot); let mut screencopy = Some(self); event_loop .insert_source(source, move |_, _, _| { screencopy.take().unwrap().submit(y_invert, timestamp); Ok(PostAction::Remove) }) .unwrap(); } } } }