1
0
Fork 0
forked from wry/wry

screencapture: implement ext_image_copy_capture_manager_v1

This commit is contained in:
Julian Orth 2024-10-08 22:22:49 +02:00
parent e91993fb18
commit f0562961e6
28 changed files with 1194 additions and 25 deletions

View file

@ -143,6 +143,7 @@ Jay supports the following wayland protocols:
| ext_foreign_toplevel_image_capture_source_manager_v1 | 1 | | | ext_foreign_toplevel_image_capture_source_manager_v1 | 1 | |
| ext_foreign_toplevel_list_v1 | 1 | Yes | | ext_foreign_toplevel_list_v1 | 1 | Yes |
| ext_idle_notifier_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_output_image_capture_source_manager_v1 | 1 | |
| ext_session_lock_manager_v1 | 1 | Yes | | ext_session_lock_manager_v1 | 1 | Yes |
| ext_transient_seat_manager_v1 | 1[^ts_rejected] | 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. [^lsaccess]: Sandboxes can restrict access to this protocol.
[^ts_rejected]: Seat creation is always rejected. [^ts_rejected]: Seat creation is always rejected.
[^composited]: Cursors are always composited.

View file

@ -6,6 +6,7 @@
- Emulate vblank events on the nvidia driver. - Emulate vblank events on the nvidia driver.
- Allow X windows to scale themselves. - Allow X windows to scale themselves.
- Implement ext-image-capture-source-v1. - Implement ext-image-capture-source-v1.
- Implement ext-image-copy-capture-v1.
# 1.6.0 (2024-09-25) # 1.6.0 (2024-09-25)

View file

