From b5931cd2403ccb4078d95ecba7c631a6f09bb8d5 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 10 Feb 2023 10:12:26 -0800 Subject: [PATCH] Initial support for filtering what to capture to only what's needed --- src/main.rs | 50 ++++++++++++++++++++++++++----------- src/wayland/capture.rs | 24 ++++++++++++++---- src/wayland/mod.rs | 52 +++++++++++++++++++++++++++++++++------ src/wayland/screencopy.rs | 9 +++---- src/wayland/workspace.rs | 15 ++++++----- 5 files changed, 110 insertions(+), 40 deletions(-) diff --git a/src/main.rs b/src/main.rs index b63f9d9..01b18e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ struct Workspace { name: String, img: Option, handle: zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, - output_name: Option, + output_name: String, is_active: bool, } @@ -70,7 +70,7 @@ struct Toplevel { struct Output { // Output, on the `iced_sctk` Wayland connection handle: wl_output::WlOutput, - name: Option, + name: String, width: i32, height: i32, } @@ -78,7 +78,7 @@ struct Output { struct LayerSurface { // Output, on the `iced_sctk` Wayland connection output: wl_output::WlOutput, - output_name: Option, + output_name: String, // for transitions, would need windows in more than one workspace? But don't capture all of // them all the time every frame. } @@ -128,7 +128,7 @@ impl App { fn create_surface( &mut self, output: wl_output::WlOutput, - output_name: Option, + output_name: String, width: i32, height: i32, ) -> Command { @@ -177,14 +177,16 @@ impl App { if !self.visible { self.visible = true; let outputs = self.outputs.clone(); - Command::batch( + let cmd = Command::batch( outputs .into_iter() .map(|output| { self.create_surface(output.handle, output.name, output.width, output.height) }) .collect::>(), - ) + ); + self.update_capture_filter(); + cmd } else { Command::none() } @@ -193,6 +195,7 @@ impl App { // Close all shell surfaces fn hide(&mut self) -> Command { self.visible = false; + self.update_capture_filter(); Command::batch( mem::take(&mut self.layer_surfaces) .into_keys() @@ -200,6 +203,24 @@ impl App { .collect::>(), ) } + + fn update_capture_filter(&self) { + if let Some(sender) = self.wayland_cmd_sender.as_ref() { + let mut capture_filter = wayland::CaptureFilter::default(); + if self.visible { + // XXX handle on wrong connection + capture_filter.workspaces_on_outputs = + self.outputs.iter().map(|x| x.name.clone()).collect(); + capture_filter.toplevels_on_workspaces = self + .workspaces + .iter() + .filter(|x| x.is_active) + .map(|x| x.handle.clone()) + .collect(); + } + let _ = sender.send(wayland::Cmd::CaptureFilter(capture_filter)); + } + } } impl Application for App { @@ -225,20 +246,16 @@ impl Application for App { Msg::WaylandEvent(evt) => match evt { WaylandEvent::Output(evt, output) => match evt { OutputEvent::Created(Some(info)) => { - if let Some((width, height)) = info.logical_size { + if let (Some((width, height)), Some(name)) = (info.logical_size, info.name) + { self.outputs.push(Output { handle: output.clone(), - name: info.name.clone(), + name: name.clone(), width, height, }); if self.visible { - return self.create_surface( - output.clone(), - info.name, - width, - height, - ); + return self.create_surface(output.clone(), name, width, height); } } } @@ -249,7 +266,9 @@ impl Application for App { output.width = width; output.height = height; } - output.name = info.name; + if let Some(name) = info.name { + output.name = name; + } // XXX re-create surface? } } @@ -300,6 +319,7 @@ impl Application for App { is_active, }); } + self.update_capture_filter(); } wayland::Event::NewToplevel(handle, info) => { println!("New toplevel: {info:?}"); diff --git a/src/wayland/capture.rs b/src/wayland/capture.rs index d5c76e1..e1238e7 100644 --- a/src/wayland/capture.rs +++ b/src/wayland/capture.rs @@ -40,15 +40,16 @@ impl std::hash::Hash for CaptureSource { #[derive(Clone, Debug, Default)] pub struct CaptureFilter { - pub workspaces_on_outputs: Vec, + // TODO: Use `WlOutput` when one Wayland connection is used + 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, + first_frame: AtomicBool, + running: AtomicBool, } impl Capture { @@ -57,7 +58,7 @@ impl Capture { buffer: Mutex::new(None), source, first_frame: AtomicBool::new(true), - cancelled: AtomicBool::new(false), + running: AtomicBool::new(false), } } @@ -67,8 +68,17 @@ impl Capture { Some(&session.data::()?.capture) } + pub fn running(&self) -> bool { + self.running.load(Ordering::SeqCst) + } + + pub fn first_frame(&self) -> bool { + self.first_frame.load(Ordering::SeqCst) + } + pub fn cancel(&self) { - self.cancelled.store(true, Ordering::SeqCst); + self.running.store(false, Ordering::SeqCst); + *self.buffer.lock().unwrap() = None; } pub fn capture( @@ -76,6 +86,10 @@ impl Capture { manager: &zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1, qh: &QueueHandle, ) { + // Mark as running. If already running, this is not the first frame. + let already_running = self.running.swap(true, Ordering::SeqCst); + self.first_frame.store(!already_running, Ordering::SeqCst); + let udata = SessionData { session_data: Default::default(), capture: self.clone(), diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 08b0f1b..c731ea3 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -58,7 +58,7 @@ pub enum Event { ToplevelManager(zcosmic_toplevel_manager_v1::ZcosmicToplevelManagerV1), WorkspaceManager(zcosmic_workspace_manager_v1::ZcosmicWorkspaceManagerV1), // XXX Output name rather than `WlOutput` - Workspaces(Vec<(Option, cctk::workspace::Workspace)>), + Workspaces(Vec<(String, cctk::workspace::Workspace)>), WorkspaceCapture( zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, image::Handle, @@ -116,16 +116,52 @@ impl AppData { } } - fn invalidate_capture_filter(&mut self) { - //for i in self.captures - // XXX drain filter - // TODO cancel captures if needed, enable capture + fn matches_capture_filter(&self, source: &CaptureSource) -> bool { + match source { + CaptureSource::Toplevel(toplevel) => { + let info = self.toplevel_info_state.info(toplevel).unwrap(); + if let Some(workspace) = &info.workspace { + self.capture_filter + .toplevels_on_workspaces + .contains(workspace) + } else { + false + } + } + CaptureSource::Workspace(_, output) => { + if let Some(name) = &self.output_state.info(&output).unwrap().name { + self.capture_filter.workspaces_on_outputs.contains(name) + } else { + false + } + } + } + } + + fn invalidate_capture_filter(&self) { + for (source, capture) in self.captures.borrow_mut().iter_mut() { + let matches = self.matches_capture_filter(source); + let running = capture.running(); + if running && !matches { + capture.cancel(); + } else if !running & matches { + capture.capture(&self.screencopy_state.screencopy_manager, &self.qh); + } + } } fn add_capture_source(&self, source: CaptureSource) { - let capture = Arc::new(Capture::new(source.clone())); - capture.capture(&self.screencopy_state.screencopy_manager, &self.qh); - self.captures.borrow_mut().insert(source, capture); + self.captures + .borrow_mut() + .entry(source.clone()) + .or_insert_with(|| { + let matches = self.matches_capture_filter(&source); + let capture = Arc::new(Capture::new(source)); + if matches { + capture.capture(&self.screencopy_state.screencopy_manager, &self.qh); + } + capture + }); } fn remove_capture_source(&self, source: CaptureSource) { diff --git a/src/wayland/screencopy.rs b/src/wayland/screencopy.rs index 05270ec..3d7b2f1 100644 --- a/src/wayland/screencopy.rs +++ b/src/wayland/screencopy.rs @@ -4,8 +4,6 @@ use cctk::{ wayland_client::{protocol::wl_shm, Connection, QueueHandle, WEnum}, }; -use std::sync::atomic::Ordering; - use super::{AppData, Buffer, Capture, CaptureSource, Event}; impl ScreencopyHandler for AppData { @@ -21,7 +19,7 @@ impl ScreencopyHandler for AppData { buffer_infos: &[BufferInfo], ) { let capture = Capture::for_session(session).unwrap(); - if capture.cancelled.load(Ordering::SeqCst) { + if !capture.running() { session.destroy(); return; } @@ -51,7 +49,7 @@ impl ScreencopyHandler for AppData { let buffer = buffer.as_ref().unwrap(); session.attach_buffer(&buffer.buffer, None, 0); // XXX age? - if capture.first_frame.load(Ordering::SeqCst) { + if capture.first_frame() { session.commit(zcosmic_screencopy_session_v1::Options::empty()); } else { session.commit(zcosmic_screencopy_session_v1::Options::OnDamage); @@ -66,7 +64,7 @@ impl ScreencopyHandler for AppData { session: &zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1, ) { let capture = Capture::for_session(session).unwrap(); - if capture.cancelled.load(Ordering::SeqCst) { + if !capture.running() { session.destroy(); return; } @@ -83,7 +81,6 @@ impl ScreencopyHandler for AppData { session.destroy(); // Capture again on damage - capture.first_frame.store(false, Ordering::SeqCst); capture.capture(&self.screencopy_state.screencopy_manager, &self.qh); } diff --git a/src/wayland/workspace.rs b/src/wayland/workspace.rs index 6c49729..36775bb 100644 --- a/src/wayland/workspace.rs +++ b/src/wayland/workspace.rs @@ -14,17 +14,20 @@ impl WorkspaceHandler for AppData { let mut workspaces = Vec::new(); // XXX remove capture source for removed workspaces + // Handle move to another output 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())); + if let Some(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.add_capture_source(CaptureSource::Workspace( + workspace.handle.clone(), + output.clone(), + )); + } } } }