diff --git a/docs/features.md b/docs/features.md index 2409c02d..4737e2eb 100644 --- a/docs/features.md +++ b/docs/features.md @@ -138,50 +138,54 @@ Jay uses frame scheduling to achieve input latency as low as 1.5 ms. Jay supports the following wayland protocols: -| Global | Version | Privileged | -|-----------------------------------------|:----------------|---------------| -| ext_foreign_toplevel_list_v1 | 1 | Yes | -| ext_idle_notifier_v1 | 1 | Yes | -| ext_session_lock_manager_v1 | 1 | Yes | -| ext_transient_seat_manager_v1 | 1[^ts_rejected] | Yes | -| org_kde_kwin_server_decoration_manager | 1 | | -| wl_compositor | 6 | | -| wl_data_device_manager | 3 | | -| wl_drm | 2 | | -| wl_output | 4 | | -| wl_seat | 9 | | -| wl_shm | 2 | | -| wl_subcompositor | 1 | | -| wp_alpha_modifier_v1 | 1 | | -| wp_content_type_manager_v1 | 1 | | -| wp_cursor_shape_manager_v1 | 1 | | -| wp_drm_lease_device_v1 | 1 | | -| wp_fractional_scale_manager_v1 | 1 | | -| wp_linux_drm_syncobj_manager_v1 | 1 | | -| wp_presentation | 1 | | -| wp_security_context_manager_v1 | 1 | | -| wp_single_pixel_buffer_manager_v1 | 1 | | -| wp_tearing_control_manager_v1 | 1 | | -| wp_viewporter | 1 | | -| xdg_activation_v1 | 1 | | -| xdg_toplevel_drag_manager_v1 | 1 | | -| xdg_wm_base | 6 | | -| xdg_wm_dialog_v1 | 1 | | -| zwlr_data_control_manager_v1 | 2 | Yes | -| zwlr_layer_shell_v1 | 5 | No[^lsaccess] | -| zwlr_screencopy_manager_v1 | 3 | Yes | -| zwp_idle_inhibit_manager_v1 | 1 | | -| zwp_input_method_manager_v2 | 1 | Yes | -| zwp_linux_dmabuf_v1 | 5 | | -| zwp_pointer_constraints_v1 | 1 | | -| zwp_pointer_gestures_v1 | 3 | | -| zwp_primary_selection_device_manager_v1 | 1 | | -| zwp_relative_pointer_manager_v1 | 1 | | -| zwp_tablet_manager_v2 | 1 | | -| zwp_text_input_manager_v3 | 1 | | -| zwp_virtual_keyboard_manager_v1 | 1 | Yes | -| zxdg_decoration_manager_v1 | 1 | | -| zxdg_output_manager_v1 | 3 | | +| Global | Version | Privileged | +|------------------------------------------------------|:----------------|---------------| +| 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 | +| org_kde_kwin_server_decoration_manager | 1 | | +| wl_compositor | 6 | | +| wl_data_device_manager | 3 | | +| wl_drm | 2 | | +| wl_output | 4 | | +| wl_seat | 9 | | +| wl_shm | 2 | | +| wl_subcompositor | 1 | | +| wp_alpha_modifier_v1 | 1 | | +| wp_content_type_manager_v1 | 1 | | +| wp_cursor_shape_manager_v1 | 1 | | +| wp_drm_lease_device_v1 | 1 | | +| wp_fractional_scale_manager_v1 | 1 | | +| wp_linux_drm_syncobj_manager_v1 | 1 | | +| wp_presentation | 1 | | +| wp_security_context_manager_v1 | 1 | | +| wp_single_pixel_buffer_manager_v1 | 1 | | +| wp_tearing_control_manager_v1 | 1 | | +| wp_viewporter | 1 | | +| xdg_activation_v1 | 1 | | +| xdg_toplevel_drag_manager_v1 | 1 | | +| xdg_wm_base | 6 | | +| xdg_wm_dialog_v1 | 1 | | +| zwlr_data_control_manager_v1 | 2 | Yes | +| zwlr_layer_shell_v1 | 5 | No[^lsaccess] | +| zwlr_screencopy_manager_v1 | 3 | Yes | +| zwp_idle_inhibit_manager_v1 | 1 | | +| zwp_input_method_manager_v2 | 1 | Yes | +| zwp_linux_dmabuf_v1 | 5 | | +| zwp_pointer_constraints_v1 | 1 | | +| zwp_pointer_gestures_v1 | 3 | | +| zwp_primary_selection_device_manager_v1 | 1 | | +| zwp_relative_pointer_manager_v1 | 1 | | +| zwp_tablet_manager_v2 | 1 | | +| zwp_text_input_manager_v3 | 1 | | +| zwp_virtual_keyboard_manager_v1 | 1 | Yes | +| zxdg_decoration_manager_v1 | 1 | | +| zxdg_output_manager_v1 | 3 | | [^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 5ce19817..fd89deb5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -5,6 +5,8 @@ - Vulkan is now the default renderer. - Emulate vblank events on the nvidia driver. - Allow X windows to scale themselves. +- Implement ext-image-capture-source-v1. +- Implement ext-image-copy-capture-v1. # 1.6.0 (2024-09-25) diff --git a/src/backends/metal/monitor.rs b/src/backends/metal/monitor.rs index 1d9a85e5..44c425bd 100644 --- a/src/backends/metal/monitor.rs +++ b/src/backends/metal/monitor.rs @@ -236,7 +236,13 @@ impl MetalBackend { return; } }; - let master = Rc::new(DrmMaster::new(&slf.state.ring, res.fd.clone())); + let master = match DrmMaster::new(&slf.state.ring, res.fd.clone()) { + Ok(m) => Rc::new(m), + Err(e) => { + log::error!("Could not open the drm device: {}", ErrorFmt(e)); + return; + } + }; let dev = match slf.create_drm_device(dev, &master) { Ok(d) => d, Err(e) => { diff --git a/src/backends/x.rs b/src/backends/x.rs index 21438f97..c5fc89ef 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -59,7 +59,7 @@ use { rc::Rc, }, thiserror::Error, - uapi::{c::dev_t, Errno}, + uapi::c::dev_t, }; #[derive(Debug, Error)] @@ -114,8 +114,6 @@ pub enum XBackendError { MapWindow(#[source] XconError), #[error("Could not query device")] QueryDevice(#[source] XconError), - #[error("Could not fstat the drm device")] - DrmDeviceFstat(#[source] Errno), #[error("Render device does not support XRGB8888 format")] XRGB8888, } @@ -174,10 +172,7 @@ pub async fn create(state: &Rc) -> Result, XBackendError> { Err(e) => return Err(XBackendError::DriOpen(e)), } }; - let drm_dev = match uapi::fstat(drm.raw()) { - Ok(s) => s.st_rdev, - Err(e) => return Err(XBackendError::DrmDeviceFstat(e)), - }; + let drm_dev = drm.dev(); let gbm = GbmDevice::new(&drm)?; let ctx = match state.create_gfx_context(&drm, None) { Ok(r) => r, diff --git a/src/client/objects.rs b/src/client/objects.rs index 599fafdf..749efb2d 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -2,6 +2,9 @@ use { crate::{ client::{Client, ClientError}, 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, @@ -31,11 +34,13 @@ use { copyhashmap::{CopyHashMap, Locked}, }, wire::{ - JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, - WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, - WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, - XdgPositionerId, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, - ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, + 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, }, }, std::{cell::RefCell, mem, rc::Rc}, @@ -67,6 +72,11 @@ pub struct Objects { pub drm_lease_outputs: CopyHashMap>, pub tablet_tools: CopyHashMap>, pub xdg_popups: CopyHashMap>, + pub image_capture_sources: CopyHashMap>, + pub foreign_toplevel_handles: + CopyHashMap>, + pub ext_copy_sessions: + CopyHashMap>, ids: RefCell>, } @@ -100,6 +110,9 @@ impl Objects { drm_lease_outputs: Default::default(), tablet_tools: Default::default(), xdg_popups: Default::default(), + image_capture_sources: Default::default(), + foreign_toplevel_handles: Default::default(), + ext_copy_sessions: Default::default(), ids: RefCell::new(vec![]), } } @@ -137,6 +150,9 @@ impl Objects { self.drm_lease_outputs.clear(); self.tablet_tools.clear(); 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 0803a0dd..60a071bd 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -576,6 +576,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/drm_feedback.rs b/src/drm_feedback.rs index f998ddc8..136aa2f9 100644 --- a/src/drm_feedback.rs +++ b/src/drm_feedback.rs @@ -36,11 +36,10 @@ impl DrmFeedback { ids: &DrmFeedbackIds, render_ctx: &dyn GfxContext, ) -> Result { - let drm = match render_ctx.allocator().drm() { - Some(drm) => drm.raw(), + let main_device = match render_ctx.allocator().drm() { + Some(drm) => drm.dev(), _ => return Err(DrmFeedbackError::NoDrmDevice), }; - let main_device = uapi::fstat(drm).map_err(OsError::from)?.st_rdev; let (data, index_map) = create_fd_data(render_ctx); let mut memfd = uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); 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.rs b/src/gfx_apis/vulkan.rs index 14e2ca82..63c5f778 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -91,8 +91,6 @@ pub enum VulkanError { CreateInstance(#[source] vk::Result), #[error("Could not create a debug-utils messenger")] Messenger(#[source] vk::Result), - #[error("Could not fstat the DRM FD")] - Fstat(#[source] OsError), #[error("Could not enumerate the physical devices")] EnumeratePhysicalDevices(#[source] vk::Result), #[error("Could not find a vulkan device that matches dev_t {0}")] diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index 5ff1d796..9a781de2 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -104,11 +104,7 @@ impl VulkanInstance { } fn find_dev(&self, drm: &Drm) -> Result { - let stat = match uapi::fstat(drm.raw()) { - Ok(s) => s, - Err(e) => return Err(VulkanError::Fstat(e.into())), - }; - let dev = stat.st_rdev; + let dev = drm.dev(); log::log!( self.log_level, "Searching for vulkan device with devnum {}:{}", 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 c8595388..5f681d03 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -3,8 +3,11 @@ use { backend::Backend, client::{Client, ClientCaps}, ifs::{ + 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::{ wl_data_device_manager::WlDataDeviceManagerGlobal, @@ -197,6 +200,9 @@ impl Globals { add_singleton!(ZwpPointerGesturesV1Global); add_singleton!(ZwpTabletManagerV2Global); 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 173493d7..55483591 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -1,7 +1,11 @@ pub mod ext_foreign_toplevel_handle_v1; +pub mod ext_foreign_toplevel_image_capture_source_manager_v1; 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; pub mod ipc; diff --git a/src/ifs/ext_foreign_toplevel_handle_v1.rs b/src/ifs/ext_foreign_toplevel_handle_v1.rs index 4ce7fc10..e5d90dbd 100644 --- a/src/ifs/ext_foreign_toplevel_handle_v1.rs +++ b/src/ifs/ext_foreign_toplevel_handle_v1.rs @@ -3,7 +3,7 @@ use { client::{Client, ClientError}, leaks::Tracker, object::{Object, Version}, - tree::ToplevelNode, + tree::ToplevelOpt, wire::{ext_foreign_toplevel_handle_v1::*, ExtForeignToplevelHandleV1Id}, }, std::rc::Rc, @@ -14,16 +14,15 @@ pub struct ExtForeignToplevelHandleV1 { pub id: ExtForeignToplevelHandleV1Id, pub client: Rc, pub tracker: Tracker, - pub toplevel: Rc, + pub toplevel: ToplevelOpt, pub version: Version, } impl ExtForeignToplevelHandleV1 { fn detach(&self) { - self.toplevel - .tl_data() - .handles - .remove(&(self.client.id, self.id)); + if let Some(tl) = self.toplevel.get() { + tl.tl_data().handles.remove(&(self.client.id, self.id)); + } } } @@ -79,7 +78,11 @@ impl Object for ExtForeignToplevelHandleV1 { } } -simple_add_obj!(ExtForeignToplevelHandleV1); +dedicated_add_obj!( + ExtForeignToplevelHandleV1, + ExtForeignToplevelHandleV1Id, + foreign_toplevel_handles +); #[derive(Debug, Error)] pub enum ExtForeignToplevelHandleV1Error { diff --git a/src/ifs/ext_foreign_toplevel_image_capture_source_manager_v1.rs b/src/ifs/ext_foreign_toplevel_image_capture_source_manager_v1.rs new file mode 100644 index 00000000..4ce2c6db --- /dev/null +++ b/src/ifs/ext_foreign_toplevel_image_capture_source_manager_v1.rs @@ -0,0 +1,110 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::ext_image_capture_source_v1::{ExtImageCaptureSourceV1, ImageCaptureSource}, + leaks::Tracker, + object::{Object, Version}, + wire::{ + ext_foreign_toplevel_image_capture_source_manager_v1::*, + ExtForeignToplevelImageCaptureSourceManagerV1Id, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtForeignToplevelImageCaptureSourceManagerV1Global { + pub name: GlobalName, +} + +impl ExtForeignToplevelImageCaptureSourceManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ExtForeignToplevelImageCaptureSourceManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), ExtForeignToplevelImageCaptureSourceManagerV1Error> { + let obj = Rc::new(ExtForeignToplevelImageCaptureSourceManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +pub struct ExtForeignToplevelImageCaptureSourceManagerV1 { + pub id: ExtForeignToplevelImageCaptureSourceManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl ExtForeignToplevelImageCaptureSourceManagerV1RequestHandler + for ExtForeignToplevelImageCaptureSourceManagerV1 +{ + type Error = ExtForeignToplevelImageCaptureSourceManagerV1Error; + + fn create_source(&self, req: CreateSource, _slf: &Rc) -> Result<(), Self::Error> { + let handle = self.client.lookup(req.toplevel_handle)?; + let obj = Rc::new(ExtImageCaptureSourceV1 { + id: req.source, + client: self.client.clone(), + tracker: Default::default(), + ty: ImageCaptureSource::Toplevel(handle.toplevel.clone()), + }); + 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!( + ExtForeignToplevelImageCaptureSourceManagerV1Global, + ExtForeignToplevelImageCaptureSourceManagerV1, + ExtForeignToplevelImageCaptureSourceManagerV1Error +); + +impl Global for ExtForeignToplevelImageCaptureSourceManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(ExtForeignToplevelImageCaptureSourceManagerV1Global); + +object_base! { + self = ExtForeignToplevelImageCaptureSourceManagerV1; + version = self.version; +} + +impl Object for ExtForeignToplevelImageCaptureSourceManagerV1 {} + +simple_add_obj!(ExtForeignToplevelImageCaptureSourceManagerV1); + +#[derive(Debug, Error)] +pub enum ExtForeignToplevelImageCaptureSourceManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!( + ExtForeignToplevelImageCaptureSourceManagerV1Error, + ClientError +); diff --git a/src/ifs/ext_foreign_toplevel_list_v1.rs b/src/ifs/ext_foreign_toplevel_list_v1.rs index 352ebb9d..cc427eba 100644 --- a/src/ifs/ext_foreign_toplevel_list_v1.rs +++ b/src/ifs/ext_foreign_toplevel_list_v1.rs @@ -8,7 +8,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - tree::{NodeVisitorBase, ToplevelNode}, + tree::{NodeVisitorBase, ToplevelOpt}, wire::{ ext_foreign_toplevel_list_v1::*, ExtForeignToplevelHandleV1Id, ExtForeignToplevelListV1Id, @@ -105,10 +105,7 @@ impl ExtForeignToplevelListV1 { }); } - pub fn publish_toplevel( - &self, - tl: &Rc, - ) -> Option> { + pub fn publish_toplevel(&self, tl: ToplevelOpt) -> Option> { let id: ExtForeignToplevelHandleV1Id = match self.client.new_id() { Ok(i) => i, Err(e) => { @@ -120,7 +117,7 @@ impl ExtForeignToplevelListV1 { id, client: self.client.clone(), tracker: Default::default(), - toplevel: tl.clone(), + toplevel: tl, version: self.version, }); track!(self.client, handle); diff --git a/src/ifs/ext_image_capture_source_v1.rs b/src/ifs/ext_image_capture_source_v1.rs new file mode 100644 index 00000000..9f309257 --- /dev/null +++ b/src/ifs/ext_image_capture_source_v1.rs @@ -0,0 +1,54 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_output::OutputGlobalOpt, + leaks::Tracker, + object::{Object, Version}, + tree::ToplevelOpt, + wire::{ext_image_capture_source_v1::*, ExtImageCaptureSourceV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +#[derive(Clone)] +pub enum ImageCaptureSource { + Output(Rc), + Toplevel(ToplevelOpt), +} + +pub struct ExtImageCaptureSourceV1 { + pub id: ExtImageCaptureSourceV1Id, + pub client: Rc, + pub tracker: Tracker, + pub ty: ImageCaptureSource, +} + +impl ExtImageCaptureSourceV1RequestHandler for ExtImageCaptureSourceV1 { + type Error = ExtImageCaptureSourceError; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ExtImageCaptureSourceV1; + version = Version(1); +} + +impl Object for ExtImageCaptureSourceV1 {} + +dedicated_add_obj!( + ExtImageCaptureSourceV1, + ExtImageCaptureSourceV1Id, + image_capture_sources +); + +#[derive(Debug, Error)] +pub enum ExtImageCaptureSourceError { + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtImageCaptureSourceError, ClientError); 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/ext_output_image_capture_source_manager_v1.rs b/src/ifs/ext_output_image_capture_source_manager_v1.rs new file mode 100644 index 00000000..ad8763e6 --- /dev/null +++ b/src/ifs/ext_output_image_capture_source_manager_v1.rs @@ -0,0 +1,104 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::ext_image_capture_source_v1::{ExtImageCaptureSourceV1, ImageCaptureSource}, + leaks::Tracker, + object::{Object, Version}, + wire::{ + ext_output_image_capture_source_manager_v1::*, ExtOutputImageCaptureSourceManagerV1Id, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtOutputImageCaptureSourceManagerV1Global { + pub name: GlobalName, +} + +impl ExtOutputImageCaptureSourceManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ExtOutputImageCaptureSourceManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), ExtOutputImageCaptureSourceManagerV1Error> { + let obj = Rc::new(ExtOutputImageCaptureSourceManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +pub struct ExtOutputImageCaptureSourceManagerV1 { + pub id: ExtOutputImageCaptureSourceManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl ExtOutputImageCaptureSourceManagerV1RequestHandler for ExtOutputImageCaptureSourceManagerV1 { + type Error = ExtOutputImageCaptureSourceManagerV1Error; + + fn create_source(&self, req: CreateSource, _slf: &Rc) -> Result<(), Self::Error> { + let output = self.client.lookup(req.output)?; + let obj = Rc::new(ExtImageCaptureSourceV1 { + id: req.source, + client: self.client.clone(), + tracker: Default::default(), + ty: ImageCaptureSource::Output(output.global.clone()), + }); + 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!( + ExtOutputImageCaptureSourceManagerV1Global, + ExtOutputImageCaptureSourceManagerV1, + ExtOutputImageCaptureSourceManagerV1Error +); + +impl Global for ExtOutputImageCaptureSourceManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(ExtOutputImageCaptureSourceManagerV1Global); + +object_base! { + self = ExtOutputImageCaptureSourceManagerV1; + version = self.version; +} + +impl Object for ExtOutputImageCaptureSourceManagerV1 {} + +simple_add_obj!(ExtOutputImageCaptureSourceManagerV1); + +#[derive(Debug, Error)] +pub enum ExtOutputImageCaptureSourceManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtOutputImageCaptureSourceManagerV1Error, 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 9576067f..34583dd9 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(); + } } } @@ -2102,7 +2106,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/ifs/wp_drm_lease_device_v1.rs b/src/ifs/wp_drm_lease_device_v1.rs index c6abc93d..88a5377f 100644 --- a/src/ifs/wp_drm_lease_device_v1.rs +++ b/src/ifs/wp_drm_lease_device_v1.rs @@ -210,6 +210,8 @@ efrom!(WpDrmLeaseDeviceV1Error, ClientError); enum ReopenError { #[error("Could not open the dev node")] OpenNode(#[source] OsError), + #[error("Could not create the DRM device")] + CreateDrm(#[source] DrmError), #[error("Could not drop DRM master")] DropMaster(#[source] DrmError), } @@ -218,7 +220,7 @@ fn reopen_card(devnode: &str) -> Result, ReopenError> { let fd = uapi::open(devnode, c::O_RDWR | c::O_CLOEXEC, 0) .map_err(|e| ReopenError::OpenNode(e.into()))?; let fd = Rc::new(fd); - let drm = Drm::open_existing(fd.clone()); + let drm = Drm::open_existing(fd.clone()).map_err(ReopenError::CreateDrm)?; if drm.is_master() { drm.drop_master().map_err(ReopenError::DropMaster)?; } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 383dab9e..62ac58d0 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -24,7 +24,7 @@ use { on_change::OnChange, oserror::OsError, syncqueue::SyncQueue, }, video::{ - drm::{ConnectorType, Drm}, + drm::{ConnectorType, Drm, DrmError}, gbm::{GbmDevice, GbmError}, }, }, @@ -42,6 +42,8 @@ pub enum TestBackendError { NoDrmNode, #[error("Could not open drm node {0}")] OpenDrmNode(String, #[source] OsError), + #[error("Could not open the drm device")] + OpenDrmDevice(#[source] DrmError), #[error("Could not create a render context")] RenderContext(#[source] GfxError), #[error("Could not create a gbm device")] @@ -264,7 +266,7 @@ where )) } }; - let drm = Drm::open_existing(file); + let drm = Drm::open_existing(file).map_err(TestBackendError::OpenDrmDevice)?; f(drm) } 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/portal/ptl_display.rs b/src/portal/ptl_display.rs index 5f555a6e..16a42cc5 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -162,13 +162,14 @@ impl UsrJayRenderCtxOwner for PortalDisplay { fn device(&self, fd: Rc, server_formats: Option>) { self.render_ctx.take(); - let dev_id = match uapi::fstat(fd.raw()) { - Ok(s) => s.st_rdev, + let drm = match Drm::open_existing(fd) { + Ok(d) => d, Err(e) => { - log::error!("Could not fstat display device: {}", ErrorFmt(e)); + log::error!("Could not open the drm device: {}", ErrorFmt(e)); return; } }; + let dev_id = drm.dev(); let mut render_ctx = None; if let Some(ctx) = self.state.render_ctxs.get(&dev_id) { if let Some(ctx) = ctx.upgrade() { @@ -176,7 +177,6 @@ impl UsrJayRenderCtxOwner for PortalDisplay { } } if render_ctx.is_none() { - let drm = Drm::open_existing(fd); let ctx = match create_gfx_context(&self.state.eng, &self.state.ring, &drm, GfxApi::OpenGl) { Ok(c) => c, diff --git a/src/state.rs b/src/state.rs index 43ada8c1..6718ff9c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -555,6 +555,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 d5566023..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,12 +25,15 @@ use { threshold_counter::ThresholdCounter, toplevel_identifier::{toplevel_identifier, ToplevelIdentifier}, }, - wire::{ExtForeignToplevelHandleV1Id, JayScreencastId, JayToplevelId}, + wire::{ + ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayScreencastId, + JayToplevelId, + }, }, std::{ cell::{Cell, RefCell}, ops::Deref, - rc::Rc, + rc::{Rc, Weak}, }, }; @@ -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()); @@ -226,6 +236,23 @@ pub struct FullscreenedData { pub workspace: Rc, } +#[derive(Clone)] +pub struct ToplevelOpt { + toplevel: Weak, + identifier: ToplevelIdentifier, +} + +impl ToplevelOpt { + pub fn get(&self) -> Option> { + let tl = self.toplevel.upgrade()?; + if tl.tl_data().identifier.get() == self.identifier { + Some(tl) + } else { + None + } + } +} + pub struct ToplevelData { pub self_active: Cell, pub client: Option>, @@ -253,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 { @@ -283,6 +312,7 @@ impl ToplevelData { render_highlight: Default::default(), jay_toplevels: Default::default(), jay_screencasts: Default::default(), + ext_copy_sessions: Default::default(), } } @@ -326,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(); @@ -372,7 +405,11 @@ impl ToplevelData { title: &str, app_id: &str, ) { - let handle = match list.publish_toplevel(toplevel) { + let opt = ToplevelOpt { + toplevel: Rc::downgrade(toplevel), + identifier: self.identifier.get(), + }; + let handle = match list.publish_toplevel(opt) { None => return, Some(handle) => handle, }; @@ -517,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; } @@ -548,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/src/video/drm.rs b/src/video/drm.rs index be0a05e2..a3293a7b 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -144,6 +144,8 @@ pub enum DrmError { DropMaster(#[source] OsError), #[error("Could not queue a CRTC sequence")] QueueSequence(#[source] OsError), + #[error("Could not stat the DRM fd")] + Stat(#[source] OsError), } fn render_node_name(fd: c::c_int) -> Result { @@ -177,17 +179,24 @@ fn reopen(fd: c::c_int, need_primary: bool) -> Result, DrmError> { pub struct Drm { fd: Rc, + dev: c::dev_t, } impl Drm { - pub fn open_existing(fd: Rc) -> Self { - Self { fd } + pub fn open_existing(fd: Rc) -> Result { + let stat = uapi::fstat(fd.raw()).map_err(|e| DrmError::Stat(e.into()))?; + Ok(Self { + fd, + dev: stat.st_rdev, + }) } pub fn reopen(fd: c::c_int, need_primary: bool) -> Result { - Ok(Self { - fd: reopen(fd, need_primary)?, - }) + Self::open_existing(reopen(fd, need_primary)?) + } + + pub fn dev(&self) -> c::dev_t { + self.dev } pub fn fd(&self) -> &Rc { @@ -291,16 +300,16 @@ impl DrmLease { } impl DrmMaster { - pub fn new(ring: &Rc, fd: Rc) -> Self { - Self { - drm: Drm { fd }, + pub fn new(ring: &Rc, fd: Rc) -> Result { + Ok(Self { + drm: Drm::open_existing(fd)?, u32_bufs: Default::default(), u64_bufs: Default::default(), gem_handles: Default::default(), events: Default::default(), ring: ring.clone(), buf: RefCell::new(Buf::new(1024)), - } + }) } pub fn raw(&self) -> c::c_int { diff --git a/wire/ext_foreign_toplevel_image_capture_source_manager_v1.txt b/wire/ext_foreign_toplevel_image_capture_source_manager_v1.txt new file mode 100644 index 00000000..314b62ae --- /dev/null +++ b/wire/ext_foreign_toplevel_image_capture_source_manager_v1.txt @@ -0,0 +1,8 @@ +request create_source { + source: id(ext_image_capture_source_v1), + toplevel_handle: id(ext_foreign_toplevel_handle_v1), +} + +request destroy { + +} diff --git a/wire/ext_image_capture_source_v1.txt b/wire/ext_image_capture_source_v1.txt new file mode 100644 index 00000000..c8816211 --- /dev/null +++ b/wire/ext_image_capture_source_v1.txt @@ -0,0 +1,3 @@ +request destroy { + +} 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 { + +} diff --git a/wire/ext_output_image_capture_source_manager_v1.txt b/wire/ext_output_image_capture_source_manager_v1.txt new file mode 100644 index 00000000..0664c20f --- /dev/null +++ b/wire/ext_output_image_capture_source_manager_v1.txt @@ -0,0 +1,8 @@ +request create_source { + source: id(ext_image_capture_source_v1), + output: id(wl_output), +} + +request destroy { + +}