@ -4,6 +4,7 @@ use {
ifs::{ ifs::{
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
ext_image_capture_source_v1::ExtImageCaptureSourceV1, ext_image_capture_source_v1::ExtImageCaptureSourceV1,
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
ipc::{ ipc::{
wl_data_source::WlDataSource, zwlr_data_control_source_v1::ZwlrDataControlSourceV1, wl_data_source::WlDataSource, zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
@ -33,9 +34,10 @@ use {
copyhashmap::{CopyHashMap, Locked}, copyhashmap::{CopyHashMap, Locked},
}, },
wire::{ wire::{
ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id, JayOutputId, JayScreencastId, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, JayToplevelId,
WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId,
WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId,
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
ZwpTabletToolV2Id, ZwpTabletToolV2Id,
@ -73,6 +75,8 @@ pub struct Objects {
pub image_capture_sources: CopyHashMap<ExtImageCaptureSourceV1Id, Rc<ExtImageCaptureSourceV1>>, pub image_capture_sources: CopyHashMap<ExtImageCaptureSourceV1Id, Rc<ExtImageCaptureSourceV1>>,
pub foreign_toplevel_handles: pub foreign_toplevel_handles:
CopyHashMap<ExtForeignToplevelHandleV1Id, Rc<ExtForeignToplevelHandleV1>>, CopyHashMap<ExtForeignToplevelHandleV1Id, Rc<ExtForeignToplevelHandleV1>>,
pub ext_copy_sessions:
CopyHashMap<ExtImageCopyCaptureSessionV1Id, Rc<ExtImageCopyCaptureSessionV1>>,
ids: RefCell<Vec<usize>>, ids: RefCell<Vec<usize>>,
} }
@ -108,6 +112,7 @@ impl Objects {
xdg_popups: Default::default(), xdg_popups: Default::default(),
image_capture_sources: Default::default(), image_capture_sources: Default::default(),
foreign_toplevel_handles: Default::default(), foreign_toplevel_handles: Default::default(),
ext_copy_sessions: Default::default(),
ids: RefCell::new(vec![]), ids: RefCell::new(vec![]),
} }
} }
@ -147,6 +152,7 @@ impl Objects {
self.xdg_popups.clear(); self.xdg_popups.clear();
self.image_capture_sources.clear(); self.image_capture_sources.clear();
self.foreign_toplevel_handles.clear(); self.foreign_toplevel_handles.clear();
self.ext_copy_sessions.clear();
} }
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError> pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>

View file

@ -576,6 +576,7 @@ fn create_dummy_output(state: &Rc<State>) {
latch_event: Default::default(), latch_event: Default::default(),
presentation_event: Default::default(), presentation_event: Default::default(),
flip_margin_ns: Default::default(), flip_margin_ns: Default::default(),
ext_copy_sessions: Default::default(),
}); });
let dummy_workspace = Rc::new(WorkspaceNode { let dummy_workspace = Rc::new(WorkspaceNode {
id: state.node_ids.next(), id: state.node_ids.next(),

View file

@ -270,6 +270,7 @@ pub trait GfxFramebuffer: Debug {
pub trait GfxInternalFramebuffer: GfxFramebuffer { pub trait GfxInternalFramebuffer: GfxFramebuffer {
fn into_fb(self: Rc<Self>) -> Rc<dyn GfxFramebuffer>; fn into_fb(self: Rc<Self>) -> Rc<dyn GfxFramebuffer>;
fn stride(&self) -> i32;
fn staging_size(&self) -> usize; fn staging_size(&self) -> usize;

View file

@ -21,6 +21,7 @@ pub struct GlRenderBuffer {
pub ctx: Rc<EglContext>, pub ctx: Rc<EglContext>,
pub width: i32, pub width: i32,
pub height: i32, pub height: i32,
pub stride: i32,
pub format: &'static Format, pub format: &'static Format,
rbo: GLuint, rbo: GLuint,
} }
@ -30,6 +31,7 @@ impl GlRenderBuffer {
ctx: &Rc<EglContext>, ctx: &Rc<EglContext>,
width: i32, width: i32,
height: i32, height: i32,
stride: i32,
format: &'static Format, format: &'static Format,
) -> Result<Rc<GlRenderBuffer>, RenderError> { ) -> Result<Rc<GlRenderBuffer>, RenderError> {
let Some(shm_info) = &format.shm_info else { let Some(shm_info) = &format.shm_info else {
@ -46,6 +48,7 @@ impl GlRenderBuffer {
ctx: ctx.clone(), ctx: ctx.clone(),
width, width,
height, height,
stride,
format, format,
rbo, rbo,
})) }))
@ -71,6 +74,7 @@ impl GlRenderBuffer {
ctx: ctx.clone(), ctx: ctx.clone(),
width: img.dmabuf.width, width: img.dmabuf.width,
height: img.dmabuf.height, height: img.dmabuf.height,
stride: 0,
format: img.dmabuf.format, format: img.dmabuf.format,
rbo, rbo,
})) }))

View file

@ -321,11 +321,11 @@ impl GfxContext for GlRenderContext {
_cpu_worker: &Rc<CpuWorker>, _cpu_worker: &Rc<CpuWorker>,
width: i32, width: i32,
height: i32, height: i32,
_stride: i32, stride: i32,
format: &'static Format, format: &'static Format,
) -> Result<Rc<dyn GfxInternalFramebuffer>, GfxError> { ) -> Result<Rc<dyn GfxInternalFramebuffer>, GfxError> {
let fb = self.ctx.with_current(|| unsafe { 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 })) Ok(Rc::new(Framebuffer { ctx: self, gl: fb }))
} }

View file

@ -119,6 +119,10 @@ impl GfxInternalFramebuffer for Framebuffer {
self self
} }
fn stride(&self) -> i32 {
self.gl.rb.stride
}
fn staging_size(&self) -> usize { fn staging_size(&self) -> usize {
0 0
} }

View file

