diff --git a/release-notes.md b/release-notes.md index 5c433219..d6bd746e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -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) diff --git a/src/portal.rs b/src/portal.rs index d601475f..76d1a03a 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -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, displays: CopyHashMap>, dbus: Rc, - screencasts: CopyHashMap>, - remote_desktop_sessions: CopyHashMap>, + sessions: CopyHashMap>, next_id: NumCell, render_ctxs: CopyHashMap>, dma_buf_ids: Rc, + pw_con: Option>, } impl PortalState { diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index 7561cc37..5ea445a9 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -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>, pub windows: CopyHashMap>, - pub screencasts: CopyHashMap>, - pub remote_desktop_sessions: CopyHashMap>, + pub sessions: CopyHashMap>, } 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) { fsm, vp, windows: Default::default(), - screencasts: Default::default(), - remote_desktop_sessions: Default::default(), + sessions: Default::default(), workspaces: Default::default(), }); diff --git a/src/portal/ptl_remote_desktop.rs b/src/portal/ptl_remote_desktop.rs index 8f14349d..166b0592 100644 --- a/src/portal/ptl_remote_desktop.rs +++ b/src/portal/ptl_remote_desktop.rs @@ -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, - pub app: String, - session_obj: DbusObject, - pub phase: CloneCell, -} - #[derive(Clone)] pub enum RemoteDesktopPhase { Init, @@ -52,25 +44,23 @@ pub enum RemoteDesktopPhase { unsafe impl UnsafeCellCloneSafe for RemoteDesktopPhase {} pub struct SelectingDisplay { - pub session: Rc, + pub session: Rc, pub request_obj: Rc, - pub reply: Rc>>, pub guis: CopyHashMap>, } pub struct StartingRemoteDesktop { - pub session: Rc, - pub _request_obj: Rc, - pub reply: Rc>>, + pub session: Rc, + pub request_obj: Rc, pub dpy: Rc, pub ei_session: Rc, } pub struct StartedRemoteDesktop { - session: Rc, - dpy: Rc, - ei_session: Rc, - ei_fd: Cell>>, + pub session: Rc, + pub dpy: Rc, + pub ei_session: Rc, + pub ei_fd: Cell>>, } bitflags! { @@ -83,34 +73,6 @@ bitflags! { impl UsrJayEiSessionOwner for StartingRemoteDesktop { fn created(&self, fd: &Rc) { - { - 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, _req: SelectDevices, reply: PendingReply>, ) { - 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, req: Start<'_>, reply: PendingReply>) { - match self.phase.get() { + fn dbus_start_remote_desktop( + self: &Rc, + req: Start<'_>, + reply: PendingReply>, + ) { + 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, ) { - 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>, ) { 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::(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, req: Start, reply: PendingReply>) { 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( state: &Rc, reply: &PendingReply, handle: &str, -) -> Option> { - let res = state.remote_desktop_sessions.get(handle); +) -> Option> { + let res = state.sessions.get(handle); if res.is_none() { let msg = format!("Remote desktop session `{}` does not exist", handle); reply.err(&msg); diff --git a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs index 1963a32f..5e11e392 100644 --- a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs +++ b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs @@ -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, + remote_desktop_session: Rc, dpy: Rc, surfaces: CopyHashMap>, } @@ -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, dpy: &Rc) -> Rc { + pub fn new(ss: &Rc, dpy: &Rc) -> Rc { 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, }; diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index 605531a3..f13b9087 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -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, - pw_con: Rc, - pub app: String, - session_obj: DbusObject, - pub phase: CloneCell, -} - #[derive(Clone)] pub enum ScreencastPhase { Init, @@ -96,9 +87,8 @@ pub struct SourcesSelectedScreencast { #[derive(Clone)] pub struct SelectingScreencastCore { - pub session: Rc, + pub session: Rc, pub request_obj: Rc, - pub reply: Rc>>, } pub struct SelectingScreencast { @@ -121,9 +111,8 @@ pub struct SelectingWorkspaceScreencast { } pub struct StartingScreencast { - pub session: Rc, + pub session: Rc, pub _request_obj: Rc, - pub reply: Rc>>, pub node: Rc, pub dpy: Rc, pub target: ScreencastTarget, @@ -136,21 +125,21 @@ pub enum ScreencastTarget { } pub struct StartedScreencast { - session: Rc, - node: Rc, - port: Rc, - buffer_objects: RefCell>>, - buffers: RefCell>, - pending_buffers: RefCell>>, - buffers_valid: Cell, - dpy: Rc, - jay_screencast: Rc, - port_buffer_valid: Cell, - fixated: Cell, - format: Cell<&'static Format>, - modifier: Cell, - width: Cell, - height: Cell, + pub session: Rc, + pub node: Rc, + pub port: Rc, + pub buffer_objects: RefCell>>, + pub buffers: RefCell>, + pub pending_buffers: RefCell>>, + pub buffers_valid: Cell, + pub dpy: Rc, + pub jay_screencast: Rc, + pub port_buffer_valid: Cell, + pub fixated: Cell, + pub format: Cell<&'static Format>, + pub modifier: Cell, + pub width: Cell, + pub height: Cell, } 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, 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, req: SelectSources, reply: PendingReply>, ) { - 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, req: Start<'_>, reply: PendingReply>) { - let restore_data = match self.phase.get() { + fn dbus_start_screencast( + self: &Rc, + req: Start<'_>, + reply: PendingReply>, + ) { + 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, request_obj: &Rc, - reply: &Rc>>, restore_data: Option, ) { 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, request_obj: &Rc, - reply: &Rc>>, restore_data: Option>, display: Option>, ) { 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, request_obj: &Rc, - reply: &Rc>>, restore_data: Result, display: Option>, ) -> 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>, ) { 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::(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, req: Start, reply: PendingReply>) { 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( state: &Rc, reply: &PendingReply, handle: &str, -) -> Option> { - let res = state.screencasts.get(handle); +) -> Option> { + let res = state.sessions.get(handle); if res.is_none() { let msg = format!("Screencast session `{}` does not exist", handle); reply.err(&msg); diff --git a/src/portal/ptl_screencast/screencast_gui.rs b/src/portal/ptl_screencast/screencast_gui.rs index 290bd6ca..9783ebfb 100644 --- a/src/portal/ptl_screencast/screencast_gui.rs +++ b/src/portal/ptl_screencast/screencast_gui.rs @@ -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, + screencast_session: Rc, dpy: Rc, surfaces: CopyHashMap>, } @@ -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, dpy: &Rc, for_restore: bool) -> Rc { + pub fn new(ss: &Rc, dpy: &Rc, for_restore: bool) -> Rc { 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); } diff --git a/src/portal/ptl_session.rs b/src/portal/ptl_session.rs new file mode 100644 index 00000000..e0d897fb --- /dev/null +++ b/src/portal/ptl_session.rs @@ -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, + pub pw_con: Option>, + pub app: String, + pub session_obj: DbusObject, + pub sc_phase: CloneCell, + pub rd_phase: CloneCell, + pub start_reply: Cell>, +} + +pub enum PortalSessionReply { + RemoteDesktop(PendingReply>), + ScreenCast(PendingReply>), +} + +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, + restore_data: Option>, + ) { + 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), + } + } + } +}