From 50845b4a2ef47d6f39271dde3ff6c46d2710efcc Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 8 Oct 2024 22:22:49 +0200 Subject: [PATCH] screencapture: implement ext_image_copy_capture_manager_v1 --- docs/features.md | 2 + release-notes.md | 1 + src/client/objects.rs | 12 +- src/compositor.rs | 1 + src/gfx_api.rs | 1 + src/gfx_apis/gl/gl/render_buffer.rs | 4 + src/gfx_apis/gl/renderer/context.rs | 4 +- src/gfx_apis/gl/renderer/framebuffer.rs | 4 + src/gfx_apis/vulkan/image.rs | 7 + src/globals.rs | 2 + src/ifs.rs | 1 + src/ifs/ext_image_capture_source_v1.rs | 2 - src/ifs/ext_image_copy.rs | 4 + ...xt_image_copy_capture_cursor_session_v1.rs | 75 ++++ .../ext_image_copy_capture_frame_v1.rs | 371 ++++++++++++++++++ .../ext_image_copy_capture_manager_v1.rs | 174 ++++++++ .../ext_image_copy_capture_session_v1.rs | 341 ++++++++++++++++ src/ifs/jay_screencast.rs | 17 +- src/ifs/wl_surface.rs | 8 +- src/it/test_gfx_api.rs | 7 + src/state.rs | 3 + src/tasks/connector.rs | 4 + src/tree/output.rs | 28 +- src/tree/toplevel.rs | 30 +- ...t_image_copy_capture_cursor_session_v1.txt | 25 ++ wire/ext_image_copy_capture_frame_v1.txt | 43 ++ wire/ext_image_copy_capture_manager_v1.txt | 15 + wire/ext_image_copy_capture_session_v1.txt | 33 ++ 28 files changed, 1194 insertions(+), 25 deletions(-) create mode 100644 src/ifs/ext_image_copy.rs create mode 100644 src/ifs/ext_image_copy/ext_image_copy_capture_cursor_session_v1.rs create mode 100644 src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs create mode 100644 src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs create mode 100644 src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs create mode 100644 wire/ext_image_copy_capture_cursor_session_v1.txt create mode 100644 wire/ext_image_copy_capture_frame_v1.txt create mode 100644 wire/ext_image_copy_capture_manager_v1.txt create mode 100644 wire/ext_image_copy_capture_session_v1.txt diff --git a/docs/features.md b/docs/features.md index 6759a6eb..4737e2eb 100644 --- a/docs/features.md +++ b/docs/features.md @@ -143,6 +143,7 @@ Jay supports the following wayland protocols: | ext_foreign_toplevel_image_capture_source_manager_v1 | 1 | | | ext_foreign_toplevel_list_v1 | 1 | Yes | | ext_idle_notifier_v1 | 1 | Yes | +| ext_image_copy_capture_manager_v1 | 1[^composited] | Yes | | ext_output_image_capture_source_manager_v1 | 1 | | | ext_session_lock_manager_v1 | 1 | Yes | | ext_transient_seat_manager_v1 | 1[^ts_rejected] | Yes | @@ -187,3 +188,4 @@ Jay supports the following wayland protocols: [^lsaccess]: Sandboxes can restrict access to this protocol. [^ts_rejected]: Seat creation is always rejected. +[^composited]: Cursors are always composited. diff --git a/release-notes.md b/release-notes.md index 91424403..0fefacfe 100644 --- a/release-notes.md +++ b/release-notes.md @@ -5,6 +5,7 @@ - Vulkan is now the default renderer. - Emulate vblank events on the nvidia driver. - Implement ext-image-capture-source-v1. +- Implement ext-image-copy-capture-v1. # 1.6.0 (2024-09-25) diff --git a/src/client/objects.rs b/src/client/objects.rs index 1ec73b23..749efb2d 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -4,6 +4,7 @@ use { ifs::{ ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_image_capture_source_v1::ExtImageCaptureSourceV1, + ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, ipc::{ wl_data_source::WlDataSource, zwlr_data_control_source_v1::ZwlrDataControlSourceV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, @@ -33,9 +34,10 @@ use { copyhashmap::{CopyHashMap, Locked}, }, wire::{ - ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id, JayOutputId, JayScreencastId, - JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, - WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, + ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id, + ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, JayToplevelId, + JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, + WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, @@ -73,6 +75,8 @@ pub struct Objects { pub image_capture_sources: CopyHashMap>, pub foreign_toplevel_handles: CopyHashMap>, + pub ext_copy_sessions: + CopyHashMap>, ids: RefCell>, } @@ -108,6 +112,7 @@ impl Objects { xdg_popups: Default::default(), image_capture_sources: Default::default(), foreign_toplevel_handles: Default::default(), + ext_copy_sessions: Default::default(), ids: RefCell::new(vec![]), } } @@ -147,6 +152,7 @@ impl Objects { self.xdg_popups.clear(); self.image_capture_sources.clear(); self.foreign_toplevel_handles.clear(); + self.ext_copy_sessions.clear(); } pub fn id(&self, client_data: &Client) -> Result diff --git a/src/compositor.rs b/src/compositor.rs index 61e02dce..3dc6a4d9 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -574,6 +574,7 @@ fn create_dummy_output(state: &Rc) { latch_event: Default::default(), presentation_event: Default::default(), flip_margin_ns: Default::default(), + ext_copy_sessions: Default::default(), }); let dummy_workspace = Rc::new(WorkspaceNode { id: state.node_ids.next(), diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 2af944fc..245a51e9 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -270,6 +270,7 @@ pub trait GfxFramebuffer: Debug { pub trait GfxInternalFramebuffer: GfxFramebuffer { fn into_fb(self: Rc) -> Rc; + fn stride(&self) -> i32; fn staging_size(&self) -> usize; diff --git a/src/gfx_apis/gl/gl/render_buffer.rs b/src/gfx_apis/gl/gl/render_buffer.rs index 545a746b..92eec843 100644 --- a/src/gfx_apis/gl/gl/render_buffer.rs +++ b/src/gfx_apis/gl/gl/render_buffer.rs @@ -21,6 +21,7 @@ pub struct GlRenderBuffer { pub ctx: Rc, pub width: i32, pub height: i32, + pub stride: i32, pub format: &'static Format, rbo: GLuint, } @@ -30,6 +31,7 @@ impl GlRenderBuffer { ctx: &Rc, width: i32, height: i32, + stride: i32, format: &'static Format, ) -> Result, RenderError> { let Some(shm_info) = &format.shm_info else { @@ -46,6 +48,7 @@ impl GlRenderBuffer { ctx: ctx.clone(), width, height, + stride, format, rbo, })) @@ -71,6 +74,7 @@ impl GlRenderBuffer { ctx: ctx.clone(), width: img.dmabuf.width, height: img.dmabuf.height, + stride: 0, format: img.dmabuf.format, rbo, })) diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 32cf2392..34a7ccd1 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -321,11 +321,11 @@ impl GfxContext for GlRenderContext { _cpu_worker: &Rc, width: i32, height: i32, - _stride: i32, + stride: i32, format: &'static Format, ) -> Result, GfxError> { let fb = self.ctx.with_current(|| unsafe { - GlRenderBuffer::new(&self.ctx, width, height, format)?.create_framebuffer() + GlRenderBuffer::new(&self.ctx, width, height, stride, format)?.create_framebuffer() })?; Ok(Rc::new(Framebuffer { ctx: self, gl: fb })) } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index bbe98c16..190e47e2 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -119,6 +119,10 @@ impl GfxInternalFramebuffer for Framebuffer { self } + fn stride(&self) -> i32 { + self.gl.rb.stride + } + fn staging_size(&self) -> usize { 0 } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 43fcb376..b7628ce6 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -520,6 +520,13 @@ impl GfxInternalFramebuffer for VulkanImage { self } + fn stride(&self) -> i32 { + let VulkanImageMemory::Internal(shm) = &self.ty else { + unreachable!(); + }; + shm.stride as _ + } + fn staging_size(&self) -> usize { let VulkanImageMemory::Internal(shm) = &self.ty else { unreachable!(); diff --git a/src/globals.rs b/src/globals.rs index 5560d9bd..5f681d03 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -6,6 +6,7 @@ use { ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global, ext_idle_notifier_v1::ExtIdleNotifierV1Global, + ext_image_copy::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1Global, ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, ipc::{ @@ -201,6 +202,7 @@ impl Globals { add_singleton!(JayDamageTrackingGlobal); add_singleton!(ExtOutputImageCaptureSourceManagerV1Global); add_singleton!(ExtForeignToplevelImageCaptureSourceManagerV1Global); + add_singleton!(ExtImageCopyCaptureManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 9bd777ad..9d053ceb 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -4,6 +4,7 @@ pub mod ext_foreign_toplevel_list_v1; pub mod ext_idle_notification_v1; pub mod ext_idle_notifier_v1; pub mod ext_image_capture_source_v1; +pub mod ext_image_copy; pub mod ext_output_image_capture_source_manager_v1; pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; diff --git a/src/ifs/ext_image_capture_source_v1.rs b/src/ifs/ext_image_capture_source_v1.rs index 7a12ee7d..9f309257 100644 --- a/src/ifs/ext_image_capture_source_v1.rs +++ b/src/ifs/ext_image_capture_source_v1.rs @@ -11,7 +11,6 @@ use { thiserror::Error, }; -#[expect(dead_code)] #[derive(Clone)] pub enum ImageCaptureSource { Output(Rc), @@ -22,7 +21,6 @@ pub struct ExtImageCaptureSourceV1 { pub id: ExtImageCaptureSourceV1Id, pub client: Rc, pub tracker: Tracker, - #[expect(dead_code)] pub ty: ImageCaptureSource, } diff --git a/src/ifs/ext_image_copy.rs b/src/ifs/ext_image_copy.rs new file mode 100644 index 00000000..d736eed2 --- /dev/null +++ b/src/ifs/ext_image_copy.rs @@ -0,0 +1,4 @@ +pub mod ext_image_copy_capture_cursor_session_v1; +pub mod ext_image_copy_capture_frame_v1; +pub mod ext_image_copy_capture_manager_v1; +pub mod ext_image_copy_capture_session_v1; diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_cursor_session_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_cursor_session_v1.rs new file mode 100644 index 00000000..08695079 --- /dev/null +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_cursor_session_v1.rs @@ -0,0 +1,75 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::{ + ext_image_capture_source_v1::ImageCaptureSource, + ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ext_image_copy_capture_cursor_session_v1::*, ExtImageCopyCaptureCursorSessionV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct ExtImageCopyCaptureCursorSessionV1 { + pub(super) id: ExtImageCopyCaptureCursorSessionV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) have_session: Cell, + pub(super) source: ImageCaptureSource, +} + +impl ExtImageCopyCaptureCursorSessionV1RequestHandler for ExtImageCopyCaptureCursorSessionV1 { + type Error = ExtImageCopyCaptureCursorSessionV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_capture_session( + &self, + req: GetCaptureSession, + _slf: &Rc, + ) -> Result<(), Self::Error> { + if self.have_session.replace(true) { + return Err(ExtImageCopyCaptureCursorSessionV1Error::HaveSession); + } + let obj = Rc::new_cyclic(|slf| { + ExtImageCopyCaptureSessionV1::new( + req.session, + &self.client, + self.version, + &self.source, + slf, + ) + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + obj.send_shm_formats(); + obj.send_buffer_size(1, 1); + obj.send_done(); + Ok(()) + } +} + +object_base! { + self = ExtImageCopyCaptureCursorSessionV1; + version = self.version; +} + +impl Object for ExtImageCopyCaptureCursorSessionV1 {} + +simple_add_obj!(ExtImageCopyCaptureCursorSessionV1); + +#[derive(Debug, Error)] +pub enum ExtImageCopyCaptureCursorSessionV1Error { + #[error(transparent)] + ClientError(Box), + #[error("The session has already been created")] + HaveSession, +} +efrom!(ExtImageCopyCaptureCursorSessionV1Error, ClientError); diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs new file mode 100644 index 00000000..810368e9 --- /dev/null +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs @@ -0,0 +1,371 @@ +use { + crate::{ + client::{Client, ClientError}, + gfx_api::{ + AcquireSync, AsyncShmGfxTextureCallback, BufferResv, GfxError, GfxFramebuffer, + GfxTexture, ReleaseSync, SyncFile, STAGING_DOWNLOAD, + }, + ifs::{ + ext_image_capture_source_v1::ImageCaptureSource, + ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, + wl_buffer::WlBufferStorage, + }, + leaks::Tracker, + object::Object, + rect::Region, + tree::{Node, OutputNode}, + utils::{cell_ext::CellExt, errorfmt::ErrorFmt, transform_ext::TransformExt}, + wire::{ext_image_copy_capture_frame_v1::*, ExtImageCopyCaptureFrameV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(super) enum FrameStatus { + Unused, + Capturing, + Captured, + Ready, + Failed, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(super) enum FrameFailureReason { + Unknown, + BufferConstraints, + Stopped, +} + +pub struct ExtImageCopyCaptureFrameV1 { + pub(super) id: ExtImageCopyCaptureFrameV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) session: Rc, +} + +impl ExtImageCopyCaptureFrameV1 { + fn ensure_unused(&self) -> Result<(), ExtImageCopyCaptureFrameV1Error> { + if self.session.status.get() != FrameStatus::Unused { + return Err(ExtImageCopyCaptureFrameV1Error::AlreadyCaptured); + } + Ok(()) + } + + pub(super) fn fail(&self, reason: FrameFailureReason) { + let reason = match reason { + FrameFailureReason::Unknown => 0, + FrameFailureReason::BufferConstraints => 1, + FrameFailureReason::Stopped => 2, + }; + self.client.event(Failed { + self_id: self.id, + reason, + }); + self.session.status.set(FrameStatus::Failed); + self.session.presentation_listener.detach(); + self.session.buffer.take(); + self.session.pending_download.take(); + self.session.force_capture.set(true); + } + + fn try_copy( + self: &Rc, + on: &OutputNode, + size: (i32, i32), + f: impl FnOnce( + Rc, + AcquireSync, + ReleaseSync, + ) -> Result, GfxError>, + ) -> Result<(), FrameFailureReason> { + let Some(ctx) = self.client.state.render_ctx.get() else { + return Err(FrameFailureReason::BufferConstraints); + }; + let buffer = self.session.buffer.get().unwrap(); + if size != buffer.rect.size() { + self.session.buffer_size_changed(); + // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/222 + // self.fail(FrameFailureReason::BufferConstraints); + // return; + } + if let Err(e) = buffer.update_framebuffer() { + log::error!("Could not import buffer: {}", ErrorFmt(e)); + return Err(FrameFailureReason::BufferConstraints); + } + let storage = &*buffer.storage.borrow(); + let Some(storage) = storage else { + return Err(FrameFailureReason::BufferConstraints); + }; + let mut shm_bridge = self.session.shm_bridge.take(); + let mut shm_staging = self.session.shm_staging.take(); + match storage { + WlBufferStorage::Shm { mem, stride } => { + if let Some(b) = &shm_bridge { + if b.physical_size() != buffer.rect.size() + || b.format() != buffer.format + || b.stride() != *stride + { + shm_bridge = None; + } + } + let bridge = match shm_bridge { + Some(b) => b, + _ => { + let res = ctx.clone().create_internal_fb( + &self.client.state.cpu_worker, + buffer.rect.width(), + buffer.rect.height(), + *stride, + buffer.format, + ); + match res { + Ok(b) => b, + Err(e) => { + log::error!("Could not allocate staging fb: {}", ErrorFmt(e)); + return Err(FrameFailureReason::Unknown); + } + } + } + }; + if let Some(s) = &shm_staging { + if s.size() != bridge.staging_size() { + shm_staging = None; + } + } + let staging = match shm_staging { + Some(s) => s, + _ => ctx.create_staging_buffer(bridge.staging_size(), STAGING_DOWNLOAD), + }; + let res = f( + bridge.clone().into_fb(), + AcquireSync::Unnecessary, + ReleaseSync::None, + ); + if let Err(e) = res { + log::error!("Could not copy frame to staging texture: {}", ErrorFmt(e)); + return Err(FrameFailureReason::Unknown); + } + let res = bridge.clone().download( + &staging, + self.clone(), + mem.clone(), + Region::new2(buffer.rect), + ); + match res { + Ok(d) => self.session.pending_download.set(d), + Err(e) => { + log::error!("Could not initiate bridge download: {}", ErrorFmt(e)); + return Err(FrameFailureReason::Unknown); + } + } + self.session.shm_bridge.set(Some(bridge)); + self.session.shm_staging.set(Some(staging)); + } + WlBufferStorage::Dmabuf { fb, .. } => { + let Some(fb) = fb else { + return Err(FrameFailureReason::BufferConstraints); + }; + let res = f(fb.clone(), AcquireSync::Implicit, ReleaseSync::Implicit); + if let Err(e) = res { + log::error!("Could not copy frame to client fb: {}", ErrorFmt(e)); + return Err(FrameFailureReason::Unknown); + } + } + } + self.session + .presentation_listener + .attach(&on.presentation_event); + Ok(()) + } + + fn copy( + self: &Rc, + on: &OutputNode, + size: (i32, i32), + f: impl FnOnce( + Rc, + AcquireSync, + ReleaseSync, + ) -> Result, GfxError>, + ) { + match self.try_copy(on, size, f) { + Ok(()) => self.session.status.set(FrameStatus::Captured), + Err(e) => self.fail(e), + } + } + + pub(super) fn copy_texture( + self: &Rc, + on: &OutputNode, + texture: &Rc, + resv: Option<&Rc>, + acquire_sync: &AcquireSync, + release_sync: ReleaseSync, + render_hardware_cursors: bool, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + ) { + let transform = on.global.persistent.transform.get(); + let req_size = size.unwrap_or(transform.maybe_swap(texture.size())); + self.copy(on, req_size, |fb, aq, re| { + self.client.state.perform_screencopy( + texture, + resv, + acquire_sync, + release_sync, + &fb, + aq, + re, + jay_config::video::Transform::None, + on.global.pos.get(), + render_hardware_cursors, + x_off, + y_off, + size, + transform, + on.global.persistent.scale.get(), + ) + }); + } + + pub(super) fn copy_node(self: &Rc, on: &OutputNode, node: &dyn Node, size: (i32, i32)) { + let scale = on.global.persistent.scale.get(); + self.copy(on, size, |fb, aq, re| { + fb.render_node( + aq, + re, + node, + &self.client.state, + Some(node.node_absolute_position()), + scale, + true, + true, + true, + jay_config::video::Transform::None, + ) + }); + } + + pub(super) fn maybe_ready(&self) { + if self.session.pending_download.is_some() { + return; + } + let Some((tv_sec, tv_nsec)) = self.session.presented.get() else { + return; + }; + if let Some(buffer) = self.session.buffer.get() { + self.client.event(Damage { + self_id: self.id, + x: 0, + y: 0, + width: buffer.rect.width(), + height: buffer.rect.height(), + }); + } + self.client.event(PresentationTime { + self_id: self.id, + tv_sec_hi: (tv_sec >> 32) as u32, + tv_sec_lo: tv_sec as u32, + tv_nsec, + }); + self.client.event(Ready { self_id: self.id }); + self.session.status.set(FrameStatus::Ready); + } +} + +impl ExtImageCopyCaptureFrameV1RequestHandler for ExtImageCopyCaptureFrameV1 { + type Error = ExtImageCopyCaptureFrameV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + if self.session.status.get() == FrameStatus::Captured { + self.session.shm_staging.take(); + self.session.shm_bridge.take(); + } + self.session.frame.take(); + self.session.presentation_listener.detach(); + self.session.presented.take(); + self.session.pending_download.take(); + self.session.status.set(FrameStatus::Unused); + self.session.buffer.take(); + self.client.remove_obj(self)?; + Ok(()) + } + + fn attach_buffer(&self, req: AttachBuffer, _slf: &Rc) -> Result<(), Self::Error> { + self.ensure_unused()?; + let buffer = self.client.lookup(req.buffer)?; + self.session.buffer.set(Some(buffer)); + self.session.size_debounce.set(false); + Ok(()) + } + + fn damage_buffer(&self, _req: DamageBuffer, _slf: &Rc) -> Result<(), Self::Error> { + self.ensure_unused()?; + Ok(()) + } + + fn capture(&self, _req: Capture, _slf: &Rc) -> Result<(), Self::Error> { + self.ensure_unused()?; + if self.session.buffer.is_none() { + return Err(ExtImageCopyCaptureFrameV1Error::NoBuffer); + } + if self.session.stopped.get() { + self.fail(FrameFailureReason::Stopped); + return Ok(()); + } + self.session.status.set(FrameStatus::Capturing); + if self.session.force_capture.get() { + self.session.force_capture.set(false); + match &self.session.source { + ImageCaptureSource::Output(o) => { + if let Some(node) = o.node.get() { + node.global.connector.damage(); + } + } + ImageCaptureSource::Toplevel(tl) => { + if let Some(tl) = tl.get() { + tl.tl_data().output().global.connector.damage(); + } + } + } + } + Ok(()) + } +} + +impl AsyncShmGfxTextureCallback for ExtImageCopyCaptureFrameV1 { + fn completed(self: Rc, res: Result<(), GfxError>) { + self.session.pending_download.take(); + if self.session.status.get() != FrameStatus::Captured { + return; + } + if let Err(e) = res { + log::error!("Bridge download failed: {}", ErrorFmt(e)); + self.fail(FrameFailureReason::Unknown); + return; + } + self.maybe_ready(); + } +} + +object_base! { + self = ExtImageCopyCaptureFrameV1; + version = self.session.version; +} + +impl Object for ExtImageCopyCaptureFrameV1 {} + +simple_add_obj!(ExtImageCopyCaptureFrameV1); + +#[derive(Debug, Error)] +pub enum ExtImageCopyCaptureFrameV1Error { + #[error(transparent)] + ClientError(Box), + #[error("The frame has already been captured")] + AlreadyCaptured, + #[error("The frame does not have a buffer attached")] + NoBuffer, +} +efrom!(ExtImageCopyCaptureFrameV1Error, ClientError); diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs new file mode 100644 index 00000000..914e920d --- /dev/null +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs @@ -0,0 +1,174 @@ +use { + crate::{ + client::{Client, ClientCaps, ClientError, CAP_SCREENCOPY_MANAGER}, + globals::{Global, GlobalName}, + ifs::{ + ext_image_capture_source_v1::ImageCaptureSource, + ext_image_copy::{ + ext_image_copy_capture_cursor_session_v1::ExtImageCopyCaptureCursorSessionV1, + ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, + }, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ext_image_copy_capture_manager_v1::*, ExtImageCopyCaptureManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtImageCopyCaptureManagerV1Global { + pub name: GlobalName, +} + +impl ExtImageCopyCaptureManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ExtImageCopyCaptureManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), ExtImageCopyCaptureManagerV1Error> { + let obj = Rc::new(ExtImageCopyCaptureManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +pub struct ExtImageCopyCaptureManagerV1 { + pub(super) id: ExtImageCopyCaptureManagerV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, +} + +impl ExtImageCopyCaptureManagerV1RequestHandler for ExtImageCopyCaptureManagerV1 { + type Error = ExtImageCopyCaptureManagerV1Error; + + fn create_session(&self, req: CreateSession, _slf: &Rc) -> Result<(), Self::Error> { + let source = self.client.lookup(req.source)?; + let obj = Rc::new_cyclic(|slf| { + ExtImageCopyCaptureSessionV1::new( + req.session, + &self.client, + self.version, + &source.ty, + slf, + ) + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + 'send_constraints: { + let id = (self.client.id, obj.id); + match &source.ty { + ImageCaptureSource::Output(o) => { + let Some(node) = o.node() else { + obj.send_stopped(); + break 'send_constraints; + }; + node.ext_copy_sessions.set(id, obj.clone()); + } + ImageCaptureSource::Toplevel(tl) => { + let Some(node) = tl.get() else { + obj.send_stopped(); + break 'send_constraints; + }; + let data = node.tl_data(); + data.ext_copy_sessions.set(id, obj.clone()); + if data.visible.get() { + obj.latch_listener.attach(&data.output().latch_event); + } + } + } + let Some(ctx) = self.client.state.render_ctx.get() else { + obj.send_stopped(); + break 'send_constraints; + }; + obj.send_current_buffer_size(); + obj.send_shm_formats(); + if let Some(drm) = ctx.allocator().drm() { + obj.send_dmabuf_device(drm.dev()); + for format in ctx.formats().values() { + if format.write_modifiers.is_empty() { + continue; + } + let modifiers: Vec<_> = format.write_modifiers.keys().copied().collect(); + obj.send_dmabuf_format(format.format, &modifiers); + } + } + obj.send_done(); + } + Ok(()) + } + + fn create_pointer_cursor_session( + &self, + req: CreatePointerCursorSession, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let source = self.client.lookup(req.source)?; + let obj = Rc::new(ExtImageCopyCaptureCursorSessionV1 { + id: req.session, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + source: source.ty.clone(), + have_session: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +global_base!( + ExtImageCopyCaptureManagerV1Global, + ExtImageCopyCaptureManagerV1, + ExtImageCopyCaptureManagerV1Error +); + +impl Global for ExtImageCopyCaptureManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_SCREENCOPY_MANAGER + } +} + +simple_add_global!(ExtImageCopyCaptureManagerV1Global); + +object_base! { + self = ExtImageCopyCaptureManagerV1; + version = self.version; +} + +impl Object for ExtImageCopyCaptureManagerV1 {} + +simple_add_obj!(ExtImageCopyCaptureManagerV1); + +#[derive(Debug, Error)] +pub enum ExtImageCopyCaptureManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtImageCopyCaptureManagerV1Error, ClientError); diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs new file mode 100644 index 00000000..e0377f42 --- /dev/null +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs @@ -0,0 +1,341 @@ +use { + crate::{ + client::{Client, ClientError}, + format::{Format, FORMATS}, + gfx_api::{ + AcquireSync, BufferResv, GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, + PendingShmTransfer, ReleaseSync, + }, + ifs::{ + ext_image_capture_source_v1::ImageCaptureSource, + ext_image_copy::ext_image_copy_capture_frame_v1::{ + ExtImageCopyCaptureFrameV1, FrameFailureReason, FrameStatus, + }, + wl_buffer::WlBuffer, + }, + leaks::Tracker, + object::{Object, Version}, + time::Time, + tree::{LatchListener, OutputNode, PresentationListener}, + utils::{cell_ext::CellExt, clonecell::CloneCell, event_listener::EventListener}, + video::Modifier, + wire::{ext_image_copy_capture_session_v1::*, ExtImageCopyCaptureSessionV1Id}, + }, + std::{ + cell::Cell, + rc::{Rc, Weak}, + }, + thiserror::Error, + uapi::c, +}; + +pub struct ExtImageCopyCaptureSessionV1 { + pub(super) id: ExtImageCopyCaptureSessionV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) frame: CloneCell>>, + pub(super) shm_bridge: CloneCell>>, + pub(super) shm_staging: CloneCell>>, + pub(super) source: ImageCaptureSource, + pub(super) force_capture: Cell, + pub(super) stopped: Cell, + pub(super) latch_listener: EventListener, + pub(super) presentation_listener: EventListener, + pub(super) size_debounce: Cell, + pub(super) status: Cell, + pub(super) buffer: CloneCell>>, + pub(super) pending_download: Cell>, + pub(super) presented: Cell>, +} + +impl ExtImageCopyCaptureSessionV1 { + pub(super) fn new( + id: ExtImageCopyCaptureSessionV1Id, + client: &Rc, + version: Version, + source: &ImageCaptureSource, + slf: &Weak, + ) -> Self { + ExtImageCopyCaptureSessionV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + frame: Default::default(), + shm_bridge: Default::default(), + shm_staging: Default::default(), + source: source.clone(), + force_capture: Cell::new(true), + stopped: Default::default(), + latch_listener: EventListener::new(slf.clone()), + presentation_listener: EventListener::new(slf.clone()), + size_debounce: Default::default(), + status: Cell::new(FrameStatus::Unused), + buffer: Default::default(), + pending_download: Default::default(), + presented: Default::default(), + } + } + + pub fn buffer_size_changed(&self) { + if self.size_debounce.replace(true) { + return; + } + self.force_capture.set(true); + self.send_current_buffer_size(); + self.send_done(); + } + + pub(super) fn send_current_buffer_size(&self) { + let (width, height) = match &self.source { + ImageCaptureSource::Output(o) => { + let Some(node) = o.node() else { + return; + }; + node.global.pixel_size() + } + ImageCaptureSource::Toplevel(o) => { + let Some(node) = o.get() else { + return; + }; + node.tl_data().desired_pixel_size() + } + }; + self.send_buffer_size(width, height); + } + + pub(super) fn send_buffer_size(&self, width: i32, height: i32) { + self.client.event(BufferSize { + self_id: self.id, + width: width as _, + height: height as _, + }); + } + + pub(super) fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn stop(&self) { + self.stopped.set(true); + self.send_stopped(); + self.stop_pending_frame(); + } + + fn stop_pending_frame(&self) { + if let Some(frame) = self.frame.get() { + if let FrameStatus::Capturing | FrameStatus::Captured = self.status.get() { + frame.fail(FrameFailureReason::Stopped); + } + } + } + + pub(super) fn send_stopped(&self) { + self.client.event(Stopped { self_id: self.id }); + } + + pub(super) fn send_shm_formats(&self) { + for format in FORMATS { + if format.shm_info.is_some() { + self.client.event(ShmFormat { + self_id: self.id, + format: format.wl_id.unwrap_or(format.drm), + }); + } + } + } + + pub(super) fn send_dmabuf_device(&self, device: c::dev_t) { + self.client.event(DmabufDevice { + self_id: self.id, + device, + }); + } + + pub(super) fn send_dmabuf_format(&self, format: &Format, modifiers: &[Modifier]) { + self.client.event(DmabufFormat { + self_id: self.id, + format: format.drm, + modifiers: uapi::as_bytes(modifiers), + }); + } + + fn detach(&self) { + let id = (self.client.id, self.id); + match &self.source { + ImageCaptureSource::Output(o) => { + if let Some(n) = o.node() { + n.ext_copy_sessions.remove(&id); + } + } + ImageCaptureSource::Toplevel(tl) => { + if let Some(n) = tl.get() { + n.tl_data().ext_copy_sessions.remove(&id); + } + } + } + self.frame.take(); + self.shm_bridge.take(); + self.shm_staging.take(); + self.latch_listener.detach(); + self.presentation_listener.detach(); + self.buffer.take(); + self.pending_download.take(); + self.presented.take(); + } + + pub fn update_latch_listener(&self) { + let ImageCaptureSource::Toplevel(tl) = &self.source else { + return; + }; + let Some(tl) = tl.get() else { + return; + }; + let data = tl.tl_data(); + if data.visible.get() { + self.latch_listener.attach(&data.output().latch_event); + } else { + self.latch_listener.detach(); + } + if self.status.get() == FrameStatus::Captured && self.presented.is_none() { + self.presentation_listener.detach(); + let now = Time::now_unchecked(); + self.presented + .set(Some((now.0.tv_sec as _, now.0.tv_nsec as _))); + if let Some(frame) = self.frame.get() { + frame.maybe_ready(); + } + } + } + + pub fn copy_texture( + self: &Rc, + on: &OutputNode, + texture: &Rc, + resv: Option<&Rc>, + acquire_sync: &AcquireSync, + release_sync: ReleaseSync, + render_hardware_cursors: bool, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + ) { + if self.status.get() == FrameStatus::Capturing { + if let Some(frame) = self.frame.get() { + frame.copy_texture( + on, + texture, + resv, + acquire_sync, + release_sync, + render_hardware_cursors, + x_off, + y_off, + size, + ); + return; + } + } + self.force_capture.set(true); + } +} + +impl ExtImageCopyCaptureSessionV1RequestHandler for ExtImageCopyCaptureSessionV1 { + type Error = ExtImageCopyCaptureSessionV1Error; + + fn create_frame(&self, req: CreateFrame, slf: &Rc) -> Result<(), Self::Error> { + if self.frame.is_some() { + return Err(ExtImageCopyCaptureSessionV1Error::HaveFrame); + } + let obj = Rc::new(ExtImageCopyCaptureFrameV1 { + id: req.frame, + client: self.client.clone(), + tracker: Default::default(), + session: slf.clone(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + self.frame.set(Some(obj)); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.stop_pending_frame(); + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +impl LatchListener for ExtImageCopyCaptureSessionV1 { + fn after_latch(self: Rc, on: &OutputNode) { + let ImageCaptureSource::Toplevel(tl) = &self.source else { + return; + }; + let Some(tl) = tl.get() else { + return; + }; + let data = tl.tl_data(); + if !data.visible.get() { + return; + } + let Some(frame) = self.frame.get() else { + self.force_capture.set(true); + return; + }; + if self.status.get() != FrameStatus::Capturing { + self.force_capture.set(true); + return; + } + frame.copy_node(on, tl.tl_as_node(), data.desired_pixel_size()); + } +} + +impl PresentationListener for ExtImageCopyCaptureSessionV1 { + fn presented( + self: Rc, + _output: &OutputNode, + tv_sec: u64, + tv_nsec: u32, + _refresh: u32, + _seq: u64, + _flags: u32, + ) { + self.presentation_listener.detach(); + let Some(frame) = self.frame.get() else { + return; + }; + if self.status.get() != FrameStatus::Captured { + return; + }; + self.presented.set(Some((tv_sec, tv_nsec))); + frame.maybe_ready(); + } +} + +object_base! { + self = ExtImageCopyCaptureSessionV1; + version = self.version; +} + +impl Object for ExtImageCopyCaptureSessionV1 { + fn break_loops(&self) { + self.detach(); + } +} + +dedicated_add_obj!( + ExtImageCopyCaptureSessionV1, + ExtImageCopyCaptureSessionV1Id, + ext_copy_sessions +); + +#[derive(Debug, Error)] +pub enum ExtImageCopyCaptureSessionV1Error { + #[error(transparent)] + ClientError(Box), + #[error("There already is a pending frame")] + HaveFrame, +} +efrom!(ExtImageCopyCaptureSessionV1Error, ClientError); diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 85e8f820..dd31dc6c 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -89,7 +89,7 @@ enum Target { } impl LatchListener for JayScreencast { - fn after_latch(self: Rc) { + fn after_latch(self: Rc, _on: &OutputNode) { self.schedule_toplevel_screencast(); } } @@ -780,17 +780,10 @@ efrom!(JayScreencastError, ClientError); fn target_size(target: Option<&Target>) -> (i32, i32) { if let Some(target) = target { - match target { - Target::Output(o) => return o.global.pixel_size(), - Target::Toplevel(t) => { - let data = t.tl_data(); - let (dw, dh) = data.desired_extents.get().size(); - if let Some(ws) = data.workspace.get() { - let scale = ws.output.get().global.persistent.scale.get(); - return scale.pixel_size(dw, dh); - }; - } - } + return match target { + Target::Output(o) => o.global.pixel_size(), + Target::Toplevel(t) => t.tl_data().desired_pixel_size(), + }; } (0, 0) } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index f8559cb2..43934aeb 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -143,9 +143,13 @@ pub struct SurfaceSendPreferredScaleVisitor; impl SurfaceSendPreferredScaleVisitor { fn schedule_realloc(&self, tl: &impl ToplevelNode) { - for sc in tl.tl_data().jay_screencasts.lock().values() { + let data = tl.tl_data(); + for sc in data.jay_screencasts.lock().values() { sc.schedule_realloc_or_reconfigure(); } + for sc in data.ext_copy_sessions.lock().values() { + sc.buffer_size_changed(); + } } } @@ -2070,7 +2074,7 @@ impl VblankListener for WlSurface { } impl LatchListener for WlSurface { - fn after_latch(self: Rc) { + fn after_latch(self: Rc, _on: &OutputNode) { if self.visible.get() { if self.latched_commit_version.get() < self.commit_version.get() { let latched = &mut *self.latched_presentation_feedback.borrow_mut(); diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 87d33a81..36a03040 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -559,6 +559,13 @@ impl GfxInternalFramebuffer for TestGfxFb { self } + fn stride(&self) -> i32 { + let TestGfxImage::Shm(shm) = &*self.img else { + unreachable!(); + }; + shm.stride + } + fn staging_size(&self) -> usize { 0 } diff --git a/src/state.rs b/src/state.rs index f9222f65..9d27c036 100644 --- a/src/state.rs +++ b/src/state.rs @@ -552,6 +552,9 @@ impl State { for sc in client.data.objects.screencasts.lock().values() { scs.push(sc.clone()); } + for sc in client.data.objects.ext_copy_sessions.lock().values() { + sc.stop(); + } } for sc in scs { sc.do_destroy(); diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 229b6b18..c8e63c71 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -185,6 +185,7 @@ impl ConnectorHandler { vblank_event: Default::default(), presentation_event: Default::default(), flip_margin_ns: Default::default(), + ext_copy_sessions: Default::default(), }); on.update_visible(); on.update_rects(); @@ -284,6 +285,9 @@ impl ConnectorHandler { for sc in on.screencopies.lock().drain_values() { sc.send_failed(); } + for sc in on.ext_copy_sessions.lock().drain_values() { + sc.stop(); + } global.destroyed.set(true); self.state.root.outputs.remove(&self.id); self.state.output_extents_changed(); diff --git a/src/tree/output.rs b/src/tree/output.rs index 92f85598..fbdb6f95 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -6,6 +6,7 @@ use { fixed::Fixed, gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, ifs::{ + ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, jay_output::JayOutput, jay_screencast::JayScreencast, wl_buffer::WlBufferStorage, @@ -42,7 +43,9 @@ use { linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller, transform_ext::TransformExt, }, - wire::{JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id}, + wire::{ + ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id, + }, }, ahash::AHashMap, jay_config::video::{TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode}, @@ -87,10 +90,12 @@ pub struct OutputNode { pub vblank_event: EventSource, pub presentation_event: EventSource, pub flip_margin_ns: Cell>, + pub ext_copy_sessions: + CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc>, } pub trait LatchListener { - fn after_latch(self: Rc); + fn after_latch(self: Rc, on: &OutputNode); } pub trait VblankListener { @@ -133,7 +138,7 @@ impl OutputNode { pub fn latched(&self) { self.schedule.latched(); for listener in self.latch_event.iter() { - listener.after_latch(); + listener.after_latch(self); } } @@ -237,6 +242,19 @@ impl OutputNode { size, ); } + for sc in self.ext_copy_sessions.lock().values() { + sc.copy_texture( + self, + tex, + resv, + acquire_sync, + release_sync, + render_hardware_cursor, + x_off, + y_off, + size, + ); + } } pub fn perform_wlr_screencopies( @@ -353,6 +371,7 @@ impl OutputNode { self.jay_outputs.clear(); self.screencasts.clear(); self.screencopies.clear(); + self.ext_copy_sessions.clear(); } pub fn on_spaces_changed(self: &Rc) { @@ -692,6 +711,9 @@ impl OutputNode { for sc in self.screencasts.lock().values() { sc.schedule_realloc_or_reconfigure(); } + for sc in self.ext_copy_sessions.lock().values() { + sc.buffer_size_changed(); + } } if transform != old_transform { diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index d42280d3..2115d92a 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -4,6 +4,7 @@ use { ifs::{ ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, + ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, jay_screencast::JayScreencast, jay_toplevel::JayToplevel, wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId}, @@ -24,7 +25,10 @@ use { threshold_counter::ThresholdCounter, toplevel_identifier::{toplevel_identifier, ToplevelIdentifier}, }, - wire::{ExtForeignToplevelHandleV1Id, JayScreencastId, JayToplevelId}, + wire::{ + ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayScreencastId, + JayToplevelId, + }, }, std::{ cell::{Cell, RefCell}, @@ -130,6 +134,9 @@ impl ToplevelNode for T { for sc in data.jay_screencasts.lock().values() { sc.update_latch_listener(); } + for sc in data.ext_copy_sessions.lock().values() { + sc.update_latch_listener(); + } } fn tl_change_extents(self: Rc, rect: &Rect) { @@ -139,6 +146,9 @@ impl ToplevelNode for T { for sc in data.jay_screencasts.lock().values() { sc.schedule_realloc_or_reconfigure(); } + for sc in data.ext_copy_sessions.lock().values() { + sc.buffer_size_changed(); + } } if data.is_floating.get() { data.float_width.set(rect.width()); @@ -270,6 +280,8 @@ pub struct ToplevelData { pub render_highlight: NumCell, pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc>, pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc>, + pub ext_copy_sessions: + CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc>, } impl ToplevelData { @@ -300,6 +312,7 @@ impl ToplevelData { render_highlight: Default::default(), jay_toplevels: Default::default(), jay_screencasts: Default::default(), + ext_copy_sessions: Default::default(), } } @@ -343,6 +356,9 @@ impl ToplevelData { for screencast in self.jay_screencasts.lock().drain_values() { screencast.do_destroy(); } + for screencast in self.ext_copy_sessions.lock().drain_values() { + screencast.stop(); + } self.identifier.set(toplevel_identifier()); { let mut handles = self.handles.lock(); @@ -538,6 +554,9 @@ impl ToplevelData { for sc in self.jay_screencasts.lock().values() { sc.update_latch_listener(); } + for sc in self.ext_copy_sessions.lock().values() { + sc.update_latch_listener(); + } if !visible { return; } @@ -569,6 +588,15 @@ impl ToplevelData { Some(ws) => ws.output.get(), } } + + pub fn desired_pixel_size(&self) -> (i32, i32) { + let (dw, dh) = self.desired_extents.get().size(); + if let Some(ws) = self.workspace.get() { + let scale = ws.output.get().global.persistent.scale.get(); + return scale.pixel_size(dw, dh); + }; + (0, 0) + } } pub struct TileDragDestination { diff --git a/wire/ext_image_copy_capture_cursor_session_v1.txt b/wire/ext_image_copy_capture_cursor_session_v1.txt new file mode 100644 index 00000000..0fd5e60c --- /dev/null +++ b/wire/ext_image_copy_capture_cursor_session_v1.txt @@ -0,0 +1,25 @@ +request destroy { + +} + +request get_capture_session { + session: id(ext_image_copy_capture_session_v1), +} + +event enter { + +} + +event leave { + +} + +event position { + x: i32, + y: i32, +} + +event hotspot { + x: i32, + y: i32, +} diff --git a/wire/ext_image_copy_capture_frame_v1.txt b/wire/ext_image_copy_capture_frame_v1.txt new file mode 100644 index 00000000..6703e54b --- /dev/null +++ b/wire/ext_image_copy_capture_frame_v1.txt @@ -0,0 +1,43 @@ +request destroy { + +} + +request attach_buffer { + buffer: id(wl_buffer), +} + +request damage_buffer { + x: i32, + y: i32, + width: i32, + height: i32, +} + +request capture { + +} + +event transform { + transform: u32, +} + +event damage { + x: i32, + y: i32, + width: i32, + height: i32, +} + +event presentation_time { + tv_sec_hi: u32, + tv_sec_lo: u32, + tv_nsec: u32, +} + +event ready { + +} + +event failed { + reason: i32, +} diff --git a/wire/ext_image_copy_capture_manager_v1.txt b/wire/ext_image_copy_capture_manager_v1.txt new file mode 100644 index 00000000..48c5d0e5 --- /dev/null +++ b/wire/ext_image_copy_capture_manager_v1.txt @@ -0,0 +1,15 @@ +request create_session { + session: id(ext_image_copy_capture_session_v1), + source: id(ext_image_capture_source_v1), + options: u32, +} + +request create_pointer_cursor_session { + session: id(ext_image_copy_capture_cursor_session_v1), + source: id(ext_image_capture_source_v1), + pointer: id(wl_pointer), +} + +request destroy { + +} diff --git a/wire/ext_image_copy_capture_session_v1.txt b/wire/ext_image_copy_capture_session_v1.txt new file mode 100644 index 00000000..b7b5f88c --- /dev/null +++ b/wire/ext_image_copy_capture_session_v1.txt @@ -0,0 +1,33 @@ +request create_frame { + frame: id(ext_image_copy_capture_frame_v1), +} + +request destroy { + +} + +event buffer_size { + width: u32, + height: u32, +} + +event shm_format { + format: u32, +} + +event dmabuf_device { + device: pod(uapi::c::dev_t), +} + +event dmabuf_format { + format: u32, + modifiers: array(pod(u8)), +} + +event done { + +} + +event stopped { + +}