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

@ -8,6 +8,7 @@
- Implement ext-image-capture-source-v1.
- Implement ext-image-copy-capture-v1.
- Implement screencast session restoration.
- Fix screen sharing in zoom.
# 1.6.0 (2024-09-25)

View file

@ -2,6 +2,7 @@ mod ptl_display;
mod ptl_remote_desktop;
mod ptl_render_ctx;
mod ptl_screencast;
mod ptl_session;
mod ptl_text;
mod ptr_gui;
@ -16,12 +17,13 @@ use {
forker::ForkerError,
io_uring::IoUring,
logger::Logger,
pipewire::pw_con::{PwConHolder, PwConOwner},
pipewire::pw_con::{PwCon, PwConHolder, PwConOwner},
portal::{
ptl_display::{watch_displays, PortalDisplay, PortalDisplayId},
ptl_remote_desktop::{add_remote_desktop_dbus_members, RemoteDesktopSession},
ptl_remote_desktop::add_remote_desktop_dbus_members,
ptl_render_ctx::PortalRenderCtx,
ptl_screencast::{add_screencast_dbus_members, ScreencastSession},
ptl_screencast::add_screencast_dbus_members,
ptl_session::PortalSession,
},
utils::{
clone3::{fork_with_pidfd, Forked},
@ -200,11 +202,11 @@ async fn run_async(
wheel,
displays: Default::default(),
dbus,
screencasts: Default::default(),
remote_desktop_sessions: Default::default(),
sessions: Default::default(),
next_id: NumCell::new(1),
render_ctxs: Default::default(),
dma_buf_ids: Default::default(),
pw_con: pw_con.as_ref().map(|c| c.con.clone()),
});
if let Some(pw_con) = &pw_con {
pw_con.con.owner.set(Some(state.clone()));
@ -295,11 +297,11 @@ struct PortalState {
wheel: Rc<Wheel>,
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
dbus: Rc<DbusSocket>,
screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
remote_desktop_sessions: CopyHashMap<String, Rc<RemoteDesktopSession>>,
sessions: CopyHashMap<String, Rc<PortalSession>>,
next_id: NumCell<u32>,
render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>,
dma_buf_ids: Rc<DmaBufIds>,
pw_con: Option<Rc<PwCon>>,
}
impl PortalState {

View file

@ -5,9 +5,8 @@ use {
ifs::wl_seat::POINTER,
object::Version,
portal::{
ptl_remote_desktop::RemoteDesktopSession,
ptl_render_ctx::{PortalRenderCtx, PortalServerRenderCtx},
ptl_screencast::ScreencastSession,
ptl_session::PortalSession,
ptr_gui::WindowData,
PortalState,
},
@ -87,8 +86,7 @@ pub struct PortalDisplay {
pub workspaces: CopyHashMap<u32, Rc<UsrJayWorkspace>>,
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
pub screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
pub remote_desktop_sessions: CopyHashMap<String, Rc<RemoteDesktopSession>>,
pub sessions: CopyHashMap<String, Rc<PortalSession>>,
}
pub struct PortalOutput {
@ -225,7 +223,7 @@ impl UsrJayRenderCtxOwner for PortalDisplay {
impl UsrConOwner for PortalDisplay {
fn killed(&self) {
log::info!("Removing display {}", self.id);
for sc in self.screencasts.lock().drain_values() {
for sc in self.sessions.lock().drain_values() {
sc.kill();
}
self.windows.clear();
@ -441,8 +439,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
fsm,
vp,
windows: Default::default(),
screencasts: Default::default(),
remote_desktop_sessions: Default::default(),
sessions: Default::default(),
workspaces: Default::default(),
});

View file

@ -2,17 +2,18 @@ mod remote_desktop_gui;
use {
crate::{
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply, FALSE},
dbus::{prelude::Variant, DbusObject, PendingReply},
ifs::jay_compositor::CREATE_EI_SESSION_SINCE,
portal::{
ptl_display::{PortalDisplay, PortalDisplayId},
ptl_remote_desktop::remote_desktop_gui::SelectionGui,
ptl_screencast::ScreencastPhase,
ptl_session::{PortalSession, PortalSessionReply},
PortalState, PORTAL_SUCCESS,
},
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap,
hash_map_ext::HashMapExt,
},
wire_dbus::{
org,
@ -21,24 +22,15 @@ use {
ConnectToEIS, ConnectToEISReply, CreateSession, CreateSessionReply,
SelectDevices, SelectDevicesReply, Start, StartReply,
},
session::{CloseReply as SessionCloseReply, Closed},
session::CloseReply as SessionCloseReply,
},
},
wl_usr::usr_ifs::usr_jay_ei_session::{UsrJayEiSession, UsrJayEiSessionOwner},
},
std::{borrow::Cow, cell::Cell, ops::Deref, rc::Rc},
std::{cell::Cell, ops::Deref, rc::Rc},
uapi::OwnedFd,
};
shared_ids!(ScreencastSessionId);
pub struct RemoteDesktopSession {
_id: ScreencastSessionId,
state: Rc<PortalState>,
pub app: String,
session_obj: DbusObject,
pub phase: CloneCell<RemoteDesktopPhase>,
}
#[derive(Clone)]
pub enum RemoteDesktopPhase {
Init,
@ -52,25 +44,23 @@ pub enum RemoteDesktopPhase {
unsafe impl UnsafeCellCloneSafe for RemoteDesktopPhase {}
pub struct SelectingDisplay {
pub session: Rc<RemoteDesktopSession>,
pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
}
pub struct StartingRemoteDesktop {
pub session: Rc<RemoteDesktopSession>,
pub _request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>,
pub dpy: Rc<PortalDisplay>,
pub ei_session: Rc<UsrJayEiSession>,
}
pub struct StartedRemoteDesktop {
session: Rc<RemoteDesktopSession>,
dpy: Rc<PortalDisplay>,
ei_session: Rc<UsrJayEiSession>,
ei_fd: Cell<Option<Rc<OwnedFd>>>,
pub session: Rc<PortalSession>,
pub dpy: Rc<PortalDisplay>,
pub ei_session: Rc<UsrJayEiSession>,
pub ei_fd: Cell<Option<Rc<OwnedFd>>>,
}
bitflags! {
@ -83,34 +73,6 @@ bitflags! {
impl UsrJayEiSessionOwner for StartingRemoteDesktop {
fn created(&self, fd: &Rc<OwnedFd>) {
{
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 variants = [
DictEntry {
key: "devices".into(),
value: Variant::U32(DeviceTypes::all().0),
},
DictEntry {
key: "clipboard_enabled".into(),
value: Variant::Bool(FALSE),
},
DictEntry {
key: "streams".into(),
value: Variant::Array(kt, vec![]),
},
];
self.reply.ok(&StartReply {
response: PORTAL_SUCCESS,
results: Cow::Borrowed(&variants[..]),
});
}
let started = Rc::new(StartedRemoteDesktop {
session: self.session.clone(),
dpy: self.dpy.clone(),
@ -118,14 +80,23 @@ impl UsrJayEiSessionOwner for StartingRemoteDesktop {
ei_fd: Cell::new(Some(fd.clone())),
});
self.session
.phase
.rd_phase
.set(RemoteDesktopPhase::Started(started.clone()));
started.ei_session.owner.set(Some(started.clone()));
if let ScreencastPhase::SourcesSelected(s) = self.session.sc_phase.get() {
self.session.screencast_restore(
&self.request_obj,
s.restore_data.take(),
Some(self.dpy.clone()),
);
} else {
self.session.send_start_reply(None, None);
}
}
fn failed(&self, reason: &str) {
log::error!("Could not create session: {}", reason);
self.reply.err(reason);
self.session.reply_err(reason);
self.session.kill();
}
}
@ -137,60 +108,28 @@ impl SelectingDisplay {
let ei_session = builder.commit();
let starting = Rc::new(StartingRemoteDesktop {
session: self.session.clone(),
_request_obj: self.request_obj.clone(),
reply: self.reply.clone(),
request_obj: self.request_obj.clone(),
dpy: dpy.clone(),
ei_session,
});
self.session
.phase
.rd_phase
.set(RemoteDesktopPhase::Starting(starting.clone()));
starting.ei_session.owner.set(Some(starting.clone()));
dpy.remote_desktop_sessions.set(
dpy.sessions.set(
self.session.session_obj.path().to_owned(),
self.session.clone(),
);
}
}
impl RemoteDesktopSession {
pub(super) fn kill(&self) {
self.session_obj.emit_signal(&Closed);
self.state
.remote_desktop_sessions
.remove(self.session_obj.path());
match self.phase.set(RemoteDesktopPhase::Terminated) {
RemoteDesktopPhase::Init => {}
RemoteDesktopPhase::DevicesSelected => {}
RemoteDesktopPhase::Terminated => {}
RemoteDesktopPhase::Selecting(s) => {
s.reply.err("Session has been terminated");
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
RemoteDesktopPhase::Starting(s) => {
s.reply.err("Session has been terminated");
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy
.remote_desktop_sessions
.remove(self.session_obj.path());
}
RemoteDesktopPhase::Started(s) => {
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy
.remote_desktop_sessions
.remove(self.session_obj.path());
}
}
}
impl PortalSession {
fn dbus_select_devices(
self: &Rc<Self>,
_req: SelectDevices,
reply: PendingReply<SelectDevicesReply<'static>>,
) {
match self.phase.get() {
match self.rd_phase.get() {
RemoteDesktopPhase::Init => {}
_ => {
self.kill();
@ -198,15 +137,19 @@ impl RemoteDesktopSession {
return;
}
}
self.phase.set(RemoteDesktopPhase::DevicesSelected);
self.rd_phase.set(RemoteDesktopPhase::DevicesSelected);
reply.ok(&SelectDevicesReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
match self.phase.get() {
fn dbus_start_remote_desktop(
self: &Rc<Self>,
req: Start<'_>,
reply: PendingReply<StartReply<'static>>,
) {
match self.rd_phase.get() {
RemoteDesktopPhase::DevicesSelected => {}
_ => {
self.kill();
@ -243,11 +186,12 @@ impl RemoteDesktopSession {
reply.err("There are no running displays");
return;
}
self.phase
self.start_reply
.set(Some(PortalSessionReply::RemoteDesktop(reply)));
self.rd_phase
.set(RemoteDesktopPhase::Selecting(Rc::new(SelectingDisplay {
session: self.clone(),
request_obj: Rc::new(request_obj),
reply: Rc::new(reply),
guis,
})));
}
@ -257,7 +201,7 @@ impl RemoteDesktopSession {
_req: ConnectToEIS,
reply: PendingReply<ConnectToEISReply>,
) {
let RemoteDesktopPhase::Started(started) = self.phase.get() else {
let RemoteDesktopPhase::Started(started) = self.rd_phase.get() else {
self.kill();
reply.err("Sources have already been selected");
return;
@ -305,10 +249,7 @@ fn dbus_create_session(
reply: PendingReply<CreateSessionReply<'static>>,
) {
log::info!("Create remote desktop session {:#?}", req);
if state
.remote_desktop_sessions
.contains(req.session_handle.0.deref())
{
if state.sessions.contains(req.session_handle.0.deref()) {
reply.err("Session already exists");
return;
}
@ -319,12 +260,15 @@ fn dbus_create_session(
return;
}
};
let session = Rc::new(RemoteDesktopSession {
let session = Rc::new(PortalSession {
_id: state.id(),
state: state.clone(),
pw_con: state.pw_con.clone(),
app: req.app_id.to_string(),
session_obj: obj,
phase: CloneCell::new(RemoteDesktopPhase::Init),
sc_phase: CloneCell::new(ScreencastPhase::Init),
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
start_reply: Default::default(),
});
{
use org::freedesktop::impl_::portal::session::*;
@ -336,7 +280,7 @@ fn dbus_create_session(
session.session_obj.set_property::<version>(Variant::U32(2));
}
state
.remote_desktop_sessions
.sessions
.set(req.session_handle.0.to_string(), session);
reply.ok(&CreateSessionReply {
response: PORTAL_SUCCESS,
@ -356,7 +300,7 @@ fn dbus_select_devices(
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_remote_desktop(req, reply);
}
}
@ -374,8 +318,8 @@ fn get_session<T>(
state: &Rc<PortalState>,
reply: &PendingReply<T>,
handle: &str,
) -> Option<Rc<RemoteDesktopSession>> {
let res = state.remote_desktop_sessions.get(handle);
) -> Option<Rc<PortalSession>> {
let res = state.sessions.get(handle);
if res.is_none() {
let msg = format!("Remote desktop session `{}` does not exist", handle);
reply.err(&msg);

View file

@ -3,7 +3,7 @@ use {
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
ptl_remote_desktop::{RemoteDesktopPhase, RemoteDesktopSession},
ptl_remote_desktop::{PortalSession, RemoteDesktopPhase},
ptr_gui::{
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
OverlayWindowOwner,
@ -19,7 +19,7 @@ const H_MARGIN: f32 = 30.0;
const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
remote_desktop_session: Rc<RemoteDesktopSession>,
remote_desktop_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
}
@ -46,7 +46,7 @@ impl SelectionGui {
for surface in self.surfaces.lock().drain_values() {
surface.overlay.data.kill(false);
}
if let RemoteDesktopPhase::Selecting(s) = self.remote_desktop_session.phase.get() {
if let RemoteDesktopPhase::Selecting(s) = self.remote_desktop_session.rd_phase.get() {
s.guis.remove(&self.dpy.id);
if upwards && s.guis.is_empty() {
self.remote_desktop_session.kill();
@ -99,7 +99,7 @@ impl OverlayWindowOwner for SelectionGuiSurface {
}
impl SelectionGui {
pub fn new(ss: &Rc<RemoteDesktopSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
pub fn new(ss: &Rc<PortalSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
let gui = Rc::new(SelectionGui {
remote_desktop_session: ss.clone(),
dpy: dpy.clone(),
@ -130,7 +130,7 @@ impl ButtonOwner for StaticButton {
match self.role {
ButtonRole::Accept => {
log::info!("User has accepted the request");
let selecting = match self.surface.gui.remote_desktop_session.phase.get() {
let selecting = match self.surface.gui.remote_desktop_session.rd_phase.get() {
RemoteDesktopPhase::Selecting(selecting) => selecting,
_ => return,
};

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

View file

@ -4,7 +4,7 @@ use {
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
ptl_screencast::{
ScreencastPhase, ScreencastSession, ScreencastTarget, SelectingWindowScreencast,
PortalSession, ScreencastPhase, ScreencastTarget, SelectingWindowScreencast,
SelectingWorkspaceScreencast,
},
ptr_gui::{
@ -27,7 +27,7 @@ const H_MARGIN: f32 = 30.0;
const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
screencast_session: Rc<ScreencastSession>,
screencast_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
}
@ -57,7 +57,7 @@ impl SelectionGui {
for surface in self.surfaces.lock().drain_values() {
surface.overlay.data.kill(false);
}
if let ScreencastPhase::Selecting(s) = self.screencast_session.phase.get() {
if let ScreencastPhase::Selecting(s) = self.screencast_session.sc_phase.get() {
s.guis.remove(&self.dpy.id);
if upwards && s.guis.is_empty() {
self.screencast_session.kill();
@ -135,7 +135,7 @@ impl OverlayWindowOwner for SelectionGuiSurface {
}
impl SelectionGui {
pub fn new(ss: &Rc<ScreencastSession>, dpy: &Rc<PortalDisplay>, for_restore: bool) -> Rc<Self> {
pub fn new(ss: &Rc<PortalSession>, dpy: &Rc<PortalDisplay>, for_restore: bool) -> Rc<Self> {
let gui = Rc::new(SelectionGui {
screencast_session: ss.clone(),
dpy: dpy.clone(),
@ -169,7 +169,7 @@ impl ButtonOwner for StaticButton {
| ButtonRole::SelectWorkspace
| ButtonRole::SelectWindow => {
log::info!("User has accepted the request");
let selecting = match self.surface.gui.screencast_session.phase.get() {
let selecting = match self.surface.gui.screencast_session.sc_phase.get() {
ScreencastPhase::Selecting(selecting) => selecting,
_ => return,
};
@ -178,9 +178,8 @@ impl ButtonOwner for StaticButton {
}
let dpy = &self.surface.output.dpy;
if self.role == ButtonRole::Restore {
selecting.core.session.restore(
selecting.core.session.screencast_restore(
&selecting.core.request_obj,
&selecting.core.reply,
selecting.restore_data.take().map(Ok),
Some(self.surface.gui.dpy.clone()),
);
@ -199,7 +198,7 @@ impl ButtonOwner for StaticButton {
self.surface
.gui
.screencast_session
.phase
.sc_phase
.set(ScreencastPhase::SelectingWorkspace(selecting));
} else {
let selector = dpy.jc.select_toplevel(&seat.wl);
@ -213,7 +212,7 @@ impl ButtonOwner for StaticButton {
self.surface
.gui
.screencast_session
.phase
.sc_phase
.set(ScreencastPhase::SelectingWindow(selecting));
}
}
@ -230,18 +229,16 @@ impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
let Some(tl) = tl else {
if self.restoring {
log::warn!("Could not restore session because toplevel no longer exists");
self.core.session.start_interactive_selection(
&self.core.request_obj,
&self.core.reply,
None,
);
self.core
.session
.start_interactive_selection(&self.core.request_obj, None);
return;
}
log::info!("User has aborted the selection");
self.core.session.kill();
return;
};
match self.core.session.phase.get() {
match self.core.session.sc_phase.get() {
ScreencastPhase::SelectingWindow(s) => {
self.dpy.con.remove_obj(&*s.selector);
}
@ -263,7 +260,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
self.core.session.kill();
return;
};
match self.core.session.phase.get() {
match self.core.session.sc_phase.get() {
ScreencastPhase::SelectingWorkspace(s) => {
self.dpy.con.remove_obj(&*s.selector);
}

162
src/portal/ptl_session.rs Normal file
View file

@ -0,0 +1,162 @@
use {
crate::{
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply, FALSE},
pipewire::pw_con::PwCon,
portal::{
ptl_remote_desktop::{DeviceTypes, RemoteDesktopPhase},
ptl_screencast::{ScreencastPhase, ScreencastTarget},
PortalState, PORTAL_SUCCESS,
},
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
wire_dbus::org::freedesktop::impl_::portal::{
remote_desktop::StartReply as RdStartReply, screen_cast::StartReply as ScStartReply,
session::Closed,
},
},
std::{borrow::Cow, cell::Cell, ops::Deref, rc::Rc},
};
shared_ids!(SessionId);
pub struct PortalSession {
pub _id: SessionId,
pub state: Rc<PortalState>,
pub pw_con: Option<Rc<PwCon>>,
pub app: String,
pub session_obj: DbusObject,
pub sc_phase: CloneCell<ScreencastPhase>,
pub rd_phase: CloneCell<RemoteDesktopPhase>,
pub start_reply: Cell<Option<PortalSessionReply>>,
}
pub enum PortalSessionReply {
RemoteDesktop(PendingReply<RdStartReply<'static>>),
ScreenCast(PendingReply<ScStartReply<'static>>),
}
impl PortalSession {
pub(super) fn kill(&self) {
self.session_obj.emit_signal(&Closed);
self.state.sessions.remove(self.session_obj.path());
self.reply_err("Session has been terminated");
match self.rd_phase.set(RemoteDesktopPhase::Terminated) {
RemoteDesktopPhase::Init => {}
RemoteDesktopPhase::DevicesSelected => {}
RemoteDesktopPhase::Terminated => {}
RemoteDesktopPhase::Selecting(s) => {
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
RemoteDesktopPhase::Starting(s) => {
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy.sessions.remove(self.session_obj.path());
}
RemoteDesktopPhase::Started(s) => {
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy.sessions.remove(self.session_obj.path());
}
}
match self.sc_phase.set(ScreencastPhase::Terminated) {
ScreencastPhase::Init => {}
ScreencastPhase::SourcesSelected(_) => {}
ScreencastPhase::Terminated => {}
ScreencastPhase::Selecting(s) => {
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
ScreencastPhase::SelectingWindow(s) => {
s.dpy.con.remove_obj(&*s.selector);
}
ScreencastPhase::SelectingWorkspace(s) => {
s.dpy.con.remove_obj(&*s.selector);
}
ScreencastPhase::Starting(s) => {
s.node.con.destroy_obj(s.node.deref());
s.dpy.sessions.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.sessions.remove(self.session_obj.path());
for buffer in s.pending_buffers.borrow_mut().drain(..) {
s.dpy.con.remove_obj(&*buffer);
}
}
}
}
pub(super) fn send_start_reply(
&self,
pw_node_id: Option<u32>,
restore_data: Option<Variant<'static>>,
) {
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 streams = vec![];
if let Some(node_id) = pw_node_id {
streams = vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])];
}
let mut variants = vec![
DictEntry {
key: "devices".into(),
value: Variant::U32(DeviceTypes::all().0),
},
DictEntry {
key: "clipboard_enabled".into(),
value: Variant::Bool(FALSE),
},
DictEntry {
key: "streams".into(),
value: Variant::Array(kt, streams),
},
];
if let Some(rd) = restore_data {
variants.push(DictEntry {
key: "restore_data".into(),
value: rd,
});
}
if let Some(reply) = self.start_reply.take() {
match reply {
PortalSessionReply::RemoteDesktop(reply) => {
reply.ok(&RdStartReply {
response: PORTAL_SUCCESS,
results: Cow::Borrowed(&variants),
});
}
PortalSessionReply::ScreenCast(reply) => {
reply.ok(&ScStartReply {
response: PORTAL_SUCCESS,
results: Cow::Borrowed(&variants),
});
}
}
}
}
pub(super) fn reply_err(&self, err: &str) {
if let Some(reply) = self.start_reply.take() {
match reply {
PortalSessionReply::RemoteDesktop(r) => r.err(err),
PortalSessionReply::ScreenCast(r) => r.err(err),
}
}
}
}