From dd100d65e43b3bec598f2caef3a76b1a8f0cae23 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 28 Oct 2022 18:34:08 +0200 Subject: [PATCH] wayland: Add screencopy protocol handler --- Cargo.lock | 81 ++-- Cargo.toml | 8 +- src/wayland/protocols/mod.rs | 1 + src/wayland/protocols/screencopy.rs | 602 ++++++++++++++++++++++++++++ 4 files changed, 655 insertions(+), 37 deletions(-) create mode 100644 src/wayland/protocols/screencopy.rs diff --git a/Cargo.lock b/Cargo.lock index df0d2da0..39f9d03b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,7 +309,7 @@ dependencies = [ "smithay-egui", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.12", "xcursor", "xdg", "xkbcommon 0.4.1", @@ -318,12 +318,12 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#3ff11df30ef551e1ccbdcb091930fe0d72266195" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=screencopy#e96daeceac966b6d21c79f2ebcc4b9934a69fa75" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] @@ -1048,6 +1048,19 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -1490,11 +1503,12 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay//smithay?rev=625cbca5#625cbca54981021000d5a33c1bdc711056ff6000" +source = "git+https://github.com/Smithay//smithay?rev=0c7dbfa8#0c7dbfa830496df064a721f9cbbff6b474e6a268" dependencies = [ "appendlist", "bitflags", "calloop", + "cc", "cgmath", "downcast-rs", "drm", @@ -1511,6 +1525,7 @@ dependencies = [ "libseat", "nix 0.24.2", "once_cell", + "pkg-config", "rand", "scan_fmt", "slog", @@ -1520,11 +1535,11 @@ dependencies = [ "udev", "wayland-backend", "wayland-egl", - "wayland-protocols 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-server", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.12", "winit", "x11rb", "xkbcommon 0.5.0", @@ -1791,17 +1806,17 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wayland-backend" -version = "0.1.0-beta.10" +version = "0.1.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c089c872513a8dcc2b5cc26209d83f5ffab1841d8c7973edf2583620ebc018" +checksum = "2fe32234ad38fee0755aeec26db486a4b788dd3cc2c9dc86ceda31bb38a53f32" dependencies = [ "cc", "downcast-rs", "io-lifetimes", - "nix 0.24.2", + "nix 0.25.0", "scoped-tls", "smallvec", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.12", ] [[package]] @@ -1845,13 +1860,13 @@ dependencies = [ [[package]] name = "wayland-egl" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cb5591d75142e1b180691df4d27f164a09abbd6efc7cba9b119ca40646809c" +checksum = "cfc1309ddfeabd942c09c21e9db2ed3de81b84d0ea7ebcdd2e503a0be0fe9c5f" dependencies = [ "thiserror", "wayland-backend", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.12", ] [[package]] @@ -1868,39 +1883,39 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60406a2fb43ff670ece4976cdd0537bbcd1e583b54c3cd9a1b61065e966df0a7" +checksum = "ca770dc814b3c93db1b4ba12a5bdfe899f8d68f9b4b06fc31e53959261cd0c39" dependencies = [ "bitflags", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] [[package]] name = "wayland-protocols-misc" -version = "0.1.0-beta.10" +version = "0.1.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653c3e200343bcfae719b668959efb9475bf9fcf43591cccfead308d32d67049" +checksum = "25bfcd2c16bed3540d1c5662d97708ac4f14d92b7b5c52c85860cdc17a96a126" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] [[package]] name = "wayland-protocols-wlr" -version = "0.1.0-beta.10" +version = "0.1.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cfe42e0cfcce47a421844ee82e49316cfab3ec6bcbb3a1076a0a2dd6c8d61e" +checksum = "8795f1c689bce3845292c1d84b8866fa65d1d1845e76769b13f71cadeb8b5853" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] @@ -1917,9 +1932,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1398566f240c3ef845bdfd310c8ee6ba800d4db695d007f23f2190eeedfff93" +checksum = "87def40ed0bc26c3eff1498812543b6d55bbd13b93e36d368052b7390a57c4ac" dependencies = [ "proc-macro2", "quick-xml", @@ -1929,16 +1944,16 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030557c71dfa3d7e0d0b5975bbdfee2eee319fde3e4c420cde63474c61331347" +checksum = "8d54b4a800b230f4fb0f42cae245cc0aaa02bf14bf7c1c3a5e1a822d05fd2cb3" dependencies = [ "bitflags", "downcast-rs", - "nix 0.24.2", + "nix 0.25.0", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.12", ] [[package]] @@ -1954,9 +1969,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bacc6b678c3350910e5ad6c057b7503666080952c4ece0a7e8958fd33937b1f5" +checksum = "1117fe4570fe063122ba2b1b1e39e56fb1a73921d395f9288af06af0dd1c7f55" dependencies = [ "dlib", "libc", diff --git a/Cargo.toml b/Cargo.toml index 43310f4a..71f4ce9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,9 @@ indexmap = "1.8.0" xdg = "^2.1" ron = "0.7" libsystemd = "0.5" -wayland-backend = "=0.1.0-beta.10" -wayland-scanner = "=0.30.0-beta.10" -cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } +wayland-backend = "=0.1.0-beta.12" +wayland-scanner = "=0.30.0-beta.12" +cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "screencopy", default-features = false, features = ["server"] } [dependencies.smithay] version = "0.3" @@ -61,4 +61,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/Smithay//smithay", rev = "625cbca5" } +smithay = { git = "https://github.com/Smithay//smithay", rev = "0c7dbfa8" } diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 069f16c5..e84f5ad1 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -3,6 +3,7 @@ pub mod drm; //pub mod export_dmabuf; pub mod output_configuration; +pub mod screencopy; pub mod toplevel_info; pub mod toplevel_management; pub mod workspace; diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs new file mode 100644 index 00000000..acd51141 --- /dev/null +++ b/src/wayland/protocols/screencopy.rs @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + +use cosmic_protocols::screencopy::v1::server::{ + zcosmic_screencopy_manager_v1::{self, CursorMode, ZcosmicScreencopyManagerV1}, + zcosmic_screencopy_session_v1::{self, FailureReason, InputType, ZcosmicScreencopySessionV1}, +}; +use smithay::{ + backend::{ + allocator::Fourcc as DrmFourcc, + drm::{DrmNode, NodeType}, + }, + desktop::Window, + reexports::wayland_server::{ + protocol::{ + wl_buffer::WlBuffer, wl_output::WlOutput, wl_seat::WlSeat, wl_shm::Format as ShmFormat, + }, + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, + utils::{user_data::UserDataMap, Buffer, Point, Rectangle, Size, Transform}, +}; +use wayland_backend::{protocol::WEnum, server::GlobalId}; + +use super::{ + toplevel_info::window_from_handle, + workspace::{WorkspaceHandle, WorkspaceHandler}, +}; + +/// Screencopy global state +pub struct ScreencopyState { + global: GlobalId, +} + +pub struct ScreencopyGlobalData { + cursor_modes: Vec, + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +impl ScreencopyState { + /// Create a new screencopy global + pub fn new( + display: &DisplayHandle, + cursor_modes: I, + client_filter: F, + ) -> ScreencopyState + where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, + I: IntoIterator, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + ScreencopyState { + global: display.create_global::( + 1, + ScreencopyGlobalData { + cursor_modes: Vec::from_iter(cursor_modes), + filter: Box::new(client_filter), + }, + ), + } + } + + /// Returns the screencopy global id + pub fn global(&self) -> GlobalId { + self.global.clone() + } +} + +#[derive(Debug)] +pub enum BufferInfo { + Shm { + format: ShmFormat, + size: Size, + stride: u32, + }, + Dmabuf { + node: DrmNode, + format: DrmFourcc, + size: Size, + }, +} + +#[derive(Debug)] +pub struct SessionDataInnerInner { + gone: bool, + pending_buffer: Option, + aux: AuxData, +} + +impl SessionDataInnerInner { + pub fn is_cursor(&self) -> bool { + match self.aux { + AuxData::Cursor { .. } => true, + _ => false, + } + } +} + +#[derive(Debug)] +enum AuxData { + Normal { cursor_sessions: Vec }, + Cursor { seat: WlSeat }, +} + +impl AuxData { + pub fn seat(&self) -> &WlSeat { + match self { + AuxData::Cursor { seat } => seat, + _ => unreachable!("Unwrapped seat from aux data"), + } + } + + pub fn cursor_sessions(&self) -> &[CursorSession] { + match self { + AuxData::Normal { cursor_sessions } => &cursor_sessions, + _ => unreachable!("Unwrapped cursor_session from aux data"), + } + } +} + +#[derive(Debug)] +pub struct SessionDataInner { + inner: Mutex, + user_data: UserDataMap, +} + +pub type SessionData = Arc; + +#[derive(Debug, Clone)] +pub struct Session { + obj: ZcosmicScreencopySessionV1, + data: SessionData, +} + +impl PartialEq for Session { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +impl Session { + pub fn cursor_enter(&self, seat: &WlSeat, input_type: InputType) { + self.obj.cursor_enter(seat, input_type) + } + + pub fn cursor_info( + &self, + seat: &WlSeat, + input_type: InputType, + geometry: Rectangle, + offset: Point, + ) { + self.obj.cursor_info( + seat, + input_type, + geometry.loc.x, + geometry.loc.y, + geometry.size.w, + geometry.size.h, + offset.x, + offset.y, + ); + let data = self.data.inner.lock().unwrap(); + for cursor_session in data.aux.cursor_sessions() { + cursor_session.obj.cursor_info( + seat, + input_type, + geometry.loc.x, + geometry.loc.y, + geometry.size.w, + geometry.size.h, + offset.x, + offset.y, + ); + } + } + + pub fn cursor_leave(&self, seat: &WlSeat, input_type: InputType) { + self.obj.cursor_leave(seat, input_type) + } + + pub fn cursor_sessions(&self) -> impl Iterator { + self.data + .inner + .lock() + .unwrap() + .aux + .cursor_sessions() + .iter() + .cloned() + .collect::>() + .into_iter() + } + + pub fn commit_buffer( + &self, + transform: Transform, + damage: Vec>, + time: Option, + ) { + self.obj.transform(transform.into()); + for rect in damage { + self.obj.damage( + rect.loc.x as u32, + rect.loc.y as u32, + rect.size.w as u32, + rect.size.h as u32, + ); + } + if let Some(time) = time { + let tv_sec_hi = (time.as_secs() >> 32) as u32; + let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; + self.obj + .commit_time(tv_sec_hi, tv_sec_lo, time.subsec_nanos()); + } + self.obj.ready() + } + + pub fn failed(&self, reason: FailureReason) { + self.obj.failed(reason); + self.data.inner.lock().unwrap().gone = true; + } + + pub fn user_data(&self) -> &UserDataMap { + &self.data.user_data + } +} + +#[derive(Debug, Clone)] +pub struct CursorSession { + obj: ZcosmicScreencopySessionV1, + data: SessionData, +} + +impl PartialEq for CursorSession { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +impl CursorSession { + pub fn seat(&self) -> WlSeat { + self.data.inner.lock().unwrap().aux.seat().clone() + } + + pub fn buffer_waiting(&self) -> Option { + self.data.inner.lock().unwrap().pending_buffer.take() + } + + pub fn commit_buffer<'a>( + &self, + transform: Transform, + damage: impl Iterator> + 'a, + ) { + self.obj.transform(transform.into()); + for rect in damage { + self.obj.damage( + rect.loc.x as u32, + rect.loc.y as u32, + rect.size.w as u32, + rect.size.h as u32, + ); + } + } + + pub fn failed(&self, reason: FailureReason) { + self.obj.failed(reason); + self.data.inner.lock().unwrap().gone = true; + } + + pub fn user_data(&self) -> &UserDataMap { + &self.data.user_data + } +} + +#[derive(Debug)] +pub struct BufferParams { + pub buffer: WlBuffer, + pub node: Option, + pub age: u32, +} + +pub trait ScreencopyHandler { + fn capture_output( + &mut self, + output: WlOutput, + cursor: CursorMode, + session: Session, + ) -> Vec; + + fn capture_workspace( + &mut self, + workspace: WorkspaceHandle, + output: WlOutput, + cursor: CursorMode, + session: Session, + ) -> Vec; + + fn capture_toplevel( + &mut self, + toplevel: Window, + cursor: CursorMode, + session: Session, + ) -> Vec; + + fn capture_cursor(&mut self, session: CursorSession) -> Vec; + + fn buffer_attached(&mut self, session: Session, buffer: BufferParams, on_damage: bool); + + fn session_destroyed(&mut self, session: Session); +} + +impl GlobalDispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + global_data: &ScreencopyGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let global = data_init.init(resource, ()); + for mode in &global_data.cursor_modes { + global.supported_cursor_mode(*mode); + } + } + + fn can_view(client: Client, global_data: &ScreencopyGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +fn init_session( + data_init: &mut DataInit<'_, D>, + session: New, + cursor: WEnum, +) -> Option<(Session, CursorMode)> +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + let data = Arc::new(SessionDataInner { + inner: Mutex::new(SessionDataInnerInner { + gone: false, + pending_buffer: None, + aux: AuxData::Normal { + cursor_sessions: Vec::new(), + }, + }), + user_data: UserDataMap::new(), + }); + let session = data_init.init(session, data.clone()); + + let cursor_mode = match cursor.into_result() { + Ok(mode) => mode, + Err(err) => { + slog_scope::warn!("Client did send unknown cursor mode: {}", err); + session.failed(FailureReason::UnknownInput); + return None; + } + }; + + let session = Session { obj: session, data }; + + Some((session, cursor_mode)) +} + +impl Dispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &ZcosmicScreencopyManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_manager_v1::Request::CaptureOutput { + session, + output, + cursor, + } => { + let (session, cursor_mode) = match init_session(data_init, session, cursor) { + Some(result) => result, + None => { + return; + } + }; + let formats = state.capture_output(output, cursor_mode, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + zcosmic_screencopy_manager_v1::Request::CaptureToplevel { + session, + toplevel, + cursor, + } => { + let (session, cursor_mode) = match init_session(data_init, session, cursor) { + Some(result) => result, + None => { + return; + } + }; + match window_from_handle(toplevel) { + Some(window) => { + let formats = state.capture_toplevel(window, cursor_mode, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + None => { + session.obj.failed(FailureReason::ToplevelDestroyed); + return; + } + } + } + zcosmic_screencopy_manager_v1::Request::CaptureWorkspace { + session, + workspace, + output, + cursor, + } => { + let (session, cursor_mode) = match init_session(data_init, session, cursor) { + Some(result) => result, + None => { + return; + } + }; + match state.workspace_state().workspace_handle(&workspace) { + Some(handle) => { + let formats = + state.capture_workspace(handle, output, cursor_mode, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + None => { + session.failed(FailureReason::InvalidOutput); + return; + } + } + } + _ => {} + } + } +} + +impl Dispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZcosmicScreencopySessionV1, + request: ::Request, + data: &SessionData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_session_v1::Request::CaptureCursor { session, seat } => { + { + let resource_data = data.inner.lock().unwrap(); + if resource_data.is_cursor() || resource_data.gone { + resource.failed(FailureReason::Unspec); + return; + } + } + + let data = Arc::new(SessionDataInner { + inner: Mutex::new(SessionDataInnerInner { + gone: false, + pending_buffer: None, + aux: AuxData::Cursor { seat }, + }), + user_data: UserDataMap::new(), + }); + let session = data_init.init(session, data.clone()); + + let cursor_session = CursorSession { obj: session, data }; + let formats = state.capture_cursor(cursor_session.clone()); + if !cursor_session.data.inner.lock().unwrap().gone { + send_formats(&cursor_session.obj, formats); + } + } + zcosmic_screencopy_session_v1::Request::AttachBuffer { buffer, node, age } => { + if data.inner.lock().unwrap().gone { + resource.failed(FailureReason::Unspec); + return; + } + let params = BufferParams { + buffer, + node: node.and_then(|p| DrmNode::from_path(p).ok()), + age, + }; + data.inner.lock().unwrap().pending_buffer = Some(params); + } + zcosmic_screencopy_session_v1::Request::Commit { options } => { + { + let resource_data = data.inner.lock().unwrap(); + if resource_data.is_cursor() || resource_data.gone { + resource.failed(FailureReason::Unspec); + return; + } + } + + if let Some(buffer) = data.inner.lock().unwrap().pending_buffer.take() { + let session = Session { + obj: resource.clone(), + data: data.clone(), + }; + state.buffer_attached( + session, + buffer, + options + .into_result() + .ok() + .map(|v| v.contains(zcosmic_screencopy_session_v1::Options::OnDamage)) + .unwrap_or(false), + ); + } else { + resource.failed(FailureReason::InvalidBuffer); + } + } + zcosmic_screencopy_session_v1::Request::Destroy => { + data.inner.lock().unwrap().gone = true; + } + _ => {} + } + } +} + +fn send_formats(session: &ZcosmicScreencopySessionV1, formats: Vec) { + for format in formats { + match format { + BufferInfo::Dmabuf { node, format, size } => { + if let Some(node_path) = node + .dev_path_with_type(NodeType::Render) + .or_else(|| node.dev_path()) + { + session.buffer_info( + zcosmic_screencopy_session_v1::BufferType::Dmabuf, + Some(node_path.as_os_str().to_string_lossy().into_owned()), + format as u32, + size.w as u32, + size.h as u32, + 0, + ); + } + } + BufferInfo::Shm { + format, + size, + stride, + } => session.buffer_info( + zcosmic_screencopy_session_v1::BufferType::WlShm, + None, + format as u32, + size.w as u32, + size.h as u32, + stride, + ), + } + } + + session.init_done(); +}