protocols/screencopy: Make frame/session send stopped/fail on drop

Previously, `Frame` was stored in KMS frame udata, but in some cases the
udata was dropped without a capture happening, and `Frame` did not
implement `Drop`, so `fail` was never sent.

Instead, rename `DropableFrame` to `Frame` and `Frame` to `FrameRef`, so
we can have a single instance of `Frame`, that will send `fail` on drop.
This guarantees either `.success` or `.fail` are send, as long as its
not leaked.

This seems to fix https://github.com/pop-os/cosmic-comp/issues/1305.
xdg-desktop-portal-cosmic prints an error, buy retries (as it should for
an `Unknown` error; though maybe there should be a retry limit) and the
session continues working.

(Not sure if it should be sending `failed`, or queing it with the next
frame so it can send `success` to the client, but this works and is
desirable as a failsafe anyway.)

`Session` and `CursorSession` are similiarly updated.

`.fail()`, `.success()`, and `.stop()` now consume
`Frame`/`Session`/`CursorSession`. So to stop a session, it is now
necessary to call `.remove_session()`, but then simply dropping with
send `.stop()`.

Factoring out some `Request::Capture` handling into a `capture_frame`
function seems to clean up error handling and such a bit.
This commit is contained in:
Ian Douglas Scott 2025-04-30 14:32:33 -07:00 • committed by Ian Douglas Scott
parent 9c7033df10
commit 194d5c8967
7 changed files with 346 additions and 367 deletions

View file

