diff --git a/client-toolkit/Cargo.toml b/client-toolkit/Cargo.toml index d8506d647f..fbe0fd5528 100644 --- a/client-toolkit/Cargo.toml +++ b/client-toolkit/Cargo.toml @@ -8,6 +8,7 @@ cosmic-protocols = { path = "../" } sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "2e9bf9f" } wayland-client = { version = "0.31.1" } smithay = { git = "https://github.com/Smithay/smithay", rev = "c35bc3e", default-features = false, features = ["backend_egl", "renderer_gl", "renderer_multi"], optional = true } +libc = "0.2.153" [build-dependencies] gl_generator = { version = "0.14.0", optional = true } diff --git a/client-toolkit/src/lib.rs b/client-toolkit/src/lib.rs index 3747992762..769953998b 100644 --- a/client-toolkit/src/lib.rs +++ b/client-toolkit/src/lib.rs @@ -6,6 +6,7 @@ pub use wayland_client; pub mod egl; #[cfg(feature = "gl")] pub mod gl; +pub mod screencopy; pub mod toplevel_info; pub mod toplevel_management; pub mod workspace; diff --git a/client-toolkit/src/screencopy.rs b/client-toolkit/src/screencopy.rs new file mode 100644 index 0000000000..f4b18aaa20 --- /dev/null +++ b/client-toolkit/src/screencopy.rs @@ -0,0 +1,413 @@ +// TODO add cursor session support + +use cosmic_protocols::{ + image_source::v1::client::{ + zcosmic_image_source_v1, zcosmic_output_image_source_manager_v1, + zcosmic_toplevel_image_source_manager_v1, zcosmic_workspace_image_source_manager_v1, + }, + screencopy::v2::client::{ + zcosmic_screencopy_frame_v2, zcosmic_screencopy_manager_v2, zcosmic_screencopy_session_v2, + }, +}; +use std::{sync::Mutex, time::Duration}; +use wayland_client::{ + globals::GlobalList, + protocol::{wl_buffer, wl_output::Transform}, + Connection, Dispatch, QueueHandle, WEnum, +}; + +#[derive(Clone, Debug)] +pub struct Rect { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, +} + +#[derive(Clone, Debug)] +pub struct Frame { + pub transform: WEnum, + pub damage: Vec, + // XXX monotonic? Is this used elsewhere in wayland? + pub present_time: Option, +} + +// TODO Better API than standalone function? +pub fn capture( + session: &zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2, + buffer: &wl_buffer::WlBuffer, + buffer_damage: &[Rect], + qh: &QueueHandle, + udata: U, +) where + D: Dispatch + 'static, +{ + let frame = session.create_frame(qh, udata); + frame.attach_buffer(buffer); + for Rect { + x, + y, + width, + height, + } in buffer_damage + { + frame.damage_buffer(*x, *y, *width, *height); + } + frame.capture(); +} + +impl Default for Frame { + fn default() -> Self { + Self { + transform: WEnum::Value(Transform::Normal), + damage: Vec::new(), + present_time: None, + } + } +} + +struct CursorInfo {} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Formats { + pub buffer_size: (u32, u32), + pub shm_formats: Vec, + pub dmabuf_device: Option, + pub dmabuf_formats: Vec<(u32, Vec)>, +} + +#[derive(Debug)] +pub struct ScreencopyState { + pub screencopy_manager: zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2, // XXX pub + pub output_source_manager: + Option, + pub toplevel_source_manager: + Option, + pub workspace_source_manager: + Option, +} + +impl ScreencopyState { + pub fn new(globals: &GlobalList, qh: &QueueHandle) -> Self + where + D: 'static, + D: Dispatch, + D: Dispatch, + D: Dispatch< + zcosmic_toplevel_image_source_manager_v1::ZcosmicToplevelImageSourceManagerV1, + (), + >, + D: Dispatch< + zcosmic_workspace_image_source_manager_v1::ZcosmicWorkspaceImageSourceManagerV1, + (), + >, + { + // TODO bind + let screencopy_manager = globals.bind(qh, 1..=1, ()).unwrap(); // XXX + let output_source_manager = globals.bind(qh, 1..=1, ()).ok(); + let toplevel_source_manager = globals.bind(qh, 1..=1, ()).ok(); + let workspace_source_manager = globals.bind(qh, 1..=1, ()).ok(); + Self { + screencopy_manager, + output_source_manager, + toplevel_source_manager, + workspace_source_manager, + } + } +} + +pub trait ScreencopyHandler: Sized { + fn screencopy_state(&mut self) -> &mut ScreencopyState; + + fn init_done( + &mut self, + conn: &Connection, + qh: &QueueHandle, + session: &zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2, + formats: &Formats, + ); + + fn stopped( + &mut self, + conn: &Connection, + qh: &QueueHandle, + session: &zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2, + ); + + fn ready( + &mut self, + conn: &Connection, + qh: &QueueHandle, + screencopy_frame: &zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2, + frame: Frame, + ); + + fn failed( + &mut self, + conn: &Connection, + qh: &QueueHandle, + screencopy_frame: &zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2, + reason: WEnum, + ); +} + +pub trait ScreencopySessionDataExt { + fn screencopy_session_data(&self) -> &ScreencopySessionData; +} + +#[derive(Default)] +pub struct ScreencopySessionData { + formats: Mutex, +} + +impl ScreencopySessionDataExt for ScreencopySessionData { + fn screencopy_session_data(&self) -> &ScreencopySessionData { + self + } +} + +#[derive(Default)] +pub struct ScreencopyFrameData { + frame: Mutex, +} + +pub trait ScreencopyFrameDataExt { + fn screencopy_frame_data(&self) -> &ScreencopyFrameData; +} + +impl ScreencopyFrameDataExt for ScreencopyFrameData { + fn screencopy_frame_data(&self) -> &ScreencopyFrameData { + self + } +} + +impl Dispatch + for ScreencopyState +where + D: Dispatch + ScreencopyHandler, +{ + fn event( + state: &mut D, + _: &zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2, + event: zcosmic_screencopy_manager_v2::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + _ => unreachable!(), + } + } +} + +impl Dispatch + for ScreencopyState +where + D: Dispatch + ScreencopyHandler, + U: ScreencopySessionDataExt, +{ + fn event( + app_data: &mut D, + session: &zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2, + event: zcosmic_screencopy_session_v2::Event, + udata: &U, + conn: &Connection, + qh: &QueueHandle, + ) { + let formats = &udata.screencopy_session_data().formats; + match event { + zcosmic_screencopy_session_v2::Event::BufferSize { width, height } => { + formats.lock().unwrap().buffer_size = (width, height); + } + zcosmic_screencopy_session_v2::Event::ShmFormat { format } => { + formats.lock().unwrap().shm_formats.push(format); + } + zcosmic_screencopy_session_v2::Event::DmabufDevice { device } => { + let device = libc::dev_t::from_ne_bytes(device.try_into().unwrap()); + formats.lock().unwrap().dmabuf_device = Some(device); + } + zcosmic_screencopy_session_v2::Event::DmabufFormat { format, modifiers } => { + let modifiers = modifiers + .chunks_exact(8) + .map(|x| u64::from_ne_bytes(x.try_into().unwrap())) + .collect(); + formats + .lock() + .unwrap() + .dmabuf_formats + .push((format, modifiers)); + } + zcosmic_screencopy_session_v2::Event::Done => { + app_data.init_done(conn, qh, session, &*formats.lock().unwrap()); + } + zcosmic_screencopy_session_v2::Event::Stopped => { + app_data.stopped(conn, qh, session); + session.destroy(); + } + _ => unreachable!(), + } + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + ScreencopyHandler, + U: ScreencopyFrameDataExt, +{ + fn event( + app_data: &mut D, + screencopy_frame: &zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2, + event: zcosmic_screencopy_frame_v2::Event, + udata: &U, + conn: &Connection, + qh: &QueueHandle, + ) { + let frame = &udata.screencopy_frame_data().frame; + match event { + zcosmic_screencopy_frame_v2::Event::Transform { transform } => { + frame.lock().unwrap().transform = transform; + } + zcosmic_screencopy_frame_v2::Event::Damage { + x, + y, + width, + height, + } => { + frame.lock().unwrap().damage.push(Rect { + x, + y, + width, + height, + }); + } + zcosmic_screencopy_frame_v2::Event::PresentationTime { + tv_sec_hi, + tv_sec_lo, + tv_nsec, + } => { + let secs = (u64::from(tv_sec_hi) << 32) + u64::from(tv_sec_lo); + let duration = Duration::new(secs, tv_nsec); + frame.lock().unwrap().present_time = Some(duration); + } + zcosmic_screencopy_frame_v2::Event::Ready => { + let frame = frame.lock().unwrap().clone(); + app_data.ready(conn, qh, screencopy_frame, frame); + screencopy_frame.destroy(); + } + zcosmic_screencopy_frame_v2::Event::Failed { reason } => { + app_data.failed(conn, qh, screencopy_frame, reason); + screencopy_frame.destroy(); + } + _ => unreachable!(), + } + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + ScreencopyHandler, +{ + fn event( + app_data: &mut D, + source: &zcosmic_image_source_v1::ZcosmicImageSourceV1, + event: zcosmic_image_source_v1::Event, + udata: &(), + conn: &Connection, + qh: &QueueHandle, + ) { + unreachable!() + } +} + +impl Dispatch + for ScreencopyState +where + D: Dispatch + + ScreencopyHandler, +{ + fn event( + app_data: &mut D, + source: &zcosmic_output_image_source_manager_v1::ZcosmicOutputImageSourceManagerV1, + event: zcosmic_output_image_source_manager_v1::Event, + udata: &(), + conn: &Connection, + qh: &QueueHandle, + ) { + unreachable!() + } +} + +impl + Dispatch + for ScreencopyState +where + D: Dispatch + + ScreencopyHandler, +{ + fn event( + app_data: &mut D, + source: &zcosmic_toplevel_image_source_manager_v1::ZcosmicToplevelImageSourceManagerV1, + event: zcosmic_toplevel_image_source_manager_v1::Event, + udata: &(), + conn: &Connection, + qh: &QueueHandle, + ) { + unreachable!() + } +} + +impl + Dispatch + for ScreencopyState +where + D: Dispatch< + zcosmic_workspace_image_source_manager_v1::ZcosmicWorkspaceImageSourceManagerV1, + (), + > + ScreencopyHandler, +{ + fn event( + app_data: &mut D, + source: &zcosmic_workspace_image_source_manager_v1::ZcosmicWorkspaceImageSourceManagerV1, + event: zcosmic_workspace_image_source_manager_v1::Event, + udata: &(), + conn: &Connection, + qh: &QueueHandle, + ) { + unreachable!() + } +} + +#[macro_export] +macro_rules! delegate_screencopy { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + $crate::delegate_screencopy($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty, + session: $crate::screencopy::ScreencopySessionData, frame: $crate::screencopy::ScreencopyFrameData); + }; + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, session: [$($session_data:ty),* $(,)?], frame: [$($frame_data:ty),* $(,)?]) => { + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::cosmic_protocols::image_source::v1::client::zcosmic_output_image_source_manager_v1::ZcosmicOutputImageSourceManagerV1: () + ] => $crate::screencopy::ScreencopyState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::cosmic_protocols::image_source::v1::client::zcosmic_toplevel_image_source_manager_v1::ZcosmicToplevelImageSourceManagerV1: () + ] => $crate::screencopy::ScreencopyState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::cosmic_protocols::image_source::v1::client::zcosmic_workspace_image_source_manager_v1::ZcosmicWorkspaceImageSourceManagerV1: () + ] => $crate::screencopy::ScreencopyState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::cosmic_protocols::image_source::v1::client::zcosmic_image_source_v1::ZcosmicImageSourceV1: () + ] => $crate::screencopy::ScreencopyState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::cosmic_protocols::screencopy::v2::client::zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2: () + ] => $crate::screencopy::ScreencopyState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $( + $crate::cosmic_protocols::screencopy::v2::client::zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2: $session_data + ),* + ] => $crate::screencopy::ScreencopyState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $( + $crate::cosmic_protocols::screencopy::v2::client::zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2: $frame_data + ),* + ] => $crate::screencopy::ScreencopyState); + }; +}