1
0
Fork 0
forked from wry/wry

portal: unify remote desktop and screencast sessions

This commit is contained in:
Julian Orth 2024-10-10 21:14:05 +02:00
parent 3e3532574b
commit 260d241f79
8 changed files with 304 additions and 279 deletions

View file

@ -3,7 +3,7 @@ mod screencast_gui;
use {
crate::{
allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING},
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply},
dbus::{prelude::Variant, DbusObject, DictEntry, PendingReply},
format::{Format, XRGB8888},
ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE},
pipewire::{
@ -21,14 +21,15 @@ use {
},
portal::{
ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput},
ptl_remote_desktop::RemoteDesktopPhase,
ptl_screencast::screencast_gui::SelectionGui,
ptl_session::{PortalSession, PortalSessionReply},
PortalState, PORTAL_SUCCESS,
},
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
hash_map_ext::HashMapExt,
opaque::Opaque,
},
video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER},
@ -40,7 +41,7 @@ use {
CreateSession, CreateSessionReply, SelectSources, SelectSourcesReply, Start,
StartReply,
},
session::{CloseReply as SessionCloseReply, Closed},
session::CloseReply as SessionCloseReply,
},
},
wl_usr::usr_ifs::{
@ -66,16 +67,6 @@ use {
thiserror::Error,
};
shared_ids!(ScreencastSessionId);
pub struct ScreencastSession {
_id: ScreencastSessionId,
state: Rc<PortalState>,
pw_con: Rc<PwCon>,
pub app: String,
session_obj: DbusObject,
pub phase: CloneCell<ScreencastPhase>,
}
#[derive(Clone)]
pub enum ScreencastPhase {
Init,
@ -96,9 +87,8 @@ pub struct SourcesSelectedScreencast {
#[derive(Clone)]
pub struct SelectingScreencastCore {
pub session: Rc<ScreencastSession>,
pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
}
pub struct SelectingScreencast {
@ -121,9 +111,8 @@ pub struct SelectingWorkspaceScreencast {
}
pub struct StartingScreencast {
pub session: Rc<ScreencastSession>,
pub session: Rc<PortalSession>,
pub _request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
pub node: Rc<PwClientNode>,
pub dpy: Rc<PortalDisplay>,
pub target: ScreencastTarget,
@ -136,21 +125,21 @@ pub enum ScreencastTarget {
}
pub struct StartedScreencast {
session: Rc<ScreencastSession>,
node: Rc<PwClientNode>,
port: Rc<PwClientNodePort>,
buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>,
buffers: RefCell<Vec<DmaBuf>>,
pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>,
buffers_valid: Cell<bool>,
dpy: Rc<PortalDisplay>,
jay_screencast: Rc<UsrJayScreencast>,
port_buffer_valid: Cell<bool>,
fixated: Cell<bool>,
format: Cell<&'static Format>,
modifier: Cell<Modifier>,
width: Cell<i32>,
height: Cell<i32>,
pub session: Rc<PortalSession>,
pub node: Rc<PwClientNode>,
pub port: Rc<PwClientNodePort>,
pub buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>,
pub buffers: RefCell<Vec<DmaBuf>>,
pub pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>,
pub buffers_valid: Cell<bool>,
pub dpy: Rc<PortalDisplay>,
pub jay_screencast: Rc<UsrJayScreencast>,
pub port_buffer_valid: Cell<bool>,
pub fixated: Cell<bool>,
pub format: Cell<&'static Format>,
pub modifier: Cell<Modifier>,
pub width: Cell<i32>,
pub height: Cell<i32>,
}
bitflags! {
@ -170,33 +159,8 @@ bitflags! {
impl PwClientNodeOwner for StartingScreencast {
fn bound_id(&self, node_id: u32) {
{
let inner_type = DynamicType::DictEntry(
Box::new(DynamicType::String),
Box::new(DynamicType::Variant),
);
let kt = DynamicType::Struct(vec![
DynamicType::U32,
DynamicType::Array(Box::new(inner_type.clone())),
]);
let mut variants = vec![DictEntry {
key: "streams".into(),
value: Variant::Array(
kt,
vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])],
),
}];
if let Some(rd) = create_restore_data(&self.dpy, &self.target) {
variants.push(DictEntry {
key: "restore_data".into(),
value: rd,
});
}
self.reply.ok(&StartReply {
response: PORTAL_SUCCESS,
results: Cow::Owned(variants),
});
}
self.session
.send_start_reply(Some(node_id), create_restore_data(&self.dpy, &self.target));
let mut supported_formats = PwClientNodePortSupportedFormats {
media_type: Some(SPA_MEDIA_TYPE_video),
media_sub_type: Some(SPA_MEDIA_SUBTYPE_raw),
@ -271,7 +235,7 @@ impl PwClientNodeOwner for StartingScreencast {
height: Cell::new(1),
});
self.session
.phase
.sc_phase
.set(ScreencastPhase::Started(started.clone()));
started.jay_screencast.owner.set(Some(started.clone()));
self.node.owner.set(Some(started.clone()));
@ -424,7 +388,11 @@ impl StartedScreencast {
impl SelectingScreencastCore {
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
let node = self.session.pw_con.create_client_node(&[
let Some(pw_con) = &self.session.pw_con else {
self.session.kill();
return;
};
let node = pw_con.create_client_node(&[
("media.class".to_string(), "Video/Source".to_string()),
("node.name".to_string(), "jay-desktop-portal".to_string()),
("node.driver".to_string(), "true".to_string()),
@ -432,76 +400,28 @@ impl SelectingScreencastCore {
let starting = Rc::new(StartingScreencast {
session: self.session.clone(),
_request_obj: self.request_obj.clone(),
reply: self.reply.clone(),
node,
dpy: dpy.clone(),
target,
});
self.session
.phase
.sc_phase
.set(ScreencastPhase::Starting(starting.clone()));
starting.node.owner.set(Some(starting.clone()));
dpy.screencasts.set(
dpy.sessions.set(
self.session.session_obj.path().to_owned(),
self.session.clone(),
);
}
}
impl ScreencastSession {
pub(super) fn kill(&self) {
self.session_obj.emit_signal(&Closed);
self.state.screencasts.remove(self.session_obj.path());
match self.phase.set(ScreencastPhase::Terminated) {
ScreencastPhase::Init => {}
ScreencastPhase::SourcesSelected(_) => {}
ScreencastPhase::Terminated => {}
ScreencastPhase::Selecting(s) => {
s.core.reply.err("Session has been terminated");
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
ScreencastPhase::SelectingWindow(s) => {
s.dpy.con.remove_obj(&*s.selector);
s.core.reply.err("Session has been terminated");
}
ScreencastPhase::SelectingWorkspace(s) => {
s.dpy.con.remove_obj(&*s.selector);
s.core.reply.err("Session has been terminated");
}
ScreencastPhase::Starting(s) => {
s.reply.err("Session has been terminated");
s.node.con.destroy_obj(s.node.deref());
s.dpy.screencasts.remove(self.session_obj.path());
match &s.target {
ScreencastTarget::Output(_) => {}
ScreencastTarget::Workspace(_, w, true) => {
s.dpy.con.remove_obj(&**w);
}
ScreencastTarget::Workspace(_, _, false) => {}
ScreencastTarget::Toplevel(t) => {
s.dpy.con.remove_obj(&**t);
}
}
}
ScreencastPhase::Started(s) => {
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
s.node.con.destroy_obj(s.node.deref());
s.dpy.screencasts.remove(self.session_obj.path());
for buffer in s.pending_buffers.borrow_mut().drain(..) {
s.dpy.con.remove_obj(&*buffer);
}
}
}
}
impl PortalSession {
fn dbus_select_sources(
self: &Rc<Self>,
req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>,
) {
match self.phase.get() {
match self.sc_phase.get() {
ScreencastPhase::Init => {}
_ => {
self.kill();
@ -509,7 +429,7 @@ impl ScreencastSession {
return;
}
}
self.phase.set(ScreencastPhase::SourcesSelected(Rc::new(
self.sc_phase.set(ScreencastPhase::SourcesSelected(Rc::new(
SourcesSelectedScreencast {
restore_data: Cell::new(get_restore_data(&req)),
},
@ -520,8 +440,12 @@ impl ScreencastSession {
});
}
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
let restore_data = match self.phase.get() {
fn dbus_start_screencast(
self: &Rc<Self>,
req: Start<'_>,
reply: PendingReply<StartReply<'static>>,
) {
let restore_data = match self.sc_phase.get() {
ScreencastPhase::SourcesSelected(s) => s.restore_data.take(),
_ => {
self.kill();
@ -547,14 +471,14 @@ impl ScreencastSession {
}
});
}
let reply = Rc::new(reply);
self.restore(&request_obj, &reply, restore_data, None);
self.start_reply
.set(Some(PortalSessionReply::ScreenCast(reply)));
self.screencast_restore(&request_obj, restore_data, None);
}
fn start_interactive_selection(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
reply: &Rc<PendingReply<StartReply<'static>>>,
restore_data: Option<RestoreData>,
) {
let guis = CopyHashMap::new();
@ -565,42 +489,39 @@ impl ScreencastSession {
}
if guis.is_empty() {
self.kill();
reply.err("There are no running displays");
self.reply_err("There are no running displays");
return;
}
self.phase
self.sc_phase
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
core: SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.clone(),
reply: reply.clone(),
},
guis,
restore_data: Cell::new(restore_data),
})));
}
fn restore(
pub fn screencast_restore(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
reply: &Rc<PendingReply<StartReply<'static>>>,
restore_data: Option<Result<RestoreData, RestoreError>>,
display: Option<Rc<PortalDisplay>>,
) {
if let Some(rd) = restore_data {
if let Err(e) = self.try_restore(&request_obj, &reply, rd, display) {
if let Err(e) = self.try_restore(&request_obj, rd, display) {
log::error!("Could not restore session: {}", ErrorFmt(e));
} else {
return;
}
}
self.start_interactive_selection(&request_obj, &reply, None);
self.start_interactive_selection(&request_obj, None);
}
fn try_restore(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
reply: &Rc<PendingReply<StartReply<'static>>>,
restore_data: Result<RestoreData, RestoreError>,
display: Option<Rc<PortalDisplay>>,
) -> Result<(), RestoreError> {
@ -623,7 +544,7 @@ impl ScreencastSession {
} else if self.state.displays.len() == 1 {
self.state.displays.lock().values().next().unwrap().clone()
} else {
self.start_interactive_selection(&request_obj, &reply, Some(rd));
self.start_interactive_selection(&request_obj, Some(rd));
return Ok(());
}
}
@ -633,7 +554,6 @@ impl ScreencastSession {
SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.clone(),
reply: reply.clone(),
}
.starting(&dpy, target);
};
@ -674,14 +594,14 @@ impl ScreencastSession {
core: SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.clone(),
reply: reply.clone(),
},
dpy: dpy.clone(),
selector: selector.clone(),
restoring: true,
});
selector.owner.set(Some(selecting.clone()));
self.phase.set(ScreencastPhase::SelectingWindow(selecting));
self.sc_phase
.set(ScreencastPhase::SelectingWindow(selecting));
}
}
Ok(())
@ -833,7 +753,7 @@ fn dbus_create_session(
reply: PendingReply<CreateSessionReply<'static>>,
) {
log::info!("Create Session {:#?}", req);
if state.screencasts.contains(req.session_handle.0.deref()) {
if state.sessions.contains(req.session_handle.0.deref()) {
reply.err("Session already exists");
return;
}
@ -844,13 +764,15 @@ fn dbus_create_session(
return;
}
};
let session = Rc::new(ScreencastSession {
let session = Rc::new(PortalSession {
_id: state.id(),
state: state.clone(),
pw_con: pw_con.clone(),
pw_con: Some(pw_con.clone()),
app: req.app_id.to_string(),
session_obj: obj,
phase: CloneCell::new(ScreencastPhase::Init),
sc_phase: CloneCell::new(ScreencastPhase::Init),
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
start_reply: Default::default(),
});
{
use org::freedesktop::impl_::portal::session::*;
@ -862,7 +784,7 @@ fn dbus_create_session(
session.session_obj.set_property::<version>(Variant::U32(4));
}
state
.screencasts
.sessions
.set(req.session_handle.0.to_string(), session);
reply.ok(&CreateSessionReply {
response: PORTAL_SUCCESS,
@ -882,7 +804,7 @@ fn dbus_select_sources(
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_start(req, reply);
s.dbus_start_screencast(req, reply);
}
}
@ -890,8 +812,8 @@ fn get_session<T>(
state: &Rc<PortalState>,
reply: &PendingReply<T>,
handle: &str,
) -> Option<Rc<ScreencastSession>> {
let res = state.screencasts.get(handle);
) -> Option<Rc<PortalSession>> {
let res = state.sessions.get(handle);
if res.is_none() {
let msg = format!("Screencast session `{}` does not exist", handle);
reply.err(&msg);