diff --git a/src/wayland/buffer.rs b/src/wayland/buffer.rs index 93bbe0e..abb382f 100644 --- a/src/wayland/buffer.rs +++ b/src/wayland/buffer.rs @@ -3,11 +3,13 @@ use cctk::{ sctk::{globals::ProvidesBoundGlobal, shm::raw::RawPool}, wayland_client::{ protocol::{wl_buffer, wl_shm}, - QueueHandle, + Connection, Dispatch, QueueHandle, }, }; use cosmic::iced::widget::image; +use super::AppData; + pub struct Buffer { pub pool: RawPool, pub buffer: wl_buffer::WlBuffer, @@ -18,7 +20,7 @@ impl Buffer { pub fn new( buffer_info: BufferInfo, shm: &impl ProvidesBoundGlobal, - qh: &QueueHandle, + qh: &QueueHandle, ) -> Self { // Assume format is already known to be valid let mut pool = @@ -57,3 +59,19 @@ impl Drop for Buffer { self.buffer.destroy(); } } + +impl Dispatch for AppData { + fn event( + _app_data: &mut Self, + _buffer: &wl_buffer::WlBuffer, + event: wl_buffer::Event, + _: &(), + _: &Connection, + _qh: &QueueHandle, + ) { + match event { + wl_buffer::Event::Release => {} + _ => unreachable!(), + } + } +} diff --git a/src/wayland/capture.rs b/src/wayland/capture.rs new file mode 100644 index 0000000..d5c76e1 --- /dev/null +++ b/src/wayland/capture.rs @@ -0,0 +1,116 @@ +use cctk::{ + cosmic_protocols::{ + screencopy::v1::client::{zcosmic_screencopy_manager_v1, zcosmic_screencopy_session_v1}, + toplevel_info::v1::client::zcosmic_toplevel_handle_v1, + workspace::v1::client::zcosmic_workspace_handle_v1, + }, + screencopy::{ScreencopySessionData, ScreencopySessionDataExt}, + wayland_client::{protocol::wl_output, Proxy, QueueHandle}, +}; + +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; + +use super::{AppData, Buffer}; + +#[derive(Clone, PartialEq, Eq)] +pub enum CaptureSource { + Toplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), + Workspace( + zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, + wl_output::WlOutput, + ), +} + +#[allow(clippy::derive_hash_xor_eq)] +impl std::hash::Hash for CaptureSource { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + match self { + Self::Toplevel(handle) => handle.id(), + Self::Workspace(handle, _output) => handle.id(), + } + .hash(state) + } +} + +#[derive(Clone, Debug, Default)] +pub struct CaptureFilter { + pub workspaces_on_outputs: Vec, + pub toplevels_on_workspaces: Vec, +} + +pub struct Capture { + pub buffer: Mutex>, + pub source: CaptureSource, + pub first_frame: AtomicBool, + pub cancelled: AtomicBool, +} + +impl Capture { + pub fn new(source: CaptureSource) -> Capture { + Capture { + buffer: Mutex::new(None), + source, + first_frame: AtomicBool::new(true), + cancelled: AtomicBool::new(false), + } + } + + pub fn for_session( + session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, + ) -> Option<&Arc> { + Some(&session.data::()?.capture) + } + + pub fn cancel(&self) { + self.cancelled.store(true, Ordering::SeqCst); + } + + pub fn capture( + self: &Arc, + manager: &zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1, + qh: &QueueHandle, + ) { + let udata = SessionData { + session_data: Default::default(), + capture: self.clone(), + }; + match &self.source { + CaptureSource::Toplevel(toplevel) => { + manager.capture_toplevel( + toplevel, + zcosmic_screencopy_manager_v1::CursorMode::Hidden, + qh, + udata, + ); + } + CaptureSource::Workspace(workspace, output) => { + manager.capture_workspace( + workspace, + output, + zcosmic_screencopy_manager_v1::CursorMode::Hidden, + qh, + udata, + ); + } + } + } +} + +struct SessionData { + session_data: ScreencopySessionData, + capture: Arc, +} + +impl ScreencopySessionDataExt for SessionData { + fn screencopy_session_data(&self) -> &ScreencopySessionData { + &self.session_data + } +} + +cctk::delegate_screencopy!(AppData, session: [SessionData]); diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index ea37771..08b0f1b 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -8,15 +8,11 @@ use cctk::{ cosmic_protocols::{ - screencopy::v1::client::{zcosmic_screencopy_manager_v1, zcosmic_screencopy_session_v1}, toplevel_info::v1::client::zcosmic_toplevel_handle_v1, toplevel_management::v1::client::zcosmic_toplevel_manager_v1, workspace::v1::client::{zcosmic_workspace_handle_v1, zcosmic_workspace_manager_v1}, }, - screencopy::{ - BufferInfo, ScreencopyHandler, ScreencopySessionData, ScreencopySessionDataExt, - ScreencopyState, - }, + screencopy::ScreencopyState, sctk::{ self, event_loop::WaylandSource, @@ -25,15 +21,15 @@ use cctk::{ seat::{SeatHandler, SeatState}, shm::{ShmHandler, ShmState}, }, - toplevel_info::{ToplevelInfo, ToplevelInfoHandler, ToplevelInfoState}, - toplevel_management::{ToplevelManagerHandler, ToplevelManagerState}, + toplevel_info::{ToplevelInfo, ToplevelInfoState}, + toplevel_management::ToplevelManagerState, wayland_client::{ backend::ObjectId, globals::registry_queue_init, - protocol::{wl_buffer, wl_output, wl_seat, wl_shm}, - Connection, Dispatch, Proxy, QueueHandle, WEnum, + protocol::{wl_output, wl_seat}, + Connection, Proxy, QueueHandle, }, - workspace::{WorkspaceHandler, WorkspaceState}, + workspace::WorkspaceState, }; use cosmic::iced::{ self, @@ -41,18 +37,17 @@ use cosmic::iced::{ widget::image, }; use futures_channel::mpsc; -use std::{ - cell::RefCell, - collections::HashMap, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, - }, - thread, -}; +use std::{cell::RefCell, collections::HashMap, sync::Arc, thread}; mod buffer; use buffer::Buffer; +mod capture; +use capture::{Capture, CaptureSource}; +mod screencopy; +mod toplevel; +mod workspace; + +pub use capture::CaptureFilter; // TODO define subscription for a particular output/workspace/toplevel (but we want to rate limit?) @@ -84,103 +79,11 @@ pub fn subscription() -> iced::Subscription { iced::subscription::run("wayland-sub", async { start() }.flatten_stream()) } -#[derive(Clone, PartialEq, Eq)] -enum CaptureSource { - Toplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), - Workspace( - zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, - wl_output::WlOutput, - ), -} - -#[allow(clippy::derive_hash_xor_eq)] -impl std::hash::Hash for CaptureSource { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - match self { - Self::Toplevel(handle) => handle.id(), - Self::Workspace(handle, _output) => handle.id(), - } - .hash(state) - } -} - -#[derive(Clone, Debug, Default)] -pub struct CaptureFilter { - pub workspaces_on_outputs: Vec, - pub toplevels_on_workspaces: Vec, -} - #[derive(Debug)] pub enum Cmd { CaptureFilter(CaptureFilter), } -struct Capture { - buffer: Mutex>, - source: CaptureSource, - first_frame: AtomicBool, - cancelled: AtomicBool, -} - -impl Capture { - fn new(source: CaptureSource) -> Capture { - Capture { - buffer: Mutex::new(None), - source, - first_frame: AtomicBool::new(true), - cancelled: AtomicBool::new(false), - } - } - - fn cancel(&self) { - self.cancelled.store(true, Ordering::SeqCst); - } - - fn capture( - self: &Arc, - manager: &zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1, - qh: &QueueHandle, - ) { - let udata = SessionData { - session_data: Default::default(), - capture: self.clone(), - }; - match &self.source { - CaptureSource::Toplevel(toplevel) => { - manager.capture_toplevel( - toplevel, - zcosmic_screencopy_manager_v1::CursorMode::Hidden, - qh, - udata, - ); - } - CaptureSource::Workspace(workspace, output) => { - manager.capture_workspace( - workspace, - output, - zcosmic_screencopy_manager_v1::CursorMode::Hidden, - qh, - udata, - ); - } - } - } -} - -struct SessionData { - session_data: ScreencopySessionData, - capture: Arc, -} - -impl ScreencopySessionDataExt for SessionData { - fn screencopy_session_data(&self) -> &ScreencopySessionData { - &self.session_data - } -} - pub struct AppData { qh: QueueHandle, output_state: OutputState, @@ -214,6 +117,7 @@ impl AppData { } fn invalidate_capture_filter(&mut self) { + //for i in self.captures // XXX drain filter // TODO cancel captures if needed, enable capture } @@ -223,6 +127,12 @@ impl AppData { capture.capture(&self.screencopy_state.screencopy_manager, &self.qh); self.captures.borrow_mut().insert(source, capture); } + + fn remove_capture_source(&self, source: CaptureSource) { + if let Some(capture) = self.captures.borrow_mut().remove(&source) { + capture.cancel(); + } + } } impl ProvidesRegistryState for AppData { @@ -308,193 +218,6 @@ impl OutputHandler for AppData { } } -// TODO any indication when we have all toplevels? -impl ToplevelInfoHandler for AppData { - fn toplevel_info_state(&mut self) -> &mut ToplevelInfoState { - &mut self.toplevel_info_state - } - - fn new_toplevel( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, - ) { - let info = self.toplevel_info_state.info(toplevel).unwrap(); - self.send_event(Event::NewToplevel(toplevel.clone(), info.clone())); - - self.add_capture_source(CaptureSource::Toplevel(toplevel.clone())); - } - - fn update_toplevel( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, - ) { - // TODO - } - - fn toplevel_closed( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, - ) { - self.send_event(Event::CloseToplevel(toplevel.clone())); - } -} - -impl WorkspaceHandler for AppData { - fn workspace_state(&mut self) -> &mut WorkspaceState { - &mut self.workspace_state - } - - fn done(&mut self) { - let mut workspaces = Vec::new(); - - for group in self.workspace_state.workspace_groups() { - for workspace in &group.workspaces { - if let Some(output) = group.output.as_ref() { - let output_name = self.output_names.get(&output.id()).unwrap().clone(); - workspaces.push((output_name, workspace.clone())); - - self.add_capture_source(CaptureSource::Workspace( - workspace.handle.clone(), - output.clone(), - )); - } - } - } - - self.send_event(Event::Workspaces(workspaces)); - } -} - -impl ScreencopyHandler for AppData { - fn screencopy_state(&mut self) -> &mut ScreencopyState { - &mut self.screencopy_state - } - - fn init_done( - &mut self, - conn: &Connection, - qh: &QueueHandle, - session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, - buffer_infos: &[BufferInfo], - ) { - let capture = &session.data::().unwrap().capture; - if capture.cancelled.load(Ordering::SeqCst) { - session.destroy(); - return; - } - - let buffer_info = buffer_infos - .iter() - .find(|x| { - x.type_ == WEnum::Value(zcosmic_screencopy_session_v1::BufferType::WlShm) - && x.format == wl_shm::Format::Abgr8888.into() - }) - .unwrap(); - - // XXX fix in compositor - if buffer_info.width == 0 || buffer_info.height == 0 || buffer_info.stride == 0 { - session.destroy(); - return; - } - - let mut buffer = capture.buffer.lock().unwrap(); - // Create new buffer if none, or different format - if !buffer - .as_ref() - .map_or(false, |x| &x.buffer_info == buffer_info) - { - *buffer = Some(Buffer::new(buffer_info.clone(), &self.shm_state, qh)); - } - let buffer = buffer.as_ref().unwrap(); - - session.attach_buffer(&buffer.buffer, None, 0); // XXX age? - if capture.first_frame.load(Ordering::SeqCst) { - session.commit(zcosmic_screencopy_session_v1::Options::empty()); - } else { - session.commit(zcosmic_screencopy_session_v1::Options::OnDamage); - } - conn.flush().unwrap(); - } - - fn ready( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, - ) { - let capture = &session.data::().unwrap().capture; - if capture.cancelled.load(Ordering::SeqCst) { - session.destroy(); - return; - } - - let mut buffer = capture.buffer.lock().unwrap(); - let image = unsafe { buffer.as_mut().unwrap().to_image() }; - let event = match &capture.source { - CaptureSource::Toplevel(toplevel) => Event::ToplevelCapture(toplevel.clone(), image), - CaptureSource::Workspace(workspace, _) => { - Event::WorkspaceCapture(workspace.clone(), image) - } - }; - self.send_event(event); - session.destroy(); - - // Capture again on damage - capture.first_frame.store(false, Ordering::SeqCst); - capture.capture(&self.screencopy_state.screencopy_manager, &self.qh); - } - - fn failed( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, - _reason: WEnum, - ) { - // TODO - println!("Failed"); - session.destroy(); - } -} - -impl ToplevelManagerHandler for AppData { - fn toplevel_manager_state(&mut self) -> &mut ToplevelManagerState { - &mut self.toplevel_manager_state - } - - fn capabilities( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _capabilities: Vec< - WEnum, - >, - ) { - } -} - -impl Dispatch for AppData { - fn event( - _app_data: &mut Self, - _buffer: &wl_buffer::WlBuffer, - event: wl_buffer::Event, - _: &(), - _: &Connection, - _qh: &QueueHandle, - ) { - match event { - wl_buffer::Event::Release => {} - _ => unreachable!(), - } - } -} - fn start() -> mpsc::Receiver { let (sender, receiver) = mpsc::channel(20); @@ -563,7 +286,3 @@ sctk::delegate_output!(AppData); sctk::delegate_registry!(AppData); sctk::delegate_seat!(AppData); sctk::delegate_shm!(AppData); -cctk::delegate_toplevel_info!(AppData); -cctk::delegate_toplevel_manager!(AppData); -cctk::delegate_workspace!(AppData); -cctk::delegate_screencopy!(AppData, session: [SessionData]); diff --git a/src/wayland/screencopy.rs b/src/wayland/screencopy.rs new file mode 100644 index 0000000..05270ec --- /dev/null +++ b/src/wayland/screencopy.rs @@ -0,0 +1,101 @@ +use cctk::{ + cosmic_protocols::screencopy::v1::client::zcosmic_screencopy_session_v1, + screencopy::{BufferInfo, ScreencopyHandler, ScreencopyState}, + wayland_client::{protocol::wl_shm, Connection, QueueHandle, WEnum}, +}; + +use std::sync::atomic::Ordering; + +use super::{AppData, Buffer, Capture, CaptureSource, Event}; + +impl ScreencopyHandler for AppData { + fn screencopy_state(&mut self) -> &mut ScreencopyState { + &mut self.screencopy_state + } + + fn init_done( + &mut self, + conn: &Connection, + qh: &QueueHandle, + session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, + buffer_infos: &[BufferInfo], + ) { + let capture = Capture::for_session(session).unwrap(); + if capture.cancelled.load(Ordering::SeqCst) { + session.destroy(); + return; + } + + let buffer_info = buffer_infos + .iter() + .find(|x| { + x.type_ == WEnum::Value(zcosmic_screencopy_session_v1::BufferType::WlShm) + && x.format == wl_shm::Format::Abgr8888.into() + }) + .unwrap(); + + // XXX fix in compositor + if buffer_info.width == 0 || buffer_info.height == 0 || buffer_info.stride == 0 { + session.destroy(); + return; + } + + let mut buffer = capture.buffer.lock().unwrap(); + // Create new buffer if none, or different format + if !buffer + .as_ref() + .map_or(false, |x| &x.buffer_info == buffer_info) + { + *buffer = Some(Buffer::new(buffer_info.clone(), &self.shm_state, qh)); + } + let buffer = buffer.as_ref().unwrap(); + + session.attach_buffer(&buffer.buffer, None, 0); // XXX age? + if capture.first_frame.load(Ordering::SeqCst) { + session.commit(zcosmic_screencopy_session_v1::Options::empty()); + } else { + session.commit(zcosmic_screencopy_session_v1::Options::OnDamage); + } + conn.flush().unwrap(); + } + + fn ready( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, + ) { + let capture = Capture::for_session(session).unwrap(); + if capture.cancelled.load(Ordering::SeqCst) { + session.destroy(); + return; + } + + let mut buffer = capture.buffer.lock().unwrap(); + let image = unsafe { buffer.as_mut().unwrap().to_image() }; + let event = match &capture.source { + CaptureSource::Toplevel(toplevel) => Event::ToplevelCapture(toplevel.clone(), image), + CaptureSource::Workspace(workspace, _) => { + Event::WorkspaceCapture(workspace.clone(), image) + } + }; + self.send_event(event); + session.destroy(); + + // Capture again on damage + capture.first_frame.store(false, Ordering::SeqCst); + capture.capture(&self.screencopy_state.screencopy_manager, &self.qh); + } + + fn failed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, + _reason: WEnum, + ) { + // TODO + println!("Failed"); + session.destroy(); + } +} diff --git a/src/wayland/toplevel.rs b/src/wayland/toplevel.rs new file mode 100644 index 0000000..8c6ef97 --- /dev/null +++ b/src/wayland/toplevel.rs @@ -0,0 +1,69 @@ +use cctk::{ + cosmic_protocols::{ + toplevel_info::v1::client::zcosmic_toplevel_handle_v1, + toplevel_management::v1::client::zcosmic_toplevel_manager_v1, + }, + toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, + toplevel_management::{ToplevelManagerHandler, ToplevelManagerState}, + wayland_client::{Connection, QueueHandle, WEnum}, +}; + +use super::{AppData, CaptureSource, Event}; + +// TODO any indication when we have all toplevels? +impl ToplevelInfoHandler for AppData { + fn toplevel_info_state(&mut self) -> &mut ToplevelInfoState { + &mut self.toplevel_info_state + } + + fn new_toplevel( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + ) { + let info = self.toplevel_info_state.info(toplevel).unwrap(); + self.send_event(Event::NewToplevel(toplevel.clone(), info.clone())); + + self.add_capture_source(CaptureSource::Toplevel(toplevel.clone())); + } + + fn update_toplevel( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + ) { + // TODO + } + + fn toplevel_closed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + ) { + self.send_event(Event::CloseToplevel(toplevel.clone())); + + self.remove_capture_source(CaptureSource::Toplevel(toplevel.clone())); + } +} + +impl ToplevelManagerHandler for AppData { + fn toplevel_manager_state(&mut self) -> &mut ToplevelManagerState { + &mut self.toplevel_manager_state + } + + fn capabilities( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _capabilities: Vec< + WEnum, + >, + ) { + } +} + +cctk::delegate_toplevel_info!(AppData); +cctk::delegate_toplevel_manager!(AppData); diff --git a/src/wayland/workspace.rs b/src/wayland/workspace.rs new file mode 100644 index 0000000..6c49729 --- /dev/null +++ b/src/wayland/workspace.rs @@ -0,0 +1,36 @@ +use cctk::{ + wayland_client::Proxy, + workspace::{WorkspaceHandler, WorkspaceState}, +}; + +use super::{AppData, CaptureSource, Event}; + +impl WorkspaceHandler for AppData { + fn workspace_state(&mut self) -> &mut WorkspaceState { + &mut self.workspace_state + } + + fn done(&mut self) { + let mut workspaces = Vec::new(); + + // XXX remove capture source for removed workspaces + + for group in self.workspace_state.workspace_groups() { + for workspace in &group.workspaces { + if let Some(output) = group.output.as_ref() { + let output_name = self.output_names.get(&output.id()).unwrap().clone(); + workspaces.push((output_name, workspace.clone())); + + self.add_capture_source(CaptureSource::Workspace( + workspace.handle.clone(), + output.clone(), + )); + } + } + } + + self.send_event(Event::Workspaces(workspaces)); + } +} + +cctk::delegate_workspace!(AppData);