@ -5,7 +5,7 @@ use crate::{
config::{AdaptiveSync, EdidProduct, OutputConfig, OutputState, ScreenFilter}, config::{AdaptiveSync, EdidProduct, OutputConfig, OutputState, ScreenFilter},
shell::Shell, shell::Shell,
utils::prelude::*, utils::prelude::*,
wayland::protocols::screencopy::Frame as ScreencopyFrame, wayland::protocols::screencopy::Frame,
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -65,7 +65,7 @@ pub type GbmDrmOutputManager = DrmOutputManager<
GbmFramebufferExporter<DrmDeviceFd>, GbmFramebufferExporter<DrmDeviceFd>,
Option<( Option<(
OutputPresentationFeedback, OutputPresentationFeedback,
Receiver<(ScreencopyFrame, Vec<Rectangle<i32, BufferCoords>>)>, Receiver<(Frame, Vec<Rectangle<i32, BufferCoords>>)>,
Duration, Duration,
)>, )>,
DrmDeviceFd, DrmDeviceFd,

View file

@ -13,7 +13,7 @@ use crate::{
wayland::{ wayland::{
handlers::screencopy::{submit_buffer, FrameHolder, SessionData}, handlers::screencopy::{submit_buffer, FrameHolder, SessionData},
protocols::screencopy::{ protocols::screencopy::{
FailureReason, Frame as ScreencopyFrame, Session as ScreencopySession, FailureReason, Frame as ScreencopyFrame, SessionRef as ScreencopySessionRef,
}, },
}, },
}; };
@ -1024,7 +1024,7 @@ impl SurfaceThreadState {
// so let's collect everything we need for screencopy now // so let's collect everything we need for screencopy now
let mut has_cursor_mode_none = false; let mut has_cursor_mode_none = false;
let frames: Vec<( let frames: Vec<(
ScreencopySession, ScreencopySessionRef,
ScreencopyFrame, ScreencopyFrame,
Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>, Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>,
)> = self )> = self

View file

@ -27,7 +27,7 @@ use crate::{
utils::{float::NextDown, prelude::*, quirks::workspace_overview_is_open}, utils::{float::NextDown, prelude::*, quirks::workspace_overview_is_open},
wayland::{ wayland::{
handlers::screencopy::SessionHolder, handlers::screencopy::SessionHolder,
protocols::screencopy::{BufferConstraints, CursorSession}, protocols::screencopy::{BufferConstraints, CursorSessionRef},
}, },
}; };
use calloop::{ use calloop::{
@ -2262,7 +2262,7 @@ impl State {
fn cursor_sessions_for_output<'a>( fn cursor_sessions_for_output<'a>(
shell: &'a Shell, shell: &'a Shell,
output: &'a Output, output: &'a Output,
) -> impl Iterator<Item = CursorSession> + 'a { ) -> impl Iterator<Item = CursorSessionRef> + 'a {
shell shell
.active_space(&output) .active_space(&output)
.into_iter() .into_iter()

View file

@ -27,8 +27,9 @@ use crate::{
wayland::protocols::{ wayland::protocols::{
image_capture_source::ImageCaptureSourceData, image_capture_source::ImageCaptureSourceData,
screencopy::{ screencopy::{
delegate_screencopy, BufferConstraints, CursorSession, DmabufConstraints, Frame, delegate_screencopy, BufferConstraints, CursorSession, CursorSessionRef,
ScreencopyHandler, ScreencopyState, Session, DmabufConstraints, Frame, FrameRef, ScreencopyHandler, ScreencopyState, Session,
SessionRef,
}, },
}, },
}; };
@ -165,7 +166,6 @@ impl ScreencopyHandler for State {
match session.source() { match session.source() {
ImageCaptureSourceData::Output(weak) => { ImageCaptureSourceData::Output(weak) => {
let Some(mut output) = weak.upgrade() else { let Some(mut output) = weak.upgrade() else {
session.stop();
return; return;
}; };
@ -196,7 +196,6 @@ impl ScreencopyHandler for State {
ImageCaptureSourceData::Workspace(handle) => { ImageCaptureSourceData::Workspace(handle) => {
let mut shell = self.common.shell.write().unwrap(); let mut shell = self.common.shell.write().unwrap();
let Some(workspace) = shell.workspaces.space_for_handle_mut(&handle) else { let Some(workspace) = shell.workspaces.space_for_handle_mut(&handle) else {
session.stop();
return; return;
}; };
@ -252,11 +251,10 @@ impl ScreencopyHandler for State {
} }
} }
fn frame(&mut self, session: Session, frame: Frame) { fn frame(&mut self, session: SessionRef, frame: Frame) {
match session.source() { match session.source() {
ImageCaptureSourceData::Output(weak) => { ImageCaptureSourceData::Output(weak) => {
let Some(mut output) = weak.upgrade() else { let Some(mut output) = weak.upgrade() else {
session.stop(); // will fail the frame as well
return; return;
}; };
@ -273,7 +271,7 @@ impl ScreencopyHandler for State {
} }
} }
fn cursor_frame(&mut self, session: CursorSession, frame: Frame) { fn cursor_frame(&mut self, session: CursorSessionRef, frame: Frame) {
if !session.has_cursor() { if !session.has_cursor() {
frame.success(Transform::Normal, Vec::new(), self.common.clock.now()); frame.success(Transform::Normal, Vec::new(), self.common.clock.now());
return; return;
@ -290,18 +288,18 @@ impl ScreencopyHandler for State {
render_cursor_to_buffer(self, &session, frame, &seat); render_cursor_to_buffer(self, &session, frame, &seat);
} }
fn frame_aborted(&mut self, frame: Frame) { fn frame_aborted(&mut self, frame: FrameRef) {
let shell = self.common.shell.read().unwrap(); let shell = self.common.shell.read().unwrap();
for mut output in shell.outputs().cloned() { for mut output in shell.outputs().cloned() {
output.remove_frame(&frame) output.remove_frame(&frame);
} }
} }
fn session_destroyed(&mut self, session: Session) { fn session_destroyed(&mut self, session: SessionRef) {
match session.source() { match session.source() {
ImageCaptureSourceData::Output(weak) => { ImageCaptureSourceData::Output(weak) => {
if let Some(mut output) = weak.upgrade() { if let Some(mut output) = weak.upgrade() {
output.remove_session(session); output.remove_session(&session);
} }
} }
ImageCaptureSourceData::Workspace(handle) => { ImageCaptureSourceData::Workspace(handle) => {
@ -313,19 +311,19 @@ impl ScreencopyHandler for State {
.workspaces .workspaces
.space_for_handle_mut(&handle) .space_for_handle_mut(&handle)
{ {
workspace.remove_session(session) workspace.remove_session(&session)
} }
} }
ImageCaptureSourceData::Toplevel(mut toplevel) => toplevel.remove_session(session), ImageCaptureSourceData::Toplevel(mut toplevel) => toplevel.remove_session(&session),
ImageCaptureSourceData::Destroyed => unreachable!(), ImageCaptureSourceData::Destroyed => unreachable!(),
} }
} }
fn cursor_session_destroyed(&mut self, session: CursorSession) { fn cursor_session_destroyed(&mut self, session: CursorSessionRef) {
match session.source() { match session.source() {
ImageCaptureSourceData::Output(weak) => { ImageCaptureSourceData::Output(weak) => {
if let Some(mut output) = weak.upgrade() { if let Some(mut output) = weak.upgrade() {
output.remove_cursor_session(session); output.remove_cursor_session(&session);
} }
} }
ImageCaptureSourceData::Workspace(handle) => { ImageCaptureSourceData::Workspace(handle) => {
@ -337,11 +335,11 @@ impl ScreencopyHandler for State {
.workspaces .workspaces
.space_for_handle_mut(&handle) .space_for_handle_mut(&handle)
{ {
workspace.remove_cursor_session(session) workspace.remove_cursor_session(&session)
} }
} }
ImageCaptureSourceData::Toplevel(mut toplevel) => { ImageCaptureSourceData::Toplevel(mut toplevel) => {
toplevel.remove_cursor_session(session) toplevel.remove_cursor_session(&session)
} }
ImageCaptureSourceData::Destroyed => unreachable!(), ImageCaptureSourceData::Destroyed => unreachable!(),
} }

View file

@ -46,13 +46,13 @@ use crate::{
constraints_for_output, constraints_for_toplevel, SessionData, SessionUserData, constraints_for_output, constraints_for_toplevel, SessionData, SessionUserData,
}, },
protocols::{ protocols::{
screencopy::{BufferConstraints, CursorSession, FailureReason, Frame, Session}, screencopy::{BufferConstraints, CursorSessionRef, FailureReason, Frame, SessionRef},
workspace::WorkspaceHandle, workspace::WorkspaceHandle,
}, },
}, },
}; };
use super::super::data_device::get_dnd_icon; use super::{super::data_device::get_dnd_icon, user_data::SessionHolder};
pub fn submit_buffer<R>( pub fn submit_buffer<R>(
frame: Frame, frame: Frame,
@ -205,17 +205,16 @@ where
pub fn render_workspace_to_buffer( pub fn render_workspace_to_buffer(
state: &mut State, state: &mut State,
session: Session, session: SessionRef,
frame: Frame, frame: Frame,
handle: WorkspaceHandle, handle: WorkspaceHandle,
) { ) {
let shell = state.common.shell.read().unwrap(); let shell = state.common.shell.read().unwrap();
let Some(workspace) = shell.workspaces.space_for_handle(&handle) else { let Some(workspace) = shell.workspaces.space_for_handle(&handle) else {
session.stop();
return; return;
}; };
let output = workspace.output().clone(); let mut output = workspace.output().clone();
let idx = shell.workspaces.idx_for_handle(&output, &handle).unwrap(); let idx = shell.workspaces.idx_for_handle(&output, &handle).unwrap();
std::mem::drop(shell); std::mem::drop(shell);
@ -227,7 +226,7 @@ pub fn render_workspace_to_buffer(
let buffer_size = buffer_dimensions(&buffer).unwrap(); let buffer_size = buffer_dimensions(&buffer).unwrap();
if mode != Some(buffer_size) { if mode != Some(buffer_size) {
let Some(constraints) = constraints_for_output(&output, &mut state.backend) else { let Some(constraints) = constraints_for_output(&output, &mut state.backend) else {
session.stop(); output.remove_session(&session);
return; return;
}; };
session.update_constraints(constraints); session.update_constraints(constraints);
@ -439,12 +438,12 @@ smithay::render_elements! {
pub fn render_window_to_buffer( pub fn render_window_to_buffer(
state: &mut State, state: &mut State,
session: Session, session: SessionRef,
frame: Frame, frame: Frame,
toplevel: &CosmicSurface, toplevel: &CosmicSurface,
) { ) {
if !toplevel.alive() { if !toplevel.alive() {
session.stop(); toplevel.clone().remove_session(&session);
return; return;
} }
@ -453,7 +452,7 @@ pub fn render_window_to_buffer(
let buffer_size = buffer_dimensions(&buffer).unwrap(); let buffer_size = buffer_dimensions(&buffer).unwrap();
if buffer_size != geometry.size.to_buffer(1, Transform::Normal) { if buffer_size != geometry.size.to_buffer(1, Transform::Normal) {
let Some(constraints) = constraints_for_toplevel(toplevel, &mut state.backend) else { let Some(constraints) = constraints_for_toplevel(toplevel, &mut state.backend) else {
session.stop(); toplevel.clone().remove_session(&session);
return; return;
}; };
session.update_constraints(constraints); session.update_constraints(constraints);
@ -663,7 +662,7 @@ pub fn render_window_to_buffer(
pub fn render_cursor_to_buffer( pub fn render_cursor_to_buffer(
state: &mut State, state: &mut State,
session: &CursorSession, session: &CursorSessionRef,
frame: Frame, frame: Frame,
seat: &Seat<State>, seat: &Seat<State>,
) { ) {

View file

@ -1,9 +1,4 @@
use std::{ use std::{cell::RefCell, collections::HashMap, sync::Mutex};
cell::RefCell,
collections::HashMap,
ops::{Deref, DerefMut},
sync::Mutex,
};
use smithay::{ use smithay::{
backend::renderer::{damage::OutputDamageTracker, utils::CommitCounter}, backend::renderer::{damage::OutputDamageTracker, utils::CommitCounter},
@ -13,11 +8,13 @@ use smithay::{
use crate::{ use crate::{
shell::{CosmicSurface, Workspace}, shell::{CosmicSurface, Workspace},
wayland::protocols::screencopy::{CursorSession, FailureReason, Frame, Session}, wayland::protocols::screencopy::{
CursorSession, CursorSessionRef, Frame, FrameRef, Session, SessionRef,
},
}; };
type ScreencopySessionsData = RefCell<ScreencopySessions>; type ScreencopySessionsData = RefCell<ScreencopySessions>;
type PendingScreencopyBuffers = Mutex<Vec<(Session, DropableFrame)>>; type PendingScreencopyBuffers = Mutex<Vec<(SessionRef, Frame)>>;
pub type SessionData = Mutex<SessionUserData>; pub type SessionData = Mutex<SessionUserData>;
@ -58,24 +55,24 @@ impl SessionUserData {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct ScreencopySessions { pub struct ScreencopySessions {
sessions: Vec<DropableSession>, sessions: Vec<Session>,
cursor_sessions: Vec<DropableCursorSession>, cursor_sessions: Vec<CursorSession>,
} }
pub trait SessionHolder { pub trait SessionHolder {
fn add_session(&mut self, session: Session); fn add_session(&mut self, session: Session);
fn remove_session(&mut self, session: Session); fn remove_session(&mut self, session: &SessionRef);
fn sessions(&self) -> Vec<Session>; fn sessions(&self) -> Vec<SessionRef>;
fn add_cursor_session(&mut self, session: CursorSession); fn add_cursor_session(&mut self, session: CursorSession);
fn remove_cursor_session(&mut self, session: CursorSession); fn remove_cursor_session(&mut self, session: &CursorSessionRef);
fn cursor_sessions(&self) -> Vec<CursorSession>; fn cursor_sessions(&self) -> Vec<CursorSessionRef>;
} }
pub trait FrameHolder { pub trait FrameHolder {
fn add_frame(&mut self, session: Session, frame: Frame); fn add_frame(&mut self, session: SessionRef, frame: Frame);
fn remove_frame(&mut self, frame: &Frame); fn remove_frame(&mut self, frame: &FrameRef);
fn take_pending_frames(&self) -> Vec<(Session, Frame)>; fn take_pending_frames(&self) -> Vec<(SessionRef, Frame)>;
} }
impl SessionHolder for Output { impl SessionHolder for Output {
@ -87,19 +84,19 @@ impl SessionHolder for Output {
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.sessions .sessions
.push(DropableSession(Some(session))); .push(session);
} }
fn remove_session(&mut self, session: Session) { fn remove_session(&mut self, session: &SessionRef) {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.sessions .sessions
.retain(|s| *s != session); .retain(|s| s != session);
} }
fn sessions(&self) -> Vec<Session> { fn sessions(&self) -> Vec<SessionRef> {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.map_or(Vec::new(), |sessions| { .map_or(Vec::new(), |sessions| {
@ -107,7 +104,7 @@ impl SessionHolder for Output {
.borrow() .borrow()
.sessions .sessions
.iter() .iter()
.flat_map(|s| s.0.clone()) .map(|s| (*s).clone())
.collect() .collect()
}) })
} }
@ -120,19 +117,19 @@ impl SessionHolder for Output {
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.cursor_sessions .cursor_sessions
.push(DropableCursorSession(Some(session))); .push(session);
} }
fn remove_cursor_session(&mut self, session: CursorSession) { fn remove_cursor_session(&mut self, session: &CursorSessionRef) {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.cursor_sessions .cursor_sessions
.retain(|s| *s != session); .retain(|s| s != session);
} }
fn cursor_sessions(&self) -> Vec<CursorSession> { fn cursor_sessions(&self) -> Vec<CursorSessionRef> {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.map_or(Vec::new(), |sessions| { .map_or(Vec::new(), |sessions| {
@ -140,14 +137,14 @@ impl SessionHolder for Output {
.borrow() .borrow()
.cursor_sessions .cursor_sessions
.iter() .iter()
.flat_map(|s| s.0.clone()) .map(|s| (*s).clone())
.collect() .collect()
}) })
} }
} }
impl FrameHolder for Output { impl FrameHolder for Output {
fn add_frame(&mut self, session: Session, frame: Frame) { fn add_frame(&mut self, session: SessionRef, frame: Frame) {
self.user_data() self.user_data()
.insert_if_missing_threadsafe(PendingScreencopyBuffers::default); .insert_if_missing_threadsafe(PendingScreencopyBuffers::default);
self.user_data() self.user_data()
@ -155,61 +152,49 @@ impl FrameHolder for Output {
.unwrap() .unwrap()
.lock() .lock()
.unwrap() .unwrap()
.push((session, DropableFrame(Some(frame)))); .push((session, frame));
} }
fn remove_frame(&mut self, frame: &Frame) { fn remove_frame(&mut self, frame: &FrameRef) {
if let Some(pending) = self.user_data().get::<PendingScreencopyBuffers>() { if let Some(pending) = self.user_data().get::<PendingScreencopyBuffers>() {
pending.lock().unwrap().retain(|(_, f)| f != frame); pending.lock().unwrap().retain(|(_, f)| f != frame);
} }
} }
fn take_pending_frames(&self) -> Vec<(Session, Frame)> { fn take_pending_frames(&self) -> Vec<(SessionRef, Frame)> {
self.user_data() self.user_data()
.get::<PendingScreencopyBuffers>() .get::<PendingScreencopyBuffers>()
.map(|pending| { .map(|pending| std::mem::take(&mut *pending.lock().unwrap()))
pending
.lock()
.unwrap()
.split_off(0)
.into_iter()
.map(|(s, mut f)| (s, f.0.take().unwrap()))
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }
} }
impl SessionHolder for Workspace { impl SessionHolder for Workspace {
fn add_session(&mut self, session: Session) { fn add_session(&mut self, session: Session) {
self.screencopy self.screencopy.sessions.push(session);
.sessions
.push(DropableSession(Some(session)));
} }
fn remove_session(&mut self, session: Session) { fn remove_session(&mut self, session: &SessionRef) {
self.screencopy.sessions.retain(|s| *s != session); self.screencopy.sessions.retain(|s| s != session);
} }
fn sessions(&self) -> Vec<Session> { fn sessions(&self) -> Vec<SessionRef> {
self.screencopy self.screencopy
.sessions .sessions
.iter() .iter()
.flat_map(|s| s.0.clone()) .map(|s| (*s).clone())
.collect() .collect()
} }
fn add_cursor_session(&mut self, session: CursorSession) { fn add_cursor_session(&mut self, session: CursorSession) {
self.screencopy self.screencopy.cursor_sessions.push(session);
.cursor_sessions
.push(DropableCursorSession(Some(session)));
} }
fn remove_cursor_session(&mut self, session: CursorSession) { fn remove_cursor_session(&mut self, session: &CursorSessionRef) {
self.screencopy.cursor_sessions.retain(|s| *s != session); self.screencopy.cursor_sessions.retain(|s| s != session);
} }
fn cursor_sessions(&self) -> Vec<CursorSession> { fn cursor_sessions(&self) -> Vec<CursorSessionRef> {
self.screencopy self.screencopy
.cursor_sessions .cursor_sessions
.iter() .iter()
.flat_map(|s| s.0.clone()) .map(|s| (*s).clone())
.collect() .collect()
} }
} }
@ -223,18 +208,18 @@ impl SessionHolder for CosmicSurface {
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.sessions .sessions
.push(DropableSession(Some(session))); .push(session);
} }
fn remove_session(&mut self, session: Session) { fn remove_session(&mut self, session: &SessionRef) {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.sessions .sessions
.retain(|s| *s != session); .retain(|s| s != session);
} }
fn sessions(&self) -> Vec<Session> { fn sessions(&self) -> Vec<SessionRef> {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.map_or(Vec::new(), |sessions| { .map_or(Vec::new(), |sessions| {
@ -242,7 +227,7 @@ impl SessionHolder for CosmicSurface {
.borrow() .borrow()
.sessions .sessions
.iter() .iter()
.flat_map(|s| s.0.clone()) .map(|s| (*s).clone())
.collect() .collect()
}) })
} }
@ -255,19 +240,19 @@ impl SessionHolder for CosmicSurface {
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.cursor_sessions .cursor_sessions
.push(DropableCursorSession(Some(session))); .push(session);
} }
fn remove_cursor_session(&mut self, session: CursorSession) { fn remove_cursor_session(&mut self, session: &CursorSessionRef) {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
.cursor_sessions .cursor_sessions
.retain(|s| *s != session); .retain(|s| s != session);
} }
fn cursor_sessions(&self) -> Vec<CursorSession> { fn cursor_sessions(&self) -> Vec<CursorSessionRef> {
self.user_data() self.user_data()
.get::<ScreencopySessionsData>() .get::<ScreencopySessionsData>()
.map_or(Vec::new(), |sessions| { .map_or(Vec::new(), |sessions| {
@ -275,86 +260,8 @@ impl SessionHolder for CosmicSurface {
.borrow() .borrow()
.cursor_sessions .cursor_sessions
.iter() .iter()
.flat_map(|s| s.0.clone()) .map(|s| (*s).clone())
.collect() .collect()
}) })
} }
} }
#[derive(Debug)]
struct DropableSession(Option<Session>);
impl Deref for DropableSession {
type Target = Session;
fn deref(&self) -> &Self::Target {
self.0.as_ref().unwrap()
}
}
impl DerefMut for DropableSession {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.as_mut().unwrap()
}
}
impl PartialEq<Session> for DropableSession {
fn eq(&self, other: &Session) -> bool {
self.0.as_ref().map(|s| s == other).unwrap_or(false)
}
}
impl Drop for DropableSession {
fn drop(&mut self) {
if let Some(s) = self.0.take() {
s.stop();
}
}
}
#[derive(Debug)]
struct DropableCursorSession(Option<CursorSession>);
impl Deref for DropableCursorSession {
type Target = CursorSession;
fn deref(&self) -> &Self::Target {
self.0.as_ref().unwrap()
}
}
impl DerefMut for DropableCursorSession {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.as_mut().unwrap()
}
}
impl PartialEq<CursorSession> for DropableCursorSession {
fn eq(&self, other: &CursorSession) -> bool {
self.0.as_ref().map(|s| s == other).unwrap_or(false)
}
}
impl Drop for DropableCursorSession {
fn drop(&mut self) {
if let Some(s) = self.0.take() {
s.stop();
}
}
}
#[derive(Debug)]
pub struct DropableFrame(Option<Frame>);
impl Deref for DropableFrame {
type Target = Frame;
fn deref(&self) -> &Self::Target {
self.0.as_ref().unwrap()
}
}
impl DerefMut for DropableFrame {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.as_mut().unwrap()
}
}
impl PartialEq<Frame> for DropableFrame {
fn eq(&self, other: &Frame) -> bool {
self.0.as_ref().map(|f| f == other).unwrap_or(false)
}
}
impl Drop for DropableFrame {
fn drop(&mut self) {
if let Some(f) = self.0.take() {
f.fail(FailureReason::Unknown);
}
}
}

View file

@ -1,4 +1,5 @@
use std::{ use std::{
ops,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::Duration, time::Duration,
}; };
@ -37,8 +38,8 @@ use super::image_capture_source::ImageCaptureSourceData;
#[derive(Debug)] #[derive(Debug)]
pub struct ScreencopyState { pub struct ScreencopyState {
global: GlobalId, global: GlobalId,
known_sessions: Vec<Session>, known_sessions: Vec<SessionRef>,
known_cursor_sessions: Vec<CursorSession>, known_cursor_sessions: Vec<CursorSessionRef>,
} }
impl ScreencopyState { impl ScreencopyState {
@ -85,13 +86,13 @@ pub struct DmabufConstraints {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Session { pub struct SessionRef {
obj: ExtImageCopyCaptureSessionV1, obj: ExtImageCopyCaptureSessionV1,
inner: Arc<Mutex<SessionInner>>, inner: Arc<Mutex<SessionInner>>,
user_data: Arc<UserDataMap>, user_data: Arc<UserDataMap>,
} }
impl PartialEq for Session { impl PartialEq for SessionRef {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.obj == other.obj self.obj == other.obj
} }
@ -103,7 +104,7 @@ struct SessionInner {
constraints: Option<BufferConstraints>, constraints: Option<BufferConstraints>,
draw_cursors: bool, draw_cursors: bool,
source: ImageCaptureSourceData, source: ImageCaptureSourceData,
active_frames: Vec<Frame>, active_frames: Vec<FrameRef>,
} }
impl SessionInner { impl SessionInner {
@ -118,13 +119,13 @@ impl SessionInner {
} }
} }
impl IsAlive for Session { impl IsAlive for SessionRef {
fn alive(&self) -> bool { fn alive(&self) -> bool {
self.obj.is_alive() self.obj.is_alive()
} }
} }
impl Session { impl SessionRef {
pub fn update_constraints(&self, constraints: BufferConstraints) { pub fn update_constraints(&self, constraints: BufferConstraints) {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.inner.lock().unwrap();
@ -168,16 +169,38 @@ impl Session {
pub fn user_data(&self) -> &UserDataMap { pub fn user_data(&self) -> &UserDataMap {
&*self.user_data &*self.user_data
} }
}
pub fn stop(self) { #[derive(Debug)]
let mut inner = self.inner.lock().unwrap(); pub struct Session(SessionRef);
impl ops::Deref for Session {
type Target = SessionRef;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq<SessionRef> for Session {
fn eq(&self, other: &SessionRef) -> bool {
self.0 == *other
}
}
impl Drop for Session {
fn drop(&mut self) {
let mut inner = self.0.inner.lock().unwrap();
if !self.obj.is_alive() || inner.stopped { if !self.obj.is_alive() || inner.stopped {
return; return;
} }
for frame in inner.active_frames.drain(..) { for frame in inner.active_frames.drain(..) {
frame.fail(FailureReason::Stopped); frame
.inner
.lock()
.unwrap()
.fail(&frame.obj, FailureReason::Stopped);
} }
self.obj.stopped(); self.obj.stopped();
@ -186,14 +209,20 @@ impl Session {
} }
} }
impl Session {
pub fn stop(self) {
let _ = self;
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CursorSession { pub struct CursorSessionRef {
obj: ExtImageCopyCaptureCursorSessionV1, obj: ExtImageCopyCaptureCursorSessionV1,
inner: Arc<Mutex<CursorSessionInner>>, inner: Arc<Mutex<CursorSessionInner>>,
user_data: Arc<UserDataMap>, user_data: Arc<UserDataMap>,
} }
impl PartialEq for CursorSession { impl PartialEq for CursorSessionRef {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.obj == other.obj self.obj == other.obj
} }
@ -207,7 +236,7 @@ struct CursorSessionInner {
source: ImageCaptureSourceData, source: ImageCaptureSourceData,
position: Option<Point<i32, BufferCoords>>, position: Option<Point<i32, BufferCoords>>,
hotspot: Point<i32, BufferCoords>, hotspot: Point<i32, BufferCoords>,
active_frames: Vec<Frame>, active_frames: Vec<FrameRef>,
} }
impl CursorSessionInner { impl CursorSessionInner {
@ -224,13 +253,13 @@ impl CursorSessionInner {
} }
} }
impl IsAlive for CursorSession { impl IsAlive for CursorSessionRef {
fn alive(&self) -> bool { fn alive(&self) -> bool {
self.obj.is_alive() self.obj.is_alive()
} }
} }
impl CursorSession { impl CursorSessionRef {
pub fn update_constraints(&self, constrains: BufferConstraints) { pub fn update_constraints(&self, constrains: BufferConstraints) {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.inner.lock().unwrap();
@ -319,11 +348,29 @@ impl CursorSession {
pub fn user_data(&self) -> &UserDataMap { pub fn user_data(&self) -> &UserDataMap {
&*self.user_data &*self.user_data
} }
}
pub fn stop(self) { #[derive(Debug)]
let mut inner = self.inner.lock().unwrap(); pub struct CursorSession(CursorSessionRef);
if !self.obj.is_alive() || inner.stopped { impl ops::Deref for CursorSession {
type Target = CursorSessionRef;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq<CursorSessionRef> for CursorSession {
fn eq(&self, other: &CursorSessionRef) -> bool {
self.0 == *other
}
}
impl Drop for CursorSession {
fn drop(&mut self) {
let mut inner = self.0.inner.lock().unwrap();
if !self.0.obj.is_alive() || inner.stopped {
return; return;
} }
@ -333,26 +380,37 @@ impl CursorSession {
inner.constraints.take(); inner.constraints.take();
for frame in inner.active_frames.drain(..) { for frame in inner.active_frames.drain(..) {
frame.fail(FailureReason::Stopped); frame
.inner
.lock()
.unwrap()
.fail(&frame.obj, FailureReason::Stopped);
} }
inner.stopped = true; inner.stopped = true;
} }
} }
#[derive(Debug)] impl CursorSession {
pub struct Frame { pub fn stop(self) {
let _ = self;
}
}
/// Un-owned reference to a frame
#[derive(Clone, Debug)]
pub struct FrameRef {
obj: ExtImageCopyCaptureFrameV1, obj: ExtImageCopyCaptureFrameV1,
inner: Arc<Mutex<FrameInner>>, inner: Arc<Mutex<FrameInner>>,
} }
impl PartialEq for Frame { impl PartialEq for FrameRef {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.obj == other.obj self.obj == other.obj
} }
} }
impl Frame { impl FrameRef {
pub fn buffer(&self) -> WlBuffer { pub fn buffer(&self) -> WlBuffer {
self.inner.lock().unwrap().buffer.clone().unwrap() self.inner.lock().unwrap().buffer.clone().unwrap()
} }
@ -364,7 +422,26 @@ impl Frame {
pub fn has_failed(&self) -> bool { pub fn has_failed(&self) -> bool {
self.inner.lock().unwrap().failed.is_some() self.inner.lock().unwrap().failed.is_some()
} }
}
#[derive(Debug, PartialEq)]
pub struct Frame(FrameRef);
impl ops::Deref for Frame {
type Target = FrameRef;
fn deref(&self) -> &FrameRef {
&self.0
}
}
impl PartialEq<FrameRef> for Frame {
fn eq(&self, other: &FrameRef) -> bool {
self.0 == *other
}
}
impl Frame {
pub fn success( pub fn success(
self, self,
transform: impl Into<Transform>, transform: impl Into<Transform>,
@ -372,7 +449,7 @@ impl Frame {
presented: impl Into<Duration>, presented: impl Into<Duration>,
) { ) {
{ {
let inner = self.inner.lock().unwrap(); let inner = self.0.inner.lock().unwrap();
if !inner.capture_requested || inner.failed.is_some() { if !inner.capture_requested || inner.failed.is_some() {
return; return;
} }
@ -390,12 +467,22 @@ impl Frame {
let tv_nsec = time.subsec_nanos(); let tv_nsec = time.subsec_nanos();
self.obj.presentation_time(tv_sec_hi, tv_sec_lo, tv_nsec); self.obj.presentation_time(tv_sec_hi, tv_sec_lo, tv_nsec);
self.inner.lock().unwrap().ready = true; self.0.inner.lock().unwrap().ready = true;
self.obj.ready() self.obj.ready()
} }
pub fn fail(self, reason: FailureReason) { pub fn fail(self, reason: FailureReason) {
self.inner.lock().unwrap().fail(&self.obj, reason); self.0.inner.lock().unwrap().fail(&self.obj, reason);
}
}
impl Drop for Frame {
fn drop(&mut self) {
// Send `fail` is `sucesss` or `fail` not already called
self.inner
.lock()
.unwrap()
.fail(&self.obj, FailureReason::Unknown);
} }
} }
@ -404,7 +491,7 @@ struct FrameInner {
constraints: Option<BufferConstraints>, constraints: Option<BufferConstraints>,
buffer: Option<WlBuffer>, buffer: Option<WlBuffer>,
damage: Vec<Rectangle<i32, BufferCoords>>, damage: Vec<Rectangle<i32, BufferCoords>>,
// `SessionInner` contains a `Vec<Frame>`, so use a weak reference here to // `SessionInner` contains a `Vec<FrameRef>`, so use a weak reference here to
// avoid a cycle. // avoid a cycle.
obj: Weak<ExtImageCopyCaptureSessionV1>, obj: Weak<ExtImageCopyCaptureSessionV1>,
capture_requested: bool, capture_requested: bool,
@ -451,14 +538,14 @@ pub trait ScreencopyHandler {
fn new_session(&mut self, session: Session); fn new_session(&mut self, session: Session);
fn new_cursor_session(&mut self, session: CursorSession); fn new_cursor_session(&mut self, session: CursorSession);
fn frame(&mut self, session: Session, frame: Frame); fn frame(&mut self, session: SessionRef, frame: Frame);
fn cursor_frame(&mut self, session: CursorSession, frame: Frame); fn cursor_frame(&mut self, session: CursorSessionRef, frame: Frame);
fn frame_aborted(&mut self, frame: Frame); fn frame_aborted(&mut self, frame_handle: FrameRef);
fn session_destroyed(&mut self, session: Session) { fn session_destroyed(&mut self, session: SessionRef) {
let _ = session; let _ = session;
} }
fn cursor_session_destroyed(&mut self, session: CursorSession) { fn cursor_session_destroyed(&mut self, session: CursorSessionRef) {
let _ = session; let _ = session;
} }
} }
@ -546,7 +633,7 @@ where
}, },
); );
let session = Session { let session = SessionRef {
obj, obj,
inner: session_data, inner: session_data,
user_data: Arc::new(UserDataMap::new()), user_data: Arc::new(UserDataMap::new()),
@ -556,7 +643,7 @@ where
.screencopy_state() .screencopy_state()
.known_sessions .known_sessions
.push(session.clone()); .push(session.clone());
state.new_session(session); state.new_session(Session(session));
return; return;
} }
} }
@ -572,11 +659,11 @@ where
inner: session_data.clone(), inner: session_data.clone(),
}, },
); );
let session = Session { let session = Session(SessionRef {
obj, obj,
inner: session_data, inner: session_data,
user_data: Arc::new(UserDataMap::new()), user_data: Arc::new(UserDataMap::new()),
}; });
session.stop(); session.stop();
} }
ext_image_copy_capture_manager_v1::Request::CreatePointerCursorSession { ext_image_copy_capture_manager_v1::Request::CreatePointerCursorSession {
@ -598,7 +685,7 @@ where
}, },
); );
let session = CursorSession { let session = CursorSessionRef {
obj, obj,
inner: session_data, inner: session_data,
user_data: Arc::new(UserDataMap::new()), user_data: Arc::new(UserDataMap::new()),
@ -608,7 +695,7 @@ where
.screencopy_state() .screencopy_state()
.known_cursor_sessions .known_cursor_sessions
.push(session.clone()); .push(session.clone());
state.new_cursor_session(session); state.new_cursor_session(CursorSession(session));
return; return;
} }
} }
@ -623,11 +710,11 @@ where
inner: session_data.clone(), inner: session_data.clone(),
}, },
); );
let session = CursorSession { let session = CursorSession(CursorSessionRef {
obj, obj,
inner: session_data, inner: session_data,
user_data: Arc::new(UserDataMap::new()), user_data: Arc::new(UserDataMap::new()),
}; });
session.stop(); session.stop();
} }
_ => {} _ => {}
@ -675,7 +762,7 @@ where
.lock() .lock()
.unwrap() .unwrap()
.active_frames .active_frames
.push(Frame { obj, inner }); .push(FrameRef { obj, inner });
} }
_ => {} _ => {}
} }
@ -808,7 +895,7 @@ where
.lock() .lock()
.unwrap() .unwrap()
.active_frames .active_frames
.push(Frame { obj, inner }); .push(FrameRef { obj, inner });
} }
_ => {} _ => {}
} }
@ -877,155 +964,32 @@ where
.push(Rectangle::new((x, y).into(), (width, height).into())); .push(Rectangle::new((x, y).into(), (width, height).into()));
} }
ext_image_copy_capture_frame_v1::Request::Capture => { ext_image_copy_capture_frame_v1::Request::Capture => {
let mut inner = data.inner.lock().unwrap(); {
let inner = data.inner.lock().unwrap();
if inner.capture_requested { if inner.capture_requested {
resource.post_error( resource.post_error(
ext_image_copy_capture_frame_v1::Error::AlreadyCaptured, ext_image_copy_capture_frame_v1::Error::AlreadyCaptured,
"Frame was captured previously", "Frame was captured previously",
); );
} return;
}
if inner.buffer.is_none() {
resource.post_error( if inner.buffer.is_none() {
ext_image_copy_capture_frame_v1::Error::NoBuffer, resource.post_error(
"Attempting to capture frame without a buffer", ext_image_copy_capture_frame_v1::Error::NoBuffer,
); "Attempting to capture frame without a buffer",
} );
return;
inner.capture_requested = true;
if let Some(reason) = inner.failed {
inner.fail(resource, reason);
return;
}
if let Some(constraints) = inner.constraints.as_ref() {
let buffer = inner.buffer.as_ref().unwrap();
match buffer_type(buffer) {
Some(BufferType::Dma) => {
let Some(dma_constraints) = constraints.dma.as_ref() else {
debug!("dma buffer not specified for screencopy");
inner.fail(resource, FailureReason::BufferConstraints);
return;
};
let dmabuf = match get_dmabuf(buffer) {
Ok(buf) => buf,
Err(err) => {
debug!(?err, "Error accessing dma buffer for screencopy");
inner.fail(resource, FailureReason::Stopped);
return;
}
};
let buffer_size = dmabuf.size();
if buffer_size.w < constraints.size.w
|| buffer_size.h < constraints.size.h
{
debug!(?buffer_size, ?constraints.size, "buffer too small for screencopy");
inner.fail(&resource, FailureReason::BufferConstraints);
return;
}
let format = dmabuf.format();
if dma_constraints
.formats
.iter()
.find(|(fourcc, _)| *fourcc == format.code)
.filter(|(_, modifiers)| modifiers.contains(&format.modifier))
.is_none()
{
debug!(
?format,
?dma_constraints,
"unsupported buffer format for screencopy"
);
inner.fail(&resource, FailureReason::BufferConstraints);
return;
}
}
Some(BufferType::Shm) => {
let buffer_data = match with_buffer_contents(buffer, |_, _, data| data)
{
Ok(data) => data,
Err(err) => {
debug!(?err, "Error accessing shm buffer for screencopy");
inner.fail(&resource, FailureReason::Unknown);
return;
}
};
if buffer_data.width < constraints.size.w
|| buffer_data.height < constraints.size.h
{
debug!(?buffer_data, ?constraints.size, "buffer too small for screencopy");
inner.fail(&resource, FailureReason::BufferConstraints);
return;
}
if !constraints.shm.contains(&buffer_data.format) {
debug!(?buffer_data.format, ?constraints.shm, "unsupported buffer format for screencopy");
inner.fail(&resource, FailureReason::BufferConstraints);
return;
}
}
x => {
debug!(?x, "Attempt to screencopy with unsupported buffer type");
inner.fail(&resource, FailureReason::BufferConstraints);
return;
}
} }
} else {
inner.fail(&resource, FailureReason::Unknown);
return;
} }
let frame = Frame { let frame = Frame(FrameRef {
obj: resource.clone(), obj: resource.clone(),
inner: data.inner.clone(), inner: data.inner.clone(),
}; });
if let Err(reason) = capture_frame(state, frame) {
let scpy = state.screencopy_state(); data.inner.lock().unwrap().fail(resource, reason);
if let Some(session) = scpy
.known_sessions
.iter()
.find(|session| session.obj == inner.obj)
.map(|s| Session {
obj: s.obj.clone(),
inner: s.inner.clone(),
user_data: s.user_data.clone(),
})
{
if session.inner.lock().unwrap().stopped {
inner.fail(&resource, FailureReason::Stopped);
return;
}
std::mem::drop(inner);
state.frame(session, frame);
} else if let Some(session) = scpy
.known_cursor_sessions
.iter()
.find(|session| {
session.inner.lock().unwrap().session.as_ref()
== inner.obj.upgrade().ok().as_ref()
})
.map(|s| CursorSession {
obj: s.obj.clone(),
inner: s.inner.clone(),
user_data: s.user_data.clone(),
})
{
if session.inner.lock().unwrap().stopped {
inner.fail(&resource, FailureReason::Stopped);
return;
}
std::mem::drop(inner);
state.cursor_frame(session, frame);
} else {
inner.fail(&resource, FailureReason::Unknown);
} }
} }
_ => {} _ => {}
@ -1038,6 +1002,10 @@ where
resource: &ExtImageCopyCaptureFrameV1, resource: &ExtImageCopyCaptureFrameV1,
data: &FrameData, data: &FrameData,
) { ) {
let frame_ref = FrameRef {
obj: resource.clone(),
inner: data.inner.clone(),
};
{ {
let scpy = state.screencopy_state(); let scpy = state.screencopy_state();
for session in &mut scpy.known_sessions { for session in &mut scpy.known_sessions {
@ -1046,7 +1014,7 @@ where
.lock() .lock()
.unwrap() .unwrap()
.active_frames .active_frames
.retain(|frame| frame.obj != *resource); .retain(|i| *i != frame_ref);
} }
for cursor_session in &mut scpy.known_cursor_sessions { for cursor_session in &mut scpy.known_cursor_sessions {
cursor_session cursor_session
@ -1054,14 +1022,121 @@ where
.lock() .lock()
.unwrap() .unwrap()
.active_frames .active_frames
.retain(|frame| frame.obj != *resource); .retain(|i| *i != frame_ref);
} }
} }
let frame = Frame { state.frame_aborted(frame_ref);
obj: resource.clone(), }
inner: data.inner.clone(), }
};
state.frame_aborted(frame); fn capture_frame<D: ScreencopyHandler>(state: &mut D, frame: Frame) -> Result<(), FailureReason> {
let mut inner = frame.0.inner.lock().unwrap();
inner.capture_requested = true;
if let Some(reason) = inner.failed {
return Err(reason);
}
if let Some(constraints) = inner.constraints.as_ref() {
let buffer = inner.buffer.as_ref().unwrap();
match buffer_type(buffer) {
Some(BufferType::Dma) => {
let Some(dma_constraints) = constraints.dma.as_ref() else {
debug!("dma buffer not specified for screencopy");
return Err(FailureReason::BufferConstraints);
};
let dmabuf = match get_dmabuf(buffer) {
Ok(buf) => buf,
Err(err) => {
debug!(?err, "Error accessing dma buffer for screencopy");
return Err(FailureReason::Stopped);
}
};
let buffer_size = dmabuf.size();
if buffer_size.w < constraints.size.w || buffer_size.h < constraints.size.h {
debug!(?buffer_size, ?constraints.size, "buffer too small for screencopy");
return Err(FailureReason::BufferConstraints);
}
let format = dmabuf.format();
if dma_constraints
.formats
.iter()
.find(|(fourcc, _)| *fourcc == format.code)
.filter(|(_, modifiers)| modifiers.contains(&format.modifier))
.is_none()
{
debug!(
?format,
?dma_constraints,
"unsupported buffer format for screencopy"
);
return Err(FailureReason::BufferConstraints);
}
}
Some(BufferType::Shm) => {
let buffer_data = match with_buffer_contents(buffer, |_, _, data| data) {
Ok(data) => data,
Err(err) => {
debug!(?err, "Error accessing shm buffer for screencopy");
return Err(FailureReason::Unknown);
}
};
if buffer_data.width < constraints.size.w || buffer_data.height < constraints.size.h
{
debug!(?buffer_data, ?constraints.size, "buffer too small for screencopy");
return Err(FailureReason::BufferConstraints);
}
if !constraints.shm.contains(&buffer_data.format) {
debug!(?buffer_data.format, ?constraints.shm, "unsupported buffer format for screencopy");
return Err(FailureReason::BufferConstraints);
}
}
x => {
debug!(?x, "Attempt to screencopy with unsupported buffer type");
return Err(FailureReason::BufferConstraints);
}
}
} else {
return Err(FailureReason::Unknown);
}
let scpy = state.screencopy_state();
if let Some(session) = scpy
.known_sessions
.iter()
.find(|session| session.obj == inner.obj)
.cloned()
{
if session.inner.lock().unwrap().stopped {
return Err(FailureReason::Stopped);
}
std::mem::drop(inner);
state.frame(session, frame);
Ok(())
} else if let Some(session) = scpy
.known_cursor_sessions
.iter()
.find(|session| {
session.inner.lock().unwrap().session.as_ref() == inner.obj.upgrade().ok().as_ref()
})
.cloned()
{
if session.inner.lock().unwrap().stopped {
return Err(FailureReason::Stopped);
}
std::mem::drop(inner);
state.cursor_frame(session, frame);
Ok(())
} else {
Err(FailureReason::Unknown)
} }
} }