From 135b52ae1cb13a09e35b5277eedd8fd35b722ee5 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 12 Mar 2024 19:43:04 +0100 Subject: [PATCH 1/4] DROP ME: cosmic-protocols update --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e8cc0ab..87bc05cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -825,7 +825,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#e65fa5e2bb47e51656221657049bd3f88ae9dae5" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=ext-screencopy#97f0be27b58bce629802694b64d3620bbd20cdf8" dependencies = [ "bitflags 2.4.2", "wayland-backend", diff --git a/Cargo.toml b/Cargo.toml index 0a020d7f..ca99851a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ bytemuck = "1.12" calloop = {version = "0.12.2", features = ["executor"]} cosmic-comp-config = {path = "cosmic-comp-config"} cosmic-config = {git = "https://github.com/pop-os/libcosmic/", features = ["calloop", "macro"]} -cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"]} +cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", branch = "ext-screencopy", default-features = false, features = ["server"]} edid-rs = {version = "0.1"} egui = {version = "0.23.0", optional = true} egui_plot = {version = "0.23.0", optional = true} From 413c7f1f96a77854b64ad90a884c32b4dfd8cd3c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 12 Mar 2024 19:42:48 +0100 Subject: [PATCH 2/4] cosmic-screencopy-v2 --- src/backend/kms/mod.rs | 385 +++-- src/backend/render/cursor.rs | 48 +- src/backend/render/element.rs | 113 +- src/backend/render/mod.rs | 231 +-- src/backend/winit.rs | 20 +- src/backend/x11.rs | 25 +- src/input/mod.rs | 96 +- src/main.rs | 35 +- src/shell/element/stack.rs | 98 +- src/shell/element/window.rs | 105 +- src/shell/workspace.rs | 9 +- src/state.rs | 56 +- src/wayland/handlers/compositor.rs | 35 +- src/wayland/handlers/image_source.rs | 4 + src/wayland/handlers/layer_shell.rs | 17 +- src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/screencopy.rs | 1336 --------------- src/wayland/handlers/screencopy/mod.rs | 415 +++++ src/wayland/handlers/screencopy/render.rs | 864 ++++++++++ src/wayland/handlers/screencopy/user_data.rs | 357 ++++ src/wayland/handlers/session_lock.rs | 4 +- src/wayland/handlers/xdg_shell/mod.rs | 33 +- src/wayland/protocols/image_source.rs | 343 ++++ src/wayland/protocols/mod.rs | 2 +- src/wayland/protocols/screencopy.rs | 1547 ++++++++++-------- src/xwayland.rs | 39 +- 26 files changed, 3562 insertions(+), 2656 deletions(-) create mode 100644 src/wayland/handlers/image_source.rs delete mode 100644 src/wayland/handlers/screencopy.rs create mode 100644 src/wayland/handlers/screencopy/mod.rs create mode 100644 src/wayland/handlers/screencopy/render.rs create mode 100644 src/wayland/handlers/screencopy/user_data.rs create mode 100644 src/wayland/protocols/image_source.rs diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 388266a1..ef6c0392 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,19 +1,23 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - backend::render::{workspace_elements, CLEAR_COLOR}, + backend::render::{ + element::{CosmicElement, DamageElement}, + workspace_elements, CLEAR_COLOR, + }, config::OutputConfig, shell::Shell, state::{BackendData, Common, Fps, SurfaceDmabufFeedback}, utils::prelude::*, wayland::{ - handlers::screencopy::{render_session, UserdataExt}, - protocols::screencopy::{BufferParams, Session as ScreencopySession}, + handlers::screencopy::{submit_buffer, FrameHolder, SessionData}, + protocols::screencopy::{ + FailureReason, Frame as ScreencopyFrame, Session as ScreencopySession, + }, }, }; use anyhow::{Context, Result}; -use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason; use libc::dev_t; use smithay::{ backend::{ @@ -31,8 +35,8 @@ use smithay::{ libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ buffer_dimensions, - damage::{Error as RenderError, RenderOutputResult}, - element::Element, + damage::Error as RenderError, + element::{Element, RenderElementStates}, gles::GlesRenderbuffer, glow::GlowRenderer, multigpu::{gbm::GbmGlesBackend, Error as MultiError, GpuManager}, @@ -66,7 +70,7 @@ use smithay::{ Client, DisplayHandle, Weak, }, }, - utils::{DeviceFd, Size, Transform}, + utils::{Buffer as BufferCoords, DeviceFd, Physical, Rectangle, Size, Transform}, wayland::{ dmabuf::{get_dmabuf, DmabufFeedbackBuilder, DmabufGlobal}, drm_lease::{DrmLease, DrmLeaseState}, @@ -82,6 +86,7 @@ use std::{ collections::{HashMap, HashSet}, fmt, path::PathBuf, + sync::mpsc::Receiver, time::Duration, }; @@ -93,6 +98,14 @@ use super::render::{element::AsGlowRenderer, init_shaders, CursorMode, GlMultiRe // for now we assume we need at least 3ms const MIN_DISPLAY_TIME: Duration = Duration::from_millis(3); +/* +TODO Screencopy: +- Fixup blit, include additional damage +- Send accurate presentation time +- Handle early exit no damage case +- input... +*/ + #[derive(Debug)] pub struct KmsState { pub devices: HashMap, @@ -154,7 +167,10 @@ pub struct Surface { pub type GbmDrmCompositor = DrmCompositor< GbmAllocator, GbmDevice, - Option, + Option<( + OutputPresentationFeedback, + Receiver<(ScreencopyFrame, Vec>)>, + )>, DrmDeviceFd, >; @@ -192,7 +208,6 @@ pub fn init_backend( &state.common.event_loop_handle, output, None, - None, ) { error!( ?err, @@ -328,16 +343,10 @@ pub fn init_backend( surface.pending = false; } for output in state.common.shell.outputs() { - let sessions = output.pending_buffers().collect::>(); if let Err(err) = state.backend.kms().schedule_render( &state.common.event_loop_handle, output, None, - if !sessions.is_empty() { - Some(sessions) - } else { - None - }, ) { error!( ?err, @@ -472,7 +481,7 @@ impl State { match surface.surface.as_mut().map(|x| x.frame_submitted()) { Some(Ok(feedback)) => { - if let Some(mut feedback) = feedback.flatten() { + if let Some((mut feedback, frames)) = feedback.flatten() { let submit_time = match metadata.take().map(|data| data.time) { Some(DrmEventTime::Monotonic(tp)) => Some(tp), @@ -511,6 +520,14 @@ impl State { seq as u64, flags, ); + + while let Ok((frame, damage)) = frames.recv() { + frame.success( + surface.output.current_transform(), + damage, + clock, + ); + } } surface.pending = false; @@ -533,21 +550,11 @@ impl State { }; if let Some((output, avg_time)) = rescheduled { - let mut scheduled_sessions = - state.workspace_session_for_output(&output); - let mut output_sessions = output.pending_buffers().peekable(); - if output_sessions.peek().is_some() { - scheduled_sessions - .get_or_insert_with(Vec::new) - .extend(output_sessions); - } - let estimated_rendertime = std::cmp::max(avg_time, MIN_DISPLAY_TIME); if let Err(err) = state.backend.kms().schedule_render( &state.common.event_loop_handle, &output, Some(estimated_rendertime), - scheduled_sessions, ) { warn!(?err, "Failed to schedule render."); } @@ -1159,7 +1166,6 @@ impl Surface { render_node: Option<&DrmNode>, target_node: &DrmNode, state: &mut Common, - screencopy: Option<&[(ScreencopySession, BufferParams)]>, ) -> Result<()> { #[cfg(feature = "debug")] puffin::profile_function!(); @@ -1194,7 +1200,7 @@ impl Surface { .map(|((w, start), idx)| (w.handle, idx, start)); let workspace = (workspace.handle, idx); - let elements = workspace_elements( + let mut elements = workspace_elements( Some(&render_node), &mut renderer, state, @@ -1210,6 +1216,65 @@ impl Surface { })?; self.fps.elements(); + let frames: Vec<( + ScreencopySession, + ScreencopyFrame, + Result<(Option>>, RenderElementStates), OutputNoMode>, + )> = self + .output + .take_pending_frames() + .into_iter() + .map(|(session, frame)| { + let additional_damage = frame.damage(); + let session_data = session.user_data().get::().unwrap(); + let mut damage_tracking = session_data.borrow_mut(); + + let old_len = if !additional_damage.is_empty() { + let area = self + .output + .current_mode() + .unwrap() + /* TODO: Mode is Buffer..., why is this Physical in the first place */ + .size + .to_logical(1) + .to_buffer(1, Transform::Normal) + .to_f64(); + + let old_len = elements.len(); + elements.extend( + additional_damage + .into_iter() + .map(|rect| { + rect.to_f64() + .to_logical( + self.output.current_scale().fractional_scale(), + self.output.current_transform(), + &area, + ) + .to_i32_round() + }) + .map(DamageElement::new) + .map(Into::into), + ); + + Some(old_len) + } else { + None + }; + + let buffer = frame.buffer(); + let age = damage_tracking.age_for_buffer(&buffer); + let res = damage_tracking.dt.damage_output(age, &elements); + + if let Some(old_len) = old_len { + elements.truncate(old_len); + } + + std::mem::drop(damage_tracking); + (session, frame, res) + }) + .collect(); + let res = compositor.render_frame( &mut renderer, &elements, @@ -1219,8 +1284,13 @@ impl Surface { match res { Ok(frame_result) => { + let (tx, rx) = std::sync::mpsc::channel(); + let feedback = if !frame_result.is_empty { - Some(state.take_presentation_feedback(&self.output, &frame_result.states)) + Some(( + state.take_presentation_feedback(&self.output, &frame_result.states), + rx, + )) } else { None }; @@ -1230,93 +1300,152 @@ impl Surface { elem.sync.wait(); } } + match compositor.queue_frame(feedback) { - Ok(()) => { - self.pending = true; - } - Err(FrameError::EmptyFrame) => { - tracing::debug!("Stopped rendering"); - } - Err(err) => { - return Err(err).with_context(|| "Failed to submit result for display") - } - }; + x @ Ok(()) | x @ Err(FrameError::EmptyFrame) => { + for (session, frame, res) in frames { + let damage = match res { + Ok((damage, _)) => damage, + Err(err) => { + tracing::warn!(?err, "Failed to screencopy"); + session + .user_data() + .get::() + .unwrap() + .borrow_mut() + .reset(); + frame.fail(FailureReason::Unknown); + continue; + } + }; + + let mut sync = SyncPoint::default(); + + if let Some(ref damage) = damage { + let buffer = frame.buffer(); + if let Ok(dmabuf) = get_dmabuf(&buffer) { + renderer + .bind(dmabuf) + .map_err(RenderError::::Rendering)?; + } else { + let size = buffer_dimensions(&buffer).ok_or(RenderError::< + GlMultiRenderer, + >::Rendering( + MultiError::ImportFailed, + ))?; + let format = + with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format)) + .map_err(|_| OutputNoMode)? // eh, we have to do some error + .expect("We should be able to convert all hardcoded shm screencopy formats"); + let render_buffer = + Offscreen::::create_buffer( + &mut renderer, + format, + size, + ) + .map_err(RenderError::::Rendering)?; + renderer + .bind(render_buffer) + .map_err(RenderError::::Rendering)?; + } - if let Some(screencopy) = screencopy { - for (session, params) in screencopy { - match render_session( - Some(*render_node), - &mut renderer, - &session, - params, - self.output.current_transform(), - |_node, buffer, renderer, dt, age| { - let res = dt.damage_output(age, &elements)?; - - let mut sync = SyncPoint::default(); - if let (Some(ref damage), _) = &res { - if let Ok(dmabuf) = get_dmabuf(buffer) { - renderer.bind(dmabuf).map_err(RenderError::Rendering)?; - } else { - let size = buffer_dimensions(buffer).ok_or( - RenderError::Rendering(MultiError::ImportFailed), - )?; - let format = - with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) - .map_err(|_| OutputNoMode)? // eh, we have to do some error - .expect("We should be able to convert all hardcoded shm screencopy formats"); - let render_buffer = - Offscreen::::create_buffer( - renderer, format, size, + let (output_size, output_scale, output_transform) = ( + self.output.current_mode().ok_or(OutputNoMode)?.size, + self.output.current_scale().fractional_scale(), + self.output.current_transform(), + ); + + let filter = (!session.draw_cursor()) + .then(|| { + elements.iter().filter_map(|elem| { + if let CosmicElement::Cursor(_) = elem { + Some(elem.id().clone()) + } else { + None + } + }) + }) + .into_iter() + .flatten(); + + match frame_result + .blit_frame_result( + output_size, + output_transform, + output_scale, + &mut renderer, + damage.iter().copied(), + filter, + ) + .map_err(|err| match err { + BlitFrameResultError::Rendering(err) => { + RenderError::::Rendering(err) + } + BlitFrameResultError::Export(_) => { + RenderError::::Rendering( + MultiError::DeviceMissing, ) - .map_err(RenderError::Rendering)?; - renderer - .bind(render_buffer) - .map_err(RenderError::Rendering)?; + } + }) { + Ok(new_sync) => { + sync = new_sync; + } + Err(err) => { + tracing::warn!(?err, "Failed to screencopy"); + session + .user_data() + .get::() + .unwrap() + .borrow_mut() + .reset(); + frame.fail(FailureReason::Unknown); + continue; } + }; + } - let (output_size, output_scale, output_transform) = ( - self.output.current_mode().ok_or(OutputNoMode)?.size, - self.output.current_scale().fractional_scale(), - self.output.current_transform(), - ); - sync = frame_result - .blit_frame_result( - output_size, - output_transform, - output_scale, - renderer, - damage.iter().copied(), - // TODO: Filter cursor element - elements.iter().map(|e| e.id().clone()), - ) - .map_err(|err| match err { - BlitFrameResultError::Rendering(err) => { - RenderError::Rendering(err) - } - BlitFrameResultError::Export(_) => { - RenderError::Rendering(MultiError::DeviceMissing) - } - })?; + match submit_buffer( + frame, + &mut renderer, + self.output.current_transform(), + damage, + sync, + ) { + Ok(Some((frame, damage))) => { + let _ = tx.send((frame, damage)); + } + Ok(None) => {} + Err(err) => { + session + .user_data() + .get::() + .unwrap() + .borrow_mut() + .reset(); + tracing::warn!(?err, "Failed to screencopy"); } - - Ok(RenderOutputResult { - damage: res.0, - states: res.1, - sync, - }) - }, - ) { - Ok(true) => {} // success - Ok(false) => state.still_pending(session.clone(), params.clone()), - Err(err) => { - warn!(?err, "Error rendering to screencopy session."); - session.failed(FailureReason::Unspec); } } + + if x.is_ok() { + self.pending = true; + } else { + tracing::debug!("Stopped rendering"); + } } - self.fps.screencopy(); - } + Err(err) => { + for (session, frame, _) in frames { + session + .user_data() + .get::() + .unwrap() + .borrow_mut() + .reset(); + frame.fail(FailureReason::Unknown); + } + return Err(err).with_context(|| "Failed to submit result for display"); + } + }; state.send_frames(&self.output, &frame_result.states, |source_node| { Some( @@ -1495,17 +1624,7 @@ impl KmsState { }; if recreated { - let sessions = output.pending_buffers().collect::>(); - if let Err(err) = self.schedule_render( - loop_handle, - output, - None, - if !sessions.is_empty() { - Some(sessions) - } else { - None - }, - ) { + if let Err(err) = self.schedule_render(loop_handle, output, None) { error!( ?err, "Error scheduling event loop for output {}.", @@ -1592,7 +1711,6 @@ impl KmsState { loop_handle: &LoopHandle<'_, State>, output: &Output, estimated_rendertime: Option, - mut screencopy_sessions: Option>, ) -> Result<(), InsertError> { if let Some((device, crtc, surface)) = self .devices @@ -1601,13 +1719,6 @@ impl KmsState { .find(|(_, _, s)| s.output == *output) { if surface.surface.is_none() { - if let Some(sessions) = screencopy_sessions { - loop_handle.insert_idle(move |state| { - for (session, params) in sessions.into_iter() { - state.common.still_pending(session, params); - } - }); - } return Ok(()); } if !surface.scheduled && !surface.pending { @@ -1654,16 +1765,9 @@ impl KmsState { Some(&render_device.render_node), &target_node, common, - screencopy_sessions.as_deref(), ) } else { - surface.render_output( - &mut backend.api, - None, - &target_node, - common, - screencopy_sessions.as_deref(), - ) + surface.render_output(&mut backend.api, None, &target_node, common) }; match result { @@ -1684,24 +1788,11 @@ impl KmsState { }; } - if let Some(sessions) = screencopy_sessions.as_mut() { - for (session, params) in sessions.drain(..) { - state.common.still_pending(session, params); - } - } TimeoutAction::Drop }, )?); trace!(?surface.render_timer_token, ?crtc, "Frame scheduled"); surface.scheduled = true; - } else { - if let Some(sessions) = screencopy_sessions { - loop_handle.insert_idle(|state| { - for (session, params) in sessions.into_iter() { - state.common.still_pending(session, params); - } - }); - } } } Ok(()) diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 72eb0778..b477f28f 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -19,7 +19,9 @@ use smithay::{ }, reexports::wayland_server::protocol::wl_surface, render_elements, - utils::{IsAlive, Logical, Monotonic, Point, Scale, Time, Transform}, + utils::{ + Buffer as BufferCoords, IsAlive, Logical, Monotonic, Point, Scale, Size, Time, Transform, + }, wayland::compositor::{get_role, with_states}, }; use std::{cell::RefCell, collections::HashMap, io::Read, sync::Mutex, time::Duration}; @@ -156,12 +158,12 @@ pub fn draw_surface_cursor( surface: &wl_surface::WlSurface, location: impl Into>, scale: impl Into>, -) -> Vec> +) -> Vec<(CursorRenderElement, Point)> where R: Renderer + ImportAll, ::TextureId: 'static, { - let mut position = location.into(); + let position = location.into(); let scale = scale.into(); let h = with_states(&surface, |states| { states @@ -171,8 +173,12 @@ where .lock() .unwrap() .hotspot + .to_buffer( + 1, + Transform::Normal, + &Size::from((1, 1)), /* Size doesn't matter for Transform::Normal */ + ) }); - position -= h; render_elements_from_surface_tree( renderer, @@ -182,6 +188,9 @@ where 1.0, Kind::Cursor, ) + .into_iter() + .map(|elem| (elem, h)) + .collect() } pub fn draw_dnd_icon( @@ -189,7 +198,7 @@ pub fn draw_dnd_icon( surface: &wl_surface::WlSurface, location: impl Into>, scale: impl Into>, -) -> Vec> +) -> Vec> where R: Renderer + ImportAll, ::TextureId: 'static, @@ -308,7 +317,7 @@ pub fn draw_cursor( scale: Scale, time: Time, draw_default: bool, -) -> Vec> +) -> Vec<(CursorRenderElement, Point)> where R: Renderer + ImportMem + ImportAll, ::TextureId: Clone + 'static, @@ -370,20 +379,23 @@ where } }; - let hotspot = Point::::from((frame.xhot as i32, frame.yhot as i32)).to_f64(); + let hotspot = Point::::from((frame.xhot as i32, frame.yhot as i32)); *state.current_image.borrow_mut() = Some(frame); - return vec![CursorRenderElement::Static( - MemoryRenderBufferRenderElement::from_buffer( - renderer, - (location - hotspot).to_physical(scale), - pointer_image, - None, - None, - None, - Kind::Cursor, - ) - .expect("Failed to import cursor bitmap"), + return vec![( + CursorRenderElement::Static( + MemoryRenderBufferRenderElement::from_buffer( + renderer, + location.to_physical(scale), + pointer_image, + None, + None, + None, + Kind::Cursor, + ) + .expect("Failed to import cursor bitmap"), + ), + hotspot, )]; } else { Vec::new() diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index d86ab742..3235e605 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -3,13 +3,15 @@ use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement}; use smithay::{ backend::renderer::{ element::{ + surface::WaylandSurfaceRenderElement, utils::{Relocate, RelocateRenderElement}, - Element, RenderElement, UnderlyingStorage, + Element, Id, RenderElement, UnderlyingStorage, }, glow::{GlowFrame, GlowRenderer}, + utils::CommitCounter, Frame, ImportAll, ImportMem, Renderer, }, - utils::{Buffer as BufferCoords, Physical, Point, Rectangle, Scale}, + utils::{Buffer as BufferCoords, Logical, Physical, Point, Rectangle, Scale}, }; #[cfg(feature = "debug")] @@ -24,8 +26,10 @@ where CosmicMappedRenderElement: RenderElement, { Workspace(RelocateRenderElement>), - Cursor(CursorRenderElement), + Cursor(RelocateRenderElement>), + Dnd(WaylandSurfaceRenderElement), MoveGrab(CosmicMappedRenderElement), + AdditionalDamage(DamageElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), } @@ -36,21 +40,25 @@ where ::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn id(&self) -> &smithay::backend::renderer::element::Id { + fn id(&self) -> &Id { match self { CosmicElement::Workspace(elem) => elem.id(), CosmicElement::Cursor(elem) => elem.id(), + CosmicElement::Dnd(elem) => elem.id(), CosmicElement::MoveGrab(elem) => elem.id(), + CosmicElement::AdditionalDamage(elem) => elem.id(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.id(), } } - fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { + fn current_commit(&self) -> CommitCounter { match self { CosmicElement::Workspace(elem) => elem.current_commit(), CosmicElement::Cursor(elem) => elem.current_commit(), + CosmicElement::Dnd(elem) => elem.current_commit(), CosmicElement::MoveGrab(elem) => elem.current_commit(), + CosmicElement::AdditionalDamage(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.current_commit(), } @@ -60,7 +68,9 @@ where match self { CosmicElement::Workspace(elem) => elem.src(), CosmicElement::Cursor(elem) => elem.src(), + CosmicElement::Dnd(elem) => elem.src(), CosmicElement::MoveGrab(elem) => elem.src(), + CosmicElement::AdditionalDamage(elem) => elem.src(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.src(), } @@ -70,7 +80,9 @@ where match self { CosmicElement::Workspace(elem) => elem.geometry(scale), CosmicElement::Cursor(elem) => elem.geometry(scale), + CosmicElement::Dnd(elem) => elem.geometry(scale), CosmicElement::MoveGrab(elem) => elem.geometry(scale), + CosmicElement::AdditionalDamage(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.geometry(scale), } @@ -80,7 +92,9 @@ where match self { CosmicElement::Workspace(elem) => elem.location(scale), CosmicElement::Cursor(elem) => elem.location(scale), + CosmicElement::Dnd(elem) => elem.location(scale), CosmicElement::MoveGrab(elem) => elem.location(scale), + CosmicElement::AdditionalDamage(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.location(scale), } @@ -90,7 +104,9 @@ where match self { CosmicElement::Workspace(elem) => elem.transform(), CosmicElement::Cursor(elem) => elem.transform(), + CosmicElement::Dnd(elem) => elem.transform(), CosmicElement::MoveGrab(elem) => elem.transform(), + CosmicElement::AdditionalDamage(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.transform(), } @@ -99,12 +115,14 @@ where fn damage_since( &self, scale: Scale, - commit: Option, + commit: Option, ) -> Vec> { match self { CosmicElement::Workspace(elem) => elem.damage_since(scale, commit), CosmicElement::Cursor(elem) => elem.damage_since(scale, commit), + CosmicElement::Dnd(elem) => elem.damage_since(scale, commit), CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), + CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.damage_since(scale, commit), } @@ -114,7 +132,9 @@ where match self { CosmicElement::Workspace(elem) => elem.opaque_regions(scale), CosmicElement::Cursor(elem) => elem.opaque_regions(scale), + CosmicElement::Dnd(elem) => elem.opaque_regions(scale), CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), + CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.opaque_regions(scale), } @@ -124,7 +144,9 @@ where match self { CosmicElement::Workspace(elem) => elem.alpha(), CosmicElement::Cursor(elem) => elem.alpha(), + CosmicElement::Dnd(elem) => elem.alpha(), CosmicElement::MoveGrab(elem) => elem.alpha(), + CosmicElement::AdditionalDamage(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.alpha(), } @@ -142,7 +164,11 @@ impl RenderElement for CosmicElement { match self { CosmicElement::Workspace(elem) => elem.draw(frame, src, dst, damage), CosmicElement::Cursor(elem) => elem.draw(frame, src, dst, damage), + CosmicElement::Dnd(elem) => elem.draw(frame, src, dst, damage), CosmicElement::MoveGrab(elem) => elem.draw(frame, src, dst, damage), + CosmicElement::AdditionalDamage(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage) + } #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) @@ -154,7 +180,9 @@ impl RenderElement for CosmicElement { match self { CosmicElement::Workspace(elem) => elem.underlying_storage(renderer), CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), + CosmicElement::Dnd(elem) => elem.underlying_storage(renderer), CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), + CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.underlying_storage(renderer), } @@ -172,7 +200,11 @@ impl<'a> RenderElement> for CosmicElement elem.draw(frame, src, dst, damage), CosmicElement::Cursor(elem) => elem.draw(frame, src, dst, damage), + CosmicElement::Dnd(elem) => elem.draw(frame, src, dst, damage), CosmicElement::MoveGrab(elem) => elem.draw(frame, src, dst, damage), + CosmicElement::AdditionalDamage(elem) => { + RenderElement::>::draw(elem, frame, src, dst, damage) + } #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let elem = { @@ -189,7 +221,9 @@ impl<'a> RenderElement> for CosmicElement elem.underlying_storage(renderer), CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), + CosmicElement::Dnd(elem) => elem.underlying_storage(renderer), CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), + CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let glow_renderer = renderer.glow_renderer_mut(); @@ -219,25 +253,25 @@ where } } -impl From> for CosmicElement +impl From> for CosmicElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn from(elem: CursorRenderElement) -> Self { - Self::Cursor(elem) + fn from(elem: CosmicMappedRenderElement) -> Self { + Self::MoveGrab(elem) } } -impl From> for CosmicElement +impl From for CosmicElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn from(elem: CosmicMappedRenderElement) -> Self { - Self::MoveGrab(elem) + fn from(elem: DamageElement) -> Self { + Self::AdditionalDamage(elem) } } @@ -304,3 +338,58 @@ impl<'renderer, 'frame> AsGlowFrame<'frame> for GlMultiFrame<'renderer, 'frame> self.as_mut() } } + +pub struct DamageElement { + id: Id, + geometry: Rectangle, +} + +impl DamageElement { + pub fn new(geometry: Rectangle) -> DamageElement { + DamageElement { + id: Id::new(), + geometry, + } + } +} + +impl Element for DamageElement { + fn id(&self) -> &Id { + &self.id + } + + fn current_commit(&self) -> CommitCounter { + CommitCounter::default() + } + + fn src(&self) -> Rectangle { + Rectangle::from_loc_and_size((0.0, 0.0), (1.0, 1.0)) + } + + fn geometry(&self, scale: Scale) -> Rectangle { + self.geometry.to_f64().to_physical(scale).to_i32_round() + } + + fn damage_since( + &self, + scale: Scale, + _commit: Option, + ) -> Vec> { + vec![Rectangle::from_loc_and_size( + (0, 0), + self.geometry(scale).size, + )] + } +} + +impl RenderElement for DamageElement { + fn draw( + &self, + _frame: &mut ::Frame<'_>, + _src: Rectangle, + _dst: Rectangle, + _damage: &[Rectangle], + ) -> Result<(), ::Error> { + Ok(()) + } +} diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 6090d9b8..f1b05b55 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -11,6 +11,7 @@ use std::{ #[cfg(feature = "debug")] use crate::debug::{fps_ui, profiler_ui}; use crate::{ + backend::render::element::DamageElement, shell::{ focus::target::WindowGroup, grabs::{SeatMenuGrabState, SeatMoveGrabState}, @@ -23,19 +24,13 @@ use crate::{ wayland::{ handlers::{ data_device::get_dnd_icon, - screencopy::{render_session, WORKSPACE_OVERVIEW_NAMESPACE}, - }, - protocols::{ - screencopy::{ - BufferParams, CursorMode as ScreencopyCursorMode, Session as ScreencopySession, - }, - workspace::WorkspaceHandle, + screencopy::{render_session, FrameHolder, SessionData, WORKSPACE_OVERVIEW_NAMESPACE}, }, + protocols::workspace::WorkspaceHandle, }, }; use cosmic_comp_config::workspace::WorkspaceLayout; -use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason; use keyframe::{ease, functions::EaseInOutCubic}; use smithay::{ backend::{ @@ -61,7 +56,7 @@ use smithay::{ }, desktop::{layer_map_for_output, PopupManager}, output::{Output, OutputNoMode}, - utils::{IsAlive, Logical, Point, Rectangle, Scale}, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Transform}, wayland::{ dmabuf::get_dmabuf, shell::wlr_layer::Layer, @@ -71,7 +66,6 @@ use smithay::{ use tracing::warn; pub mod cursor; -use self::cursor::CursorRenderElement; pub mod element; use self::element::{AsGlowRenderer, CosmicElement}; @@ -388,17 +382,16 @@ pub enum CursorMode { All, } -pub fn cursor_elements<'frame, E, R>( +pub fn cursor_elements<'frame, R>( renderer: &mut R, state: &Common, output: &Output, mode: CursorMode, -) -> Vec +) -> Vec> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Clone + 'static, CosmicMappedRenderElement: RenderElement, - E: From> + From>, { #[cfg(feature = "debug")] puffin::profile_function!(); @@ -424,7 +417,13 @@ where mode != CursorMode::NotDefault, ) .into_iter() - .map(E::from), + .map(|(elem, hotspot)| { + CosmicElement::Cursor(RelocateRenderElement::from_element( + elem, + Point::from((-hotspot.x, -hotspot.y)), + Relocate::Relative, + )) + }), ); } @@ -432,7 +431,7 @@ where elements.extend( cursor::draw_dnd_icon(renderer, &wl_surface, location.to_i32_round(), scale) .into_iter() - .map(E::from), + .map(CosmicElement::Dnd), ); } @@ -443,7 +442,7 @@ where .unwrap() .borrow() .as_ref() - .map(|state| state.render::(renderer, seat, output, theme)) + .map(|state| state.render::, R>(renderer, seat, output, theme)) { elements.extend(grab_elements); } @@ -964,7 +963,7 @@ where } } -pub fn render_output( +pub fn render_output( gpu: Option<&DrmNode>, renderer: &mut R, target: Target, @@ -973,7 +972,6 @@ pub fn render_output( state: &mut Common, output: &Output, cursor_mode: CursorMode, - screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, fps: Option<&mut Fps>, ) -> Result> where @@ -984,14 +982,14 @@ where + Bind + Bind + Offscreen - + Blit + + Blit + AsGlowRenderer, ::TextureId: Clone + 'static, ::Error: From, CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, - Source: Clone, + Target: Clone, { let (previous_workspace, workspace) = state.shell.workspaces.active(output); let (previous_idx, idx) = state.shell.workspaces.active_num(output); @@ -1003,37 +1001,127 @@ where let result = render_workspace( gpu, renderer, - target, + target.clone(), damage_tracker, age, + None, state, output, previous_workspace, workspace, cursor_mode, - screencopy, fps, false, ); - result + match result { + Ok((res, mut elements)) => { + for (session, frame) in output.take_pending_frames() { + if let Some((frame, damage)) = render_session( + renderer, + &session.user_data().get::().unwrap(), + frame, + output.current_transform(), + |buffer, renderer, dt, age, additional_damage| { + let old_len = if !additional_damage.is_empty() { + let area = output + .current_mode() + .ok_or(RenderError::OutputNoMode(OutputNoMode)) + .map( + |mode| { + mode.size + .to_logical(1) + .to_buffer(1, Transform::Normal) + .to_f64() + }, /* TODO: Mode is Buffer..., why is this Physical in the first place */ + )?; + + let old_len = elements.len(); + elements.extend( + additional_damage + .into_iter() + .map(|rect| { + rect.to_f64() + .to_logical( + output.current_scale().fractional_scale(), + output.current_transform(), + &area, + ) + .to_i32_round() + }) + .map(DamageElement::new) + .map(Into::into), + ); + + Some(old_len) + } else { + None + }; + + let res = dt.damage_output(age, &elements)?; + + if let Some(old_len) = old_len { + elements.truncate(old_len); + } + + if let (Some(ref damage), _) = &res { + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer.bind(dmabuf).map_err(RenderError::Rendering)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let format = with_buffer_contents(buffer, |_, _, data| { + shm_format_to_fourcc(data.format) + }) + .map_err(|_| OutputNoMode)? // eh, we have to do some error + .expect( + "We should be able to convert all hardcoded shm screencopy formats", + ); + let render_buffer = renderer + .create_buffer(format, size) + .map_err(RenderError::Rendering)?; + renderer + .bind(render_buffer) + .map_err(RenderError::Rendering)?; + } + for rect in damage { + renderer + .blit_from(target.clone(), *rect, *rect, TextureFilter::Nearest) + .map_err(RenderError::Rendering)?; + } + } + + Ok(RenderOutputResult { + damage: res.0, + sync: SyncPoint::default(), + states: res.1, + }) + }, + )? { + frame.success(output.current_transform(), damage, state.clock.now()); + } + } + + Ok(res) + } + Err(err) => Err(err), + } } -pub fn render_workspace( +pub fn render_workspace( gpu: Option<&DrmNode>, renderer: &mut R, target: Target, damage_tracker: &mut OutputDamageTracker, age: usize, + additional_damage: Option>>, state: &mut Common, output: &Output, previous: Option<(WorkspaceHandle, usize, Instant)>, current: (WorkspaceHandle, usize), - mut cursor_mode: CursorMode, - screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, + cursor_mode: CursorMode, mut fps: Option<&mut Fps>, exclude_workspace_overview: bool, -) -> Result> +) -> Result<(RenderOutputResult, Vec>), RenderError> where R: Renderer + ImportAll @@ -1042,14 +1130,12 @@ where + Bind + Bind + Offscreen - + Blit + AsGlowRenderer, ::TextureId: Clone + 'static, ::Error: From, CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, - Source: Clone, { #[cfg(feature = "debug")] puffin::profile_function!(); @@ -1065,22 +1151,7 @@ where } } - let screencopy_contains_embedded = screencopy.as_ref().map_or(false, |(_, sessions)| { - sessions - .iter() - .any(|(s, _)| s.cursor_mode() == ScreencopyCursorMode::Embedded) - }); - // cursor handling without a cursor_plane in this case is horrible. - // because what if some session disagree and/or the backend wants to render with a different mode? - // It seems we would need to render to an offscreen buffer in those cases (and do multiple renders, which messes with damage tracking). - // So for now, we just pick the worst mode (embedded), if any requires it. - // - // Once we move to a cursor_plane, the default framebuffer will never contain a cursor and we can just composite the cursor for each session separately on top (or not). - if screencopy_contains_embedded { - cursor_mode = CursorMode::All; - }; - - let elements: Vec> = workspace_elements( + let mut elements: Vec> = workspace_elements( gpu, renderer, state, @@ -1091,6 +1162,18 @@ where &mut fps, exclude_workspace_overview, )?; + + if let Some(additional_damage) = additional_damage { + let output_geo = output.geometry().to_local(&output).as_logical(); + elements.extend( + additional_damage + .into_iter() + .filter_map(|rect| rect.intersection(output_geo)) + .map(DamageElement::new) + .map(Into::>::into), + ); + } + if let Some(fps) = fps.as_mut() { fps.elements(); } @@ -1107,62 +1190,6 @@ where fps.render(); } - if let Some((source, buffers)) = screencopy { - if res.is_ok() { - for (session, params) in buffers { - match render_session( - gpu.cloned(), - renderer, - &session, - params, - output.current_transform(), - |_node, buffer, renderer, dt, age| { - let res = dt.damage_output(age, &elements)?; - - if let (Some(ref damage), _) = &res { - if let Ok(dmabuf) = get_dmabuf(buffer) { - renderer.bind(dmabuf).map_err(RenderError::Rendering)?; - } else { - let size = buffer_dimensions(buffer).unwrap(); - let format = - with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) - .map_err(|_| OutputNoMode)? // eh, we have to do some error - .expect("We should be able to convert all hardcoded shm screencopy formats"); - let render_buffer = renderer - .create_buffer(format, size) - .map_err(RenderError::Rendering)?; - renderer - .bind(render_buffer) - .map_err(RenderError::Rendering)?; - } - for rect in damage { - renderer - .blit_from(source.clone(), *rect, *rect, TextureFilter::Nearest) - .map_err(RenderError::Rendering)?; - } - } - - Ok(RenderOutputResult { - damage: res.0, - sync: SyncPoint::default(), - states: res.1, - }) - }, - ) { - Ok(true) => {} // success - Ok(false) => state.still_pending(session.clone(), params.clone()), - Err(err) => { - warn!(?err, "Error rendering to screencopy session."); - session.failed(FailureReason::Unspec); - } - } - } - } - if let Some(fps) = fps.as_mut() { - fps.screencopy(); - } - } - #[cfg(feature = "debug")] if let Some(ref mut fps) = fps { if let Some(rd) = fps.rd.as_mut() { @@ -1175,5 +1202,5 @@ where puffin::GlobalProfiler::lock().new_frame(); } - res + res.map(|res| (res, elements)) } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 1e4134a4..57297a78 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -6,7 +6,6 @@ use crate::{ input::Devices, state::{BackendData, Common}, utils::prelude::*, - wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession}, }; use anyhow::{anyhow, Context, Result}; use smithay::{ @@ -45,7 +44,6 @@ pub struct WinitState { pub backend: WinitGraphicsBackend, output: Output, damage_tracker: OutputDamageTracker, - screencopy: Vec<(ScreencopySession, BufferParams)>, #[cfg(feature = "debug")] fps: Fps, } @@ -58,7 +56,7 @@ impl WinitState { let age = self.backend.buffer_age().unwrap_or(0); let surface = self.backend.egl_surface(); - match render::render_output::<_, _, GlesRenderbuffer, _>( + match render::render_output::<_, _, GlesRenderbuffer>( None, self.backend.renderer(), surface.clone(), @@ -67,11 +65,6 @@ impl WinitState { state, &self.output, CursorMode::NotDefault, - if !self.screencopy.is_empty() { - Some((surface, &self.screencopy)) - } else { - None - }, #[cfg(not(feature = "debug"))] None, #[cfg(feature = "debug")] @@ -84,7 +77,6 @@ impl WinitState { self.backend .submit(damage.as_deref()) .with_context(|| "Failed to submit buffer for display")?; - self.screencopy.clear(); #[cfg(feature = "debug")] self.fps.displayed(); state.send_frames(&self.output, &states, |_| None); @@ -103,9 +95,6 @@ impl WinitState { } } Err(err) => { - for (session, params) in self.screencopy.drain(..) { - state.still_pending(session, params) - } anyhow::bail!("Rendering failed: {}", err); } }; @@ -135,12 +124,6 @@ impl WinitState { Ok(()) } } - - pub fn pending_screencopy(&mut self, new: Option>) { - if let Some(sessions) = new { - self.screencopy.extend(sessions); - } - } } pub fn init_backend( @@ -231,7 +214,6 @@ pub fn init_backend( backend, output: output.clone(), damage_tracker: OutputDamageTracker::from_output(&output), - screencopy: Vec::new(), #[cfg(feature = "debug")] fps, }); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 6e361a37..5685e3ab 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -6,7 +6,6 @@ use crate::{ input::Devices, state::{BackendData, Common}, utils::prelude::*, - wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession}, }; use anyhow::{Context, Result}; use smithay::{ @@ -150,7 +149,6 @@ impl X11State { render: ping.clone(), dirty: false, pending: true, - screencopy: Vec::new(), #[cfg(feature = "debug")] fps: Fps::new(&mut self.renderer), }); @@ -160,16 +158,9 @@ impl X11State { Ok(output) } - pub fn schedule_render( - &mut self, - output: &Output, - screencopy: Option>, - ) { + pub fn schedule_render(&mut self, output: &Output) { if let Some(surface) = self.surfaces.iter_mut().find(|s| s.output == *output) { surface.dirty = true; - if let Some(sessions) = screencopy { - surface.screencopy.extend(sessions); - } if !surface.pending { surface.render.ping(); } @@ -210,7 +201,6 @@ impl X11State { pub struct Surface { window: Window, damage_tracker: OutputDamageTracker, - screencopy: Vec<(ScreencopySession, BufferParams)>, surface: X11Surface, output: Output, render: ping::Ping, @@ -226,7 +216,7 @@ impl Surface { .surface .buffer() .with_context(|| "Failed to allocate buffer")?; - match render::render_output::<_, _, GlesRenderbuffer, _>( + match render::render_output::<_, _, GlesRenderbuffer>( None, renderer, buffer.clone(), @@ -235,18 +225,12 @@ impl Surface { state, &self.output, render::CursorMode::NotDefault, - if !self.screencopy.is_empty() { - Some((buffer, &self.screencopy)) - } else { - None - }, #[cfg(not(feature = "debug"))] None, #[cfg(feature = "debug")] Some(&mut self.fps), ) { Ok(RenderOutputResult { damage, states, .. }) => { - self.screencopy.clear(); self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; @@ -268,9 +252,6 @@ impl Surface { } } Err(err) => { - for (session, params) in self.screencopy.drain(..) { - state.still_pending(session, params) - } self.surface.reset_buffers(); anyhow::bail!("Rendering failed: {}", err); } @@ -540,7 +521,7 @@ impl State { self.process_input_event(event, false); // TODO actually figure out the output for output in self.common.shell.outputs() { - self.backend.x11().schedule_render(output, None); + self.backend.x11().schedule_render(output); } } } diff --git a/src/input/mod.rs b/src/input/mod.rs index 08bf703b..424cc453 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -15,14 +15,13 @@ use crate::{ state::Common, utils::prelude::*, wayland::{ - handlers::{screencopy::ScreencopySessions, xdg_activation::ActivationContext}, - protocols::screencopy::Session, + handlers::{screencopy::SessionHolder, xdg_activation::ActivationContext}, + protocols::screencopy::{BufferConstraints, CursorSession}, }, }; use calloop::{timer::Timer, RegistrationToken}; use cosmic_comp_config::{workspace::WorkspaceLayout, TileBehavior}; use cosmic_config::ConfigSet; -use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, GestureBeginEvent, @@ -43,7 +42,10 @@ use smithay::{ Seat, SeatState, }, output::Output, - reexports::{input::Device as InputDevice, wayland_server::DisplayHandle}, + reexports::{ + input::Device as InputDevice, + wayland_server::{protocol::wl_shm::Format as ShmFormat, DisplayHandle}, + }, utils::{Point, Serial, SERIAL_COUNTER}, wayland::{ keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, @@ -772,18 +774,13 @@ impl State { } if output != current_output { - for session in sessions_for_output(&self.common, ¤t_output) { - session.cursor_leave(&seat, InputType::Pointer); - } - - for session in sessions_for_output(&self.common, &output) { - session.cursor_enter(&seat, InputType::Pointer); + for session in cursor_sessions_for_output(&self.common, ¤t_output) { + session.set_cursor_pos(None); } - seat.set_active_output(&output); } - for session in sessions_for_output(&self.common, &output) { + for session in cursor_sessions_for_output(&self.common, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( position.as_logical().to_buffer( output.current_scale().fractional_scale(), @@ -792,7 +789,19 @@ impl State { ), self.common.clock.now(), ) { - session.cursor_info(&seat, InputType::Pointer, geometry, offset); + if session + .current_constraints() + .map(|constraint| constraint.size != geometry.size) + .unwrap_or(true) + { + session.update_constraints(BufferConstraints { + size: geometry.size, + shm: vec![ShmFormat::Argb8888], + dma: None, + }); + } + session.set_cursor_hotspot(offset); + session.set_cursor_pos(Some(geometry.loc)); } } #[cfg(feature = "debug")] @@ -818,7 +827,7 @@ impl State { let under = State::surface_under(position, &output, &mut self.common.shell) .map(|(target, pos)| (target, pos.as_logical())); - for session in sessions_for_output(&self.common, &output) { + for session in cursor_sessions_for_output(&self.common, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( position.as_logical().to_buffer( output.current_scale().fractional_scale(), @@ -827,7 +836,19 @@ impl State { ), self.common.clock.now(), ) { - session.cursor_info(&seat, InputType::Pointer, geometry, offset); + if session + .current_constraints() + .map(|constraint| constraint.size != geometry.size) + .unwrap_or(true) + { + session.update_constraints(BufferConstraints { + size: geometry.size, + shm: vec![ShmFormat::Argb8888], + dma: None, + }); + } + session.set_cursor_hotspot(offset); + session.set_cursor_pos(Some(geometry.loc)); } } let ptr = seat.get_pointer().unwrap(); @@ -2274,51 +2295,22 @@ impl State { } } -fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator { +fn cursor_sessions_for_output( + state: &Common, + output: &Output, +) -> impl Iterator { let workspace = state.shell.active_space(&output); let maybe_fullscreen = workspace.get_fullscreen(); workspace - .screencopy_sessions - .iter() - .map(|s| (&**s).clone()) + .cursor_sessions() + .into_iter() .chain( maybe_fullscreen - .as_ref() - .and_then(|w| { - if let Some(sessions) = w.user_data().get::() { - Some( - sessions - .0 - .borrow() - .iter() - .map(|s| (&**s).clone()) - .collect::>(), - ) - } else { - None - } - }) - .into_iter() - .flatten(), - ) - .chain( - output - .user_data() - .get::() - .map(|sessions| { - sessions - .0 - .borrow() - .iter() - .map(|s| (&**s).clone()) - .collect::>() - }) - .into_iter() + .map(|w| w.cursor_sessions()) .into_iter() .flatten(), ) - .collect::>() - .into_iter() + .chain(output.cursor_sessions().into_iter()) } // TODO Is it possible to determine mapping for external touchscreen? diff --git a/src/main.rs b/src/main.rs index 66bdbe9a..43268195 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,12 +13,8 @@ use std::{env, ffi::OsString, process, sync::Arc}; use tracing::{error, info, warn}; use crate::{ - state::BackendData, - utils::prelude::SeatExt, - wayland::{ - handlers::{compositor::client_compositor_state, screencopy::PendingScreencopyBuffers}, - protocols::screencopy::SessionType, - }, + state::BackendData, utils::prelude::SeatExt, + wayland::handlers::compositor::client_compositor_state, }; pub mod backend; @@ -110,30 +106,9 @@ fn main() -> Result<()> { .collect::>() .into_iter() { - let mut scheduled_sessions = state.workspace_session_for_output(&output); - if let Some(sessions) = output.user_data().get::() { - scheduled_sessions - .get_or_insert_with(Vec::new) - .extend(sessions.borrow_mut().drain(..)); - } - state.backend.schedule_render( - &state.common.event_loop_handle, - &output, - scheduled_sessions.as_ref().map(|sessions| { - sessions - .iter() - .filter(|(s, _)| match s.session_type() { - SessionType::Output(o) | SessionType::Workspace(o, _) - if o == output => - { - true - } - _ => false, - }) - .cloned() - .collect::>() - }), - ); + state + .backend + .schedule_render(&state.common.event_loop_handle, &output); } } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 9a884c06..a24594c5 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -12,7 +12,7 @@ use crate::{ iced::{IcedElement, Program}, prelude::*, }, - wayland::handlers::screencopy::ScreencopySessions, + wayland::handlers::screencopy::SessionHolder, }; use calloop::LoopHandle; use cosmic::{ @@ -22,7 +22,6 @@ use cosmic::{ iced_widget::scrollable::AbsoluteOffset, theme, widget as cosmic_widget, Apply, Element as CosmicElement, }; -use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use once_cell::sync::Lazy; use smithay::{ backend::{ @@ -60,6 +59,7 @@ use std::{ atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, Arc, Mutex, }, + time::Duration, }; mod tab; @@ -492,17 +492,28 @@ impl CosmicStack { if previous != active { let windows = p.windows.lock().unwrap(); if let Some(previous) = windows.get(previous) { - if let Some(sessions) = previous.user_data().get::() { - for session in &*sessions.0.borrow() { - session.cursor_leave(seat, InputType::Pointer) - } + for session in previous.cursor_sessions() { + session.set_cursor_pos(None); } PointerTarget::leave(previous, seat, data, serial, time); } - if let Some(sessions) = windows[active].user_data().get::() { - for session in &*sessions.0.borrow() { - session.cursor_enter(seat, InputType::Pointer) + for session in windows[active].cursor_sessions() { + session.set_cursor_pos(Some( + location + .to_buffer( + 1.0, + Transform::Normal, + &windows[active].geometry().size.to_f64(), + ) + .to_i32_round(), + )); + if let Some((_, hotspot)) = + seat.cursor_geometry((0.0, 0.0), Duration::from_millis(time as u64).into()) + { + session.set_cursor_hotspot(hotspot); + } else { + session.set_cursor_hotspot((0, 0)); } } PointerTarget::enter( @@ -1130,12 +1141,6 @@ impl PointerTarget for CosmicStack { let mut event = event.clone(); if self.0.with_program(|p| { let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)]; - if let Some(sessions) = active_window.user_data().get::() { - for session in &*sessions.0.borrow() { - session.cursor_enter(seat, InputType::Pointer) - } - } - let geo = active_window.geometry(); let loc = event.location.to_i32_round::(); let (old_focus, shape) = if loc.y - geo.loc.y < 0 && loc.x - geo.loc.x < 0 { @@ -1179,11 +1184,33 @@ impl PointerTarget for CosmicStack { p.previous_pointer.store(active, Ordering::SeqCst); PointerTarget::enter(active_window, seat, data, &event); + + for session in active_window.cursor_sessions() { + session.set_cursor_pos(Some( + event + .location + .to_buffer(1.0, Transform::Normal, &geo.size.to_f64()) + .to_i32_round(), + )); + if let Some((_, hotspot)) = seat.cursor_geometry( + (0.0, 0.0), + Duration::from_millis(event.time as u64).into(), + ) { + session.set_cursor_hotspot(hotspot); + } else { + session.set_cursor_hotspot((0, 0)); + } + } + return false; }; if old_focus == Focus::Window { PointerTarget::leave(active_window, seat, data, event.serial, event.time); + + for session in active_window.cursor_sessions() { + session.set_cursor_pos(None); + } } let cursor_state = seat.user_data().get::().unwrap(); @@ -1212,17 +1239,6 @@ impl PointerTarget for CosmicStack { let (previous, next) = self.0.with_program(|p| { let active_window = &p.windows.lock().unwrap()[active]; - if let Some(sessions) = active_window.user_data().get::() { - for session in &*sessions.0.borrow() { - let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale - if let Some((geo, hotspot)) = - seat.cursor_geometry(buffer_loc, data.common.clock.now()) - { - session.cursor_info(seat, InputType::Pointer, geo, hotspot); - } - } - } - let geo = active_window.geometry(); let loc = event.location.to_i32_round::(); let (next, shape) = if loc.y - geo.loc.y < 0 && loc.x - geo.loc.x < 0 { @@ -1256,12 +1272,33 @@ impl PointerTarget for CosmicStack { PointerTarget::motion(active_window, seat, data, &event); } + for session in active_window.cursor_sessions() { + session.set_cursor_pos(Some( + event + .location + .to_buffer(1.0, Transform::Normal, &geo.size.to_f64()) + .to_i32_round(), + )); + if let Some((_, hotspot)) = seat.cursor_geometry( + (0.0, 0.0), + Duration::from_millis(event.time as u64).into(), + ) { + session.set_cursor_hotspot(hotspot); + } else { + session.set_cursor_hotspot((0, 0)); + } + } + return (previous, Focus::Window); }; let previous = p.swap_focus(next); if previous == Focus::Window { PointerTarget::leave(active_window, seat, data, event.serial, event.time); + + for session in active_window.cursor_sessions() { + session.set_cursor_pos(None); + } } let cursor_state = seat.user_data().get::().unwrap(); @@ -1477,13 +1514,10 @@ impl PointerTarget for CosmicStack { } let previous = self.0.with_program(|p| { - if let Some(sessions) = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)] - .user_data() - .get::() + for session in + p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].cursor_sessions() { - for session in &*sessions.0.borrow() { - session.cursor_leave(seat, InputType::Pointer) - } + session.set_cursor_pos(None); } let cursor_state = seat.user_data().get::().unwrap(); diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 46915f68..b89232fb 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -9,11 +9,10 @@ use crate::{ iced::{IcedElement, Program}, prelude::*, }, - wayland::handlers::screencopy::ScreencopySessions, + wayland::handlers::screencopy::SessionHolder, }; use calloop::LoopHandle; use cosmic::{iced::Command, widget::mouse_area, Apply}; -use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::{ input::KeyState, @@ -39,7 +38,7 @@ use smithay::{ output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface, render_elements, - utils::{Buffer as BufferCoords, IsAlive, Logical, Point, Rectangle, Serial, Size}, + utils::{Buffer as BufferCoords, IsAlive, Logical, Point, Rectangle, Serial, Size, Transform}, wayland::seat::WaylandFocus, }; use std::{ @@ -50,6 +49,7 @@ use std::{ atomic::{AtomicBool, AtomicU8, Ordering}, Arc, Mutex, }, + time::Duration, }; use wayland_backend::server::ObjectId; @@ -573,12 +573,6 @@ impl PointerTarget for CosmicWindow { fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { let mut event = event.clone(); if self.0.with_program(|p| { - if let Some(sessions) = p.window.user_data().get::() { - for session in &*sessions.0.borrow() { - session.cursor_enter(seat, InputType::Pointer) - } - } - if p.has_ssd(false) { let geo = p.window.geometry(); let loc = event.location.to_i32_round::(); @@ -621,6 +615,27 @@ impl PointerTarget for CosmicWindow { let mut event = event.clone(); event.location.y -= SSD_HEIGHT as f64; PointerTarget::enter(&p.window, seat, data, &event); + + for session in p.window.cursor_sessions() { + session.set_cursor_pos(Some( + event + .location + .to_buffer( + 1.0, + Transform::Normal, + &p.window.geometry().size.to_f64(), + ) + .to_i32_round(), + )); + if let Some((_, hotspot)) = seat.cursor_geometry( + (0.0, 0.0), + Duration::from_millis(event.time as u64).into(), + ) { + session.set_cursor_hotspot(hotspot); + } else { + session.set_cursor_hotspot((0, 0)); + } + } return false; }; @@ -637,6 +652,22 @@ impl PointerTarget for CosmicWindow { } else { p.swap_focus(Focus::Window); PointerTarget::enter(&p.window, seat, data, &event); + for session in p.window.cursor_sessions() { + session.set_cursor_pos(Some( + event + .location + .to_buffer(1.0, Transform::Normal, &p.window.geometry().size.to_f64()) + .to_i32_round(), + )); + if let Some((_, hotspot)) = seat.cursor_geometry( + (0.0, 0.0), + Duration::from_millis(event.time as u64).into(), + ) { + session.set_cursor_hotspot(hotspot); + } else { + session.set_cursor_hotspot((0, 0)); + } + } false } }) { @@ -648,17 +679,6 @@ impl PointerTarget for CosmicWindow { fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { let mut event = event.clone(); if let Some((previous, next)) = self.0.with_program(|p| { - if let Some(sessions) = p.window.user_data().get::() { - for session in &*sessions.0.borrow() { - let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale - if let Some((geo, hotspot)) = - seat.cursor_geometry(buffer_loc, data.common.clock.now()) - { - session.cursor_info(seat, InputType::Pointer, geo, hotspot); - } - } - } - if p.has_ssd(false) { let geo = p.window.geometry(); let loc = event.location.to_i32_round::(); @@ -690,6 +710,27 @@ impl PointerTarget for CosmicWindow { PointerTarget::motion(&p.window, seat, data, &event); } + for session in p.window.cursor_sessions() { + session.set_cursor_pos(Some( + event + .location + .to_buffer( + 1.0, + Transform::Normal, + &p.window.geometry().size.to_f64(), + ) + .to_i32_round(), + )); + if let Some((_, hotspot)) = seat.cursor_geometry( + (0.0, 0.0), + Duration::from_millis(event.time as u64).into(), + ) { + session.set_cursor_hotspot(hotspot); + } else { + session.set_cursor_hotspot((0, 0)); + } + } + return Some((previous, Focus::Window)); }; @@ -710,6 +751,24 @@ impl PointerTarget for CosmicWindow { } else { p.swap_focus(Focus::Window); PointerTarget::motion(&p.window, seat, data, &event); + + for session in p.window.cursor_sessions() { + session.set_cursor_pos(Some( + event + .location + .to_buffer(1.0, Transform::Normal, &p.window.geometry().size.to_f64()) + .to_i32_round(), + )); + if let Some((_, hotspot)) = seat.cursor_geometry( + (0.0, 0.0), + Duration::from_millis(event.time as u64).into(), + ) { + session.set_cursor_hotspot(hotspot); + } else { + session.set_cursor_hotspot((0, 0)); + } + } + None } }) { @@ -798,10 +857,8 @@ impl PointerTarget for CosmicWindow { fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { let previous = self.0.with_program(|p| { - if let Some(sessions) = p.window.user_data().get::() { - for session in &*sessions.0.borrow() { - session.cursor_leave(seat, InputType::Pointer) - } + for session in p.window.cursor_sessions() { + session.set_cursor_pos(None); } let cursor_state = seat.user_data().get::().unwrap(); diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 13e85dbb..c4567cde 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -10,9 +10,8 @@ use crate::{ state::State, utils::{prelude::*, tween::EaseRectangle}, wayland::{ - handlers::screencopy::DropableSession, + handlers::screencopy::ScreencopySessions, protocols::{ - screencopy::{BufferParams, Session as ScreencopySession}, toplevel_info::ToplevelInfoState, workspace::{WorkspaceHandle, WorkspaceUpdateGuard}, }, @@ -84,8 +83,7 @@ pub struct Workspace { pub handle: WorkspaceHandle, pub focus_stack: FocusStacks, - pub pending_buffers: Vec<(ScreencopySession, BufferParams)>, - pub screencopy_sessions: Vec, + pub screencopy: ScreencopySessions, pub output_stack: VecDeque, pub pending_tokens: HashSet, pub(super) backdrop_id: Id, @@ -273,8 +271,7 @@ impl Workspace { fullscreen: None, handle, focus_stack: FocusStacks::default(), - pending_buffers: Vec::new(), - screencopy_sessions: Vec::new(), + screencopy: ScreencopySessions::default(), output_stack: { let mut queue = VecDeque::new(); queue.push_back(output_name); diff --git a/src/state.rs b/src/state.rs index 1a492f0f..7b763cda 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,14 +7,12 @@ use crate::{ shell::{grabs::SeatMoveGrabState, Shell}, utils::prelude::*, wayland::protocols::{ - drm::WlDrmState, - output_configuration::OutputConfigurationState, - screencopy::{BufferParams, ScreencopyState, Session as ScreencopySession}, + drm::WlDrmState, image_source::ImageSourceState, + output_configuration::OutputConfigurationState, screencopy::ScreencopyState, workspace::WorkspaceClientState, }, }; use anyhow::Context; -use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode; use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, DesktopLanguageRequester, @@ -176,6 +174,7 @@ pub struct Common { pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, pub data_control_state: Option, + pub image_source_state: ImageSourceState, pub screencopy_state: ScreencopyState, pub seat_state: SeatState, pub session_lock_manager_state: SessionLockManagerState, @@ -269,19 +268,14 @@ impl BackendData { result } - pub fn schedule_render( - &mut self, - loop_handle: &LoopHandle<'_, State>, - output: &Output, - screencopy: Option>, - ) { + pub fn schedule_render(&mut self, loop_handle: &LoopHandle<'_, State>, output: &Output) { match self { - BackendData::Winit(ref mut state) => state.pending_screencopy(screencopy), // We cannot do this on the winit backend. + BackendData::Winit(_) => {} // We cannot do this on the winit backend. // Winit has a very strict render-loop and skipping frames breaks atleast the wayland winit-backend. // Swapping with damage (which should be empty on these frames) is likely good enough anyway. - BackendData::X11(ref mut state) => state.schedule_render(output, screencopy), + BackendData::X11(ref mut state) => state.schedule_render(output), BackendData::Kms(ref mut state) => { - if let Err(err) = state.schedule_render(loop_handle, output, None, screencopy) { + if let Err(err) = state.schedule_render(loop_handle, output, None) { error!(?err, "Failed to schedule event, are we shutting down?"); } } @@ -366,11 +360,10 @@ impl State { OutputConfigurationState::new(dh, client_should_see_privileged_protocols); let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh); - let screencopy_state = ScreencopyState::new::( - dh, - vec![CursorMode::Embedded, CursorMode::Hidden], - client_should_see_privileged_protocols, - ); + let image_source_state = + ImageSourceState::new::(dh, client_should_see_privileged_protocols); + let screencopy_state = + ScreencopyState::new::(dh, client_should_see_privileged_protocols); let shm_state = ShmState::new::(dh, vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888]); let seat_state = SeatState::::new(); @@ -431,6 +424,7 @@ impl State { data_device_state, dmabuf_state, fractional_scale_state, + image_source_state, screencopy_state, shm_state, seat_state, @@ -880,7 +874,6 @@ struct PendingFrame { start: Instant, duration_elements: Option, duration_render: Option, - duration_screencopy: Option, duration_displayed: Option, } @@ -889,7 +882,6 @@ pub struct Frame { pub start: Instant, pub duration_elements: Duration, pub duration_render: Duration, - pub duration_screencopy: Option, pub duration_displayed: Duration, } @@ -899,16 +891,11 @@ impl Frame { } fn frame_time(&self) -> Duration { - self.duration_elements - + self.duration_render - + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) + self.duration_elements + self.duration_render } fn time_to_display(&self) -> Duration { - self.duration_elements - + self.duration_render - + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) - + self.duration_displayed + self.duration_elements + self.duration_render + self.duration_displayed } } @@ -918,7 +905,6 @@ impl From for Frame { start: pending.start, duration_elements: pending.duration_elements.unwrap_or(Duration::ZERO), duration_render: pending.duration_render.unwrap_or(Duration::ZERO), - duration_screencopy: pending.duration_screencopy, duration_displayed: pending.duration_displayed.unwrap_or(Duration::ZERO), } } @@ -932,7 +918,6 @@ impl Fps { start: Instant::now(), duration_elements: None, duration_render: None, - duration_screencopy: None, duration_displayed: None, }); } @@ -952,23 +937,12 @@ impl Fps { } } - pub fn screencopy(&mut self) { - if let Some(frame) = self.pending_frame.as_mut() { - frame.duration_screencopy = Some( - Instant::now().duration_since(frame.start) - - frame.duration_elements.clone().unwrap_or(Duration::ZERO) - - frame.duration_render.clone().unwrap_or(Duration::ZERO), - ); - } - } - pub fn displayed(&mut self) { if let Some(mut frame) = self.pending_frame.take() { frame.duration_displayed = Some( Instant::now().duration_since(frame.start) - frame.duration_elements.clone().unwrap_or(Duration::ZERO) - - frame.duration_render.clone().unwrap_or(Duration::ZERO) - - frame.duration_screencopy.clone().unwrap_or(Duration::ZERO), + - frame.duration_render.clone().unwrap_or(Duration::ZERO), ); self.frames.push_back(frame.into()); diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 68669a08..585c3ca1 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{state::ClientState, utils::prelude::*, wayland::protocols::screencopy::SessionType}; +use crate::{state::ClientState, utils::prelude::*}; use calloop::Interest; use smithay::{ backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state}, @@ -25,8 +25,6 @@ use smithay::{ }; use std::sync::Mutex; -use super::screencopy::PendingScreencopyBuffers; - impl State { fn toplevel_ensure_initial_configure(&mut self, toplevel: &ToplevelSurface) -> bool { // send the initial configure if relevant @@ -197,9 +195,6 @@ impl CompositorHandler for State { } } - //handle window screencopy sessions - self.schedule_window_session(surface); - // and refresh smithays internal state self.common.shell.popups.commit(surface); @@ -221,34 +216,10 @@ impl CompositorHandler for State { } } - let mut scheduled_sessions = self.schedule_workspace_sessions(surface); - // schedule a new render if let Some(output) = self.common.shell.visible_output_for_surface(surface) { - if let Some(sessions) = output.user_data().get::() { - scheduled_sessions - .get_or_insert_with(Vec::new) - .extend(sessions.borrow_mut().drain(..)); - } - - self.backend.schedule_render( - &self.common.event_loop_handle, - &output, - scheduled_sessions.as_ref().map(|sessions| { - sessions - .iter() - .filter(|(s, _)| match s.session_type() { - SessionType::Output(o) | SessionType::Workspace(o, _) - if &o == output => - { - true - } - _ => false, - }) - .cloned() - .collect::>() - }), - ); + self.backend + .schedule_render(&self.common.event_loop_handle, &output); } } } diff --git a/src/wayland/handlers/image_source.rs b/src/wayland/handlers/image_source.rs new file mode 100644 index 00000000..fef8c406 --- /dev/null +++ b/src/wayland/handlers/image_source.rs @@ -0,0 +1,4 @@ +use crate::state::State; +use crate::wayland::protocols::image_source::delegate_image_source; + +delegate_image_source!(State); diff --git a/src/wayland/handlers/layer_shell.rs b/src/wayland/handlers/layer_shell.rs index 957a732b..22808e58 100644 --- a/src/wayland/handlers/layer_shell.rs +++ b/src/wayland/handlers/layer_shell.rs @@ -14,8 +14,6 @@ use smithay::{ }, }; -use super::screencopy::PendingScreencopyBuffers; - impl WlrLayerShellHandler for State { fn shell_state(&mut self) -> &mut WlrLayerShellState { &mut self.common.shell.layer_shell_state @@ -76,19 +74,8 @@ impl WlrLayerShellHandler for State { self.common.shell.workspaces.recalculate(); - // collect screencopy sessions needing an update - let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface()); - if let Some(sessions) = output.user_data().get::() { - scheduled_sessions - .get_or_insert_with(Vec::new) - .extend(sessions.borrow_mut().drain(..)); - } - - self.backend.schedule_render( - &self.common.event_loop_handle, - &output, - scheduled_sessions, - ); + self.backend + .schedule_render(&self.common.event_loop_handle, &output); } } } diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index cf1c4ab5..a5c02584 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -9,6 +9,7 @@ pub mod dmabuf; pub mod drm; pub mod drm_lease; pub mod fractional_scale; +pub mod image_source; pub mod input_method; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs deleted file mode 100644 index b2ee3248..00000000 --- a/src/wayland/handlers/screencopy.rs +++ /dev/null @@ -1,1336 +0,0 @@ -use std::{ - borrow::Borrow, - cell::RefCell, - collections::HashSet, - ops::{Deref, DerefMut}, -}; - -use anyhow::anyhow; -use calloop::LoopHandle; -use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::{ - FailureReason, InputType, -}; -use smithay::{ - backend::{ - allocator::{dmabuf::Dmabuf, format::get_transparent}, - drm::DrmNode, - egl::EGLDevice, - renderer::{ - buffer_dimensions, buffer_type, - damage::{Error as DTError, OutputDamageTracker, RenderOutputResult}, - element::{surface::WaylandSurfaceRenderElement, AsRenderElements, RenderElement}, - gles::{Capability, GlesError, GlesRenderbuffer, GlesRenderer}, - Bind, Blit, BufferType, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, - }, - }, - desktop::{layer_map_for_output, space::SpaceElement}, - output::{Output, OutputNoMode}, - reexports::wayland_server::{ - protocol::{wl_buffer::WlBuffer, wl_shm::Format as ShmFormat, wl_surface::WlSurface}, - Resource, - }, - utils::{IsAlive, Logical, Physical, Rectangle, Scale, Transform}, - wayland::{ - dmabuf::get_dmabuf, - seat::WaylandFocus, - shm::{shm_format_to_fourcc, with_buffer_contents, with_buffer_contents_mut}, - }, - xwayland::XWaylandClientData, -}; -use tracing::warn; - -use crate::{ - backend::render::{ - cursor, - element::{AsGlowRenderer, CosmicElement}, - render_output, render_workspace, CursorMode, CLEAR_COLOR, - }, - shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, - state::{BackendData, ClientState, Common, State}, - utils::prelude::{OutputExt, PointExt}, - wayland::protocols::{ - screencopy::{ - delegate_screencopy, BufferInfo, BufferParams, CursorMode as ScreencopyCursorMode, - CursorSession, ScreencopyHandler, Session, SessionType, - }, - workspace::WorkspaceHandle, - }, -}; - -use super::data_device::get_dnd_icon; - -pub const WORKSPACE_OVERVIEW_NAMESPACE: &str = "cosmic-workspace-overview"; - -pub type PendingScreencopyBuffers = RefCell>; - -#[derive(Debug, Default)] -pub struct ScreencopySessions(pub RefCell>); - -#[derive(Debug)] -pub struct DropableSession(Session, FailureReason); -impl Deref for DropableSession { - type Target = Session; - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl DerefMut for DropableSession { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} -impl Drop for DropableSession { - fn drop(&mut self) { - self.0.failed(self.1); - } -} -impl PartialEq for DropableSession { - fn eq(&self, other: &Session) -> bool { - &self.0 == other - } -} - -pub type SessionDT = RefCell; - -impl ScreencopyHandler for State { - fn capture_output(&mut self, output: Output, session: Session) -> Vec { - let formats = match formats_for_output(&output, &mut self.backend) { - Ok(formats) => formats, - Err(reason) => { - session.failed(reason); - return Vec::new(); - } - }; - - for seat in self.common.seats() { - if let Some(pointer) = seat.get_pointer() { - if output - .geometry() - .contains(pointer.current_location().to_i32_round().as_global()) - { - session.cursor_enter(seat, InputType::Pointer); - } - } - } - - session - .user_data() - .insert_if_missing(|| SessionDT::new(OutputDamageTracker::from_output(&output))); - output - .user_data() - .insert_if_missing(ScreencopySessions::default); - output - .user_data() - .get::() - .unwrap() - .0 - .borrow_mut() - .push(DropableSession(session, FailureReason::InvalidOutput)); - - formats - } - - fn capture_workspace( - &mut self, - handle: WorkspaceHandle, - output: Output, - session: Session, - ) -> Vec { - let formats = match formats_for_output(&output, &mut self.backend) { - Ok(formats) => formats, - Err(reason) => { - session.failed(reason); - return Vec::new(); - } - }; - - let workspace = match self.common.shell.workspaces.space_for_handle_mut(&handle) { - Some(workspace) => workspace, - None => { - session.failed(FailureReason::InvalidWorkspace); - return Vec::new(); - } - }; - - session - .user_data() - .insert_if_missing(|| SessionDT::new(OutputDamageTracker::from_output(&output))); - - workspace - .screencopy_sessions - .push(DropableSession(session, FailureReason::InvalidWorkspace)); - - formats - } - - fn capture_toplevel(&mut self, toplevel: CosmicSurface, session: Session) -> Vec { - let Some(surface) = toplevel.wl_surface() else { - return Vec::new(); - }; - let size = toplevel.geometry().size.to_buffer(1, Transform::Normal); - - let mut _kms_renderer = None; - let renderer = match self.backend { - BackendData::Kms(ref mut kms) => { - let node = self - .common - .display_handle - .get_client(surface.id()) - .ok() - .and_then(|client| { - // Lets check the global drm-node the client got either through default-feedback or wl_drm - if let Some(normal_client) = client.get_data::() { - return normal_client.advertised_drm_node.clone(); - } - // last but not least all xwayland-surfaces should also share a single node - if let Some(xwayland_client) = client.get_data::() { - return xwayland_client.user_data().get::().cloned(); - } - None - }) - .unwrap_or(kms.primary_node.clone()); - _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); - _kms_renderer.as_mut().unwrap().as_mut() - } - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - _ => unreachable!(), - }; - - let mut formats = vec![ - BufferInfo::Shm { - format: ShmFormat::Abgr8888, - size, - stride: size.w as u32 * 4, - }, - BufferInfo::Shm { - format: ShmFormat::Xbgr8888, - size, - stride: size.w as u32 * 4, - }, - ]; - if (renderer as &dyn Borrow) - .borrow() - .capabilities() - .contains(&Capability::ColorTransformations) - { - formats.extend([ - BufferInfo::Shm { - format: ShmFormat::Abgr2101010, - size, - stride: size.w as u32 * 4, - }, - BufferInfo::Shm { - format: ShmFormat::Xbgr2101010, - size, - stride: size.w as u32 * 4, - }, - ]); - } - - if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display()) - .ok() - .and_then(|device| device.try_get_render_node().ok().flatten()) - { - formats.extend( - renderer - .egl_context() - .dmabuf_render_formats() - .iter() - .map(|format| format.code) - .collect::>() - .into_iter() - .map(|format| BufferInfo::Dmabuf { node, format, size }), - ); - } - - let size = toplevel.geometry().size.to_physical(1); - session.user_data().insert_if_missing(|| { - SessionDT::new(OutputDamageTracker::new(size, 1.0, Transform::Normal)) - }); - toplevel - .user_data() - .insert_if_missing(ScreencopySessions::default); - toplevel - .user_data() - .get::() - .unwrap() - .0 - .borrow_mut() - .push(DropableSession(session, FailureReason::InvalidToplevel)); - - formats - } - - fn capture_cursor(&mut self, _session: CursorSession) -> Vec { - unimplemented!("We don't advertise the capture cursor mode") - } - - fn buffer_attached(&mut self, session: Session, params: BufferParams, on_damage: bool) { - // verify buffer size - let buffer_size = match buffer_dimensions(¶ms.buffer) { - Some(size) => size.to_logical(1, Transform::Normal), - None => { - warn!("Error during screencopy session: Buffer has no size"); - session.failed(FailureReason::InvalidBuffer); - return; - } - }; - match session.session_type() { - SessionType::Output(output) | SessionType::Workspace(output, _) => { - let mode = match output.current_mode() { - Some(mode) => mode, - None => { - warn!("Error during screencopy session: Output has no mode"); - session.failed(FailureReason::InvalidOutput); - return; - } - } - .size; - - if buffer_size.to_physical(1) != mode { - warn!("Error during screencopy session: Buffer size doesn't match"); - session.failed(FailureReason::InvalidSize); - return; - } - } - SessionType::Window(window) => { - let geometry = window.geometry(); - if buffer_size != geometry.size { - warn!("Error during screencopy session: Buffer size doesn't match"); - session.failed(FailureReason::InvalidSize); - return; - } - } - _ => {} - }; - - if !matches!( - buffer_type(¶ms.buffer), - Some(BufferType::Shm) | Some(BufferType::Dma) - ) { - warn!("Error during screencopy session: Buffer is neither shm or dma"); - session.failed(FailureReason::InvalidBuffer); - return; - } - - if let Some(BufferType::Shm) = buffer_type(¶ms.buffer) { - if with_buffer_contents(¶ms.buffer, |_, _, info| { - info.format != ShmFormat::Abgr8888 - && info.format != ShmFormat::Xbgr8888 - && info.format != ShmFormat::Abgr2101010 - && info.format != ShmFormat::Xbgr2101010 - }) - .unwrap() - { - warn!("Error during screencopy session: Invalid shm buffer format"); - session.failed(FailureReason::InvalidBuffer); - return; - } - } - - if on_damage { - match session.session_type() { - SessionType::Output(output) => { - output - .user_data() - .insert_if_missing(PendingScreencopyBuffers::default); - output - .user_data() - .get::() - .unwrap() - .borrow_mut() - .push((session, params)); - } - SessionType::Workspace(_output, handle) => { - match self.common.shell.workspaces.space_for_handle_mut(&handle) { - Some(workspace) => workspace.pending_buffers.push((session, params)), - None => session.failed(FailureReason::InvalidWorkspace), - }; - } - SessionType::Window(window) => { - window - .user_data() - .insert_if_missing(PendingScreencopyBuffers::default); - window - .user_data() - .get::() - .unwrap() - .borrow_mut() - .push((session, params)); - } - _ => unreachable!(), - }; - } else { - let buffer = params.buffer.clone(); - let result = match session.session_type() { - SessionType::Output(output) => { - render_output_to_buffer(self, &session, params, &output) - } - SessionType::Workspace(output, handle) => { - render_workspace_to_buffer( - self, - &session, - params, - &output, - (handle, 0), /* TODO: hack, we should have the index */ - ) - } - SessionType::Window(window) => { - render_window_to_buffer(self, &session, params, &window) - } - _ => unreachable!("Session types not supported"), - }; - - match result { - Ok(false) => { - // client didn't wanna wait for damage, so it gets empty damage - session.commit_buffer( - match session.session_type() { - SessionType::Output(output) | SessionType::Workspace(output, _) => { - output.current_transform() - } - _ => Transform::Normal, - }, - Vec::new(), - None, - ); - buffer.release(); - } - Ok(true) => {} // success - Err((reason, err)) => { - warn!(?err, "Error during screencopy session"); - session.failed(reason); - } - } - } - } - - fn cursor_session_destroyed(&mut self, _session: CursorSession) { - unreachable!("We currently don't support cursor sessions"); - } - - fn session_destroyed(&mut self, session: Session) { - match session.session_type() { - SessionType::Output(output) => { - if let Some(pending_buffers) = output.user_data().get::() - { - pending_buffers.borrow_mut().retain(|(s, _)| s != &session); - } - if let Some(sessions) = output.user_data().get::() { - sessions.0.borrow_mut().retain(|s| s != &session); - } - } - SessionType::Workspace(_, handle) => { - if let Some(workspace) = self.common.shell.workspaces.space_for_handle_mut(&handle) - { - workspace.pending_buffers.retain(|(s, _)| s != &session); - workspace.screencopy_sessions.retain(|s| s != &session); - } - } - SessionType::Window(window) => { - if let Some(pending_buffers) = window.user_data().get::() - { - pending_buffers.borrow_mut().retain(|(s, _)| s != &session); - } - if let Some(sessions) = window.user_data().get::() { - sessions.0.borrow_mut().retain(|s| s != &session); - } - } - _ => {} - } - } -} - -fn formats_for_output( - output: &Output, - backend: &mut BackendData, -) -> Result, FailureReason> { - let mode = match output.current_mode() { - Some(mode) => mode.size.to_logical(1).to_buffer(1, Transform::Normal), - None => { - return Err(FailureReason::InvalidOutput); - } - }; - - let mut _kms_renderer = None; - let renderer = match backend { - BackendData::Kms(ref mut kms) => { - let node = kms - .target_node_for_output(&output) - .unwrap_or(kms.primary_node); - _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); - _kms_renderer.as_mut().unwrap().as_mut() - } - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - _ => unreachable!(), - }; - - let mut formats = vec![ - BufferInfo::Shm { - format: ShmFormat::Abgr8888, - size: mode, - stride: mode.w as u32 * 4, - }, - BufferInfo::Shm { - format: ShmFormat::Xbgr8888, - size: mode, - stride: mode.w as u32 * 4, - }, - ]; - if (renderer as &dyn Borrow) - .borrow() - .capabilities() - .contains(&Capability::ColorTransformations) - { - formats.extend([ - BufferInfo::Shm { - format: ShmFormat::Abgr2101010, - size: mode, - stride: mode.w as u32 * 4, - }, - BufferInfo::Shm { - format: ShmFormat::Xbgr2101010, - size: mode, - stride: mode.w as u32 * 4, - }, - ]); - } - - if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display()) - .ok() - .and_then(|device| device.try_get_render_node().ok().flatten()) - { - formats.extend( - renderer - .egl_context() - .dmabuf_render_formats() - .iter() - .map(|format| format.code) - .collect::>() - .into_iter() - .map(|format| BufferInfo::Dmabuf { - node, - format, - size: mode, - }), - ); - } - - Ok(formats) -} - -fn node_from_params( - params: &BufferParams, - backend: &BackendData, - output: Option<&Output>, -) -> Option { - match buffer_type(¶ms.buffer) { - Some(BufferType::Dma) if params.node.is_some() => params.node.clone(), - Some(BufferType::Shm) | Some(BufferType::Dma) => match backend { - BackendData::Kms(kms) => Some( - output - .and_then(|output| kms.target_node_for_output(output)) - .unwrap_or(kms.primary_node), - ), - _ => None, - }, - _ => unreachable!(), - } -} - -fn submit_buffer( - session: &Session, - buffer: &WlBuffer, - renderer: &mut R, - transform: Transform, - damage: Vec>, -) -> Result<(), ::Error> -where - R: ExportMem, -{ - if matches!(buffer_type(buffer), Some(BufferType::Shm)) { - let buffer_size = buffer_dimensions(buffer).unwrap(); - with_buffer_contents_mut(buffer, |ptr, len, data| { - let offset = data.offset as i32; - let width = data.width as i32; - let height = data.height as i32; - let stride = data.stride as i32; - let format = shm_format_to_fourcc(data.format) - .expect("We should be able to convert all hardcoded shm screencopy formats"); - - // number of bytes per pixel - // TODO: compute from data.format - let pixelsize = 4i32; - - // ensure consistency, the SHM handler of smithay should ensure this - assert!((offset + (height - 1) * stride + width * pixelsize) as usize <= len); - - let format = get_transparent(format).unwrap_or(format); - let mapping = renderer - .copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size), format)?; - let gl_data = renderer.map_texture(&mapping)?; - assert!((width * height * pixelsize) as usize <= gl_data.len()); - - for i in 0..height { - unsafe { - std::ptr::copy_nonoverlapping::( - gl_data.as_ptr().offset((width * pixelsize * i) as isize), - ptr.offset((offset + stride * i) as isize), - (width * pixelsize) as usize, - ); - } - } - Ok(()) - }) - .unwrap()?; - } - - session.commit_buffer(transform, damage, None); - buffer.release(); - - Ok(()) -} - -pub fn render_session( - node: Option, - renderer: &mut R, - session: &Session, - params: &BufferParams, - transform: Transform, - render_fn: F, -) -> Result> -where - R: ExportMem, - F: FnOnce( - Option<&DrmNode>, - &WlBuffer, - &mut R, - &mut OutputDamageTracker, - usize, - ) -> Result>, -{ - #[cfg(feature = "debug")] - puffin::profile_function!(); - - let mut dt = session.user_data().get::().unwrap().borrow_mut(); - - let res = render_fn( - node.as_ref(), - ¶ms.buffer, - renderer, - &mut *dt, - params.age as usize, - )?; - - if let RenderOutputResult { - damage: Some(damage), - .. - } = res - { - submit_buffer(session, ¶ms.buffer, renderer, transform, damage) - .map_err(DTError::Rendering)?; - Ok(true) - } else { - Ok(false) - } -} - -pub fn render_output_to_buffer( - state: &mut State, - session: &Session, - params: BufferParams, - output: &Output, -) -> Result { - let mode = output - .current_mode() - .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal)); - let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); - if mode != Some(buffer_size) { - return Err((FailureReason::InvalidSize, anyhow!("Output changed mode"))); - } - - fn render_fn( - node: Option<&DrmNode>, - buffer: &WlBuffer, - renderer: &mut R, - dt: &mut OutputDamageTracker, - age: usize, - common: &mut Common, - session: &Session, - output: &Output, - ) -> Result> - where - R: Renderer - + ImportAll - + ImportMem - + ExportMem - + Bind - + Offscreen - + Blit - + AsGlowRenderer, - ::TextureId: Clone + 'static, - ::Error: From, - CosmicElement: RenderElement, - CosmicMappedRenderElement: RenderElement, - WorkspaceRenderElement: RenderElement, - { - let cursor_mode = match session.cursor_mode() { - ScreencopyCursorMode::Embedded => CursorMode::All, - ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => CursorMode::None, - }; - - if let Ok(dmabuf) = get_dmabuf(buffer) { - render_output::<_, _, GlesRenderbuffer, Dmabuf>( - node, - renderer, - dmabuf, - dt, - age, - common, - &output, - cursor_mode, - None, - None, - ) - } else { - let size = buffer_dimensions(buffer).unwrap(); - let format = - with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) - .map_err(|_| DTError::OutputNoMode(OutputNoMode))? // eh, we have to do some error - .expect("We should be able to convert all hardcoded shm screencopy formats"); - let render_buffer = - Offscreen::::create_buffer(renderer, format, size) - .map_err(DTError::Rendering)?; - render_output::<_, _, GlesRenderbuffer, Dmabuf>( - node, - renderer, - render_buffer, - dt, - age, - common, - &output, - cursor_mode, - None, - None, - ) - } - } - - let common = &mut state.common; - let node = node_from_params(¶ms, &mut state.backend, Some(output)); - match &mut state.backend { - BackendData::Kms(kms) => { - let mut multirenderer = kms - .api - .single_renderer(node.as_ref().unwrap()) - .map_err(|err| (FailureReason::Unspec, err.into()))?; - render_session::<_, _>( - node, - &mut multirenderer, - session, - ¶ms, - output.current_transform(), - |node, buffer, renderer, dt, age| { - render_fn(node, buffer, renderer, dt, age, common, session, output) - }, - ) - .map_err(|err| match err { - DTError::OutputNoMode(x) => (FailureReason::Unspec, x.into()), - DTError::Rendering(x) => (FailureReason::Unspec, x.into()), - }) - } - BackendData::Winit(winit) => render_session::<_, _>( - node, - winit.backend.renderer(), - session, - ¶ms, - output.current_transform(), - |node, buffer, renderer, dt, age| { - render_fn(node, buffer, renderer, dt, age, common, session, output) - }, - ) - .map_err(|err| (FailureReason::Unspec, err.into())), - BackendData::X11(x11) => render_session::<_, _>( - node, - &mut x11.renderer, - session, - ¶ms, - output.current_transform(), - |node, buffer, renderer, dt, age| { - render_fn(node, buffer, renderer, dt, age, common, session, output) - }, - ) - .map_err(|err| (FailureReason::Unspec, err.into())), - _ => unreachable!(), - } -} - -pub fn render_workspace_to_buffer( - state: &mut State, - session: &Session, - params: BufferParams, - output: &Output, - handle: (WorkspaceHandle, usize), -) -> Result { - let mode = output - .current_mode() - .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal)); - let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); - if mode != Some(buffer_size) { - return Err((FailureReason::InvalidSize, anyhow!("Output changed mode"))); - } - - fn render_fn( - node: Option<&DrmNode>, - buffer: &WlBuffer, - renderer: &mut R, - dt: &mut OutputDamageTracker, - age: usize, - common: &mut Common, - session: &Session, - output: &Output, - handle: (WorkspaceHandle, usize), - ) -> Result> - where - R: Renderer - + ImportAll - + ImportMem - + ExportMem - + Bind - + Offscreen - + Blit - + AsGlowRenderer, - ::TextureId: Clone + 'static, - ::Error: From, - CosmicElement: RenderElement, - CosmicMappedRenderElement: RenderElement, - WorkspaceRenderElement: RenderElement, - { - let cursor_mode = match session.cursor_mode() { - ScreencopyCursorMode::Embedded => CursorMode::All, - ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => CursorMode::None, - }; - if let Ok(dmabuf) = get_dmabuf(buffer) { - render_workspace::<_, _, GlesRenderbuffer, Dmabuf>( - node, - renderer, - dmabuf, - dt, - age, - common, - &output, - None, - handle, - cursor_mode, - None, - None, - true, - ) - } else { - let size = buffer_dimensions(buffer).unwrap(); - let format = - with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) - .map_err(|_| DTError::OutputNoMode(OutputNoMode))? // eh, we have to do some error - .expect("We should be able to convert all hardcoded shm screencopy formats"); - let render_buffer = - Offscreen::::create_buffer(renderer, format, size) - .map_err(DTError::Rendering)?; - render_workspace::<_, _, GlesRenderbuffer, Dmabuf>( - node, - renderer, - render_buffer, - dt, - age, - common, - &output, - None, - handle, - cursor_mode, - None, - None, - true, - ) - } - } - - let node = node_from_params(¶ms, &mut state.backend, Some(output)); - let common = &mut state.common; - match &mut state.backend { - BackendData::Kms(kms) => { - let mut multirenderer = kms - .api - .single_renderer(node.as_ref().unwrap()) - .map_err(|err| (FailureReason::Unspec, err.into()))?; - render_session::<_, _>( - node, - &mut multirenderer, - session, - ¶ms, - output.current_transform(), - |node, buffer, renderer, dt, age| { - render_fn( - node, buffer, renderer, dt, age, common, session, output, handle, - ) - }, - ) - .map_err(|err| match err { - DTError::OutputNoMode(x) => (FailureReason::Unspec, x.into()), - DTError::Rendering(x) => (FailureReason::Unspec, x.into()), - }) - } - BackendData::Winit(winit) => render_session::<_, _>( - node, - winit.backend.renderer(), - session, - ¶ms, - output.current_transform(), - |node, buffer, renderer, dt, age| { - render_fn( - node, buffer, renderer, dt, age, common, session, output, handle, - ) - }, - ) - .map_err(|err| (FailureReason::Unspec, err.into())), - BackendData::X11(x11) => render_session::<_, _>( - node, - &mut x11.renderer, - session, - ¶ms, - output.current_transform(), - |node, buffer, renderer, dt, age| { - render_fn( - node, buffer, renderer, dt, age, common, session, output, handle, - ) - }, - ) - .map_err(|err| (FailureReason::Unspec, err.into())), - _ => unreachable!(), - } -} - -smithay::render_elements! { - pub WindowCaptureElement where R: ImportAll + ImportMem; - WaylandElement=WaylandSurfaceRenderElement, - CursorElement=cursor::CursorRenderElement, -} - -pub fn render_window_to_buffer( - state: &mut State, - session: &Session, - params: BufferParams, - window: &CosmicSurface, -) -> Result { - #[cfg(feature = "debug")] - puffin::profile_function!(); - - let geometry = window.geometry(); - let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); - if buffer_size != geometry.size.to_buffer(1, Transform::Normal) { - return Err((FailureReason::InvalidSize, anyhow!("Window changed size"))); - } - - fn render_fn( - buffer: &WlBuffer, - renderer: &mut R, - dt: &mut OutputDamageTracker, - age: usize, - session: &Session, - common: &mut Common, - window: &CosmicSurface, - geometry: Rectangle, - ) -> Result> - where - R: Renderer - + ImportAll - + ImportMem - + ExportMem - + Bind - + Offscreen - + Blit - + AsGlowRenderer, - ::TextureId: Clone + 'static, - ::Error: From, - CosmicElement: RenderElement, - CosmicMappedRenderElement: RenderElement, - { - // TODO cursor elements! - let mut elements = AsRenderElements::::render_elements::>( - window, - renderer, - (-geometry.loc.x, -geometry.loc.y).into(), - Scale::from(1.0), - 1.0, - ); - - for seat in common.seats() { - if let Some(location) = { - // we need to find the mapped element in that case - if let Some(mapped) = common.shell.element_for_surface(&window) { - mapped.cursor_position(seat).and_then(|mut p| { - p -= mapped.active_window_offset().to_f64(); - if p.x < 0. || p.y < 0. { - None - } else { - Some(p) - } - }) - } else { - None - } - } { - if session.cursor_mode() == ScreencopyCursorMode::Embedded { - elements.extend( - cursor::draw_cursor( - renderer, - seat, - location, - 1.0.into(), - common.clock.now(), - true, - ) - .into_iter() - .map(WindowCaptureElement::from), - ); - } - - if let Some(wl_surface) = get_dnd_icon(seat) { - elements.extend( - cursor::draw_dnd_icon(renderer, &wl_surface, location.to_i32_round(), 1.0) - .into_iter() - .map(WindowCaptureElement::from), - ); - } - } - } - - if let Ok(dmabuf) = get_dmabuf(buffer) { - renderer.bind(dmabuf).map_err(DTError::Rendering)?; - } else { - let size = buffer_dimensions(buffer).unwrap(); - let format = - with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) - .map_err(|_| DTError::OutputNoMode(OutputNoMode))? // eh, we have to do some error - .expect("We should be able to convert all hardcoded shm screencopy formats"); - let render_buffer = - Offscreen::::create_buffer(renderer, format, size) - .map_err(DTError::Rendering)?; - renderer.bind(render_buffer).map_err(DTError::Rendering)?; - } - - dt.render_output( - renderer, - age, - &elements, - CLEAR_COLOR, // TODO use a theme neutral color - ) - } - - let node = node_from_params(¶ms, &mut state.backend, None); - let common = &mut state.common; - match &mut state.backend { - BackendData::Kms(kms) => { - let mut multirenderer = kms - .api - .single_renderer(node.as_ref().unwrap()) - .map_err(|err| (FailureReason::Unspec, err.into()))?; - render_session::<_, _>( - node, - &mut multirenderer, - session, - ¶ms, - Transform::Normal, - |_node, buffer, renderer, dt, age| { - render_fn(buffer, renderer, dt, age, session, common, window, geometry) - }, - ) - .map_err(|err| match err { - DTError::OutputNoMode(x) => (FailureReason::Unspec, x.into()), - DTError::Rendering(x) => (FailureReason::Unspec, x.into()), - }) - } - BackendData::Winit(winit) => render_session::<_, _>( - node, - winit.backend.renderer(), - session, - ¶ms, - Transform::Normal, - |_node, buffer, renderer, dt, age| { - render_fn(buffer, renderer, dt, age, session, common, window, geometry) - }, - ) - .map_err(|err| (FailureReason::Unspec, err.into())), - BackendData::X11(x11) => render_session::<_, _>( - node, - &mut x11.renderer, - session, - ¶ms, - Transform::Normal, - |_node, buffer, renderer, dt, age| { - render_fn(buffer, renderer, dt, age, session, common, window, geometry) - }, - ) - .map_err(|err| (FailureReason::Unspec, err.into())), - _ => unreachable!(), - } -} - -impl Common { - pub fn still_pending(&mut self, session: Session, params: BufferParams) { - match session.session_type() { - SessionType::Output(output) => { - if output - .user_data() - .get::() - .map_or(false, |sessions| { - sessions.0.borrow().iter().any(|s| &*s == &session) - }) - { - output - .user_data() - .get::() - .unwrap() - .borrow_mut() - .push((session, params)); - } - } - SessionType::Workspace(_output, handle) => { - if let Some(space) = self.shell.workspaces.space_for_handle_mut(&handle) { - if space.screencopy_sessions.iter().any(|s| s == &session) { - space.pending_buffers.push((session, params)); - } - } - } - SessionType::Window(window) => { - if window - .user_data() - .get::() - .map_or(false, |sessions| { - sessions.0.borrow().iter().any(|s| &*s == &session) - }) - { - window - .user_data() - .get::() - .unwrap() - .borrow_mut() - .push((session, params)); - } - } - _ => {} - } - } -} - -pub trait UserdataExt { - fn sessions(&self) -> Vec; - fn pending_buffers( - &self, - ) -> std::iter::Flatten>>; -} - -impl UserdataExt for Output { - fn sessions(&self) -> Vec { - self.user_data() - .get::() - .map_or(Vec::new(), |sessions| { - sessions.0.borrow().iter().map(|s| s.0.clone()).collect() - }) - } - - fn pending_buffers( - &self, - ) -> std::iter::Flatten>> - { - self.user_data() - .get::() - .map(|pending| pending.borrow_mut().split_off(0).into_iter()) - .into_iter() - .flatten() - } -} - -impl UserdataExt for CosmicSurface { - fn sessions(&self) -> Vec { - self.user_data() - .get::() - .map_or(Vec::new(), |sessions| { - sessions.0.borrow().iter().map(|s| s.0.clone()).collect() - }) - } - - fn pending_buffers( - &self, - ) -> std::iter::Flatten>> - { - self.user_data() - .get::() - .map(|pending| pending.borrow_mut().split_off(0).into_iter()) - .into_iter() - .flatten() - } -} - -impl State { - pub fn schedule_window_session(&mut self, surface: &WlSurface) { - if let Some(element) = surface - .wl_surface() - .and_then(|surface| self.common.shell.element_for_wl_surface(&surface).cloned()) - { - let active = element.active_window(); - if active.wl_surface().as_ref() == Some(surface) { - for (session, params) in active.pending_buffers() { - let window = active.clone(); - self.common.event_loop_handle.insert_idle(move |state| { - if !session.alive() { - return; - } - - match render_window_to_buffer(state, &session, params.clone(), &window) { - // rendering yielded no damage, buffer is still pending - Ok(false) => state.common.still_pending(session, params), - Ok(true) => {} // success - Err((reason, err)) => { - warn!(?err, "Screencopy session failed"); - session.failed(reason); - } - } - }); - } - } - } - } - - pub fn workspace_session_for_output( - &mut self, - output: &Output, - ) -> Option> { - let workspace = self.common.shell.active_space_mut(output); - let mut pending_buffers = std::mem::take(&mut workspace.pending_buffers); - - let mut i = 0; - while i < pending_buffers.len() { - let layer_map = layer_map_for_output(&output); - if layer_map - .layers() - .any(|s| s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) - { - let (session, params) = pending_buffers.remove(i); - schedule_offscreen_workspace_session( - &self.common.event_loop_handle, - session, - params, - output.clone(), - workspace.handle.clone(), - ); - } else { - i += 1; - } - } - - if !pending_buffers.is_empty() { - Some(pending_buffers) - } else { - None - } - } - - pub fn schedule_workspace_sessions( - &mut self, - surface: &WlSurface, - ) -> Option> { - // here we store additional workspace_sessions, we should handle, when rendering the corresponding output anyway - let mut output_sessions: Option> = None; - - // lets check which workspaces this surface belongs to - let active_spaces = self - .common - .shell - .outputs() - .map(|o| (o.clone(), self.common.shell.active_space(o).handle.clone())) - .collect::>(); - if let Some((handle, output)) = self.common.shell.workspace_for_surface(surface) { - let workspace = self - .common - .shell - .workspaces - .space_for_handle_mut(&handle) - .unwrap(); - if !workspace.pending_buffers.is_empty() { - // TODO: replace with drain_filter.... - let mut i = 0; - while i < workspace.pending_buffers.len() { - if let SessionType::Workspace(o, w) = - workspace.pending_buffers[i].0.session_type() - { - let layer_map = layer_map_for_output(&o); - if active_spaces.contains(&(o.clone(), w)) - && !layer_map - .layers() - .any(|s| s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) - { - // surface is on an active workspace/output combo, add to workspace_sessions - let (session, params) = workspace.pending_buffers.remove(i); - output_sessions - .get_or_insert_with(Vec::new) - .push((session, params)); - } else if handle == w && output == o { - // surface is visible on an offscreen workspace session, schedule a new render - let (session, params) = workspace.pending_buffers.remove(i); - schedule_offscreen_workspace_session( - &self.common.event_loop_handle, - session, - params, - output.clone(), - handle, - ); - } else { - i += 1; - } - } else { - unreachable!(); - } - } - } - } - - output_sessions - } -} - -pub fn schedule_offscreen_workspace_session( - event_loop_handle: &LoopHandle<'static, State>, - session: Session, - params: BufferParams, - output: Output, - handle: WorkspaceHandle, -) { - event_loop_handle.insert_idle(move |state| { - if !session.alive() { - return; - } - if !state.common.shell.outputs().any(|o| o == &output) { - return; - } - match render_workspace_to_buffer( - state, - &session, - params.clone(), - &output, - (handle, 0), /* TODO: Hack, we should know the idx */ - ) { - Ok(false) => { - // rendering yielded no new damage, buffer still pending - state.common.still_pending(session, params); - } - Ok(true) => {} - Err((reason, err)) => { - warn!(?err, "Screencopy session failed."); - session.failed(reason); - } - } - }); -} - -delegate_screencopy!(State); diff --git a/src/wayland/handlers/screencopy/mod.rs b/src/wayland/handlers/screencopy/mod.rs new file mode 100644 index 00000000..539a885a --- /dev/null +++ b/src/wayland/handlers/screencopy/mod.rs @@ -0,0 +1,415 @@ +use std::{borrow::Borrow, cell::RefCell, collections::HashMap}; + +use smithay::{ + backend::{ + allocator::{Fourcc, Modifier}, + egl::EGLDevice, + renderer::{ + damage::OutputDamageTracker, + gles::{Capability, GlesRenderer}, + glow::GlowRenderer, + utils::with_renderer_surface_state, + }, + }, + desktop::space::SpaceElement, + output::Output, + reexports::wayland_server::protocol::wl_shm::Format as ShmFormat, + utils::{Buffer as BufferCoords, Point, Size, Transform}, + wayland::{dmabuf::get_dmabuf, seat::WaylandFocus}, +}; + +use crate::{ + shell::CosmicSurface, + state::{BackendData, State}, + utils::prelude::{ + OutputExt, PointExt, PointGlobalExt, PointLocalExt, RectExt, RectLocalExt, SeatExt, + }, + wayland::protocols::{ + image_source::ImageSourceData, + screencopy::{ + delegate_screencopy, BufferConstraints, CursorSession, DmabufConstraints, Frame, + ScreencopyHandler, ScreencopyState, Session, + }, + }, +}; + +mod render; +mod user_data; +pub use self::render::*; +use self::user_data::*; +pub use self::user_data::{FrameHolder, ScreencopySessions, SessionData, SessionHolder}; + +pub const WORKSPACE_OVERVIEW_NAMESPACE: &str = "cosmic-workspace-overview"; + +impl ScreencopyHandler for State { + fn screencopy_state(&mut self) -> &mut ScreencopyState { + &mut self.common.screencopy_state + } + + fn capture_source(&mut self, source: &ImageSourceData) -> Option { + match source { + ImageSourceData::Output(weak) => weak + .upgrade() + .and_then(|output| constraints_for_output(&output, &mut self.backend)), + ImageSourceData::Workspace(handle) => { + let workspace = self.common.shell.workspaces.space_for_handle(&handle)?; + constraints_for_output(workspace.output(), &mut self.backend) + } + ImageSourceData::Toplevel(window) => { + constraints_for_toplevel(window, &mut self.backend) + } + _ => None, + } + } + fn capture_cursor_source(&mut self, _source: &ImageSourceData) -> Option { + let size = if let Some((geometry, _)) = self + .common + .last_active_seat() + .cursor_geometry((0.0, 0.0), self.common.clock.now()) + { + geometry.size + } else { + Size::from((64, 64)) + }; + + Some(BufferConstraints { + size, + shm: vec![ShmFormat::Argb8888], + dma: None, + }) + } + + fn new_session(&mut self, session: Session) { + match session.source() { + ImageSourceData::Output(weak) => { + let Some(mut output) = weak.upgrade() else { + session.stop(); + return; + }; + + session.user_data().insert_if_missing(|| { + RefCell::new(SessionUserData::new(OutputDamageTracker::from_output( + &output, + ))) + }); + + output.add_session(session); + } + ImageSourceData::Workspace(handle) => { + let Some(workspace) = self.common.shell.workspaces.space_for_handle_mut(&handle) + else { + session.stop(); + return; + }; + + session.user_data().insert_if_missing(|| { + RefCell::new(SessionUserData::new(OutputDamageTracker::from_output( + workspace.output(), + ))) + }); + workspace.add_session(session); + } + ImageSourceData::Toplevel(mut toplevel) => { + let size = toplevel.geometry().size.to_physical(1); + session.user_data().insert_if_missing(|| { + RefCell::new(SessionUserData::new(OutputDamageTracker::new( + size, + 1.0, + Transform::Normal, + ))) + }); + toplevel.add_session(session); + } + ImageSourceData::Destroyed => unreachable!(), + } + } + fn new_cursor_session(&mut self, session: CursorSession) { + let (pointer_loc, pointer_size, hotspot) = { + let seat = self.common.last_active_seat(); + + let pointer = seat.get_pointer().unwrap(); + let pointer_loc = pointer.current_location().to_i32_round().as_global(); + + let (pointer_size, hotspot) = if let Some((geometry, hotspot)) = + seat.cursor_geometry((0.0, 0.0), self.common.clock.now()) + { + (geometry.size, hotspot) + } else { + (Size::from((64, 64)), Point::from((0, 0))) + }; + + (pointer_loc, pointer_size, hotspot) + }; + + session.user_data().insert_if_missing(|| { + RefCell::new(SessionUserData::new(OutputDamageTracker::new( + pointer_size.to_logical(1, Transform::Normal).to_physical(1), + 1.0, + Transform::Normal, + ))) + }); + + match session.source() { + ImageSourceData::Output(weak) => { + let Some(mut output) = weak.upgrade() else { + session.stop(); + return; + }; + + if output.geometry().contains(pointer_loc) { + let buffer_pos = pointer_loc + .to_local(&output) + .as_logical() + .to_f64() + .to_buffer( + output.current_scale().fractional_scale(), + output.current_transform(), + &output + .current_mode() + .map(|mode| { + mode.size + .to_f64() + .to_logical(output.current_scale().fractional_scale()) + }) + .unwrap_or(Size::from((0.0, 0.0))), + ) + .to_i32_round(); + session.set_cursor_hotspot(hotspot); + session.set_cursor_pos(Some(buffer_pos)); + } + + output.add_cursor_session(session); + } + ImageSourceData::Workspace(handle) => { + let Some(workspace) = self.common.shell.workspaces.space_for_handle_mut(&handle) + else { + session.stop(); + return; + }; + + let output = workspace.output().clone(); + if output.geometry().contains(pointer_loc) { + let buffer_pos = pointer_loc + .to_local(&output) + .as_logical() + .to_f64() + .to_buffer( + output.current_scale().fractional_scale(), + output.current_transform(), + &output + .current_mode() + .map(|mode| { + mode.size + .to_f64() + .to_logical(output.current_scale().fractional_scale()) + }) + .unwrap_or(Size::from((0.0, 0.0))), + ) + .to_i32_round(); + session.set_cursor_hotspot(hotspot); + session.set_cursor_pos(Some(buffer_pos)); + } + + workspace.add_cursor_session(session); + } + ImageSourceData::Toplevel(mut toplevel) => { + if let Some(element) = self.common.shell.element_for_surface(&toplevel) { + if element.has_active_window(&toplevel) { + if let Some(workspace) = self.common.shell.space_for(element) { + if let Some(geometry) = workspace.element_geometry(element) { + let mut surface_geo = element.active_window_geometry().as_local(); + surface_geo.loc += geometry.loc; + let global_geo = surface_geo.to_global(workspace.output()); + if global_geo.contains(pointer_loc) { + let buffer_pos = (pointer_loc - global_geo.loc) + .as_logical() + .to_buffer(1, Transform::Normal, &toplevel.geometry().size); + session.set_cursor_hotspot(hotspot); + session.set_cursor_pos(Some(buffer_pos)); + } + } + } + } + } + + toplevel.add_cursor_session(session); + } + ImageSourceData::Destroyed => unreachable!(), + } + } + + fn frame(&mut self, session: Session, frame: Frame) { + match session.source() { + ImageSourceData::Output(weak) => { + let Some(mut output) = weak.upgrade() else { + session.stop(); // will fail the frame as well + return; + }; + + output.add_frame(session, frame); + self.backend + .schedule_render(&self.common.event_loop_handle, &output); + } + ImageSourceData::Workspace(handle) => { + render_workspace_to_buffer(self, session, frame, handle) + } + ImageSourceData::Toplevel(toplevel) => { + render_window_to_buffer(self, session, frame, &toplevel) + } + ImageSourceData::Destroyed => unreachable!(), + } + } + + fn cursor_frame(&mut self, session: CursorSession, frame: Frame) { + if !session.has_cursor() { + frame.success(Transform::Normal, Vec::new(), self.common.clock.now()); + return; + } + + let seat = self.common.last_active_seat().clone(); + render_cursor_to_buffer(self, &session, frame, &seat); + } + + fn frame_aborted(&mut self, frame: Frame) { + for mut output in self.common.shell.outputs().cloned() { + output.remove_frame(&frame) + } + } + + fn session_destroyed(&mut self, session: Session) { + match session.source() { + ImageSourceData::Output(weak) => { + if let Some(mut output) = weak.upgrade() { + output.remove_session(session); + } + } + ImageSourceData::Workspace(handle) => { + if let Some(workspace) = self.common.shell.workspaces.space_for_handle_mut(&handle) + { + workspace.remove_session(session) + } + } + ImageSourceData::Toplevel(mut toplevel) => toplevel.remove_session(session), + ImageSourceData::Destroyed => unreachable!(), + } + } + + fn cursor_session_destroyed(&mut self, session: CursorSession) { + match session.source() { + ImageSourceData::Output(weak) => { + if let Some(mut output) = weak.upgrade() { + output.remove_cursor_session(session); + } + } + ImageSourceData::Workspace(handle) => { + if let Some(workspace) = self.common.shell.workspaces.space_for_handle_mut(&handle) + { + workspace.remove_cursor_session(session) + } + } + ImageSourceData::Toplevel(mut toplevel) => toplevel.remove_cursor_session(session), + ImageSourceData::Destroyed => unreachable!(), + } + } +} + +fn constraints_for_output(output: &Output, backend: &mut BackendData) -> Option { + let mode = match output.current_mode() { + Some(mode) => mode.size.to_logical(1).to_buffer(1, Transform::Normal), + None => { + return None; + } + }; + + let mut _kms_renderer = None; + let renderer = match backend { + BackendData::Kms(ref mut kms) => { + let node = kms + .target_node_for_output(&output) + .unwrap_or(kms.primary_node); + _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); + _kms_renderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(ref mut winit) => winit.backend.renderer(), + BackendData::X11(ref mut x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + Some(constraints_for_renderer(mode, renderer)) +} + +fn constraints_for_toplevel( + surface: &CosmicSurface, + backend: &mut BackendData, +) -> Option { + let size = surface.geometry().size.to_buffer(1, Transform::Normal); + let wl_surface = surface.wl_surface()?; + + let mut _kms_renderer = None; + let renderer = match backend { + BackendData::Kms(ref mut kms) => { + let dma_node = with_renderer_surface_state(&wl_surface, |state| { + let buffer = state.buffer()?; + let dmabuf = get_dmabuf(&*buffer).ok()?; + dmabuf.node() + }) + .flatten(); + + let node = dma_node.unwrap_or(kms.primary_node); + _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); + _kms_renderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(ref mut winit) => winit.backend.renderer(), + BackendData::X11(ref mut x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + Some(constraints_for_renderer(size, renderer)) +} + +fn constraints_for_renderer( + size: Size, + renderer: &mut GlowRenderer, +) -> BufferConstraints { + let mut constraints = BufferConstraints { + size, + shm: vec![ShmFormat::Abgr8888, ShmFormat::Xbgr8888], + dma: None, + }; + + if (renderer as &dyn Borrow) + .borrow() + .capabilities() + .contains(&Capability::ColorTransformations) + { + constraints + .shm + .extend([ShmFormat::Abgr2101010, ShmFormat::Xbgr2101010]); + } + + if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display()) + .ok() + .and_then(|device| device.try_get_render_node().ok().flatten()) + { + constraints.dma = Some(DmabufConstraints { + node, + formats: renderer + .egl_context() + .dmabuf_render_formats() + .iter() + .fold( + HashMap::>::new(), + |mut map, format| { + map.entry(format.code).or_default().push(format.modifier); + map + }, + ) + .into_iter() + .collect::>(), + }); + } + + constraints +} + +delegate_screencopy!(State); diff --git a/src/wayland/handlers/screencopy/render.rs b/src/wayland/handlers/screencopy/render.rs new file mode 100644 index 00000000..975bdce4 --- /dev/null +++ b/src/wayland/handlers/screencopy/render.rs @@ -0,0 +1,864 @@ +use smithay::{ + backend::{ + allocator::{dmabuf::Dmabuf, format::get_transparent, Buffer, Fourcc}, + renderer::{ + buffer_dimensions, buffer_type, + damage::{Error as DTError, OutputDamageTracker, RenderOutputResult}, + element::{ + surface::WaylandSurfaceRenderElement, + utils::{Relocate, RelocateRenderElement}, + AsRenderElements, RenderElement, + }, + gles::{GlesError, GlesRenderbuffer}, + sync::SyncPoint, + utils::with_renderer_surface_state, + Bind, Blit, BufferType, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, + }, + }, + desktop::space::SpaceElement, + input::Seat, + output::{Output, OutputNoMode}, + reexports::wayland_server::protocol::{wl_buffer::WlBuffer, wl_shm::Format as ShmFormat}, + utils::{ + Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, + Transform, + }, + wayland::{ + dmabuf::get_dmabuf, + seat::WaylandFocus, + shm::{shm_format_to_fourcc, with_buffer_contents, with_buffer_contents_mut}, + }, +}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tracing::warn; + +use crate::{ + backend::render::{ + cursor, + element::{AsGlowRenderer, CosmicElement, DamageElement}, + render_workspace, CursorMode, CLEAR_COLOR, + }, + shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, + state::{BackendData, Common, State}, + utils::prelude::SeatExt, + wayland::{ + handlers::screencopy::{ + constraints_for_output, constraints_for_toplevel, SessionData, SessionUserData, + }, + protocols::{ + screencopy::{BufferConstraints, CursorSession, FailureReason, Frame, Session}, + workspace::WorkspaceHandle, + }, + }, +}; + +use super::super::data_device::get_dnd_icon; + +pub fn submit_buffer( + frame: Frame, + renderer: &mut R, + transform: Transform, + damage: Option>>, + sync: SyncPoint, +) -> Result>)>, ::Error> +where + R: ExportMem, +{ + let Some(damage) = damage else { + frame.success( + transform, + None, + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::ZERO), + ); + return Ok(None); + }; + + let buffer = frame.buffer(); + if matches!(buffer_type(&buffer), Some(BufferType::Shm)) { + let buffer_size = buffer_dimensions(&buffer).unwrap(); + if let Err(err) = with_buffer_contents_mut(&buffer, |ptr, len, data| { + let offset = data.offset as i32; + let width = data.width as i32; + let height = data.height as i32; + let stride = data.stride as i32; + let format = shm_format_to_fourcc(data.format) + .expect("We should be able to convert all hardcoded shm screencopy formats"); + + // number of bytes per pixel + // TODO: compute from data.format + let pixelsize = 4i32; + + // ensure consistency, the SHM handler of smithay should ensure this + assert!((offset + (height - 1) * stride + width * pixelsize) as usize <= len); + + // ensure rendering is done + renderer.wait(&sync)?; + + let format = get_transparent(format).unwrap_or(format); + let mapping = renderer + .copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size), format)?; + let gl_data = renderer.map_texture(&mapping)?; + assert!((width * height * pixelsize) as usize <= gl_data.len()); + + for i in 0..height { + unsafe { + std::ptr::copy_nonoverlapping::( + gl_data.as_ptr().offset((width * pixelsize * i) as isize), + ptr.offset((offset + stride * i) as isize), + (width * pixelsize) as usize, + ); + } + } + Ok(()) + }) + .unwrap() + { + frame.fail(FailureReason::Unknown); + return Err(err); + } + } + + Ok(Some(( + frame, + damage + .into_iter() + .map(|rect| { + let logical = rect.to_logical(1); + logical.to_buffer(1, transform, &logical.size) + }) + .collect(), + ))) +} + +pub fn render_session( + renderer: &mut R, + session: &SessionData, + frame: Frame, + transform: Transform, + render_fn: F, +) -> Result>)>, DTError> +where + R: ExportMem, + F: FnOnce( + &WlBuffer, + &mut R, + &mut OutputDamageTracker, + usize, + Vec>, + ) -> Result>, +{ + #[cfg(feature = "debug")] + puffin::profile_function!(); + + let mut session_damage_tracking = session.borrow_mut(); + + let buffer = frame.buffer(); + let age = session_damage_tracking.age_for_buffer(&buffer); + let res = render_fn( + &frame.buffer(), + renderer, + &mut session_damage_tracking.dt, + age, + frame.damage(), + ); + + match res { + Ok(result) => submit_buffer(frame, renderer, transform, result.damage, result.sync) + .map_err(DTError::Rendering), + Err(err) => { + frame.fail(FailureReason::Unknown); + Err(err) + } + } +} + +pub fn render_workspace_to_buffer( + state: &mut State, + session: Session, + frame: Frame, + handle: WorkspaceHandle, +) { + #[cfg(feature = "debug")] + puffin::profile_function!(); + + let Some(workspace) = state.common.shell.workspaces.space_for_handle(&handle) else { + session.stop(); + return; + }; + + let output = workspace.output().clone(); + let idx = state + .common + .shell + .workspaces + .idx_for_handle(&output, &handle) + .unwrap(); + let mode = output + .current_mode() + .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal)); + + let buffer = frame.buffer(); + let buffer_size = buffer_dimensions(&buffer).unwrap(); + if mode != Some(buffer_size) { + let Some(constraints) = constraints_for_output(&output, &mut state.backend) else { + session.stop(); + return; + }; + session.update_constraints(constraints); + if let Some(data) = session.user_data().get::() { + *data.borrow_mut() = SessionUserData::new(OutputDamageTracker::from_output(&output)); + } + frame.fail(FailureReason::BufferConstraints); + return; + } + + fn render_fn( + buffer: &WlBuffer, + renderer: &mut R, + dt: &mut OutputDamageTracker, + age: usize, + additional_damage: Vec>, + draw_cursor: bool, + common: &mut Common, + output: &Output, + handle: (WorkspaceHandle, usize), + ) -> Result> + where + R: Renderer + + ImportAll + + ImportMem + + ExportMem + + Bind + + Offscreen + + Blit + + AsGlowRenderer, + ::TextureId: Clone + 'static, + ::Error: From, + CosmicElement: RenderElement, + CosmicMappedRenderElement: RenderElement, + WorkspaceRenderElement: RenderElement, + { + let cursor_mode = if draw_cursor { + CursorMode::All + } else { + CursorMode::None + }; + + let area = output + .current_mode() + .ok_or(DTError::OutputNoMode(OutputNoMode)) + .map( + |mode| { + mode.size + .to_logical(1) + .to_buffer(1, Transform::Normal) + .to_f64() + }, /* TODO: Mode is Buffer..., why is this Physical in the first place */ + )?; + let additional_damage = (!additional_damage.is_empty()).then(|| { + additional_damage + .into_iter() + .map(|rect| { + rect.to_f64() + .to_logical( + output.current_scale().fractional_scale(), + output.current_transform(), + &area, + ) + .to_i32_round() + }) + .collect() + }); + + if let Ok(dmabuf) = get_dmabuf(buffer) { + render_workspace::<_, _, GlesRenderbuffer>( + None, + renderer, + dmabuf, + dt, + age, + additional_damage, + common, + &output, + None, + handle, + cursor_mode, + None, + true, + ) + .map(|res| res.0) + } else { + let size = buffer_dimensions(buffer).unwrap(); + let format = + with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) + .map_err(|_| DTError::OutputNoMode(OutputNoMode))? // eh, we have to do some error + .expect("We should be able to convert all hardcoded shm screencopy formats"); + let render_buffer = + Offscreen::::create_buffer(renderer, format, size) + .map_err(DTError::Rendering)?; + render_workspace::<_, _, GlesRenderbuffer>( + None, + renderer, + render_buffer, + dt, + age, + additional_damage, + common, + &output, + None, + handle, + cursor_mode, + None, + true, + ) + .map(|res| res.0) + } + } + + let draw_cursor = session.draw_cursor(); + let transform = output.current_transform(); + let common = &mut state.common; + + let result = match &mut state.backend { + BackendData::Kms(kms) => { + let render_node = kms + .target_node_for_output(&output) + .unwrap_or(kms.primary_node); + let target_node = get_dmabuf(&buffer) + .ok() + .and_then(|dma| dma.node()) + .unwrap_or(render_node); + + let buffer_format = match buffer_type(&buffer) { + Some(BufferType::Dma) => Some(get_dmabuf(&buffer).unwrap().format().code), + Some(BufferType::Shm) => { + with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format)) + .unwrap() + } + _ => None, + }; + let mut multirenderer = match kms.api.renderer( + &render_node, + &target_node, + buffer_format.unwrap_or(Fourcc::Abgr8888), + ) { + Ok(renderer) => renderer, + Err(err) => { + warn!(?err, "Couldn't use nodes for screencopy"); + frame.fail(FailureReason::Unknown); + return; + } + }; + + match render_session::<_, _>( + &mut multirenderer, + session.user_data().get::().unwrap(), + frame, + transform, + |buffer, renderer, dt, age, additional_damage| { + render_fn( + buffer, + renderer, + dt, + age, + additional_damage, + draw_cursor, + common, + &output, + (handle, idx), + ) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + } + } + BackendData::Winit(winit) => match render_session::<_, _>( + winit.backend.renderer(), + session.user_data().get::().unwrap(), + frame, + transform, + |buffer, renderer, dt, age, additional_damage| { + render_fn( + buffer, + renderer, + dt, + age, + additional_damage, + draw_cursor, + common, + &output, + (handle, idx), + ) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + }, + BackendData::X11(x11) => match render_session::<_, _>( + &mut x11.renderer, + session.user_data().get::().unwrap(), + frame, + transform, + |buffer, renderer, dt, age, additional_damage| { + render_fn( + buffer, + renderer, + dt, + age, + additional_damage, + draw_cursor, + common, + &output, + (handle, idx), + ) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + }, + _ => unreachable!(), + }; + + if let Some((frame, damage)) = result { + frame.success(transform, damage, common.clock.now()) + } +} + +smithay::render_elements! { + pub WindowCaptureElement where R: ImportAll + ImportMem; + WaylandElement=WaylandSurfaceRenderElement, + CursorElement=RelocateRenderElement>, + AdditionalDamage=DamageElement, +} + +pub fn render_window_to_buffer( + state: &mut State, + session: Session, + frame: Frame, + toplevel: &CosmicSurface, +) { + #[cfg(feature = "debug")] + puffin::profile_function!(); + + if !toplevel.alive() { + session.stop(); + return; + } + + let buffer = frame.buffer(); + let geometry = toplevel.geometry(); + let buffer_size = buffer_dimensions(&buffer).unwrap(); + if buffer_size != geometry.size.to_buffer(1, Transform::Normal) { + let Some(constraints) = constraints_for_toplevel(toplevel, &mut state.backend) else { + session.stop(); + return; + }; + session.update_constraints(constraints); + if let Some(data) = session.user_data().get::() { + let size = geometry.size.to_physical(1); + *data.borrow_mut() = + SessionUserData::new(OutputDamageTracker::new(size, 1.0, Transform::Normal)); + } + frame.fail(FailureReason::BufferConstraints); + return; + } + + fn render_fn( + buffer: &WlBuffer, + renderer: &mut R, + dt: &mut OutputDamageTracker, + age: usize, + additional_damage: Vec>, + draw_cursor: bool, + common: &mut Common, + window: &CosmicSurface, + geometry: Rectangle, + ) -> Result> + where + R: Renderer + + ImportAll + + ImportMem + + ExportMem + + Bind + + Offscreen + + Blit + + AsGlowRenderer, + ::TextureId: Clone + 'static, + ::Error: From, + CosmicElement: RenderElement, + CosmicMappedRenderElement: RenderElement, + { + let mut elements = AsRenderElements::::render_elements::>( + window, + renderer, + (-geometry.loc.x, -geometry.loc.y).into(), + Scale::from(1.0), + 1.0, + ); + + elements.extend( + additional_damage + .into_iter() + .filter_map(|rect| { + let logical_rect = rect.to_logical( + 1, + Transform::Normal, + &geometry.size.to_buffer(1, Transform::Normal), + ); + logical_rect.intersection(Rectangle::from_loc_and_size((0, 0), geometry.size)) + }) + .map(DamageElement::new) + .map(Into::>::into), + ); + + let seat = common.last_active_seat().clone(); + if let Some(location) = { + if let Some(mapped) = common.shell.element_for_surface(&window) { + mapped.cursor_position(&seat).and_then(|mut p| { + p -= mapped.active_window_offset().to_f64(); + if p.x < 0. || p.y < 0. { + None + } else { + Some(p) + } + }) + } else { + None + } + } { + if draw_cursor { + elements.extend( + cursor::draw_cursor( + renderer, + &seat, + location, + 1.0.into(), + common.clock.now(), + true, + ) + .into_iter() + .map(|(elem, hotspot)| { + WindowCaptureElement::CursorElement(RelocateRenderElement::from_element( + elem, + Point::from((-hotspot.x, -hotspot.y)), + Relocate::Relative, + )) + }), + ); + } + + if let Some(wl_surface) = get_dnd_icon(&seat) { + elements.extend( + cursor::draw_dnd_icon(renderer, &wl_surface, location.to_i32_round(), 1.0) + .into_iter() + .map(WindowCaptureElement::from), + ); + } + } + + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer.bind(dmabuf).map_err(DTError::Rendering)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let format = + with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) + .map_err(|_| DTError::OutputNoMode(OutputNoMode))? // eh, we have to do some error + .expect("We should be able to convert all hardcoded shm screencopy formats"); + let render_buffer = + Offscreen::::create_buffer(renderer, format, size) + .map_err(DTError::Rendering)?; + renderer.bind(render_buffer).map_err(DTError::Rendering)?; + } + + dt.render_output( + renderer, + age, + &elements, + CLEAR_COLOR, // TODO use a theme neutral color + ) + } + + let common = &mut state.common; + let draw_cursor = session.draw_cursor(); + + let result = match &mut state.backend { + BackendData::Kms(kms) => { + let node = get_dmabuf(&buffer) + .ok() + .and_then(|dmabuf| dmabuf.node()) + .or_else(|| { + toplevel + .wl_surface() + .and_then(|wl_surface| { + with_renderer_surface_state(&wl_surface, |state| { + let buffer = state.buffer()?; + let dmabuf = get_dmabuf(&*buffer).ok()?; + dmabuf.node() + }) + }) + .flatten() + }) + .unwrap_or(kms.primary_node); + + let mut multirenderer = match kms.api.single_renderer(&node) { + Ok(renderer) => renderer, + Err(err) => { + warn!(?err, "Couldn't use node for screencopy"); + frame.fail(FailureReason::Unknown); + return; + } + }; + match render_session::<_, _>( + &mut multirenderer, + session.user_data().get::().unwrap(), + frame, + Transform::Normal, + |buffer, renderer, dt, age, additional_damage| { + render_fn( + buffer, + renderer, + dt, + age, + additional_damage, + draw_cursor, + common, + toplevel, + geometry, + ) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + } + } + BackendData::Winit(winit) => match render_session::<_, _>( + winit.backend.renderer(), + session.user_data().get::().unwrap(), + frame, + Transform::Normal, + |buffer, renderer, dt, age, additional_damage| { + render_fn( + buffer, + renderer, + dt, + age, + additional_damage, + draw_cursor, + common, + toplevel, + geometry, + ) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + }, + BackendData::X11(x11) => match render_session::<_, _>( + &mut x11.renderer, + session.user_data().get::().unwrap(), + frame, + Transform::Normal, + |buffer, renderer, dt, age, additional_damage| { + render_fn( + buffer, + renderer, + dt, + age, + additional_damage, + draw_cursor, + common, + toplevel, + geometry, + ) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + }, + _ => unreachable!(), + }; + + if let Some((frame, damage)) = result { + frame.success(Transform::Normal, damage, common.clock.now()) + } +} + +pub fn render_cursor_to_buffer( + state: &mut State, + session: &CursorSession, + frame: Frame, + seat: &Seat, +) { + let buffer = frame.buffer(); + let cursor_size = seat + .cursor_geometry((0.0, 0.0), state.common.clock.now()) + .map(|(geo, _hotspot)| geo.size) + .unwrap_or_else(|| Size::from((64, 64))); + let buffer_size = buffer_dimensions(&buffer).unwrap(); + if buffer_size != cursor_size { + let constraints = BufferConstraints { + size: cursor_size, + shm: vec![ShmFormat::Argb8888], + dma: None, + }; + session.update_constraints(constraints); + if let Some(data) = session.user_data().get::() { + *data.borrow_mut() = SessionUserData::new(OutputDamageTracker::new( + cursor_size.to_logical(1, Transform::Normal).to_physical(1), + 1.0, + Transform::Normal, + )); + } + frame.fail(FailureReason::BufferConstraints); + return; + } + + fn render_fn( + buffer: &WlBuffer, + renderer: &mut R, + dt: &mut OutputDamageTracker, + age: usize, + additional_damage: Vec>, + common: &mut Common, + seat: &Seat, + ) -> Result> + where + R: Renderer + + ImportAll + + ImportMem + + ExportMem + + Bind + + Offscreen + + Blit + + AsGlowRenderer, + ::TextureId: Clone + 'static, + ::Error: From, + CosmicElement: RenderElement, + CosmicMappedRenderElement: RenderElement, + { + let mut elements = cursor::draw_cursor( + renderer, + &seat, + Point::from((0.0, 0.0)), + 1.0.into(), + common.clock.now(), + true, + ) + .into_iter() + .map(|(elem, _)| RelocateRenderElement::from_element(elem, (0, 0), Relocate::Relative)) + .map(WindowCaptureElement::from) + .collect::>(); + + elements.extend( + additional_damage + .into_iter() + .filter_map(|rect| { + let logical_rect = rect.to_logical(1, Transform::Normal, &Size::from((64, 64))); + logical_rect.intersection(Rectangle::from_loc_and_size((0, 0), (64, 64))) + }) + .map(DamageElement::new) + .map(Into::>::into), + ); + + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer.bind(dmabuf).map_err(DTError::Rendering)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let format = + with_buffer_contents(buffer, |_, _, data| shm_format_to_fourcc(data.format)) + .map_err(|_| DTError::OutputNoMode(OutputNoMode))? // eh, we have to do some error + .expect("We should be able to convert all hardcoded shm screencopy formats"); + let render_buffer = + Offscreen::::create_buffer(renderer, format, size) + .map_err(DTError::Rendering)?; + renderer.bind(render_buffer).map_err(DTError::Rendering)?; + } + + dt.render_output(renderer, age, &elements, [0.0, 0.0, 0.0, 0.0]) + } + + let common = &mut state.common; + let result = match &mut state.backend { + BackendData::Kms(kms) => { + let mut multirenderer = match kms.api.single_renderer(&kms.primary_node) { + Ok(renderer) => renderer, + Err(err) => { + warn!(?err, "Couldn't use node for screencopy"); + frame.fail(FailureReason::Unknown); + return; + } + }; + match render_session::<_, _>( + &mut multirenderer, + session.user_data().get::().unwrap(), + frame, + Transform::Normal, + |buffer, renderer, dt, age, additional_damage| { + render_fn(buffer, renderer, dt, age, additional_damage, common, seat) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + } + } + BackendData::Winit(winit) => match render_session::<_, _>( + winit.backend.renderer(), + session.user_data().get::().unwrap(), + frame, + Transform::Normal, + |buffer, renderer, dt, age, additional_damage| { + render_fn(buffer, renderer, dt, age, additional_damage, common, seat) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + }, + BackendData::X11(x11) => match render_session::<_, _>( + &mut x11.renderer, + session.user_data().get::().unwrap(), + frame, + Transform::Normal, + |buffer, renderer, dt, age, additional_damage| { + render_fn(buffer, renderer, dt, age, additional_damage, common, seat) + }, + ) { + Ok(frame) => frame, + Err(err) => { + tracing::warn!(?err, "Failed to render to screencopy buffer"); + None + } + }, + _ => unreachable!(), + }; + + if let Some((frame, damage)) = result { + frame.success(Transform::Normal, damage, common.clock.now()) + } +} diff --git a/src/wayland/handlers/screencopy/user_data.rs b/src/wayland/handlers/screencopy/user_data.rs new file mode 100644 index 00000000..f40e4031 --- /dev/null +++ b/src/wayland/handlers/screencopy/user_data.rs @@ -0,0 +1,357 @@ +use std::{ + cell::RefCell, + collections::HashMap, + ops::{Deref, DerefMut}, +}; + +use smithay::{ + backend::renderer::{damage::OutputDamageTracker, utils::CommitCounter}, + output::Output, + reexports::wayland_server::{protocol::wl_buffer::WlBuffer, Resource, Weak}, +}; + +use crate::{ + shell::{CosmicSurface, Workspace}, + wayland::protocols::screencopy::{CursorSession, FailureReason, Frame, Session}, +}; + +type ScreencopySessionsData = RefCell; +type PendingScreencopyBuffers = RefCell>; + +pub type SessionData = RefCell; + +pub struct SessionUserData { + pub dt: OutputDamageTracker, + commit_counter: CommitCounter, + buffer_age: HashMap, CommitCounter>, +} + +impl SessionUserData { + pub fn new(tracker: OutputDamageTracker) -> SessionUserData { + SessionUserData { + dt: tracker, + commit_counter: CommitCounter::default(), + buffer_age: HashMap::new(), + } + } + + pub fn age_for_buffer(&mut self, buffer: &WlBuffer) -> usize { + self.buffer_age.retain(|k, _| k.upgrade().is_ok()); + + let weak = buffer.downgrade(); + let age = self + .commit_counter + .distance(self.buffer_age.get(&weak).copied()) + .unwrap_or(0); + self.buffer_age.insert(weak, self.commit_counter); + + self.commit_counter.increment(); + age + } + + pub fn reset(&mut self) { + self.commit_counter = CommitCounter::default(); + self.buffer_age.clear(); + } +} + +#[derive(Debug, Default)] +pub struct ScreencopySessions { + sessions: Vec, + cursor_sessions: Vec, +} + +pub trait SessionHolder { + fn add_session(&mut self, session: Session); + fn remove_session(&mut self, session: Session); + fn sessions(&self) -> Vec; + + fn add_cursor_session(&mut self, session: CursorSession); + fn remove_cursor_session(&mut self, session: CursorSession); + fn cursor_sessions(&self) -> Vec; +} + +pub trait FrameHolder { + fn add_frame(&mut self, session: Session, frame: Frame); + fn remove_frame(&mut self, frame: &Frame); + fn take_pending_frames(&self) -> Vec<(Session, Frame)>; +} + +impl SessionHolder for Output { + fn add_session(&mut self, session: Session) { + self.user_data() + .insert_if_missing(ScreencopySessionsData::default); + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .sessions + .push(DropableSession(Some(session))); + } + + fn remove_session(&mut self, session: Session) { + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .sessions + .retain(|s| *s != session); + } + + fn sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions + .borrow() + .sessions + .iter() + .flat_map(|s| s.0.clone()) + .collect() + }) + } + + fn add_cursor_session(&mut self, session: CursorSession) { + self.user_data() + .insert_if_missing(ScreencopySessionsData::default); + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .cursor_sessions + .push(DropableCursorSession(Some(session))); + } + + fn remove_cursor_session(&mut self, session: CursorSession) { + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .cursor_sessions + .retain(|s| *s != session); + } + + fn cursor_sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions + .borrow() + .cursor_sessions + .iter() + .flat_map(|s| s.0.clone()) + .collect() + }) + } +} + +impl FrameHolder for Output { + fn add_frame(&mut self, session: Session, frame: Frame) { + self.user_data() + .insert_if_missing(PendingScreencopyBuffers::default); + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, DropableFrame(Some(frame)))); + } + fn remove_frame(&mut self, frame: &Frame) { + if let Some(pending) = self.user_data().get::() { + pending.borrow_mut().retain(|(_, f)| f != frame); + } + } + fn take_pending_frames(&self) -> Vec<(Session, Frame)> { + self.user_data() + .get::() + .map(|pending| { + pending + .borrow_mut() + .split_off(0) + .into_iter() + .map(|(s, mut f)| (s, f.0.take().unwrap())) + .collect() + }) + .unwrap_or_default() + } +} + +impl SessionHolder for Workspace { + fn add_session(&mut self, session: Session) { + self.screencopy + .sessions + .push(DropableSession(Some(session))); + } + + fn remove_session(&mut self, session: Session) { + self.screencopy.sessions.retain(|s| *s != session); + } + fn sessions(&self) -> Vec { + self.screencopy + .sessions + .iter() + .flat_map(|s| s.0.clone()) + .collect() + } + + fn add_cursor_session(&mut self, session: CursorSession) { + self.screencopy + .cursor_sessions + .push(DropableCursorSession(Some(session))); + } + + fn remove_cursor_session(&mut self, session: CursorSession) { + self.screencopy.cursor_sessions.retain(|s| *s != session); + } + fn cursor_sessions(&self) -> Vec { + self.screencopy + .cursor_sessions + .iter() + .flat_map(|s| s.0.clone()) + .collect() + } +} + +impl SessionHolder for CosmicSurface { + fn add_session(&mut self, session: Session) { + self.user_data() + .insert_if_missing(ScreencopySessionsData::default); + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .sessions + .push(DropableSession(Some(session))); + } + + fn remove_session(&mut self, session: Session) { + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .sessions + .retain(|s| *s != session); + } + fn sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions + .borrow() + .sessions + .iter() + .flat_map(|s| s.0.clone()) + .collect() + }) + } + + fn add_cursor_session(&mut self, session: CursorSession) { + self.user_data() + .insert_if_missing(ScreencopySessionsData::default); + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .cursor_sessions + .push(DropableCursorSession(Some(session))); + } + + fn remove_cursor_session(&mut self, session: CursorSession) { + self.user_data() + .get::() + .unwrap() + .borrow_mut() + .cursor_sessions + .retain(|s| *s != session); + } + + fn cursor_sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions + .borrow() + .cursor_sessions + .iter() + .flat_map(|s| s.0.clone()) + .collect() + }) + } +} + +#[derive(Debug)] +struct DropableSession(Option); +impl Deref for DropableSession { + type Target = Session; + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap() + } +} +impl DerefMut for DropableSession { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap() + } +} +impl PartialEq for DropableSession { + fn eq(&self, other: &Session) -> bool { + self.0.as_ref().map(|s| s == other).unwrap_or(false) + } +} +impl Drop for DropableSession { + fn drop(&mut self) { + if let Some(s) = self.0.take() { + s.stop(); + } + } +} + +#[derive(Debug)] +struct DropableCursorSession(Option); +impl Deref for DropableCursorSession { + type Target = CursorSession; + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap() + } +} +impl DerefMut for DropableCursorSession { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap() + } +} +impl PartialEq for DropableCursorSession { + fn eq(&self, other: &CursorSession) -> bool { + self.0.as_ref().map(|s| s == other).unwrap_or(false) + } +} +impl Drop for DropableCursorSession { + fn drop(&mut self) { + if let Some(s) = self.0.take() { + s.stop(); + } + } +} + +#[derive(Debug)] +pub struct DropableFrame(Option); +impl Deref for DropableFrame { + type Target = Frame; + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap() + } +} +impl DerefMut for DropableFrame { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap() + } +} +impl PartialEq for DropableFrame { + fn eq(&self, other: &Frame) -> bool { + self.0.as_ref().map(|f| f == other).unwrap_or(false) + } +} +impl Drop for DropableFrame { + fn drop(&mut self) { + if let Some(f) = self.0.take() { + f.fail(FailureReason::Unknown); + } + } +} diff --git a/src/wayland/handlers/session_lock.rs b/src/wayland/handlers/session_lock.rs index a5b46619..e0f4141f 100644 --- a/src/wayland/handlers/session_lock.rs +++ b/src/wayland/handlers/session_lock.rs @@ -39,7 +39,7 @@ impl SessionLockHandler for State { for output in self.common.shell.outputs() { self.backend - .schedule_render(&self.common.event_loop_handle, &output, None); + .schedule_render(&self.common.event_loop_handle, &output); } } @@ -48,7 +48,7 @@ impl SessionLockHandler for State { for output in self.common.shell.outputs() { self.backend - .schedule_render(&self.common.event_loop_handle, &output, None); + .schedule_render(&self.common.event_loop_handle, &output); } } diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index e779c50c..4d27bc22 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -3,7 +3,6 @@ use crate::{ shell::{element::CosmicWindow, grabs::ReleaseMode, CosmicMapped, CosmicSurface, ManagedLayer}, utils::prelude::*, - wayland::protocols::screencopy::SessionType, }; use smithay::{ delegate_xdg_shell, @@ -30,10 +29,7 @@ use smithay::{ use std::cell::Cell; use tracing::warn; -use super::{ - compositor::client_compositor_state, screencopy::PendingScreencopyBuffers, - toplevel_management::ToplevelManagementExt, -}; +use super::{compositor::client_compositor_state, toplevel_management::ToplevelManagementExt}; pub mod popup; @@ -400,32 +396,9 @@ impl XdgShellHandler for State { } } - // screencopy - let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface()); if let Some(output) = output.as_ref() { - if let Some(sessions) = output.user_data().get::() { - scheduled_sessions - .get_or_insert_with(Vec::new) - .extend(sessions.borrow_mut().drain(..)); - } - self.backend.schedule_render( - &self.common.event_loop_handle, - &output, - scheduled_sessions.as_ref().map(|sessions| { - sessions - .iter() - .filter(|(s, _)| match s.session_type() { - SessionType::Output(o) | SessionType::Workspace(o, _) - if &o == output => - { - true - } - _ => false, - }) - .cloned() - .collect::>() - }), - ); + self.backend + .schedule_render(&self.common.event_loop_handle, &output); } } diff --git a/src/wayland/protocols/image_source.rs b/src/wayland/protocols/image_source.rs new file mode 100644 index 00000000..1032de82 --- /dev/null +++ b/src/wayland/protocols/image_source.rs @@ -0,0 +1,343 @@ +use super::{ + toplevel_info::window_from_handle, + workspace::{WorkspaceHandle, WorkspaceHandler}, +}; +use crate::shell::CosmicSurface; +use cosmic_protocols::image_source::v1::server::{ + zcosmic_image_source_v1::ZcosmicImageSourceV1, + zcosmic_output_image_source_manager_v1::{ + Request as OutputSourceRequest, ZcosmicOutputImageSourceManagerV1, + }, + zcosmic_toplevel_image_source_manager_v1::{ + Request as ToplevelSourceRequest, ZcosmicToplevelImageSourceManagerV1, + }, + zcosmic_workspace_image_source_manager_v1::{ + Request as WorkspaceSourceRequest, ZcosmicWorkspaceImageSourceManagerV1, + }, +}; +use smithay::{ + output::{Output, WeakOutput}, + reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, +}; +use wayland_backend::server::GlobalId; + +#[derive(Debug)] +pub struct ImageSourceState { + output_source_global: GlobalId, + workspace_source_global: GlobalId, + toplevel_source_global: GlobalId, +} + +pub struct OutputImageSourceManagerGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} +pub struct WorkspaceImageSourceManagerGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} +pub struct ToplevelImageSourceManagerGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ImageSourceData { + Output(WeakOutput), + Workspace(WorkspaceHandle), + Toplevel(CosmicSurface), + Destroyed, +} + +impl ImageSourceState { + pub fn new(display: &DisplayHandle, client_filter: F) -> ImageSourceState + where + D: GlobalDispatch + + Dispatch + + GlobalDispatch< + ZcosmicWorkspaceImageSourceManagerV1, + WorkspaceImageSourceManagerGlobalData, + > + Dispatch + + GlobalDispatch< + ZcosmicToplevelImageSourceManagerV1, + ToplevelImageSourceManagerGlobalData, + > + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + Clone + 'static, + { + ImageSourceState { + output_source_global: display.create_global::( + 1, + OutputImageSourceManagerGlobalData { + filter: Box::new(client_filter.clone()), + }, + ), + workspace_source_global: display + .create_global::( + 1, + WorkspaceImageSourceManagerGlobalData { + filter: Box::new(client_filter.clone()), + }, + ), + toplevel_source_global: display + .create_global::( + 1, + ToplevelImageSourceManagerGlobalData { + filter: Box::new(client_filter), + }, + ), + } + } + + pub fn output_source_id(&self) -> &GlobalId { + &self.output_source_global + } + + pub fn workspace_source_id(&self) -> &GlobalId { + &self.workspace_source_global + } + + pub fn toplevel_source_id(&self) -> &GlobalId { + &self.toplevel_source_global + } +} + +impl GlobalDispatch + for ImageSourceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &OutputImageSourceManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &OutputImageSourceManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl + GlobalDispatch + for ImageSourceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &WorkspaceImageSourceManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &WorkspaceImageSourceManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl GlobalDispatch + for ImageSourceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &ToplevelImageSourceManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &ToplevelImageSourceManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for ImageSourceState +where + D: Dispatch + + Dispatch + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _resource: &ZcosmicOutputImageSourceManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + OutputSourceRequest::CreateSource { source, output } => { + let data = match Output::from_resource(&output) { + Some(output) => ImageSourceData::Output(output.downgrade()), + None => ImageSourceData::Destroyed, + }; + data_init.init(source, data); + } + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ZcosmicOutputImageSourceManagerV1, + _data: &(), + ) { + } +} + +impl Dispatch for ImageSourceState +where + D: Dispatch + + Dispatch + + WorkspaceHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &ZcosmicWorkspaceImageSourceManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + WorkspaceSourceRequest::CreateSource { source, output } => { + let data = match state.workspace_state().workspace_handle(&output) { + Some(workspace) => ImageSourceData::Workspace(workspace), + None => ImageSourceData::Destroyed, + }; + data_init.init(source, data); + } + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ZcosmicWorkspaceImageSourceManagerV1, + _data: &(), + ) { + } +} + +impl Dispatch for ImageSourceState +where + D: Dispatch + + Dispatch + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _resource: &ZcosmicToplevelImageSourceManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + ToplevelSourceRequest::CreateSource { + source, + toplevel_handle, + } => { + let data = match window_from_handle(toplevel_handle) { + Some(toplevel) => ImageSourceData::Toplevel(toplevel), + None => ImageSourceData::Destroyed, + }; + data_init.init(source, data); + } + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ZcosmicToplevelImageSourceManagerV1, + _data: &(), + ) { + } +} + +impl Dispatch for ImageSourceState +where + D: Dispatch + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _resource: &ZcosmicImageSourceV1, + request: ::Request, + _data: &ImageSourceData, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ZcosmicImageSourceV1, + _data: &ImageSourceData, + ) { + } +} + +macro_rules! delegate_image_source { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::image_source::v1::server::zcosmic_output_image_source_manager_v1::ZcosmicOutputImageSourceManagerV1: $crate::wayland::protocols::image_source::OutputImageSourceManagerGlobalData + ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::image_source::v1::server::zcosmic_output_image_source_manager_v1::ZcosmicOutputImageSourceManagerV1: () + ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::image_source::v1::server::zcosmic_workspace_image_source_manager_v1::ZcosmicWorkspaceImageSourceManagerV1: $crate::wayland::protocols::image_source::WorkspaceImageSourceManagerGlobalData + ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::image_source::v1::server::zcosmic_workspace_image_source_manager_v1::ZcosmicWorkspaceImageSourceManagerV1: () + ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::image_source::v1::server::zcosmic_toplevel_image_source_manager_v1::ZcosmicToplevelImageSourceManagerV1: $crate::wayland::protocols::image_source::ToplevelImageSourceManagerGlobalData + ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::image_source::v1::server::zcosmic_toplevel_image_source_manager_v1::ZcosmicToplevelImageSourceManagerV1: () + ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::image_source::v1::server::zcosmic_image_source_v1::ZcosmicImageSourceV1: $crate::wayland::protocols::image_source::ImageSourceData + ] => $crate::wayland::protocols::image_source::ImageSourceState); + }; +} +pub(crate) use delegate_image_source; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index e84f5ad1..c35b0b89 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod drm; -//pub mod export_dmabuf; +pub mod image_source; pub mod output_configuration; pub mod screencopy; pub mod toplevel_info; diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs index 5167110a..54188759 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy.rs @@ -1,532 +1,502 @@ -// 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 as WlCursorMode, ZcosmicScreencopyManagerV1}, - zcosmic_screencopy_session_v1::{ - self, BufferType, FailureReason, InputType, ZcosmicScreencopySessionV1, - }, +pub use cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_frame_v2::FailureReason; +use cosmic_protocols::screencopy::v2::server::{ + zcosmic_screencopy_cursor_session_v2::{self, ZcosmicScreencopyCursorSessionV2}, + zcosmic_screencopy_frame_v2::{self, ZcosmicScreencopyFrameV2}, + zcosmic_screencopy_manager_v2::{self, ZcosmicScreencopyManagerV2}, + zcosmic_screencopy_session_v2::{self, ZcosmicScreencopySessionV2}, }; use smithay::{ backend::{ - allocator::Fourcc as DrmFourcc, - drm::{DrmNode, NodeType}, + allocator::{Buffer, Fourcc, Modifier}, + drm::DrmNode, + renderer::{buffer_type, BufferType}, }, - input::{Seat, SeatHandler}, - output::Output, + utils::{user_data::UserDataMap, Buffer as BufferCoords, IsAlive, Size, Transform}, + wayland::{dmabuf::get_dmabuf, shm::with_buffer_contents}, +}; +use smithay::{reexports::wayland_server::protocol::wl_buffer::WlBuffer, utils::Point}; +use smithay::{ reexports::wayland_server::{ - protocol::{wl_buffer::WlBuffer, wl_output, wl_seat::WlSeat, wl_shm::Format as ShmFormat}, - Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + protocol::wl_shm, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, }, - utils::{user_data::UserDataMap, Buffer, IsAlive, Physical, Point, Rectangle, Size, Transform}, + utils::Rectangle, }; -use tracing::warn; -use wayland_backend::{ - protocol::WEnum, - server::{GlobalId, ObjectId}, -}; - -use crate::{shell::CosmicSurface, state::State}; +use tracing::debug; +use wayland_backend::server::GlobalId; -use super::{ - toplevel_info::{window_from_handle, ToplevelInfoHandler}, - workspace::{WorkspaceHandle, WorkspaceHandler}, -}; +use super::image_source::ImageSourceData; -/// Screencopy global state #[derive(Debug)] pub struct ScreencopyState { global: GlobalId, -} - -pub struct ScreencopyGlobalData { - cursor_modes: Vec, - filter: Box Fn(&'a Client) -> bool + Send + Sync>, + known_sessions: Vec, + known_cursor_sessions: Vec, } impl ScreencopyState { - /// Create a new screencopy global - pub fn new( - display: &DisplayHandle, - cursor_modes: I, - client_filter: F, - ) -> ScreencopyState + pub fn new(display: &DisplayHandle, client_filter: F) -> ScreencopyState where - D: GlobalDispatch - + Dispatch> - + Dispatch + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + ScreencopyHandler - + WorkspaceHandler + 'static, - I: IntoIterator, F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, { ScreencopyState { - global: display.create_global::( + global: display.create_global::( 1, ScreencopyGlobalData { - cursor_modes: Vec::from_iter(cursor_modes), filter: Box::new(client_filter), }, ), + known_sessions: Vec::new(), + known_cursor_sessions: Vec::new(), } } - /// Returns the screencopy global id - pub fn global(&self) -> GlobalId { - self.global.clone() + pub fn global_id(&self) -> &GlobalId { + &self.global } } -#[derive(Debug)] -pub enum BufferInfo { - Shm { - format: ShmFormat, - size: Size, - stride: u32, - }, - Dmabuf { - node: DrmNode, - format: DrmFourcc, - size: Size, - }, +#[derive(Debug, Clone)] +pub struct BufferConstraints { + pub size: Size, + pub shm: Vec, + pub dma: Option, } -#[derive(Debug)] -pub struct SessionDataInnerInner { - gone: bool, - pending_buffer: Option, - _type: SessionType, - aux: AuxData, +#[derive(Debug, Clone)] +pub struct DmabufConstraints { + pub node: DrmNode, + pub formats: Vec<(Fourcc, Vec)>, } -impl SessionDataInnerInner { - pub fn is_cursor(&self) -> bool { - match self.aux { - AuxData::Cursor { .. } => true, - _ => false, - } - } +#[derive(Debug, Clone)] +pub struct Session { + obj: ZcosmicScreencopySessionV2, + inner: Arc>, + user_data: Arc, } -#[derive(Debug, Clone, PartialEq)] -pub enum SessionType { - Output(Output), - Workspace(Output, WorkspaceHandle), - Window(CosmicSurface), - Cursor(Seat), - #[doc(hidden)] - Unknown, +impl PartialEq for Session { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } } #[derive(Debug)] -enum AuxData { - Normal { cursor: CursorMode }, - Cursor { seat: WlSeat }, +struct SessionInner { + stopped: bool, + constraints: Option, + draw_cursors: bool, + source: ImageSourceData, + active_frames: Vec, } -#[derive(Debug, Clone, PartialEq)] -pub enum CursorMode { - Captured(Vec), - Embedded, - None, -} - -impl AuxData { - pub fn seat(&self) -> &WlSeat { - match self { - AuxData::Cursor { seat } => seat, - _ => unreachable!("Unwrapped seat from aux data"), - } - } - - pub fn cursor(&self) -> &CursorMode { - match self { - AuxData::Normal { cursor } => &cursor, - _ => unreachable!("Unwrapped cursor from aux data"), +impl SessionInner { + fn new(source: ImageSourceData, draw_cursors: bool) -> SessionInner { + SessionInner { + stopped: false, + constraints: None, + draw_cursors, + source, + active_frames: Vec::new(), } } } -impl CursorMode { - pub fn sessions<'a>(&'a self) -> impl Iterator { - match self { - CursorMode::Captured(sessions) => Some(sessions.iter()).into_iter().flatten(), - _ => None.into_iter().flatten(), - } +impl IsAlive for Session { + fn alive(&self) -> bool { + self.obj.is_alive() } } -#[derive(Debug)] -pub struct SessionDataInner { - inner: Mutex, - user_data: UserDataMap, -} - -pub type SessionData = Arc; - -#[derive(Debug, Clone)] -pub struct Session { - obj: SessionResource, - data: SessionData, -} - -#[derive(Debug, Clone)] -enum SessionResource { - Alive(ZcosmicScreencopySessionV1), - Destroyed(ObjectId), -} +impl Session { + pub fn update_constraints(&self, constraints: BufferConstraints) { + let mut inner = self.inner.lock().unwrap(); -impl SessionResource { - fn client(&self) -> Option { - match self { - SessionResource::Alive(obj) => obj.client(), - _ => None, + if !self.obj.is_alive() || inner.stopped { + return; } - } - fn buffer_info( - &self, - _type: BufferType, - node: Option, - format: u32, - width: u32, - height: u32, - stride: u32, - ) { - if let SessionResource::Alive(obj) = self { - obj.buffer_info(_type, node, format, width, height, stride) + self.obj + .buffer_size(constraints.size.w as u32, constraints.size.h as u32); + for fmt in &constraints.shm { + self.obj.shm_format(*fmt as u32); } - } - - fn init_done(&self) { - if let SessionResource::Alive(obj) = self { - obj.init_done() + if let Some(dma) = constraints.dma.as_ref() { + let node = Vec::from(dma.node.dev_id().to_ne_bytes()); + self.obj.dmabuf_device(node); + for (fmt, modifiers) in &dma.formats { + let mut modifiers = modifiers.clone(); + let modifiers: Vec = { + let ptr = modifiers.as_mut_ptr() as *mut u8; + let len = modifiers.len() * 4; + let cap = modifiers.capacity() * 4; + std::mem::forget(modifiers); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + self.obj.dmabuf_format(*fmt as u32, modifiers); + } } - } + self.obj.done(); - fn transform(&self, transform: wl_output::Transform) { - if let SessionResource::Alive(obj) = self { - obj.transform(transform) - } + inner.constraints = Some(constraints); } - fn damage(&self, x: u32, y: u32, w: u32, h: u32) { - if let SessionResource::Alive(obj) = self { - obj.damage(x, y, w, h) - } + pub fn current_constraints(&self) -> Option { + self.inner.lock().unwrap().constraints.clone() } - fn commit_time(&self, time_sec_hi: u32, time_sec_lo: u32, time_nsec: u32) { - if let SessionResource::Alive(obj) = self { - obj.commit_time(time_sec_hi, time_sec_lo, time_nsec) - } + pub fn source(&self) -> ImageSourceData { + self.inner.lock().unwrap().source.clone() } - fn ready(&self) { - if let SessionResource::Alive(obj) = self { - obj.ready() - } + pub fn draw_cursor(&self) -> bool { + self.inner.lock().unwrap().draw_cursors } - fn failed(&self, reason: FailureReason) { - if let SessionResource::Alive(obj) = self { - obj.failed(reason) - } + pub fn user_data(&self) -> &UserDataMap { + &*self.user_data } - fn cursor_enter(&self, wl_seat: &WlSeat, input_type: InputType) { - if let SessionResource::Alive(obj) = self { - obj.cursor_enter(wl_seat, input_type) - } - } + pub fn stop(self) { + let mut inner = self.inner.lock().unwrap(); - fn cursor_info( - &self, - wl_seat: &WlSeat, - input_type: InputType, - x: i32, - y: i32, - w: i32, - h: i32, - dx: i32, - dy: i32, - ) { - if let SessionResource::Alive(obj) = self { - obj.cursor_info(wl_seat, input_type, x, y, w, h, dx, dy) + if !self.obj.is_alive() || inner.stopped { + return; } - } - fn cursor_leave(&self, wl_seat: &WlSeat, input_type: InputType) { - if let SessionResource::Alive(obj) = self { - obj.cursor_leave(wl_seat, input_type) + for frame in inner.active_frames.drain(..) { + let mut inner = frame.inner.lock().unwrap(); + if inner.failed.replace(FailureReason::Stopped).is_none() && inner.capture_requested { + frame.obj.failed(FailureReason::Stopped); + } } + + self.obj.stopped(); + inner.constraints.take(); + inner.stopped = true; } } -impl PartialEq for SessionResource { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (SessionResource::Alive(obj1), SessionResource::Alive(obj2)) => obj1 == obj2, - (SessionResource::Alive(obj), SessionResource::Destroyed(id)) - | (SessionResource::Destroyed(id), SessionResource::Alive(obj)) => obj.id() == *id, - (SessionResource::Destroyed(id1), SessionResource::Destroyed(id2)) => id1 == id2, - } - } +#[derive(Debug, Clone)] +pub struct CursorSession { + obj: ZcosmicScreencopyCursorSessionV2, + inner: Arc>, + user_data: Arc, } -impl PartialEq for Session { +impl PartialEq for CursorSession { fn eq(&self, other: &Self) -> bool { self.obj == other.obj } } -// TODO: Handle Alive - -// TODO: Better errors +#[derive(Debug)] +struct CursorSessionInner { + session: Option, + stopped: bool, + constraints: Option, + source: ImageSourceData, + position: Option>, + hotspot: Point, + active_frames: Vec, +} -impl Session { - pub fn cursor_enter(&self, seat: &Seat, input_type: InputType) { - if !self.alive() { - return; - } - if let Some(client) = self.obj.client() { - for wl_seat in seat.client_seats(&client) { - self.obj.cursor_enter(&wl_seat, input_type) - } +impl CursorSessionInner { + fn new(source: ImageSourceData) -> CursorSessionInner { + CursorSessionInner { + session: None, + stopped: false, + constraints: None, + source, + position: None, + hotspot: Point::from((0, 0)), + active_frames: Vec::new(), } } +} - pub fn cursor_info( - &self, - seat: &Seat, - input_type: InputType, - geometry: Rectangle, - offset: Point, - ) { - if !self.alive() { +impl IsAlive for CursorSession { + fn alive(&self) -> bool { + self.obj.is_alive() + } +} + +impl CursorSession { + pub fn update_constraints(&self, constrains: BufferConstraints) { + let mut inner = self.inner.lock().unwrap(); + + if !self.obj.is_alive() || inner.stopped { return; } - if let Some(client) = self.obj.client() { - for wl_seat in seat.client_seats(&client) { - self.obj.cursor_info( - &wl_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( - &wl_seat, - input_type, - geometry.loc.x, - geometry.loc.y, - geometry.size.w, - geometry.size.h, - offset.x, - offset.y, - ); + + if let Some(session_obj) = inner.session.as_ref() { + session_obj.buffer_size(constrains.size.w as u32, constrains.size.h as u32); + for fmt in &constrains.shm { + session_obj.shm_format(*fmt as u32); + } + if let Some(dma) = constrains.dma.as_ref() { + let node = Vec::from(dma.node.dev_id().to_ne_bytes()); + session_obj.dmabuf_device(node); + for (fmt, modifiers) in &dma.formats { + let mut modifiers = modifiers.clone(); + let modifiers: Vec = { + let ptr = modifiers.as_mut_ptr() as *mut u8; + let len = modifiers.len() * 4; + let cap = modifiers.capacity() * 4; + std::mem::forget(modifiers); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + session_obj.dmabuf_format(*fmt as u32, modifiers); } } + session_obj.done(); } + + inner.constraints = Some(constrains); + } + + pub fn current_constraints(&self) -> Option { + self.inner.lock().unwrap().constraints.clone() } - pub fn cursor_leave(&self, seat: &Seat, input_type: InputType) { - if !self.alive() { + pub fn source(&self) -> ImageSourceData { + self.inner.lock().unwrap().source.clone() + } + + pub fn has_cursor(&self) -> bool { + self.inner.lock().unwrap().position.is_some() + } + + pub fn set_cursor_pos(&self, position: Option>) { + if !self.obj.is_alive() { return; } - if let Some(client) = self.obj.client() { - for wl_seat in seat.client_seats(&client) { - self.obj.cursor_leave(&wl_seat, input_type) - } + + let mut inner = self.inner.lock().unwrap(); + + if inner.position == position { + return; } - } - pub fn cursor_sessions(&self) -> impl Iterator { - if !self.alive() { - return Vec::new().into_iter(); + if inner.position.is_none() { + self.obj.enter(); + self.obj.hotspot(inner.hotspot.x, inner.hotspot.y) } - self.data - .inner - .lock() - .unwrap() - .aux - .cursor() - .sessions() - .cloned() - .collect::>() - .into_iter() + + if let Some(new_pos) = position { + self.obj.position(new_pos.x, new_pos.y); + } else { + self.obj.leave() + } + + inner.position = position; } - pub fn commit_buffer( - &self, - transform: Transform, - damage: Vec>, - time: Option, - ) { - if !self.alive() { + pub fn set_cursor_hotspot(&self, hotspot: impl Into>) { + if !self.obj.is_alive() { return; } - 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) { - if !self.alive() { + let hotspot = hotspot.into(); + + let mut inner = self.inner.lock().unwrap(); + + if inner.hotspot == hotspot { return; } - self.obj.failed(reason); - self.data.inner.lock().unwrap().gone = true; + + inner.hotspot = hotspot; + if inner.position.is_some() { + self.obj.hotspot(hotspot.x, hotspot.y); + } } pub fn user_data(&self) -> &UserDataMap { - &self.data.user_data + &*self.user_data } - pub fn session_type(&self) -> SessionType { - self.data.inner.lock().unwrap()._type.clone() - } + pub fn stop(self) { + let mut inner = self.inner.lock().unwrap(); - pub fn cursor_mode(&self) -> CursorMode { - self.data.inner.lock().unwrap().aux.cursor().clone() - } -} + if !self.obj.is_alive() || inner.stopped { + return; + } -impl IsAlive for Session { - fn alive(&self) -> bool { - !self.data.inner.lock().unwrap().gone + if let Some(session_obj) = inner.session.as_ref() { + session_obj.stopped(); + } + inner.constraints.take(); + + for frame in inner.active_frames.drain(..) { + let mut inner = frame.inner.lock().unwrap(); + if inner.failed.replace(FailureReason::Stopped).is_none() && inner.capture_requested { + frame.obj.failed(FailureReason::Stopped); + } + } + + inner.stopped = true; } } -#[derive(Debug, Clone)] -pub struct CursorSession { - obj: SessionResource, - data: SessionData, +#[derive(Debug)] +pub struct Frame { + obj: ZcosmicScreencopyFrameV2, + inner: Arc>, } -impl PartialEq for CursorSession { +impl PartialEq for Frame { 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() +impl Frame { + pub fn buffer(&self) -> WlBuffer { + self.inner.lock().unwrap().buffer.clone().unwrap() } - pub fn buffer_waiting(&self) -> Option { - self.data.inner.lock().unwrap().pending_buffer.take() + pub fn damage(&self) -> Vec> { + self.inner.lock().unwrap().damage.clone() } - pub fn commit_buffer<'a>( - &self, - transform: Transform, - damage: impl Iterator> + 'a, + pub fn has_failed(&self) -> bool { + self.inner.lock().unwrap().failed.is_some() + } + + pub fn success( + self, + transform: impl Into, + damage: impl Into>>>, + presented: impl Into, ) { - 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, - ); + { + let inner = self.inner.lock().unwrap(); + if !inner.capture_requested || inner.failed.is_some() { + return; + } } - } - pub fn failed(&self, reason: FailureReason) { - self.obj.failed(reason); - self.data.inner.lock().unwrap().gone = true; + self.obj.transform(transform.into().into()); + for damage in damage.into().into_iter().flatten() { + self.obj + .damage(damage.loc.x, damage.loc.y, damage.size.w, damage.size.h); + } + + let time = presented.into(); + let tv_sec_hi = (time.as_secs() >> 32) as u32; + let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; + let tv_nsec = time.subsec_nanos(); + self.obj.presentation_time(tv_sec_hi, tv_sec_lo, tv_nsec); + + self.obj.ready() } - pub fn user_data(&self) -> &UserDataMap { - &self.data.user_data + pub fn fail(self, reason: FailureReason) { + let mut inner = self.inner.lock().unwrap(); + inner.failed = Some(reason); + if inner.capture_requested { + self.obj.failed(reason); + } } } -impl IsAlive for CursorSession { - fn alive(&self) -> bool { - !self.data.inner.lock().unwrap().gone - } +#[derive(Debug)] +struct FrameInner { + constraints: Option, + buffer: Option, + damage: Vec>, + obj: ZcosmicScreencopySessionV2, + capture_requested: bool, + failed: Option, } -#[derive(Debug, Clone)] -pub struct BufferParams { - pub buffer: WlBuffer, - pub node: Option, - pub age: u32, +impl FrameInner { + fn new( + obj: ZcosmicScreencopySessionV2, + constraints: impl Into>, + ) -> Self { + FrameInner { + constraints: constraints.into(), + buffer: None, + damage: Vec::new(), + obj, + capture_requested: false, + failed: None, + } + } } pub trait ScreencopyHandler { - fn capture_output(&mut self, output: Output, session: Session) -> Vec; + fn screencopy_state(&mut self) -> &mut ScreencopyState; - fn capture_workspace( - &mut self, - workspace: WorkspaceHandle, - output: Output, - session: Session, - ) -> Vec; + fn capture_source(&mut self, source: &ImageSourceData) -> Option; + fn capture_cursor_source(&mut self, source: &ImageSourceData) -> Option; - fn capture_toplevel(&mut self, toplevel: CosmicSurface, session: Session) -> Vec; + fn new_session(&mut self, session: Session); + fn new_cursor_session(&mut self, session: CursorSession); - fn capture_cursor(&mut self, session: CursorSession) -> Vec; + fn frame(&mut self, session: Session, frame: Frame); + fn cursor_frame(&mut self, session: CursorSession, frame: Frame); - fn buffer_attached(&mut self, session: Session, buffer: BufferParams, on_damage: bool); - - fn cursor_session_destroyed(&mut self, session: CursorSession) { + fn frame_aborted(&mut self, frame: Frame); + fn session_destroyed(&mut self, session: Session) { let _ = session; } - - fn session_destroyed(&mut self, session: Session) { + fn cursor_session_destroyed(&mut self, session: CursorSession) { let _ = session; } } -impl GlobalDispatch for ScreencopyState +pub struct ScreencopyGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +pub struct ScreencopyData; + +pub struct SessionData { + inner: Arc>, +} + +pub struct CursorSessionData { + inner: Arc>, +} +pub struct FrameData { + inner: Arc>, +} + +impl GlobalDispatch for ScreencopyState where - D: GlobalDispatch - + Dispatch> - + Dispatch + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + ScreencopyHandler - + WorkspaceHandler + 'static, { fn bind( _state: &mut D, _handle: &DisplayHandle, _client: &Client, - resource: New, - global_data: &ScreencopyGlobalData, + resource: New, + _global_data: &ScreencopyGlobalData, data_init: &mut DataInit<'_, D>, ) { - let global = data_init.init(resource, global_data.cursor_modes.clone()); - for mode in &global_data.cursor_modes { - global.supported_cursor_mode(*mode); - } + data_init.init(resource, ScreencopyData); } fn can_view(client: Client, global_data: &ScreencopyGlobalData) -> bool { @@ -534,348 +504,541 @@ where } } -fn check_cursor( - cursor: WEnum, - supported: &[WlCursorMode], - resource: &ZcosmicScreencopyManagerV1, -) -> Option { - match cursor.into_result() { - Ok(mode) => { - if !supported.contains(&mode) { - warn!(?mode, "Client did send unsupported cursor mode"); - resource.post_error( - zcosmic_screencopy_manager_v1::Error::InvalidCursorMode, - "Unsupported cursor mode", +impl Dispatch for ScreencopyState +where + D: Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &ZcosmicScreencopyManagerV2, + request: ::Request, + _data: &ScreencopyData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_manager_v2::Request::CreateSession { + session, + source, + options, + } => { + if let Some(src) = source.data::() { + if *src != ImageSourceData::Destroyed { + if let Some(buffer_constraints) = state.capture_source(src) { + let session_data = Arc::new(Mutex::new(SessionInner::new( + src.clone(), + Into::::into(options) == 1, + ))); + let obj = data_init.init( + session, + SessionData { + inner: session_data.clone(), + }, + ); + + let session = Session { + obj, + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.update_constraints(buffer_constraints); + state + .screencopy_state() + .known_sessions + .push(session.clone()); + state.new_session(session); + return; + } + } + } + + let session_data = Arc::new(Mutex::new(SessionInner::new( + ImageSourceData::Destroyed, + false, + ))); + let obj = data_init.init( + session, + SessionData { + inner: session_data.clone(), + }, ); - return None; + let session = Session { + obj, + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.stop(); } - Some(mode) - } - Err(err) => { - warn!(?err, "Client did send unknown cursor mode"); - resource.post_error( - zcosmic_screencopy_manager_v1::Error::InvalidCursorMode, - "Unknown cursor mode, wrong protocol version?", - ); - None + zcosmic_screencopy_manager_v2::Request::CreatePointerCursorSession { + session, + source, + pointer: _, + options: _, + } => { + // TODO: use pointer, but we need new smithay api for that. + + if let Some(src) = source.data::() { + if *src != ImageSourceData::Destroyed { + if let Some(buffer_constraints) = state.capture_cursor_source(src) { + let session_data = + Arc::new(Mutex::new(CursorSessionInner::new(src.clone()))); + let obj = data_init.init( + session, + CursorSessionData { + inner: session_data.clone(), + }, + ); + + let session = CursorSession { + obj, + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.update_constraints(buffer_constraints); + state + .screencopy_state() + .known_cursor_sessions + .push(session.clone()); + state.new_cursor_session(session); + return; + } + } + } + + let session_data = Arc::new(Mutex::new(CursorSessionInner::new( + ImageSourceData::Destroyed, + ))); + let obj = data_init.init( + session, + CursorSessionData { + inner: session_data.clone(), + }, + ); + let session = CursorSession { + obj, + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.stop(); + } + _ => {} } } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ZcosmicScreencopyManagerV2, + _data: &ScreencopyData, + ) { + } } -fn init_session( - data_init: &mut DataInit<'_, D>, - session: New, - cursor: WlCursorMode, - _type: SessionType, -) -> Option +impl Dispatch for ScreencopyState where - D: GlobalDispatch - + Dispatch> - + Dispatch + D: Dispatch + + Dispatch + ScreencopyHandler - + WorkspaceHandler + 'static, { - let data = Arc::new(SessionDataInner { - inner: Mutex::new(SessionDataInnerInner { - gone: false, - pending_buffer: None, - aux: AuxData::Normal { - cursor: match cursor { - WlCursorMode::Capture => CursorMode::Captured(Vec::new()), - WlCursorMode::Embedded => CursorMode::Embedded, - _ => CursorMode::None, - }, - }, - _type, - }), - user_data: UserDataMap::new(), - }); - let session = data_init.init(session, data.clone()); - - let session = Session { - obj: SessionResource::Alive(session), - data, - }; + fn request( + _state: &mut D, + _client: &Client, + resource: &ZcosmicScreencopySessionV2, + request: ::Request, + data: &SessionData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_session_v2::Request::CreateFrame { frame } => { + let inner = Arc::new(Mutex::new(FrameInner::new( + resource.clone(), + data.inner.lock().unwrap().constraints.clone(), + ))); + let obj = data_init.init( + frame, + FrameData { + inner: inner.clone(), + }, + ); + data.inner + .lock() + .unwrap() + .active_frames + .push(Frame { obj, inner }); + } + _ => {} + } + } - Some(session) + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: &ZcosmicScreencopySessionV2, + _data: &SessionData, + ) { + let scpy = state.screencopy_state(); + if let Some(pos) = scpy + .known_sessions + .iter() + .position(|session| session.obj == *resource) + { + let session = scpy.known_sessions.remove(pos); + state.session_destroyed(session); + } + } } -impl Dispatch, D> for ScreencopyState +impl Dispatch for ScreencopyState where - D: GlobalDispatch - + Dispatch> - + Dispatch - + ToplevelInfoHandler + D: Dispatch + + Dispatch + + Dispatch + ScreencopyHandler - + WorkspaceHandler + 'static, { fn request( - state: &mut D, + _state: &mut D, _client: &Client, - resource: &ZcosmicScreencopyManagerV1, - request: ::Request, - data: &Vec, + resource: &ZcosmicScreencopyCursorSessionV2, + request: ::Request, + data: &CursorSessionData, _dhandle: &DisplayHandle, data_init: &mut DataInit<'_, D>, ) { match request { - zcosmic_screencopy_manager_v1::Request::CaptureOutput { - session, - output, - cursor, - } => { - let Some(cursor) = check_cursor(cursor, &data, resource) else { - return; + zcosmic_screencopy_cursor_session_v2::Request::GetScreencopySession { session } => { + let new_data = CursorSessionData { + inner: data.inner.clone(), }; + let session = data_init.init(session, new_data); - match Output::from_resource(&output) { - Some(output) => { - let session = match init_session( - data_init, - session, - cursor, - SessionType::Output(output.clone()), - ) { - Some(result) => result, - None => { - return; - } - }; - let formats = state.capture_output(output, session.clone()); - if !session.data.inner.lock().unwrap().gone { - send_formats(&session.obj, formats); - } - } - None => { - let session = - match init_session(data_init, session, cursor, SessionType::Unknown) { - Some(result) => result, - None => { - return; - } - }; - session.failed(FailureReason::InvalidOutput); - return; - } - } - } - zcosmic_screencopy_manager_v1::Request::CaptureToplevel { - session, - toplevel, - cursor, - } => { - let Some(cursor) = check_cursor(cursor, &data, resource) else { + let mut inner = data.inner.lock().unwrap(); + if inner.session.is_some() { + resource.post_error( + zcosmic_screencopy_cursor_session_v2::Error::DuplicateSession, + "Duplicate session", + ); return; - }; - - match window_from_handle::<::Window>(toplevel) { - Some(window) => { - let session = match init_session( - data_init, - session, - cursor, - SessionType::Window(window.clone()), - ) { - Some(result) => result, - None => { - return; - } - }; - - let formats = state.capture_toplevel(window, session.clone()); - if !session.data.inner.lock().unwrap().gone { - send_formats(&session.obj, formats); - } - } - None => { - let session = - match init_session(data_init, session, cursor, SessionType::Unknown) { - Some(result) => result, - None => { - return; - } - }; - session.obj.failed(FailureReason::InvalidToplevel); - return; - } } - } - zcosmic_screencopy_manager_v1::Request::CaptureWorkspace { - session, - workspace, - output, - cursor, - } => { - let Some(cursor) = check_cursor(cursor, &data, resource) else { - return; - }; - match Output::from_resource(&output) { - Some(output) => match state.workspace_state().workspace_handle(&workspace) { - Some(handle) => { - let session = match init_session( - data_init, - session, - cursor, - SessionType::Workspace(output.clone(), handle.clone()), - ) { - Some(result) => result, - None => { - return; - } + if inner.stopped { + session.stopped(); + } else if let Some(constraints) = inner.constraints.as_ref() { + session.buffer_size(constraints.size.w as u32, constraints.size.h as u32); + for fmt in &constraints.shm { + session.shm_format(*fmt as u32); + } + if let Some(dma) = constraints.dma.as_ref() { + let node = Vec::from(dma.node.dev_id().to_ne_bytes()); + session.dmabuf_device(node); + for (fmt, modifiers) in &dma.formats { + let mut modifiers = modifiers.clone(); + let modifiers: Vec = { + let ptr = modifiers.as_mut_ptr() as *mut u8; + let len = modifiers.len() * 4; + let cap = modifiers.capacity() * 4; + std::mem::forget(modifiers); + unsafe { Vec::from_raw_parts(ptr, len, cap) } }; - let formats = state.capture_workspace(handle, output, session.clone()); - if !session.data.inner.lock().unwrap().gone { - send_formats(&session.obj, formats); - } + session.dmabuf_format(*fmt as u32, modifiers); } - None => { - let session = match init_session( - data_init, - session, - cursor, - SessionType::Unknown, - ) { - Some(result) => result, - None => { - return; - } - }; - session.failed(FailureReason::InvalidWorkspace); - return; - } - }, - None => { - let session = - match init_session(data_init, session, cursor, SessionType::Unknown) { - Some(result) => result, - None => { - return; - } - }; - session.failed(FailureReason::InvalidOutput); - return; } + session.done(); } + inner.session = Some(session); } _ => {} } } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: &ZcosmicScreencopyCursorSessionV2, + _data: &CursorSessionData, + ) { + let scpy = state.screencopy_state(); + if let Some(pos) = scpy + .known_cursor_sessions + .iter() + .position(|session| session.obj == *resource) + { + let session = scpy.known_cursor_sessions.remove(pos); + state.cursor_session_destroyed(session); + } + } } -impl Dispatch for ScreencopyState +impl Dispatch for ScreencopyState where - D: GlobalDispatch - + Dispatch> - + Dispatch + D: Dispatch + + Dispatch + ScreencopyHandler - + WorkspaceHandler + 'static, { fn request( - state: &mut D, + _state: &mut D, _client: &Client, - resource: &ZcosmicScreencopySessionV1, - request: ::Request, - data: &SessionData, + resource: &ZcosmicScreencopySessionV2, + request: ::Request, + data: &CursorSessionData, _dhandle: &DisplayHandle, data_init: &mut DataInit<'_, D>, ) { match request { - zcosmic_screencopy_session_v1::Request::CaptureCursor { - session, - seat: wl_seat, - } => match Seat::from_resource(&wl_seat) { - Some(seat) => { - { - let resource_data = data.inner.lock().unwrap(); - if resource_data.is_cursor() || resource_data.gone { - resource.failed(FailureReason::Unspec); - return; - } - } + zcosmic_screencopy_session_v2::Request::CreateFrame { frame } => { + let inner = Arc::new(Mutex::new(FrameInner::new( + resource.clone(), + data.inner.lock().unwrap().constraints.clone(), + ))); + let obj = data_init.init( + frame, + FrameData { + inner: inner.clone(), + }, + ); + data.inner + .lock() + .unwrap() + .active_frames + .push(Frame { obj, inner }); + } + _ => {} + } + } - let data = Arc::new(SessionDataInner { - inner: Mutex::new(SessionDataInnerInner { - gone: false, - pending_buffer: None, - aux: AuxData::Cursor { seat: wl_seat }, - _type: SessionType::Cursor(seat), - }), - user_data: UserDataMap::new(), - }); - let session = data_init.init(session, data.clone()); - - let cursor_session = CursorSession { - obj: SessionResource::Alive(session), - data, - }; - let formats = state.capture_cursor(cursor_session.clone()); - if !cursor_session.data.inner.lock().unwrap().gone { - send_formats(&cursor_session.obj, formats); - } + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ZcosmicScreencopySessionV2, + _data: &CursorSessionData, + ) { + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + ScreencopyHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZcosmicScreencopyFrameV2, + request: ::Request, + data: &FrameData, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_frame_v2::Request::AttachBuffer { buffer } => { + let mut inner = data.inner.lock().unwrap(); + + if inner.capture_requested { + resource.post_error( + zcosmic_screencopy_frame_v2::Error::AlreadyCaptured, + "Frame was captured previously", + ); } - None => { - let session = match init_session( - data_init, - session, - WlCursorMode::Capture, - SessionType::Unknown, - ) { - Some(result) => result, - None => { - return; - } - }; - session.failed(FailureReason::InvalidSeat); + + inner.buffer = Some(buffer); + } + zcosmic_screencopy_frame_v2::Request::DamageBuffer { + x, + y, + width, + height, + } => { + let mut inner = data.inner.lock().unwrap(); + + if inner.capture_requested { + resource.post_error( + zcosmic_screencopy_frame_v2::Error::AlreadyCaptured, + "Frame was captured previously", + ); + } + + if x < 0 || y < 0 || width <= 0 || height <= 0 { + resource.post_error( + zcosmic_screencopy_frame_v2::Error::InvalidBufferDamage, + "Coordinates negative or size equal to zero", + ); return; } - }, - zcosmic_screencopy_session_v1::Request::AttachBuffer { buffer, node, age } => { - if data.inner.lock().unwrap().gone { - resource.failed(FailureReason::Unspec); + + inner + .damage + .push(Rectangle::from_loc_and_size((x, y), (width, height))); + } + zcosmic_screencopy_frame_v2::Request::Capture => { + let mut inner = data.inner.lock().unwrap(); + + if inner.capture_requested { + resource.post_error( + zcosmic_screencopy_frame_v2::Error::AlreadyCaptured, + "Frame was captured previously", + ); + } + + if inner.buffer.is_none() { + resource.post_error( + zcosmic_screencopy_frame_v2::Error::NoBuffer, + "Attempting to capture frame without a buffer", + ); + } + + inner.capture_requested = true; + + if let Some(reason) = inner.failed { + resource.failed(reason); return; } - let params = BufferParams { - buffer, - node: node.and_then(|p| DrmNode::from_path(p).ok()), - age, + + if let Some(constraints) = inner.constraints.as_ref() { + let buffer = inner.buffer.as_ref().unwrap(); + match buffer_type(buffer) { + Some(BufferType::Dma) => { + let Some(dma_constraints) = constraints.dma.as_ref() else { + debug!("dma buffer not specified for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed(FailureReason::BufferConstraints); + return; + }; + + let dmabuf = match get_dmabuf(buffer) { + Ok(buf) => buf, + Err(err) => { + debug!(?err, "Error accessing dma buffer for screencopy"); + inner.failed = Some(FailureReason::Unknown); + resource.failed(FailureReason::Unknown); + return; + } + }; + + let buffer_size = dmabuf.size(); + if buffer_size.w < constraints.size.w + || buffer_size.h < constraints.size.h + { + debug!(?buffer_size, ?constraints.size, "buffer too small for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed(FailureReason::BufferConstraints); + return; + } + + let format = dmabuf.format(); + if dma_constraints + .formats + .iter() + .find(|(fourcc, _)| *fourcc == format.code) + .filter(|(_, modifiers)| modifiers.contains(&format.modifier)) + .is_none() + { + debug!( + ?format, + ?dma_constraints, + "unsupported buffer format for screencopy" + ); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed(FailureReason::BufferConstraints); + return; + } + } + Some(BufferType::Shm) => { + let buffer_data = match with_buffer_contents(buffer, |_, _, data| data) + { + Ok(data) => data, + Err(err) => { + debug!(?err, "Error accessing shm buffer for screencopy"); + inner.failed = Some(FailureReason::Unknown); + resource.failed(FailureReason::Unknown); + return; + } + }; + + if buffer_data.width < constraints.size.w + || buffer_data.height < constraints.size.h + { + debug!(?buffer_data, ?constraints.size, "buffer too small for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed(FailureReason::BufferConstraints); + return; + } + + if !constraints.shm.contains(&buffer_data.format) { + debug!(?buffer_data.format, ?constraints.shm, "unsupported buffer format for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed(FailureReason::BufferConstraints); + return; + } + } + x => { + debug!(?x, "Attempt to screencopy with unsupported buffer type"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed(FailureReason::BufferConstraints); + return; + } + } + } else { + inner.failed = Some(FailureReason::Unknown); + resource.failed(FailureReason::Unknown); + return; + } + + let frame = Frame { + obj: resource.clone(), + inner: data.inner.clone(), }; - data.inner.lock().unwrap().pending_buffer = Some(params); - } - zcosmic_screencopy_session_v1::Request::Commit { options } => { - let buffer = { - let mut resource_data = data.inner.lock().unwrap(); - if resource_data.is_cursor() || resource_data.gone { - resource.failed(FailureReason::Unspec); + + let scpy = state.screencopy_state(); + if let Some(session) = scpy + .known_sessions + .iter() + .find(|session| session.obj == inner.obj) + .map(|s| Session { + obj: s.obj.clone(), + inner: s.inner.clone(), + user_data: s.user_data.clone(), + }) + { + if session.inner.lock().unwrap().stopped { + resource.failed(FailureReason::Stopped); return; } - resource_data.pending_buffer.take() - }; - if let Some(buffer) = buffer { - let session = Session { - obj: SessionResource::Alive(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), - ); + std::mem::drop(inner); + state.frame(session, frame); + } else if let Some(session) = scpy + .known_cursor_sessions + .iter() + .find(|session| { + session.inner.lock().unwrap().session.as_ref() == Some(&inner.obj) + }) + .map(|s| CursorSession { + obj: s.obj.clone(), + inner: s.inner.clone(), + user_data: s.user_data.clone(), + }) + { + if session.inner.lock().unwrap().stopped { + resource.failed(FailureReason::Stopped); + return; + } + + std::mem::drop(inner); + state.cursor_frame(session, frame); } else { - resource.failed(FailureReason::InvalidBuffer); + inner.failed = Some(FailureReason::Unknown); + resource.failed(FailureReason::Unknown); } } - zcosmic_screencopy_session_v1::Request::Destroy => { - data.inner.lock().unwrap().gone = true; - } _ => {} } } @@ -883,71 +1046,55 @@ where fn destroyed( state: &mut D, _client: wayland_backend::server::ClientId, - resource: &ZcosmicScreencopySessionV1, - data: &SessionData, + resource: &ZcosmicScreencopyFrameV2, + data: &FrameData, ) { - if data.inner.lock().unwrap().is_cursor() { - let session = CursorSession { - obj: SessionResource::Destroyed(resource.id()), - data: data.clone(), - }; - state.cursor_session_destroyed(session) - } else { - let session = Session { - obj: SessionResource::Destroyed(resource.id()), - data: data.clone(), - }; - state.session_destroyed(session) - } - } -} - -fn send_formats(session: &SessionResource, 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, - ); - } + { + let scpy = state.screencopy_state(); + for session in &mut scpy.known_sessions { + session + .inner + .lock() + .unwrap() + .active_frames + .retain(|frame| frame.obj != *resource); + } + for cursor_session in &mut scpy.known_cursor_sessions { + cursor_session + .inner + .lock() + .unwrap() + .active_frames + .retain(|frame| frame.obj != *resource); } - 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, - ), } + let frame = Frame { + obj: resource.clone(), + inner: data.inner.clone(), + }; + state.frame_aborted(frame); } - - session.init_done(); } macro_rules! delegate_screencopy { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: $crate::wayland::protocols::screencopy::ScreencopyGlobalData + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2: $crate::wayland::protocols::screencopy::ScreencopyGlobalData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2: $crate::wayland::protocols::screencopy::ScreencopyData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2: $crate::wayland::protocols::screencopy::SessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_cursor_session_v2::ZcosmicScreencopyCursorSessionV2: $crate::wayland::protocols::screencopy::CursorSessionData ] => $crate::wayland::protocols::screencopy::ScreencopyState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: std::vec::Vec + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2: $crate::wayland::protocols::screencopy::CursorSessionData ] => $crate::wayland::protocols::screencopy::ScreencopyState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1: $crate::wayland::protocols::screencopy::SessionData + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2: $crate::wayland::protocols::screencopy::FrameData ] => $crate::wayland::protocols::screencopy::ScreencopyState); }; } diff --git a/src/xwayland.rs b/src/xwayland.rs index 2d644e12..6d3e6224 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -5,12 +5,8 @@ use crate::{ shell::{focus::target::KeyboardFocusTarget, grabs::ReleaseMode, CosmicSurface, Shell}, state::State, utils::prelude::*, - wayland::{ - handlers::{ - screencopy::PendingScreencopyBuffers, toplevel_management::ToplevelManagementExt, - xdg_activation::ActivationContext, - }, - protocols::screencopy::SessionType, + wayland::handlers::{ + toplevel_management::ToplevelManagementExt, xdg_activation::ActivationContext, }, }; use smithay::{ @@ -275,36 +271,9 @@ impl XwmHandler for State { self.common.shell.refresh_active_space(output); } - // screencopy - let mut scheduled_sessions = window - .wl_surface() - .map(|wl_surface| self.schedule_workspace_sessions(&wl_surface)) - .unwrap_or_default(); - for output in outputs.into_iter() { - if let Some(sessions) = output.user_data().get::() { - scheduled_sessions - .get_or_insert_with(Vec::new) - .extend(sessions.borrow_mut().drain(..)); - } - self.backend.schedule_render( - &self.common.event_loop_handle, - &output, - scheduled_sessions.as_ref().map(|sessions| { - sessions - .iter() - .filter(|(s, _)| match s.session_type() { - SessionType::Output(o) | SessionType::Workspace(o, _) - if o == output => - { - true - } - _ => false, - }) - .cloned() - .collect::>() - }), - ); + self.backend + .schedule_render(&self.common.event_loop_handle, &output); } } From b6f92ae49d90a0d1e2fa382d1cde7bcdc4a4f41c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Mar 2024 18:29:14 +0100 Subject: [PATCH 3/4] DROP ME: use wayland-protocols fork --- Cargo.lock | 51 +++++++++++++++++++++------------------------------ Cargo.toml | 11 +++++++++++ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87bc05cc..a419ec26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -977,7 +977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16e44ab292b1dddfdaf7be62cfd8877df52f2f3fde5858d95bab606be259f20" dependencies = [ "bitflags 2.4.2", - "libloading 0.7.4", + "libloading 0.8.1", "winapi", ] @@ -1148,7 +1148,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.1", ] [[package]] @@ -3862,9 +3862,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] @@ -5388,13 +5388,12 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4" +version = "0.3.3" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "cc", "downcast-rs", - "nix 0.26.4", + "rustix 0.38.30", "scoped-tls", "smallvec", "wayland-sys", @@ -5402,12 +5401,11 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" +version = "0.31.2" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "bitflags 2.4.2", - "nix 0.26.4", + "rustix 0.38.30", "wayland-backend", "wayland-scanner", ] @@ -5437,8 +5435,7 @@ dependencies = [ [[package]] name = "wayland-egl" version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355f652e5a24ae02d2ad536c8fc2d3dcc6c2bd635027cd6103a193e7d75eeda2" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "wayland-backend", "wayland-sys", @@ -5447,8 +5444,7 @@ dependencies = [ [[package]] name = "wayland-protocols" version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "bitflags 2.4.2", "wayland-backend", @@ -5460,8 +5456,7 @@ dependencies = [ [[package]] name = "wayland-protocols-misc" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5933740b200188c9b4c38601b8212e8c154d7de0d2cb171944e137a77de1e" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "bitflags 2.4.2", "wayland-backend", @@ -5486,8 +5481,7 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "bitflags 2.4.2", "wayland-backend", @@ -5499,9 +5493,8 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c" +version = "0.31.1" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "proc-macro2", "quick-xml", @@ -5510,14 +5503,13 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3f0c52a445936ca1184c98f1a69cf4ad9c9130788884531ef04428468cb1ce" +version = "0.31.1" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "bitflags 2.4.2", "downcast-rs", "io-lifetimes 2.0.3", - "nix 0.26.4", + "rustix 0.38.30", "wayland-backend", "wayland-scanner", ] @@ -5525,8 +5517,7 @@ dependencies = [ [[package]] name = "wayland-sys" version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +source = "git+https://github.com/drakulix/wayland-rs?branch=ext-screencopy#4f7eaa5498a4d59d937c6b2efd21553fb4aa0a9a" dependencies = [ "dlib", "libc", @@ -5633,7 +5624,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.7.4", + "libloading 0.8.1", "log", "metal", "naga", diff --git a/Cargo.toml b/Cargo.toml index ca99851a..4c6974d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,3 +109,14 @@ lto = "fat" [patch."https://github.com/Smithay/smithay.git"] smithay = {git = "https://github.com/smithay//smithay", rev = "c17297b"} + +[patch.crates-io] +wayland-egl = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-protocols = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-protocols-wlr = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-protocols-misc = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-server = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-client = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-sys = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-backend = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } +wayland-scanner = { git = "https://github.com/drakulix/wayland-rs", branch = "ext-screencopy" } From aa698b337cdffd7b0c8bde9f3a468a1aae90f783 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Mar 2024 18:29:44 +0100 Subject: [PATCH 4/4] wayland: Implement ext-{image-source, screencopy} on top of cosmic-variants --- src/wayland/protocols/image_source.rs | 118 +++- .../{screencopy.rs => screencopy/cosmic.rs} | 522 +------------- src/wayland/protocols/screencopy/ext.rs | 635 ++++++++++++++++++ src/wayland/protocols/screencopy/mod.rs | 543 +++++++++++++++ src/wayland/protocols/screencopy/types.rs | 226 +++++++ 5 files changed, 1543 insertions(+), 501 deletions(-) rename src/wayland/protocols/{screencopy.rs => screencopy/cosmic.rs} (59%) create mode 100644 src/wayland/protocols/screencopy/ext.rs create mode 100644 src/wayland/protocols/screencopy/mod.rs create mode 100644 src/wayland/protocols/screencopy/types.rs diff --git a/src/wayland/protocols/image_source.rs b/src/wayland/protocols/image_source.rs index 1032de82..d6750c17 100644 --- a/src/wayland/protocols/image_source.rs +++ b/src/wayland/protocols/image_source.rs @@ -15,6 +15,12 @@ use cosmic_protocols::image_source::v1::server::{ Request as WorkspaceSourceRequest, ZcosmicWorkspaceImageSourceManagerV1, }, }; +use smithay::reexports::wayland_protocols::ext::image_source::v1::server::{ + ext_image_source_v1::ExtImageSourceV1, + ext_output_image_source_manager_v1::{ + ExtOutputImageSourceManagerV1, Request as ExtOutputSourceRequest, + }, +}; use smithay::{ output::{Output, WeakOutput}, reexports::wayland_server::{ @@ -28,6 +34,7 @@ pub struct ImageSourceState { output_source_global: GlobalId, workspace_source_global: GlobalId, toplevel_source_global: GlobalId, + ext_output_source_global: GlobalId, } pub struct OutputImageSourceManagerGlobalData { @@ -62,6 +69,9 @@ impl ImageSourceState { ToplevelImageSourceManagerGlobalData, > + Dispatch + Dispatch + + GlobalDispatch + + Dispatch + + Dispatch + WorkspaceHandler + 'static, F: for<'a> Fn(&'a Client) -> bool + Send + Sync + Clone + 'static, @@ -84,9 +94,15 @@ impl ImageSourceState { .create_global::( 1, ToplevelImageSourceManagerGlobalData { - filter: Box::new(client_filter), + filter: Box::new(client_filter.clone()), }, ), + ext_output_source_global: display.create_global::( + 1, + OutputImageSourceManagerGlobalData { + filter: Box::new(client_filter), + }, + ), } } @@ -101,6 +117,10 @@ impl ImageSourceState { pub fn toplevel_source_id(&self) -> &GlobalId { &self.toplevel_source_global } + + pub fn ext_output_source_id(&self) -> &GlobalId { + &self.ext_output_source_global + } } impl GlobalDispatch @@ -176,6 +196,30 @@ where } } +impl GlobalDispatch + for ImageSourceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &OutputImageSourceManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &OutputImageSourceManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + impl Dispatch for ImageSourceState where D: Dispatch @@ -288,6 +332,42 @@ where } } +impl Dispatch for ImageSourceState +where + D: Dispatch + + Dispatch + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _resource: &ExtOutputImageSourceManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + ExtOutputSourceRequest::CreateSource { source, output } => { + let data = match Output::from_resource(&output) { + Some(output) => ImageSourceData::Output(output.downgrade()), + None => ImageSourceData::Destroyed, + }; + data_init.init(source, data); + } + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ExtOutputImageSourceManagerV1, + _data: &(), + ) { + } +} + impl Dispatch for ImageSourceState where D: Dispatch + 'static, @@ -315,6 +395,33 @@ where } } +impl Dispatch for ImageSourceState +where + D: Dispatch + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _resource: &ExtImageSourceV1, + request: ::Request, + _data: &ImageSourceData, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ExtImageSourceV1, + _data: &ImageSourceData, + ) { + } +} + macro_rules! delegate_image_source { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ @@ -335,9 +442,18 @@ macro_rules! delegate_image_source { smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::image_source::v1::server::zcosmic_toplevel_image_source_manager_v1::ZcosmicToplevelImageSourceManagerV1: () ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::image_source::v1::server::ext_output_image_source_manager_v1::ExtOutputImageSourceManagerV1: $crate::wayland::protocols::image_source::OutputImageSourceManagerGlobalData + ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::image_source::v1::server::ext_output_image_source_manager_v1::ExtOutputImageSourceManagerV1: () + ] => $crate::wayland::protocols::image_source::ImageSourceState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::image_source::v1::server::zcosmic_image_source_v1::ZcosmicImageSourceV1: $crate::wayland::protocols::image_source::ImageSourceData ] => $crate::wayland::protocols::image_source::ImageSourceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::image_source::v1::server::ext_image_source_v1::ExtImageSourceV1: $crate::wayland::protocols::image_source::ImageSourceData + ] => $crate::wayland::protocols::image_source::ImageSourceState); }; } pub(crate) use delegate_image_source; diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy/cosmic.rs similarity index 59% rename from src/wayland/protocols/screencopy.rs rename to src/wayland/protocols/screencopy/cosmic.rs index 54188759..7d37281d 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy/cosmic.rs @@ -1,7 +1,4 @@ -use std::{ - sync::{Arc, Mutex}, - time::Duration, -}; +use std::sync::{Arc, Mutex}; pub use cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_frame_v2::FailureReason; use cosmic_protocols::screencopy::v2::server::{ @@ -12,470 +9,21 @@ use cosmic_protocols::screencopy::v2::server::{ }; use smithay::{ backend::{ - allocator::{Buffer, Fourcc, Modifier}, - drm::DrmNode, + allocator::Buffer, renderer::{buffer_type, BufferType}, }, - utils::{user_data::UserDataMap, Buffer as BufferCoords, IsAlive, Size, Transform}, + utils::user_data::UserDataMap, wayland::{dmabuf::get_dmabuf, shm::with_buffer_contents}, }; -use smithay::{reexports::wayland_server::protocol::wl_buffer::WlBuffer, utils::Point}; use smithay::{ reexports::wayland_server::{ - protocol::wl_shm, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, }, utils::Rectangle, }; use tracing::debug; -use wayland_backend::server::GlobalId; - -use super::image_source::ImageSourceData; - -#[derive(Debug)] -pub struct ScreencopyState { - global: GlobalId, - known_sessions: Vec, - known_cursor_sessions: Vec, -} - -impl ScreencopyState { - pub fn new(display: &DisplayHandle, client_filter: F) -> ScreencopyState - where - D: GlobalDispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + ScreencopyHandler - + 'static, - F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, - { - ScreencopyState { - global: display.create_global::( - 1, - ScreencopyGlobalData { - filter: Box::new(client_filter), - }, - ), - known_sessions: Vec::new(), - known_cursor_sessions: Vec::new(), - } - } - - pub fn global_id(&self) -> &GlobalId { - &self.global - } -} - -#[derive(Debug, Clone)] -pub struct BufferConstraints { - pub size: Size, - pub shm: Vec, - pub dma: Option, -} - -#[derive(Debug, Clone)] -pub struct DmabufConstraints { - pub node: DrmNode, - pub formats: Vec<(Fourcc, Vec)>, -} - -#[derive(Debug, Clone)] -pub struct Session { - obj: ZcosmicScreencopySessionV2, - inner: Arc>, - user_data: Arc, -} - -impl PartialEq for Session { - fn eq(&self, other: &Self) -> bool { - self.obj == other.obj - } -} - -#[derive(Debug)] -struct SessionInner { - stopped: bool, - constraints: Option, - draw_cursors: bool, - source: ImageSourceData, - active_frames: Vec, -} - -impl SessionInner { - fn new(source: ImageSourceData, draw_cursors: bool) -> SessionInner { - SessionInner { - stopped: false, - constraints: None, - draw_cursors, - source, - active_frames: Vec::new(), - } - } -} - -impl IsAlive for Session { - fn alive(&self) -> bool { - self.obj.is_alive() - } -} - -impl Session { - pub fn update_constraints(&self, constraints: BufferConstraints) { - let mut inner = self.inner.lock().unwrap(); - - if !self.obj.is_alive() || inner.stopped { - return; - } - - self.obj - .buffer_size(constraints.size.w as u32, constraints.size.h as u32); - for fmt in &constraints.shm { - self.obj.shm_format(*fmt as u32); - } - if let Some(dma) = constraints.dma.as_ref() { - let node = Vec::from(dma.node.dev_id().to_ne_bytes()); - self.obj.dmabuf_device(node); - for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; - self.obj.dmabuf_format(*fmt as u32, modifiers); - } - } - self.obj.done(); - - inner.constraints = Some(constraints); - } - - pub fn current_constraints(&self) -> Option { - self.inner.lock().unwrap().constraints.clone() - } - - pub fn source(&self) -> ImageSourceData { - self.inner.lock().unwrap().source.clone() - } - - pub fn draw_cursor(&self) -> bool { - self.inner.lock().unwrap().draw_cursors - } - - pub fn user_data(&self) -> &UserDataMap { - &*self.user_data - } - - pub fn stop(self) { - let mut inner = self.inner.lock().unwrap(); - - if !self.obj.is_alive() || inner.stopped { - return; - } - - for frame in inner.active_frames.drain(..) { - let mut inner = frame.inner.lock().unwrap(); - if inner.failed.replace(FailureReason::Stopped).is_none() && inner.capture_requested { - frame.obj.failed(FailureReason::Stopped); - } - } - - self.obj.stopped(); - inner.constraints.take(); - inner.stopped = true; - } -} - -#[derive(Debug, Clone)] -pub struct CursorSession { - obj: ZcosmicScreencopyCursorSessionV2, - inner: Arc>, - user_data: Arc, -} - -impl PartialEq for CursorSession { - fn eq(&self, other: &Self) -> bool { - self.obj == other.obj - } -} - -#[derive(Debug)] -struct CursorSessionInner { - session: Option, - stopped: bool, - constraints: Option, - source: ImageSourceData, - position: Option>, - hotspot: Point, - active_frames: Vec, -} - -impl CursorSessionInner { - fn new(source: ImageSourceData) -> CursorSessionInner { - CursorSessionInner { - session: None, - stopped: false, - constraints: None, - source, - position: None, - hotspot: Point::from((0, 0)), - active_frames: Vec::new(), - } - } -} - -impl IsAlive for CursorSession { - fn alive(&self) -> bool { - self.obj.is_alive() - } -} - -impl CursorSession { - pub fn update_constraints(&self, constrains: BufferConstraints) { - let mut inner = self.inner.lock().unwrap(); - - if !self.obj.is_alive() || inner.stopped { - return; - } - - if let Some(session_obj) = inner.session.as_ref() { - session_obj.buffer_size(constrains.size.w as u32, constrains.size.h as u32); - for fmt in &constrains.shm { - session_obj.shm_format(*fmt as u32); - } - if let Some(dma) = constrains.dma.as_ref() { - let node = Vec::from(dma.node.dev_id().to_ne_bytes()); - session_obj.dmabuf_device(node); - for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; - session_obj.dmabuf_format(*fmt as u32, modifiers); - } - } - session_obj.done(); - } - - inner.constraints = Some(constrains); - } - - pub fn current_constraints(&self) -> Option { - self.inner.lock().unwrap().constraints.clone() - } - - pub fn source(&self) -> ImageSourceData { - self.inner.lock().unwrap().source.clone() - } - - pub fn has_cursor(&self) -> bool { - self.inner.lock().unwrap().position.is_some() - } - - pub fn set_cursor_pos(&self, position: Option>) { - if !self.obj.is_alive() { - return; - } - - let mut inner = self.inner.lock().unwrap(); - - if inner.position == position { - return; - } - - if inner.position.is_none() { - self.obj.enter(); - self.obj.hotspot(inner.hotspot.x, inner.hotspot.y) - } - - if let Some(new_pos) = position { - self.obj.position(new_pos.x, new_pos.y); - } else { - self.obj.leave() - } - - inner.position = position; - } - - pub fn set_cursor_hotspot(&self, hotspot: impl Into>) { - if !self.obj.is_alive() { - return; - } - - let hotspot = hotspot.into(); - - let mut inner = self.inner.lock().unwrap(); - - if inner.hotspot == hotspot { - return; - } - - inner.hotspot = hotspot; - if inner.position.is_some() { - self.obj.hotspot(hotspot.x, hotspot.y); - } - } - - pub fn user_data(&self) -> &UserDataMap { - &*self.user_data - } - - pub fn stop(self) { - let mut inner = self.inner.lock().unwrap(); - - if !self.obj.is_alive() || inner.stopped { - return; - } - - if let Some(session_obj) = inner.session.as_ref() { - session_obj.stopped(); - } - inner.constraints.take(); - - for frame in inner.active_frames.drain(..) { - let mut inner = frame.inner.lock().unwrap(); - if inner.failed.replace(FailureReason::Stopped).is_none() && inner.capture_requested { - frame.obj.failed(FailureReason::Stopped); - } - } - - inner.stopped = true; - } -} - -#[derive(Debug)] -pub struct Frame { - obj: ZcosmicScreencopyFrameV2, - inner: Arc>, -} - -impl PartialEq for Frame { - fn eq(&self, other: &Self) -> bool { - self.obj == other.obj - } -} - -impl Frame { - pub fn buffer(&self) -> WlBuffer { - self.inner.lock().unwrap().buffer.clone().unwrap() - } - - pub fn damage(&self) -> Vec> { - self.inner.lock().unwrap().damage.clone() - } - - pub fn has_failed(&self) -> bool { - self.inner.lock().unwrap().failed.is_some() - } - pub fn success( - self, - transform: impl Into, - damage: impl Into>>>, - presented: impl Into, - ) { - { - let inner = self.inner.lock().unwrap(); - if !inner.capture_requested || inner.failed.is_some() { - return; - } - } - - self.obj.transform(transform.into().into()); - for damage in damage.into().into_iter().flatten() { - self.obj - .damage(damage.loc.x, damage.loc.y, damage.size.w, damage.size.h); - } - - let time = presented.into(); - let tv_sec_hi = (time.as_secs() >> 32) as u32; - let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; - let tv_nsec = time.subsec_nanos(); - self.obj.presentation_time(tv_sec_hi, tv_sec_lo, tv_nsec); - - self.obj.ready() - } - - pub fn fail(self, reason: FailureReason) { - let mut inner = self.inner.lock().unwrap(); - inner.failed = Some(reason); - if inner.capture_requested { - self.obj.failed(reason); - } - } -} - -#[derive(Debug)] -struct FrameInner { - constraints: Option, - buffer: Option, - damage: Vec>, - obj: ZcosmicScreencopySessionV2, - capture_requested: bool, - failed: Option, -} - -impl FrameInner { - fn new( - obj: ZcosmicScreencopySessionV2, - constraints: impl Into>, - ) -> Self { - FrameInner { - constraints: constraints.into(), - buffer: None, - damage: Vec::new(), - obj, - capture_requested: false, - failed: None, - } - } -} - -pub trait ScreencopyHandler { - fn screencopy_state(&mut self) -> &mut ScreencopyState; - - fn capture_source(&mut self, source: &ImageSourceData) -> Option; - fn capture_cursor_source(&mut self, source: &ImageSourceData) -> Option; - - fn new_session(&mut self, session: Session); - fn new_cursor_session(&mut self, session: CursorSession); - - fn frame(&mut self, session: Session, frame: Frame); - fn cursor_frame(&mut self, session: CursorSession, frame: Frame); - - fn frame_aborted(&mut self, frame: Frame); - fn session_destroyed(&mut self, session: Session) { - let _ = session; - } - fn cursor_session_destroyed(&mut self, session: CursorSession) { - let _ = session; - } -} - -pub struct ScreencopyGlobalData { - filter: Box Fn(&'a Client) -> bool + Send + Sync>, -} - -pub struct ScreencopyData; - -pub struct SessionData { - inner: Arc>, -} - -pub struct CursorSessionData { - inner: Arc>, -} -pub struct FrameData { - inner: Arc>, -} +use super::{super::image_source::ImageSourceData, *}; impl GlobalDispatch for ScreencopyState where @@ -544,7 +92,7 @@ where ); let session = Session { - obj, + obj: obj.into(), inner: session_data, user_data: Arc::new(UserDataMap::new()), }; @@ -570,7 +118,7 @@ where }, ); let session = Session { - obj, + obj: obj.into(), inner: session_data, user_data: Arc::new(UserDataMap::new()), }; @@ -597,7 +145,7 @@ where ); let session = CursorSession { - obj, + obj: obj.into(), inner: session_data, user_data: Arc::new(UserDataMap::new()), }; @@ -622,7 +170,7 @@ where }, ); let session = CursorSession { - obj, + obj: obj.into(), inner: session_data, user_data: Arc::new(UserDataMap::new()), }; @@ -660,7 +208,7 @@ where match request { zcosmic_screencopy_session_v2::Request::CreateFrame { frame } => { let inner = Arc::new(Mutex::new(FrameInner::new( - resource.clone(), + resource.clone().into(), data.inner.lock().unwrap().constraints.clone(), ))); let obj = data_init.init( @@ -669,11 +217,10 @@ where inner: inner.clone(), }, ); - data.inner - .lock() - .unwrap() - .active_frames - .push(Frame { obj, inner }); + data.inner.lock().unwrap().active_frames.push(Frame { + obj: obj.into(), + inner, + }); } _ => {} } @@ -754,7 +301,7 @@ where } session.done(); } - inner.session = Some(session); + inner.session = Some(session.into()); } _ => {} } @@ -797,7 +344,7 @@ where match request { zcosmic_screencopy_session_v2::Request::CreateFrame { frame } => { let inner = Arc::new(Mutex::new(FrameInner::new( - resource.clone(), + resource.clone().into(), data.inner.lock().unwrap().constraints.clone(), ))); let obj = data_init.init( @@ -806,11 +353,10 @@ where inner: inner.clone(), }, ); - data.inner - .lock() - .unwrap() - .active_frames - .push(Frame { obj, inner }); + data.inner.lock().unwrap().active_frames.push(Frame { + obj: obj.into(), + inner, + }); } _ => {} } @@ -993,7 +539,7 @@ where } let frame = Frame { - obj: resource.clone(), + obj: resource.clone().into(), inner: data.inner.clone(), }; @@ -1069,33 +615,9 @@ where } } let frame = Frame { - obj: resource.clone(), + obj: resource.clone().into(), inner: data.inner.clone(), }; state.frame_aborted(frame); } } - -macro_rules! delegate_screencopy { - ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { - smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2: $crate::wayland::protocols::screencopy::ScreencopyGlobalData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2: $crate::wayland::protocols::screencopy::ScreencopyData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2: $crate::wayland::protocols::screencopy::SessionData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_cursor_session_v2::ZcosmicScreencopyCursorSessionV2: $crate::wayland::protocols::screencopy::CursorSessionData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2: $crate::wayland::protocols::screencopy::CursorSessionData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2: $crate::wayland::protocols::screencopy::FrameData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - }; -} -pub(crate) use delegate_screencopy; diff --git a/src/wayland/protocols/screencopy/ext.rs b/src/wayland/protocols/screencopy/ext.rs new file mode 100644 index 00000000..31cf7ef8 --- /dev/null +++ b/src/wayland/protocols/screencopy/ext.rs @@ -0,0 +1,635 @@ +use std::sync::{Arc, Mutex}; + +use smithay::reexports::wayland_protocols::ext::screencopy::v1::server::{ + ext_screencopy_cursor_session_v1::{self, ExtScreencopyCursorSessionV1}, + ext_screencopy_frame_v1::{self, ExtScreencopyFrameV1}, + ext_screencopy_manager_v1::{self, ExtScreencopyManagerV1}, + ext_screencopy_session_v1::{self, ExtScreencopySessionV1}, +}; +use smithay::{ + backend::{ + allocator::Buffer, + renderer::{buffer_type, BufferType}, + }, + utils::user_data::UserDataMap, + wayland::{dmabuf::get_dmabuf, shm::with_buffer_contents}, +}; +use smithay::{ + reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, + utils::Rectangle, +}; +use tracing::debug; + +use super::{super::image_source::ImageSourceData, *}; + +impl GlobalDispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &ScreencopyGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ScreencopyData); + } + + fn can_view(client: Client, global_data: &ScreencopyGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &ExtScreencopyManagerV1, + request: ::Request, + _data: &ScreencopyData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_screencopy_manager_v1::Request::CreateSession { + session, + source, + options, + } => { + if let Some(src) = source.data::() { + if *src != ImageSourceData::Destroyed { + if let Some(buffer_constraints) = state.capture_source(src) { + let session_data = Arc::new(Mutex::new(SessionInner::new( + src.clone(), + Into::::into(options) == 1, + ))); + let obj = data_init.init( + session, + SessionData { + inner: session_data.clone(), + }, + ); + + let session = Session { + obj: obj.into(), + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.update_constraints(buffer_constraints); + state + .screencopy_state() + .known_sessions + .push(session.clone()); + state.new_session(session); + return; + } + } + } + + let session_data = Arc::new(Mutex::new(SessionInner::new( + ImageSourceData::Destroyed, + false, + ))); + let obj = data_init.init( + session, + SessionData { + inner: session_data.clone(), + }, + ); + let session = Session { + obj: obj.into(), + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.stop(); + } + ext_screencopy_manager_v1::Request::CreatePointerCursorSession { + session, + source, + pointer: _, + options: _, + } => { + // TODO: use pointer, but we need new smithay api for that. + + if let Some(src) = source.data::() { + if *src != ImageSourceData::Destroyed { + if let Some(buffer_constraints) = state.capture_cursor_source(src) { + let session_data = + Arc::new(Mutex::new(CursorSessionInner::new(src.clone()))); + let obj = data_init.init( + session, + CursorSessionData { + inner: session_data.clone(), + }, + ); + + let session = CursorSession { + obj: obj.into(), + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.update_constraints(buffer_constraints); + state + .screencopy_state() + .known_cursor_sessions + .push(session.clone()); + state.new_cursor_session(session); + return; + } + } + } + + let session_data = Arc::new(Mutex::new(CursorSessionInner::new( + ImageSourceData::Destroyed, + ))); + let obj = data_init.init( + session, + CursorSessionData { + inner: session_data.clone(), + }, + ); + let session = CursorSession { + obj: obj.into(), + inner: session_data, + user_data: Arc::new(UserDataMap::new()), + }; + session.stop(); + } + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ExtScreencopyManagerV1, + _data: &ScreencopyData, + ) { + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + resource: &ExtScreencopySessionV1, + request: ::Request, + data: &SessionData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_screencopy_session_v1::Request::CreateFrame { frame } => { + let inner = Arc::new(Mutex::new(FrameInner::new( + resource.clone().into(), + data.inner.lock().unwrap().constraints.clone(), + ))); + let obj = data_init.init( + frame, + FrameData { + inner: inner.clone(), + }, + ); + data.inner.lock().unwrap().active_frames.push(Frame { + obj: obj.into(), + inner, + }); + } + _ => {} + } + } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: &ExtScreencopySessionV1, + _data: &SessionData, + ) { + let scpy = state.screencopy_state(); + if let Some(pos) = scpy + .known_sessions + .iter() + .position(|session| session.obj == *resource) + { + let session = scpy.known_sessions.remove(pos); + state.session_destroyed(session); + } + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + resource: &ExtScreencopyCursorSessionV1, + request: ::Request, + data: &CursorSessionData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_screencopy_cursor_session_v1::Request::GetScreencopySession { session } => { + let new_data = CursorSessionData { + inner: data.inner.clone(), + }; + let session = data_init.init(session, new_data); + + let mut inner = data.inner.lock().unwrap(); + if inner.session.is_some() { + resource.post_error( + ext_screencopy_cursor_session_v1::Error::DuplicateSession, + "Duplicate session", + ); + return; + } + + if inner.stopped { + session.stopped(); + } else if let Some(constraints) = inner.constraints.as_ref() { + session.buffer_size(constraints.size.w as u32, constraints.size.h as u32); + for fmt in &constraints.shm { + session.shm_format(*fmt); + } + if let Some(dma) = constraints.dma.as_ref() { + let node = Vec::from(dma.node.dev_id().to_ne_bytes()); + session.dmabuf_device(node); + for (fmt, modifiers) in &dma.formats { + let mut modifiers = modifiers.clone(); + let modifiers: Vec = { + let ptr = modifiers.as_mut_ptr() as *mut u8; + let len = modifiers.len() * 4; + let cap = modifiers.capacity() * 4; + std::mem::forget(modifiers); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + session.dmabuf_format(*fmt as u32, modifiers); + } + } + session.done(); + } + inner.session = Some(session.into()); + } + _ => {} + } + } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: &ExtScreencopyCursorSessionV1, + _data: &CursorSessionData, + ) { + let scpy = state.screencopy_state(); + if let Some(pos) = scpy + .known_cursor_sessions + .iter() + .position(|session| session.obj == *resource) + { + let session = scpy.known_cursor_sessions.remove(pos); + state.cursor_session_destroyed(session); + } + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + resource: &ExtScreencopySessionV1, + request: ::Request, + data: &CursorSessionData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_screencopy_session_v1::Request::CreateFrame { frame } => { + let inner = Arc::new(Mutex::new(FrameInner::new( + resource.clone().into(), + data.inner.lock().unwrap().constraints.clone(), + ))); + let obj = data_init.init( + frame, + FrameData { + inner: inner.clone(), + }, + ); + data.inner.lock().unwrap().active_frames.push(Frame { + obj: obj.into(), + inner, + }); + } + _ => {} + } + } + + fn destroyed( + _state: &mut D, + _client: wayland_backend::server::ClientId, + _resource: &ExtScreencopySessionV1, + _data: &CursorSessionData, + ) { + } +} + +impl Dispatch for ScreencopyState +where + D: Dispatch + ScreencopyHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ExtScreencopyFrameV1, + request: ::Request, + data: &FrameData, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_screencopy_frame_v1::Request::AttachBuffer { buffer } => { + let mut inner = data.inner.lock().unwrap(); + + if inner.capture_requested { + resource.post_error( + ext_screencopy_frame_v1::Error::AlreadyCaptured, + "Frame was captured previously", + ); + } + + inner.buffer = Some(buffer); + } + ext_screencopy_frame_v1::Request::DamageBuffer { + x, + y, + width, + height, + } => { + let mut inner = data.inner.lock().unwrap(); + + if inner.capture_requested { + resource.post_error( + ext_screencopy_frame_v1::Error::AlreadyCaptured, + "Frame was captured previously", + ); + } + + if x < 0 || y < 0 || width <= 0 || height <= 0 { + resource.post_error( + ext_screencopy_frame_v1::Error::InvalidBufferDamage, + "Coordinates negative or size equal to zero", + ); + return; + } + + inner + .damage + .push(Rectangle::from_loc_and_size((x, y), (width, height))); + } + ext_screencopy_frame_v1::Request::Capture => { + let mut inner = data.inner.lock().unwrap(); + + if inner.capture_requested { + resource.post_error( + ext_screencopy_frame_v1::Error::AlreadyCaptured, + "Frame was captured previously", + ); + } + + if inner.buffer.is_none() { + resource.post_error( + ext_screencopy_frame_v1::Error::NoBuffer, + "Attempting to capture frame without a buffer", + ); + } + + inner.capture_requested = true; + + if let Some(reason) = inner.failed { + resource.failed((reason as u32).try_into().unwrap()); + return; + } + + if let Some(constraints) = inner.constraints.as_ref() { + let buffer = inner.buffer.as_ref().unwrap(); + match buffer_type(buffer) { + Some(BufferType::Dma) => { + let Some(dma_constraints) = constraints.dma.as_ref() else { + debug!("dma buffer not specified for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed( + ext_screencopy_frame_v1::FailureReason::BufferConstraints, + ); + return; + }; + + let dmabuf = match get_dmabuf(buffer) { + Ok(buf) => buf, + Err(err) => { + debug!(?err, "Error accessing dma buffer for screencopy"); + inner.failed = Some(FailureReason::Unknown); + resource + .failed(ext_screencopy_frame_v1::FailureReason::Unknown); + return; + } + }; + + let buffer_size = dmabuf.size(); + if buffer_size.w < constraints.size.w + || buffer_size.h < constraints.size.h + { + debug!(?buffer_size, ?constraints.size, "buffer too small for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed( + ext_screencopy_frame_v1::FailureReason::BufferConstraints, + ); + return; + } + + let format = dmabuf.format(); + if dma_constraints + .formats + .iter() + .find(|(fourcc, _)| *fourcc == format.code) + .filter(|(_, modifiers)| modifiers.contains(&format.modifier)) + .is_none() + { + debug!( + ?format, + ?dma_constraints, + "unsupported buffer format for screencopy" + ); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed( + ext_screencopy_frame_v1::FailureReason::BufferConstraints, + ); + return; + } + } + Some(BufferType::Shm) => { + let buffer_data = match with_buffer_contents(buffer, |_, _, data| data) + { + Ok(data) => data, + Err(err) => { + debug!(?err, "Error accessing shm buffer for screencopy"); + inner.failed = Some(FailureReason::Unknown); + resource + .failed(ext_screencopy_frame_v1::FailureReason::Unknown); + return; + } + }; + + if buffer_data.width < constraints.size.w + || buffer_data.height < constraints.size.h + { + debug!(?buffer_data, ?constraints.size, "buffer too small for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed( + ext_screencopy_frame_v1::FailureReason::BufferConstraints, + ); + return; + } + + if !constraints.shm.contains(&buffer_data.format) { + debug!(?buffer_data.format, ?constraints.shm, "unsupported buffer format for screencopy"); + inner.failed = Some(FailureReason::BufferConstraints); + resource.failed( + ext_screencopy_frame_v1::FailureReason::BufferConstraints, + ); + return; + } + } + x => { + debug!(?x, "Attempt to screencopy with unsupported buffer type"); + inner.failed = Some(FailureReason::BufferConstraints); + resource + .failed(ext_screencopy_frame_v1::FailureReason::BufferConstraints); + return; + } + } + } else { + inner.failed = Some(FailureReason::Unknown); + resource.failed(ext_screencopy_frame_v1::FailureReason::Unknown); + return; + } + + let frame = Frame { + obj: resource.clone().into(), + inner: data.inner.clone(), + }; + + let scpy = state.screencopy_state(); + if let Some(session) = scpy + .known_sessions + .iter() + .find(|session| session.obj == inner.obj) + .map(|s| Session { + obj: s.obj.clone(), + inner: s.inner.clone(), + user_data: s.user_data.clone(), + }) + { + if session.inner.lock().unwrap().stopped { + resource.failed(ext_screencopy_frame_v1::FailureReason::Stopped); + return; + } + + std::mem::drop(inner); + state.frame(session, frame); + } else if let Some(session) = scpy + .known_cursor_sessions + .iter() + .find(|session| { + session.inner.lock().unwrap().session.as_ref() == Some(&inner.obj) + }) + .map(|s| CursorSession { + obj: s.obj.clone(), + inner: s.inner.clone(), + user_data: s.user_data.clone(), + }) + { + if session.inner.lock().unwrap().stopped { + resource.failed(ext_screencopy_frame_v1::FailureReason::Stopped); + return; + } + + std::mem::drop(inner); + state.cursor_frame(session, frame); + } else { + inner.failed = Some(FailureReason::Unknown); + resource.failed(ext_screencopy_frame_v1::FailureReason::Unknown); + } + } + _ => {} + } + } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: &ExtScreencopyFrameV1, + data: &FrameData, + ) { + { + let scpy = state.screencopy_state(); + for session in &mut scpy.known_sessions { + session + .inner + .lock() + .unwrap() + .active_frames + .retain(|frame| frame.obj != *resource); + } + for cursor_session in &mut scpy.known_cursor_sessions { + cursor_session + .inner + .lock() + .unwrap() + .active_frames + .retain(|frame| frame.obj != *resource); + } + } + let frame = Frame { + obj: resource.clone().into(), + inner: data.inner.clone(), + }; + state.frame_aborted(frame); + } +} diff --git a/src/wayland/protocols/screencopy/mod.rs b/src/wayland/protocols/screencopy/mod.rs new file mode 100644 index 00000000..bb16b22d --- /dev/null +++ b/src/wayland/protocols/screencopy/mod.rs @@ -0,0 +1,543 @@ +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + +pub use cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_frame_v2::FailureReason; +use cosmic_protocols::screencopy::v2::server::{ + zcosmic_screencopy_cursor_session_v2::ZcosmicScreencopyCursorSessionV2, + zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2, + zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2, + zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2, +}; +use smithay::reexports::wayland_protocols::ext::screencopy::v1::server::{ + ext_screencopy_cursor_session_v1::ExtScreencopyCursorSessionV1, + ext_screencopy_frame_v1::ExtScreencopyFrameV1, + ext_screencopy_manager_v1::ExtScreencopyManagerV1, + ext_screencopy_session_v1::ExtScreencopySessionV1, +}; +use smithay::{ + backend::{ + allocator::{Fourcc, Modifier}, + drm::DrmNode, + }, + utils::{user_data::UserDataMap, Buffer as BufferCoords, IsAlive, Size, Transform}, +}; +use smithay::{reexports::wayland_server::protocol::wl_buffer::WlBuffer, utils::Point}; +use smithay::{ + reexports::wayland_server::{ + protocol::wl_shm, Client, Dispatch, DisplayHandle, GlobalDispatch, + }, + utils::Rectangle, +}; +use wayland_backend::server::GlobalId; + +use super::image_source::ImageSourceData; + +mod cosmic; +mod ext; +mod types; + +use self::types::*; + +#[derive(Debug)] +pub struct ScreencopyState { + global: GlobalId, + ext_global: GlobalId, + known_sessions: Vec, + known_cursor_sessions: Vec, +} + +impl ScreencopyState { + pub fn new(display: &DisplayHandle, client_filter: F) -> ScreencopyState + where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + Clone + 'static, + { + ScreencopyState { + global: display.create_global::( + 1, + ScreencopyGlobalData { + filter: Box::new(client_filter.clone()), + }, + ), + ext_global: display.create_global::( + 1, + ScreencopyGlobalData { + filter: Box::new(client_filter), + }, + ), + known_sessions: Vec::new(), + known_cursor_sessions: Vec::new(), + } + } + + pub fn global_id(&self) -> &GlobalId { + &self.global + } + + pub fn ext_global_id(&self) -> &GlobalId { + &self.ext_global + } +} + +#[derive(Debug, Clone)] +pub struct BufferConstraints { + pub size: Size, + pub shm: Vec, + pub dma: Option, +} + +#[derive(Debug, Clone)] +pub struct DmabufConstraints { + pub node: DrmNode, + pub formats: Vec<(Fourcc, Vec)>, +} + +#[derive(Debug, Clone)] +pub struct Session { + obj: SessionObj, + inner: Arc>, + user_data: Arc, +} + +impl PartialEq for Session { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +#[derive(Debug)] +struct SessionInner { + stopped: bool, + constraints: Option, + draw_cursors: bool, + source: ImageSourceData, + active_frames: Vec, +} + +impl SessionInner { + fn new(source: ImageSourceData, draw_cursors: bool) -> SessionInner { + SessionInner { + stopped: false, + constraints: None, + draw_cursors, + source, + active_frames: Vec::new(), + } + } +} + +impl IsAlive for Session { + fn alive(&self) -> bool { + self.obj.is_alive() + } +} + +impl Session { + pub fn update_constraints(&self, constraints: BufferConstraints) { + let mut inner = self.inner.lock().unwrap(); + + if !self.obj.is_alive() || inner.stopped { + return; + } + + self.obj + .buffer_size(constraints.size.w as u32, constraints.size.h as u32); + for fmt in &constraints.shm { + self.obj.shm_format(*fmt); + } + if let Some(dma) = constraints.dma.as_ref() { + let node = Vec::from(dma.node.dev_id().to_ne_bytes()); + self.obj.dmabuf_device(node); + for (fmt, modifiers) in &dma.formats { + let mut modifiers = modifiers.clone(); + let modifiers: Vec = { + let ptr = modifiers.as_mut_ptr() as *mut u8; + let len = modifiers.len() * 4; + let cap = modifiers.capacity() * 4; + std::mem::forget(modifiers); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + self.obj.dmabuf_format(*fmt as u32, modifiers); + } + } + self.obj.done(); + + inner.constraints = Some(constraints); + } + + pub fn current_constraints(&self) -> Option { + self.inner.lock().unwrap().constraints.clone() + } + + pub fn source(&self) -> ImageSourceData { + self.inner.lock().unwrap().source.clone() + } + + pub fn draw_cursor(&self) -> bool { + self.inner.lock().unwrap().draw_cursors + } + + pub fn user_data(&self) -> &UserDataMap { + &*self.user_data + } + + pub fn stop(self) { + let mut inner = self.inner.lock().unwrap(); + + if !self.obj.is_alive() || inner.stopped { + return; + } + + for frame in inner.active_frames.drain(..) { + let mut inner = frame.inner.lock().unwrap(); + if inner.failed.replace(FailureReason::Stopped).is_none() && inner.capture_requested { + frame.obj.failed(FailureReason::Stopped); + } + } + + self.obj.stopped(); + inner.constraints.take(); + inner.stopped = true; + } +} + +#[derive(Debug, Clone)] +pub struct CursorSession { + obj: CursorSessionObj, + inner: Arc>, + user_data: Arc, +} + +impl PartialEq for CursorSession { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +#[derive(Debug)] +struct CursorSessionInner { + session: Option, + stopped: bool, + constraints: Option, + source: ImageSourceData, + position: Option>, + hotspot: Point, + active_frames: Vec, +} + +impl CursorSessionInner { + fn new(source: ImageSourceData) -> CursorSessionInner { + CursorSessionInner { + session: None, + stopped: false, + constraints: None, + source, + position: None, + hotspot: Point::from((0, 0)), + active_frames: Vec::new(), + } + } +} + +impl IsAlive for CursorSession { + fn alive(&self) -> bool { + self.obj.is_alive() + } +} + +impl CursorSession { + pub fn update_constraints(&self, constrains: BufferConstraints) { + let mut inner = self.inner.lock().unwrap(); + + if !self.obj.is_alive() || inner.stopped { + return; + } + + if let Some(session_obj) = inner.session.as_ref() { + session_obj.buffer_size(constrains.size.w as u32, constrains.size.h as u32); + for fmt in &constrains.shm { + session_obj.shm_format(*fmt); + } + if let Some(dma) = constrains.dma.as_ref() { + let node = Vec::from(dma.node.dev_id().to_ne_bytes()); + session_obj.dmabuf_device(node); + for (fmt, modifiers) in &dma.formats { + let mut modifiers = modifiers.clone(); + let modifiers: Vec = { + let ptr = modifiers.as_mut_ptr() as *mut u8; + let len = modifiers.len() * 4; + let cap = modifiers.capacity() * 4; + std::mem::forget(modifiers); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + session_obj.dmabuf_format(*fmt as u32, modifiers); + } + } + session_obj.done(); + } + + inner.constraints = Some(constrains); + } + + pub fn current_constraints(&self) -> Option { + self.inner.lock().unwrap().constraints.clone() + } + + pub fn source(&self) -> ImageSourceData { + self.inner.lock().unwrap().source.clone() + } + + pub fn has_cursor(&self) -> bool { + self.inner.lock().unwrap().position.is_some() + } + + pub fn set_cursor_pos(&self, position: Option>) { + if !self.obj.is_alive() { + return; + } + + let mut inner = self.inner.lock().unwrap(); + + if inner.position == position { + return; + } + + if inner.position.is_none() { + self.obj.enter(); + self.obj.hotspot(inner.hotspot.x, inner.hotspot.y) + } + + if let Some(new_pos) = position { + self.obj.position(new_pos.x, new_pos.y); + } else { + self.obj.leave() + } + + inner.position = position; + } + + pub fn set_cursor_hotspot(&self, hotspot: impl Into>) { + if !self.obj.is_alive() { + return; + } + + let hotspot = hotspot.into(); + + let mut inner = self.inner.lock().unwrap(); + + if inner.hotspot == hotspot { + return; + } + + inner.hotspot = hotspot; + if inner.position.is_some() { + self.obj.hotspot(hotspot.x, hotspot.y); + } + } + + pub fn user_data(&self) -> &UserDataMap { + &*self.user_data + } + + pub fn stop(self) { + let mut inner = self.inner.lock().unwrap(); + + if !self.obj.is_alive() || inner.stopped { + return; + } + + if let Some(session_obj) = inner.session.as_ref() { + session_obj.stopped(); + } + inner.constraints.take(); + + for frame in inner.active_frames.drain(..) { + let mut inner = frame.inner.lock().unwrap(); + if inner.failed.replace(FailureReason::Stopped).is_none() && inner.capture_requested { + frame.obj.failed(FailureReason::Stopped); + } + } + + inner.stopped = true; + } +} + +#[derive(Debug)] +pub struct Frame { + obj: FrameObj, + inner: Arc>, +} + +impl PartialEq for Frame { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +impl Frame { + pub fn buffer(&self) -> WlBuffer { + self.inner.lock().unwrap().buffer.clone().unwrap() + } + + pub fn damage(&self) -> Vec> { + self.inner.lock().unwrap().damage.clone() + } + + pub fn has_failed(&self) -> bool { + self.inner.lock().unwrap().failed.is_some() + } + + pub fn success( + self, + transform: impl Into, + damage: impl Into>>>, + presented: impl Into, + ) { + { + let inner = self.inner.lock().unwrap(); + if !inner.capture_requested || inner.failed.is_some() { + return; + } + } + + self.obj.transform(transform.into().into()); + for damage in damage.into().into_iter().flatten() { + self.obj + .damage(damage.loc.x, damage.loc.y, damage.size.w, damage.size.h); + } + + let time = presented.into(); + let tv_sec_hi = (time.as_secs() >> 32) as u32; + let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; + let tv_nsec = time.subsec_nanos(); + self.obj.presentation_time(tv_sec_hi, tv_sec_lo, tv_nsec); + + self.obj.ready() + } + + pub fn fail(self, reason: FailureReason) { + let mut inner = self.inner.lock().unwrap(); + inner.failed = Some(reason); + if inner.capture_requested { + self.obj.failed(reason); + } + } +} + +#[derive(Debug)] +struct FrameInner { + constraints: Option, + buffer: Option, + damage: Vec>, + obj: SessionObj, + capture_requested: bool, + failed: Option, +} + +impl FrameInner { + fn new(obj: SessionObj, constraints: impl Into>) -> Self { + FrameInner { + constraints: constraints.into(), + buffer: None, + damage: Vec::new(), + obj, + capture_requested: false, + failed: None, + } + } +} + +pub trait ScreencopyHandler { + fn screencopy_state(&mut self) -> &mut ScreencopyState; + + fn capture_source(&mut self, source: &ImageSourceData) -> Option; + fn capture_cursor_source(&mut self, source: &ImageSourceData) -> Option; + + fn new_session(&mut self, session: Session); + fn new_cursor_session(&mut self, session: CursorSession); + + fn frame(&mut self, session: Session, frame: Frame); + fn cursor_frame(&mut self, session: CursorSession, frame: Frame); + + fn frame_aborted(&mut self, frame: Frame); + fn session_destroyed(&mut self, session: Session) { + let _ = session; + } + fn cursor_session_destroyed(&mut self, session: CursorSession) { + let _ = session; + } +} + +pub struct ScreencopyGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +pub struct ScreencopyData; + +pub struct SessionData { + inner: Arc>, +} + +pub struct CursorSessionData { + inner: Arc>, +} +pub struct FrameData { + inner: Arc>, +} + +macro_rules! delegate_screencopy { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2: $crate::wayland::protocols::screencopy::ScreencopyGlobalData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2: $crate::wayland::protocols::screencopy::ScreencopyData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2: $crate::wayland::protocols::screencopy::SessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_cursor_session_v2::ZcosmicScreencopyCursorSessionV2: $crate::wayland::protocols::screencopy::CursorSessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2: $crate::wayland::protocols::screencopy::CursorSessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2: $crate::wayland::protocols::screencopy::FrameData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::screencopy::v1::server::ext_screencopy_manager_v1::ExtScreencopyManagerV1: $crate::wayland::protocols::screencopy::ScreencopyGlobalData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::screencopy::v1::server::ext_screencopy_manager_v1::ExtScreencopyManagerV1: $crate::wayland::protocols::screencopy::ScreencopyData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::screencopy::v1::server::ext_screencopy_session_v1::ExtScreencopySessionV1: $crate::wayland::protocols::screencopy::SessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::screencopy::v1::server::ext_screencopy_cursor_session_v1::ExtScreencopyCursorSessionV1: $crate::wayland::protocols::screencopy::CursorSessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::screencopy::v1::server::ext_screencopy_session_v1::ExtScreencopySessionV1: $crate::wayland::protocols::screencopy::CursorSessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols::ext::screencopy::v1::server::ext_screencopy_frame_v1::ExtScreencopyFrameV1: $crate::wayland::protocols::screencopy::FrameData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + }; +} +pub(crate) use delegate_screencopy; diff --git a/src/wayland/protocols/screencopy/types.rs b/src/wayland/protocols/screencopy/types.rs new file mode 100644 index 00000000..37b44076 --- /dev/null +++ b/src/wayland/protocols/screencopy/types.rs @@ -0,0 +1,226 @@ +pub use cosmic_protocols::screencopy::v2::server::zcosmic_screencopy_frame_v2::FailureReason; +use cosmic_protocols::screencopy::v2::server::{ + zcosmic_screencopy_cursor_session_v2::ZcosmicScreencopyCursorSessionV2, + zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2, + zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2, +}; +use smithay::reexports::wayland_protocols::ext::screencopy::v1::server::{ + ext_screencopy_cursor_session_v1::ExtScreencopyCursorSessionV1, + ext_screencopy_frame_v1::ExtScreencopyFrameV1, + ext_screencopy_session_v1::ExtScreencopySessionV1, +}; +use smithay::reexports::wayland_server::{ + protocol::{wl_output, wl_shm}, + Resource, +}; + +#[derive(Debug, Clone, PartialEq)] +pub enum SessionObj { + Cosmic(ZcosmicScreencopySessionV2), + Ext(ExtScreencopySessionV1), +} +impl From for SessionObj { + fn from(val: ZcosmicScreencopySessionV2) -> Self { + SessionObj::Cosmic(val) + } +} +impl From for SessionObj { + fn from(val: ExtScreencopySessionV1) -> Self { + SessionObj::Ext(val) + } +} +impl PartialEq for SessionObj { + fn eq(&self, other: &ZcosmicScreencopySessionV2) -> bool { + match self { + SessionObj::Cosmic(val) => val == other, + _ => false, + } + } +} +impl PartialEq for SessionObj { + fn eq(&self, other: &ExtScreencopySessionV1) -> bool { + match self { + SessionObj::Ext(val) => val == other, + _ => false, + } + } +} +impl SessionObj { + pub fn is_alive(&self) -> bool { + match self { + SessionObj::Cosmic(val) => val.is_alive(), + SessionObj::Ext(val) => val.is_alive(), + } + } + + pub fn buffer_size(&self, w: u32, h: u32) { + match self { + SessionObj::Cosmic(val) => val.buffer_size(w, h), + SessionObj::Ext(val) => val.buffer_size(w, h), + } + } + + pub fn shm_format(&self, format: wl_shm::Format) { + match self { + SessionObj::Cosmic(val) => val.shm_format(format as u32), + SessionObj::Ext(val) => val.shm_format(format), + } + } + + pub fn dmabuf_device(&self, dev: Vec) { + match self { + SessionObj::Cosmic(val) => val.dmabuf_device(dev), + SessionObj::Ext(val) => val.dmabuf_device(dev), + } + } + + pub fn dmabuf_format(&self, format: u32, modifiers: Vec) { + match self { + SessionObj::Cosmic(val) => val.dmabuf_format(format, modifiers), + SessionObj::Ext(val) => val.dmabuf_format(format, modifiers), + } + } + + pub fn done(&self) { + match self { + SessionObj::Cosmic(val) => val.done(), + SessionObj::Ext(val) => val.done(), + } + } + + pub fn stopped(&self) { + match self { + SessionObj::Cosmic(val) => val.done(), + SessionObj::Ext(val) => val.done(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum CursorSessionObj { + Cosmic(ZcosmicScreencopyCursorSessionV2), + Ext(ExtScreencopyCursorSessionV1), +} +impl From for CursorSessionObj { + fn from(val: ZcosmicScreencopyCursorSessionV2) -> Self { + CursorSessionObj::Cosmic(val) + } +} +impl From for CursorSessionObj { + fn from(val: ExtScreencopyCursorSessionV1) -> Self { + CursorSessionObj::Ext(val) + } +} +impl PartialEq for CursorSessionObj { + fn eq(&self, other: &ZcosmicScreencopyCursorSessionV2) -> bool { + match self { + CursorSessionObj::Cosmic(val) => val == other, + _ => false, + } + } +} +impl PartialEq for CursorSessionObj { + fn eq(&self, other: &ExtScreencopyCursorSessionV1) -> bool { + match self { + CursorSessionObj::Ext(val) => val == other, + _ => false, + } + } +} +impl CursorSessionObj { + pub fn is_alive(&self) -> bool { + match self { + CursorSessionObj::Cosmic(val) => val.is_alive(), + CursorSessionObj::Ext(val) => val.is_alive(), + } + } + pub fn enter(&self) { + match self { + CursorSessionObj::Cosmic(val) => val.enter(), + CursorSessionObj::Ext(val) => val.enter(), + } + } + pub fn hotspot(&self, x: i32, y: i32) { + match self { + CursorSessionObj::Cosmic(val) => val.hotspot(x, y), + CursorSessionObj::Ext(val) => val.hotspot(x, y), + } + } + pub fn position(&self, x: i32, y: i32) { + match self { + CursorSessionObj::Cosmic(val) => val.position(x, y), + CursorSessionObj::Ext(val) => val.position(x, y), + } + } + pub fn leave(&self) { + match self { + CursorSessionObj::Cosmic(val) => val.leave(), + CursorSessionObj::Ext(val) => val.leave(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum FrameObj { + Cosmic(ZcosmicScreencopyFrameV2), + Ext(ExtScreencopyFrameV1), +} +impl From for FrameObj { + fn from(val: ZcosmicScreencopyFrameV2) -> Self { + FrameObj::Cosmic(val) + } +} +impl From for FrameObj { + fn from(val: ExtScreencopyFrameV1) -> Self { + FrameObj::Ext(val) + } +} +impl PartialEq for FrameObj { + fn eq(&self, other: &ZcosmicScreencopyFrameV2) -> bool { + match self { + FrameObj::Cosmic(val) => val == other, + _ => false, + } + } +} +impl PartialEq for FrameObj { + fn eq(&self, other: &ExtScreencopyFrameV1) -> bool { + match self { + FrameObj::Ext(val) => val == other, + _ => false, + } + } +} + +impl FrameObj { + pub fn transform(&self, transform: wl_output::Transform) { + match self { + FrameObj::Cosmic(val) => val.transform(transform), + FrameObj::Ext(val) => val.transform(transform), + } + } + pub fn damage(&self, x: i32, y: i32, w: i32, h: i32) { + match self { + FrameObj::Cosmic(val) => val.damage(x, y, w, h), + FrameObj::Ext(val) => val.damage(x, y, w, h), + } + } + pub fn presentation_time(&self, sec_hi: u32, sec_lo: u32, nsec: u32) { + match self { + FrameObj::Cosmic(val) => val.presentation_time(sec_hi, sec_lo, nsec), + FrameObj::Ext(val) => val.presentation_time(sec_hi, sec_lo, nsec), + } + } + pub fn ready(&self) { + match self { + FrameObj::Cosmic(val) => val.ready(), + FrameObj::Ext(val) => val.ready(), + } + } + pub fn failed(&self, reason: FailureReason) { + match self { + FrameObj::Cosmic(val) => val.failed(reason), + FrameObj::Ext(val) => val.failed((reason as u32).try_into().unwrap()), + } + } +}