@ -520,6 +520,13 @@ impl GfxInternalFramebuffer for VulkanImage {
self self
} }
fn stride(&self) -> i32 {
let VulkanImageMemory::Internal(shm) = &self.ty else {
unreachable!();
};
shm.stride as _
}
fn staging_size(&self) -> usize { fn staging_size(&self) -> usize {
let VulkanImageMemory::Internal(shm) = &self.ty else { let VulkanImageMemory::Internal(shm) = &self.ty else {
unreachable!(); unreachable!();

View file

@ -6,6 +6,7 @@ use {
ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global, ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global,
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global,
ext_idle_notifier_v1::ExtIdleNotifierV1Global, ext_idle_notifier_v1::ExtIdleNotifierV1Global,
ext_image_copy::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1Global,
ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1Global, ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1Global,
ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global,
ipc::{ ipc::{
@ -201,6 +202,7 @@ impl Globals {
add_singleton!(JayDamageTrackingGlobal); add_singleton!(JayDamageTrackingGlobal);
add_singleton!(ExtOutputImageCaptureSourceManagerV1Global); add_singleton!(ExtOutputImageCaptureSourceManagerV1Global);
add_singleton!(ExtForeignToplevelImageCaptureSourceManagerV1Global); add_singleton!(ExtForeignToplevelImageCaptureSourceManagerV1Global);
add_singleton!(ExtImageCopyCaptureManagerV1Global);
} }
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) { pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {

View file

@ -4,6 +4,7 @@ pub mod ext_foreign_toplevel_list_v1;
pub mod ext_idle_notification_v1; pub mod ext_idle_notification_v1;
pub mod ext_idle_notifier_v1; pub mod ext_idle_notifier_v1;
pub mod ext_image_capture_source_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_output_image_capture_source_manager_v1;
pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_manager_v1;
pub mod ext_session_lock_v1; pub mod ext_session_lock_v1;

View file

@ -11,7 +11,6 @@ use {
thiserror::Error, thiserror::Error,
}; };
#[expect(dead_code)]
#[derive(Clone)] #[derive(Clone)]
pub enum ImageCaptureSource { pub enum ImageCaptureSource {
Output(Rc<OutputGlobalOpt>), Output(Rc<OutputGlobalOpt>),
@ -22,7 +21,6 @@ pub struct ExtImageCaptureSourceV1 {
pub id: ExtImageCaptureSourceV1Id, pub id: ExtImageCaptureSourceV1Id,
pub client: Rc<Client>, pub client: Rc<Client>,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
#[expect(dead_code)]
pub ty: ImageCaptureSource, pub ty: ImageCaptureSource,
} }

View file

@ -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;

View file

@ -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<Client>,
pub(super) tracker: Tracker<Self>,
pub(super) version: Version,
pub(super) have_session: Cell<bool>,
pub(super) source: ImageCaptureSource,
}
impl ExtImageCopyCaptureCursorSessionV1RequestHandler for ExtImageCopyCaptureCursorSessionV1 {
type Error = ExtImageCopyCaptureCursorSessionV1Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get_capture_session(
&self,
req: GetCaptureSession,
_slf: &Rc<Self>,
) -> 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<ClientError>),
#[error("The session has already been created")]
HaveSession,
}
efrom!(ExtImageCopyCaptureCursorSessionV1Error, ClientError);

View file

@ -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<Client>,
pub(super) tracker: Tracker<Self>,
pub(super) session: Rc<ExtImageCopyCaptureSessionV1>,
}
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<Self>,
on: &OutputNode,
size: (i32, i32),
f: impl FnOnce(
Rc<dyn GfxFramebuffer>,
AcquireSync,
ReleaseSync,
) -> Result<Option<SyncFile>, 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<Self>,
on: &OutputNode,
size: (i32, i32),
f: impl FnOnce(
Rc<dyn GfxFramebuffer>,
AcquireSync,
ReleaseSync,
) -> Result<Option<SyncFile>, 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<Self>,
on: &OutputNode,
texture: &Rc<dyn GfxTexture>,
resv: Option<&Rc<dyn BufferResv>>,
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<Self>, 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<Self>) -> 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<Self>) -> 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<Self>) -> Result<(), Self::Error> {
self.ensure_unused()?;
Ok(())
}
fn capture(&self, _req: Capture, _slf: &Rc<Self>) -> 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<Self>, 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<ClientError>),
#[error("The frame has already been captured")]
AlreadyCaptured,
#[error("The frame does not have a buffer attached")]
NoBuffer,
}
efrom!(ExtImageCopyCaptureFrameV1Error, ClientError);

