cosmic-comp/src/wayland/handlers/image_copy_capture/mod.rs
Ian Douglas Scott cac7a5aca6 image-copy: Use abstraction that's now provided by Smithay
This doesn't change much, since the Smithay implementation is based on
the `cosmic-comp` version, but made more generic. We provide our own
implementation for our workspace capture protocol, but otherwise Smithay
handles the boilerplate now.

This should not cause any change in behavior.
2026-02-05 02:01:14 +01:00

453 lines
16 KiB
Rust

// SPDX-License-Identifier: GPL-3.0-only
use std::{borrow::Borrow, collections::HashMap, sync::Mutex};
use smithay::{
backend::{
allocator::{Fourcc, Modifier},
egl::EGLDevice,
renderer::{
damage::OutputDamageTracker,
gles::{Capability, GlesRenderer},
glow::GlowRenderer,
utils::with_renderer_surface_state,
},
},
desktop::space::SpaceElement,
output::Output,
reexports::wayland_server::protocol::wl_shm::Format as ShmFormat,
utils::{Buffer as BufferCoords, Point, Size, Transform},
wayland::{
dmabuf::get_dmabuf,
image_capture_source::ImageCaptureSource,
image_copy_capture::{
BufferConstraints, CursorSession, CursorSessionRef, DmabufConstraints, Frame, FrameRef,
ImageCopyCaptureHandler, ImageCopyCaptureState, Session, SessionRef,
},
seat::WaylandFocus,
},
};
use crate::{
shell::CosmicSurface,
state::{BackendData, State},
utils::prelude::{
OutputExt, PointExt, PointGlobalExt, PointLocalExt, RectExt, RectLocalExt, SeatExt,
},
wayland::protocols::image_capture_source::ImageCaptureSourceKind,
};
mod render;
mod user_data;
pub use self::render::*;
use self::user_data::*;
pub use self::user_data::{FrameHolder, ImageCopySessions, SessionData, SessionHolder};
impl ImageCopyCaptureHandler for State {
fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {
&mut self.common.image_copy_capture_state
}
fn capture_constraints(&mut self, source: &ImageCaptureSource) -> Option<BufferConstraints> {
let kind = source.user_data().get::<ImageCaptureSourceKind>().unwrap();
match kind {
ImageCaptureSourceKind::Output(weak) => weak
.upgrade()
.and_then(|output| constraints_for_output(&output, &mut self.backend)),
ImageCaptureSourceKind::Workspace(handle) => {
let shell = self.common.shell.read();
let output = shell.workspaces.space_for_handle(handle)?.output();
constraints_for_output(output, &mut self.backend)
}
ImageCaptureSourceKind::Toplevel(window) => {
constraints_for_toplevel(window, &mut self.backend)
}
_ => None,
}
}
fn cursor_capture_constraints(
&mut self,
_source: &ImageCaptureSource,
) -> Option<BufferConstraints> {
let size = if let Some((geometry, _)) = self
.common
.shell
.read()
.seats
.last_active()
.cursor_geometry((0.0, 0.0), self.common.clock.now())
{
geometry.size
} else {
Size::from((64, 64))
};
Some(BufferConstraints {
size,
shm: vec![ShmFormat::Argb8888],
dma: None,
})
}
fn new_session(&mut self, session: Session) {
let kind = session
.source()
.user_data()
.get::<ImageCaptureSourceKind>()
.unwrap()
.clone();
match kind {
ImageCaptureSourceKind::Output(weak) => {
let Some(mut output) = weak.upgrade() else {
session.stop();
return;
};
session.user_data().insert_if_missing_threadsafe(|| {
Mutex::new(SessionUserData::new(OutputDamageTracker::from_output(
&output,
)))
});
output.add_session(session);
}
ImageCaptureSourceKind::Workspace(handle) => {
let mut shell = self.common.shell.write();
let Some(workspace) = shell.workspaces.space_for_handle_mut(&handle) else {
session.stop();
return;
};
session.user_data().insert_if_missing_threadsafe(|| {
Mutex::new(SessionUserData::new(OutputDamageTracker::from_output(
workspace.output(),
)))
});
workspace.add_session(session);
}
ImageCaptureSourceKind::Toplevel(mut toplevel) => {
let size = toplevel.geometry().size.to_physical(1);
session.user_data().insert_if_missing_threadsafe(|| {
Mutex::new(SessionUserData::new(OutputDamageTracker::new(
size,
1.0,
Transform::Normal,
)))
});
toplevel.add_session(session);
}
ImageCaptureSourceKind::Destroyed => unreachable!(),
}
}
fn new_cursor_session(&mut self, session: CursorSession) {
let (pointer_loc, pointer_size, hotspot) = {
let seat = self.common.shell.read().seats.last_active().clone();
let pointer = seat.get_pointer().unwrap();
let pointer_loc = pointer.current_location().to_i32_round().as_global();
let (pointer_size, hotspot) = if let Some((geometry, hotspot)) =
seat.cursor_geometry((0.0, 0.0), self.common.clock.now())
{
(geometry.size, hotspot)
} else {
(Size::from((64, 64)), Point::from((0, 0)))
};
(pointer_loc, pointer_size, hotspot)
};
session.user_data().insert_if_missing_threadsafe(|| {
Mutex::new(SessionUserData::new(OutputDamageTracker::new(
pointer_size.to_logical(1, Transform::Normal).to_physical(1),
1.0,
Transform::Normal,
)))
});
let kind = session
.source()
.user_data()
.get::<ImageCaptureSourceKind>()
.unwrap()
.clone();
match kind {
ImageCaptureSourceKind::Output(weak) => {
let Some(mut output) = weak.upgrade() else {
return;
};
if output.geometry().contains(pointer_loc) {
let buffer_pos = pointer_loc
.to_local(&output)
.as_logical()
.to_f64()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output
.current_mode()
.map(|mode| {
mode.size
.to_f64()
.to_logical(output.current_scale().fractional_scale())
})
.unwrap_or(Size::from((0.0, 0.0))),
)
.to_i32_round();
session.set_cursor_hotspot(hotspot);
session.set_cursor_pos(Some(buffer_pos));
}
output.add_cursor_session(session);
}
ImageCaptureSourceKind::Workspace(handle) => {
let mut shell = self.common.shell.write();
let Some(workspace) = shell.workspaces.space_for_handle_mut(&handle) else {
return;
};
let output = workspace.output().clone();
if output.geometry().contains(pointer_loc) {
let buffer_pos = pointer_loc
.to_local(&output)
.as_logical()
.to_f64()
.to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output
.current_mode()
.map(|mode| {
mode.size
.to_f64()
.to_logical(output.current_scale().fractional_scale())
})
.unwrap_or(Size::from((0.0, 0.0))),
)
.to_i32_round();
session.set_cursor_hotspot(hotspot);
session.set_cursor_pos(Some(buffer_pos));
}
workspace.add_cursor_session(session);
}
ImageCaptureSourceKind::Toplevel(mut toplevel) => {
let shell = self.common.shell.read();
if let Some(element) = shell.element_for_surface(&toplevel) {
if element.has_active_window(&toplevel) {
if let Some(workspace) = shell.space_for(element) {
if let Some(geometry) = workspace.element_geometry(element) {
let mut surface_geo = element.active_window_geometry().as_local();
surface_geo.loc += geometry.loc;
let global_geo = surface_geo.to_global(workspace.output());
if global_geo.contains(pointer_loc) {
let buffer_pos = (pointer_loc - global_geo.loc)
.as_logical()
.to_buffer(1, Transform::Normal, &toplevel.geometry().size);
session.set_cursor_hotspot(hotspot);
session.set_cursor_pos(Some(buffer_pos));
}
}
}
}
}
toplevel.add_cursor_session(session);
}
ImageCaptureSourceKind::Destroyed => unreachable!(),
}
}
fn frame(&mut self, session: &SessionRef, frame: Frame) {
let kind = session
.source()
.user_data()
.get::<ImageCaptureSourceKind>()
.unwrap()
.clone();
match kind {
ImageCaptureSourceKind::Output(weak) => {
let Some(mut output) = weak.upgrade() else {
return;
};
output.add_frame(session.clone(), frame);
self.backend.schedule_render(&output);
}
ImageCaptureSourceKind::Workspace(handle) => {
render_workspace_to_buffer(self, session, frame, handle)
}
ImageCaptureSourceKind::Toplevel(toplevel) => {
render_window_to_buffer(self, session, frame, &toplevel)
}
ImageCaptureSourceKind::Destroyed => unreachable!(),
}
}
fn cursor_frame(&mut self, session: &CursorSessionRef, frame: Frame) {
if !session.has_cursor() {
frame.success(Transform::Normal, Vec::new(), self.common.clock.now());
return;
}
let seat = self.common.shell.read().seats.last_active().clone();
render_cursor_to_buffer(self, session, frame, &seat);
}
fn frame_aborted(&mut self, frame: FrameRef) {
let shell = self.common.shell.read();
for mut output in shell.outputs().cloned() {
output.remove_frame(&frame);
}
}
fn session_destroyed(&mut self, session: SessionRef) {
let kind = session
.source()
.user_data()
.get::<ImageCaptureSourceKind>()
.unwrap()
.clone();
match kind {
ImageCaptureSourceKind::Output(weak) => {
if let Some(mut output) = weak.upgrade() {
output.remove_session(&session);
}
}
ImageCaptureSourceKind::Workspace(handle) => {
if let Some(workspace) = self
.common
.shell
.write()
.workspaces
.space_for_handle_mut(&handle)
{
workspace.remove_session(&session)
}
}
ImageCaptureSourceKind::Toplevel(mut toplevel) => toplevel.remove_session(&session),
ImageCaptureSourceKind::Destroyed => unreachable!(),
}
}
fn cursor_session_destroyed(&mut self, session: CursorSessionRef) {
let kind = session
.source()
.user_data()
.get::<ImageCaptureSourceKind>()
.unwrap()
.clone();
match kind {
ImageCaptureSourceKind::Output(weak) => {
if let Some(mut output) = weak.upgrade() {
output.remove_cursor_session(&session);
}
}
ImageCaptureSourceKind::Workspace(handle) => {
if let Some(workspace) = self
.common
.shell
.write()
.workspaces
.space_for_handle_mut(&handle)
{
workspace.remove_cursor_session(&session)
}
}
ImageCaptureSourceKind::Toplevel(mut toplevel) => {
toplevel.remove_cursor_session(&session)
}
ImageCaptureSourceKind::Destroyed => unreachable!(),
}
}
}
fn constraints_for_output(output: &Output, backend: &mut BackendData) -> Option<BufferConstraints> {
let mode = match output.current_mode() {
Some(mode) => mode.size.to_logical(1).to_buffer(1, Transform::Normal),
None => {
return None;
}
};
let mut renderer = backend
.offscreen_renderer(|kms| {
kms.target_node_for_output(output)
.or(*kms.primary_node.read().unwrap())
})
.unwrap();
Some(constraints_for_renderer(mode, renderer.as_mut()))
}
fn constraints_for_toplevel(
surface: &CosmicSurface,
backend: &mut BackendData,
) -> Option<BufferConstraints> {
let size = surface.geometry().size.to_buffer(1, Transform::Normal);
let wl_surface = surface.wl_surface()?;
let mut renderer = backend
.offscreen_renderer(|kms| {
let dma_node = with_renderer_surface_state(&wl_surface, |state| {
let buffer = state.buffer()?;
let dmabuf = get_dmabuf(buffer).ok()?;
dmabuf.node()
})
.flatten();
dma_node.or(*kms.primary_node.read().unwrap())
})
.unwrap();
Some(constraints_for_renderer(size, renderer.as_mut()))
}
fn constraints_for_renderer(
size: Size<i32, BufferCoords>,
renderer: &mut GlowRenderer,
) -> BufferConstraints {
let mut constraints = BufferConstraints {
size,
shm: vec![ShmFormat::Abgr8888, ShmFormat::Xbgr8888],
dma: None,
};
if (renderer as &dyn Borrow<GlesRenderer>)
.borrow()
.capabilities()
.contains(&Capability::_10Bit)
{
constraints
.shm
.extend([ShmFormat::Abgr2101010, ShmFormat::Xbgr2101010]);
}
if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display())
.ok()
.and_then(|device| device.try_get_render_node().ok().flatten())
{
constraints.dma = Some(DmabufConstraints {
node,
formats: renderer
.egl_context()
.dmabuf_render_formats()
.iter()
.fold(
HashMap::<Fourcc, Vec<Modifier>>::new(),
|mut map, format| {
map.entry(format.code).or_default().push(format.modifier);
map
},
)
.into_iter()
.collect::<Vec<_>>(),
});
}
constraints
}
smithay::delegate_image_copy_capture!(State);