View file

@ -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<Self>,
id: ExtImageCopyCaptureManagerV1Id,
client: &Rc<Client>,
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<Client>,
pub(super) tracker: Tracker<Self>,
pub(super) version: Version,
}
impl ExtImageCopyCaptureManagerV1RequestHandler for ExtImageCopyCaptureManagerV1 {
type Error = ExtImageCopyCaptureManagerV1Error;
fn create_session(&self, req: CreateSession, _slf: &Rc<Self>) -> 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<Self>,
) -> 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<Self>) -> 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<ClientError>),
}
efrom!(ExtImageCopyCaptureManagerV1Error, ClientError);

View file

@ -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<Client>,
pub(super) tracker: Tracker<Self>,
pub(super) version: Version,
pub(super) frame: CloneCell<Option<Rc<ExtImageCopyCaptureFrameV1>>>,
pub(super) shm_bridge: CloneCell<Option<Rc<dyn GfxInternalFramebuffer>>>,
pub(super) shm_staging: CloneCell<Option<Rc<dyn GfxStagingBuffer>>>,
pub(super) source: ImageCaptureSource,
pub(super) force_capture: Cell<bool>,
pub(super) stopped: Cell<bool>,
pub(super) latch_listener: EventListener<dyn LatchListener>,
pub(super) presentation_listener: EventListener<dyn PresentationListener>,
pub(super) size_debounce: Cell<bool>,
pub(super) status: Cell<FrameStatus>,
pub(super) buffer: CloneCell<Option<Rc<WlBuffer>>>,
pub(super) pending_download: Cell<Option<PendingShmTransfer>>,
pub(super) presented: Cell<Option<(u64, u32)>>,
}
impl ExtImageCopyCaptureSessionV1 {
pub(super) fn new(
id: ExtImageCopyCaptureSessionV1Id,
client: &Rc<Client>,
version: Version,
source: &ImageCaptureSource,
slf: &Weak<Self>,
) -> 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<Self>,
on: &OutputNode,
texture: &Rc<dyn GfxTexture>,
resv: Option<&Rc<dyn BufferResv>>,
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<Self>) -> 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<Self>) -> Result<(), Self::Error> {
self.stop_pending_frame();
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
}
impl LatchListener for ExtImageCopyCaptureSessionV1 {
fn after_latch(self: Rc<Self>, 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<Self>,
_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<ClientError>),
#[error("There already is a pending frame")]
HaveFrame,
}
efrom!(ExtImageCopyCaptureSessionV1Error, ClientError);

View file

@ -89,7 +89,7 @@ enum Target {
} }
impl LatchListener for JayScreencast { impl LatchListener for JayScreencast {
fn after_latch(self: Rc<Self>) { fn after_latch(self: Rc<Self>, _on: &OutputNode) {
self.schedule_toplevel_screencast(); self.schedule_toplevel_screencast();
} }
} }
@ -780,17 +780,10 @@ efrom!(JayScreencastError, ClientError);
fn target_size(target: Option<&Target>) -> (i32, i32) { fn target_size(target: Option<&Target>) -> (i32, i32) {
if let Some(target) = target { if let Some(target) = target {
match target { return match target {
Target::Output(o) => return o.global.pixel_size(), Target::Output(o) => o.global.pixel_size(),
Target::Toplevel(t) => { Target::Toplevel(t) => t.tl_data().desired_pixel_size(),
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);
};
}
}
} }
(0, 0) (0, 0)
} }

View file

@ -143,9 +143,13 @@ pub struct SurfaceSendPreferredScaleVisitor;
impl SurfaceSendPreferredScaleVisitor { impl SurfaceSendPreferredScaleVisitor {
fn schedule_realloc(&self, tl: &impl ToplevelNode) { 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(); 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 { impl LatchListener for WlSurface {
fn after_latch(self: Rc<Self>) { fn after_latch(self: Rc<Self>, _on: &OutputNode) {
if self.visible.get() { if self.visible.get() {
if self.latched_commit_version.get() < self.commit_version.get() { if self.latched_commit_version.get() < self.commit_version.get() {
let latched = &mut *self.latched_presentation_feedback.borrow_mut(); let latched = &mut *self.latched_presentation_feedback.borrow_mut();

View file

@ -559,6 +559,13 @@ impl GfxInternalFramebuffer for TestGfxFb {
self self
} }
fn stride(&self) -> i32 {
let TestGfxImage::Shm(shm) = &*self.img else {
unreachable!();
};
shm.stride
}
fn staging_size(&self) -> usize { fn staging_size(&self) -> usize {
0 0
} }

View file

@ -555,6 +555,9 @@ impl State {
for sc in client.data.objects.screencasts.lock().values() { for sc in client.data.objects.screencasts.lock().values() {
scs.push(sc.clone()); scs.push(sc.clone());
} }
for sc in client.data.objects.ext_copy_sessions.lock().values() {
sc.stop();
}
} }
for sc in scs { for sc in scs {
sc.do_destroy(); sc.do_destroy();

View file

@ -185,6 +185,7 @@ impl ConnectorHandler {
vblank_event: Default::default(), vblank_event: Default::default(),
presentation_event: Default::default(), presentation_event: Default::default(),
flip_margin_ns: Default::default(), flip_margin_ns: Default::default(),
ext_copy_sessions: Default::default(),
}); });
on.update_visible(); on.update_visible();
on.update_rects(); on.update_rects();
@ -284,6 +285,9 @@ impl ConnectorHandler {
for sc in on.screencopies.lock().drain_values() { for sc in on.screencopies.lock().drain_values() {
sc.send_failed(); sc.send_failed();
} }
for sc in on.ext_copy_sessions.lock().drain_values() {
sc.stop();
}
global.destroyed.set(true); global.destroyed.set(true);
self.state.root.outputs.remove(&self.id); self.state.root.outputs.remove(&self.id);
self.state.output_extents_changed(); self.state.output_extents_changed();

View file

@ -6,6 +6,7 @@ use {
fixed::Fixed, fixed::Fixed,
gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync},
ifs::{ ifs::{
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
jay_output::JayOutput, jay_output::JayOutput,
jay_screencast::JayScreencast, jay_screencast::JayScreencast,
wl_buffer::WlBufferStorage, wl_buffer::WlBufferStorage,
@ -42,7 +43,9 @@ use {
linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller, linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller,
transform_ext::TransformExt, transform_ext::TransformExt,
}, },
wire::{JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id}, wire::{
ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id,
},
}, },
ahash::AHashMap, ahash::AHashMap,
jay_config::video::{TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode}, jay_config::video::{TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode},
@ -87,10 +90,12 @@ pub struct OutputNode {
pub vblank_event: EventSource<dyn VblankListener>, pub vblank_event: EventSource<dyn VblankListener>,
pub presentation_event: EventSource<dyn PresentationListener>, pub presentation_event: EventSource<dyn PresentationListener>,
pub flip_margin_ns: Cell<Option<u64>>, pub flip_margin_ns: Cell<Option<u64>>,
pub ext_copy_sessions:
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
} }
pub trait LatchListener { pub trait LatchListener {
fn after_latch(self: Rc<Self>); fn after_latch(self: Rc<Self>, on: &OutputNode);
} }
pub trait VblankListener { pub trait VblankListener {
@ -133,7 +138,7 @@ impl OutputNode {
pub fn latched(&self) { pub fn latched(&self) {
self.schedule.latched(); self.schedule.latched();
for listener in self.latch_event.iter() { for listener in self.latch_event.iter() {
listener.after_latch(); listener.after_latch(self);
} }
} }
@ -237,6 +242,19 @@ impl OutputNode {
size, 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( pub fn perform_wlr_screencopies(
@ -353,6 +371,7 @@ impl OutputNode {
self.jay_outputs.clear(); self.jay_outputs.clear();
self.screencasts.clear(); self.screencasts.clear();
self.screencopies.clear(); self.screencopies.clear();
self.ext_copy_sessions.clear();
} }
pub fn on_spaces_changed(self: &Rc<Self>) { pub fn on_spaces_changed(self: &Rc<Self>) {
@ -692,6 +711,9 @@ impl OutputNode {
for sc in self.screencasts.lock().values() { for sc in self.screencasts.lock().values() {
sc.schedule_realloc_or_reconfigure(); sc.schedule_realloc_or_reconfigure();
} }
for sc in self.ext_copy_sessions.lock().values() {
sc.buffer_size_changed();
}
} }
if transform != old_transform { if transform != old_transform {

View file

@ -4,6 +4,7 @@ use {
ifs::{ ifs::{
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
jay_screencast::JayScreencast, jay_screencast::JayScreencast,
jay_toplevel::JayToplevel, jay_toplevel::JayToplevel,
wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId}, wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId},
@ -24,7 +25,10 @@ use {
threshold_counter::ThresholdCounter, threshold_counter::ThresholdCounter,
toplevel_identifier::{toplevel_identifier, ToplevelIdentifier}, toplevel_identifier::{toplevel_identifier, ToplevelIdentifier},
}, },
wire::{ExtForeignToplevelHandleV1Id, JayScreencastId, JayToplevelId}, wire::{
ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayScreencastId,
JayToplevelId,
},
}, },
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
@ -130,6 +134,9 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
for sc in data.jay_screencasts.lock().values() { for sc in data.jay_screencasts.lock().values() {
sc.update_latch_listener(); sc.update_latch_listener();
} }
for sc in data.ext_copy_sessions.lock().values() {
sc.update_latch_listener();
}
} }
fn tl_change_extents(self: Rc<Self>, rect: &Rect) { fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
@ -139,6 +146,9 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
for sc in data.jay_screencasts.lock().values() { for sc in data.jay_screencasts.lock().values() {
sc.schedule_realloc_or_reconfigure(); sc.schedule_realloc_or_reconfigure();
} }
for sc in data.ext_copy_sessions.lock().values() {
sc.buffer_size_changed();
}
} }
if data.is_floating.get() { if data.is_floating.get() {
data.float_width.set(rect.width()); data.float_width.set(rect.width());
@ -270,6 +280,8 @@ pub struct ToplevelData {
pub render_highlight: NumCell<u32>, pub render_highlight: NumCell<u32>,
pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc<JayToplevel>>, pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc<JayToplevel>>,
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>, pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
pub ext_copy_sessions:
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
} }
impl ToplevelData { impl ToplevelData {
@ -300,6 +312,7 @@ impl ToplevelData {
render_highlight: Default::default(), render_highlight: Default::default(),
jay_toplevels: Default::default(), jay_toplevels: Default::default(),
jay_screencasts: 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() { for screencast in self.jay_screencasts.lock().drain_values() {
screencast.do_destroy(); screencast.do_destroy();
} }
for screencast in self.ext_copy_sessions.lock().drain_values() {
screencast.stop();
}
self.identifier.set(toplevel_identifier()); self.identifier.set(toplevel_identifier());
{ {
let mut handles = self.handles.lock(); let mut handles = self.handles.lock();
@ -538,6 +554,9 @@ impl ToplevelData {
for sc in self.jay_screencasts.lock().values() { for sc in self.jay_screencasts.lock().values() {
sc.update_latch_listener(); sc.update_latch_listener();
} }
for sc in self.ext_copy_sessions.lock().values() {
sc.update_latch_listener();
}
if !visible { if !visible {
return; return;
} }
@ -569,6 +588,15 @@ impl ToplevelData {
Some(ws) => ws.output.get(), 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 { pub struct TileDragDestination {

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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 {
}

View file

@ -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 {
}