From 43bb787d52e948bde335349fbb026e1387994297 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 16:52:43 +0200 Subject: [PATCH 01/10] all: address clippy lints --- src/macros.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 5fed533d..3a1a764f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -740,20 +740,28 @@ macro_rules! ei_object_base { macro_rules! logical_to_client_wire_scale { ($client:expr, $($field:expr),+ $(,)?) => { - if let Some(scale) = $client.wire_scale.get() { - $( - $field = $field * scale; - )+ + #[expect(clippy::allow_attributes)] + { + #[allow(clippy::assign_op_pattern)] + if let Some(scale) = $client.wire_scale.get() { + $( + $field = $field * scale; + )+ + } } }; } macro_rules! client_wire_scale_to_logical { ($client:expr, $($field:expr),+ $(,)?) => { - if let Some(scale) = $client.wire_scale.get() { - $( - $field = $field / scale; - )+ + #[expect(clippy::allow_attributes)] + { + #[allow(clippy::assign_op_pattern)] + if let Some(scale) = $client.wire_scale.get() { + $( + $field = $field / scale; + )+ + } } }; } From e6c3c9c1ed206894dd42508419ac9e785d300f29 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 12:43:43 +0200 Subject: [PATCH 02/10] tree: map toplevel identifiers to toplevels --- src/compositor.rs | 1 + src/ifs/wl_seat/pointer_owner.rs | 2 +- src/ifs/wl_surface/x_surface/xwindow.rs | 27 +++++++++-------- src/ifs/wl_surface/xdg_surface.rs | 2 +- .../wl_surface/xdg_surface/xdg_toplevel.rs | 5 ++-- src/state.rs | 5 +++- src/tree/container.rs | 4 +-- src/tree/placeholder.rs | 9 +++--- src/tree/toplevel.rs | 29 ++++++++++++++++--- 9 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/compositor.rs b/src/compositor.rs index 60a071bd..55baeb81 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -271,6 +271,7 @@ fn start_compositor2( cpu_worker, ui_drag_enabled: Cell::new(true), ui_drag_threshold_squared: Cell::new(10), + toplevels: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index 5d88ae24..7701c3b4 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -1227,7 +1227,7 @@ impl UiDragUsecase for TileDragUsecase { return; }; let detach = || { - let placeholder = Rc::new(PlaceholderNode::new_empty(&seat.state)); + let placeholder = Rc::new_cyclic(|weak| PlaceholderNode::new_empty(&seat.state, weak)); src_parent .clone() .cnode_replace_child(src.tl_as_node(), placeholder.clone()); diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index cc4b48f1..b5208c5d 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -205,18 +205,21 @@ impl Xwindow { if xsurface.xwindow.is_some() { return Err(XWindowError::AlreadyAttached); } - let tld = ToplevelData::new( - &data.state, - data.info.title.borrow_mut().clone().unwrap_or_default(), - Some(surface.client.clone()), - ); - tld.pos.set(surface.extents.get()); - let slf = Rc::new(Self { - id: data.state.node_ids.next(), - data: data.clone(), - display_link: Default::default(), - toplevel_data: tld, - x: xsurface, + let slf = Rc::new_cyclic(|weak| { + let tld = ToplevelData::new( + &data.state, + data.info.title.borrow_mut().clone().unwrap_or_default(), + Some(surface.client.clone()), + weak, + ); + tld.pos.set(surface.extents.get()); + Self { + id: data.state.node_ids.next(), + data: data.clone(), + display_link: Default::default(), + toplevel_data: tld, + x: xsurface, + } }); slf.x.xwindow.set(Some(slf.clone())); slf.x.surface.set_toplevel(Some(slf.clone())); diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 2821bfe4..f951a44e 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -347,7 +347,7 @@ impl XdgSurfaceRequestHandler for XdgSurface { ); return Err(XdgSurfaceError::AlreadyConstructed); } - let toplevel = Rc::new(XdgToplevel::new(req.id, slf)); + let toplevel = Rc::new_cyclic(|weak| XdgToplevel::new(req.id, slf, weak)); track!(self.surface.client, toplevel); self.surface.client.add_client_obj(&toplevel)?; self.ext.set(Some(toplevel.clone())); diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 96cdcfec..3516d1ba 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -38,7 +38,7 @@ use { cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, - rc::Rc, + rc::{Rc, Weak}, }, thiserror::Error, }; @@ -115,7 +115,7 @@ impl Debug for XdgToplevel { } impl XdgToplevel { - pub fn new(id: XdgToplevelId, surface: &Rc) -> Self { + pub fn new(id: XdgToplevelId, surface: &Rc, slf: &Weak) -> Self { let mut states = AHashSet::new(); states.insert(STATE_TILED_LEFT); states.insert(STATE_TILED_RIGHT); @@ -141,6 +141,7 @@ impl XdgToplevel { state, String::new(), Some(surface.surface.client.clone()), + slf, ), drag: Default::default(), is_mapped: Cell::new(false), diff --git a/src/state.rs b/src/state.rs index 6718ff9c..51c3e17f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -78,6 +78,7 @@ use { clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser, hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, + toplevel_identifier::ToplevelIdentifier, }, video::{ dmabuf::DmaBufIds, @@ -107,7 +108,7 @@ use { mem, num::Wrapping, ops::DerefMut, - rc::Rc, + rc::{Rc, Weak}, sync::Arc, time::Duration, }, @@ -220,6 +221,7 @@ pub struct State { pub cpu_worker: Rc, pub ui_drag_enabled: Cell, pub ui_drag_threshold_squared: Cell, + pub toplevels: CopyHashMap>, } // impl Drop for State { @@ -875,6 +877,7 @@ impl State { self.ei_acceptor_future.take(); self.ei_clients.clear(); self.slow_ei_clients.clear(); + self.toplevels.clear(); } pub fn damage_hardware_cursors(&self, render: bool) { diff --git a/src/tree/container.rs b/src/tree/container.rs index 2a562e99..1bc9e99c 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -213,7 +213,7 @@ impl ContainerNode { let child_node_ref = child_node.clone(); let mut child_nodes = AHashMap::new(); child_nodes.insert(child.node_id(), child_node); - let slf = Rc::new(Self { + let slf = Rc::new_cyclic(|weak| Self { id: state.node_ids.next(), split: Cell::new(split), mono_child: CloneCell::new(None), @@ -238,7 +238,7 @@ impl ContainerNode { state: state.clone(), render_data: Default::default(), scroller: Default::default(), - toplevel_data: ToplevelData::new(state, Default::default(), None), + toplevel_data: ToplevelData::new(state, Default::default(), None, weak), attention_requests: Default::default(), }); child.tl_set_parent(slf.clone()); diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index 88010198..b51bc665 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -22,7 +22,7 @@ use { std::{ cell::{Cell, RefCell}, ops::Deref, - rc::Rc, + rc::{Rc, Weak}, sync::Arc, }, }; @@ -48,13 +48,14 @@ pub async fn placeholder_render_textures(state: Rc) { } impl PlaceholderNode { - pub fn new_for(state: &Rc, node: Rc) -> Self { + pub fn new_for(state: &Rc, node: Rc, slf: &Weak) -> Self { Self { id: state.node_ids.next(), toplevel: ToplevelData::new( state, node.tl_data().title.borrow().clone(), node.node_client(), + slf, ), destroyed: Default::default(), update_textures_scheduled: Cell::new(false), @@ -63,10 +64,10 @@ impl PlaceholderNode { } } - pub fn new_empty(state: &Rc) -> Self { + pub fn new_empty(state: &Rc, slf: &Weak) -> Self { Self { id: state.node_ids.next(), - toplevel: ToplevelData::new(state, String::new(), None), + toplevel: ToplevelData::new(state, String::new(), None, slf), destroyed: Default::default(), update_textures_scheduled: Default::default(), state: state.clone(), diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 2115d92a..05730aaf 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -282,10 +282,18 @@ pub struct ToplevelData { pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc>, pub ext_copy_sessions: CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc>, + pub slf: Weak, } impl ToplevelData { - pub fn new(state: &Rc, title: String, client: Option>) -> Self { + pub fn new( + state: &Rc, + title: String, + client: Option>, + slf: &Weak, + ) -> Self { + let id = toplevel_identifier(); + state.toplevels.set(id, slf.clone()); Self { self_active: Cell::new(false), client, @@ -307,12 +315,13 @@ impl ToplevelData { wants_attention: Cell::new(false), requested_attention: Cell::new(false), app_id: Default::default(), - identifier: Cell::new(toplevel_identifier()), + identifier: Cell::new(id), handles: Default::default(), render_highlight: Default::default(), jay_toplevels: Default::default(), jay_screencasts: Default::default(), ext_copy_sessions: Default::default(), + slf: slf.clone(), } } @@ -359,7 +368,12 @@ impl ToplevelData { for screencast in self.ext_copy_sessions.lock().drain_values() { screencast.stop(); } - self.identifier.set(toplevel_identifier()); + { + let id = toplevel_identifier(); + let prev = self.identifier.replace(id); + self.state.toplevels.remove(&prev); + self.state.toplevels.set(id, self.slf.clone()); + } { let mut handles = self.handles.lock(); for handle in handles.drain_values() { @@ -476,7 +490,8 @@ impl ToplevelData { log::warn!("Cannot fullscreen root container in a workspace"); return; } - let placeholder = Rc::new(PlaceholderNode::new_for(state, node.clone())); + let placeholder = + Rc::new_cyclic(|weak| PlaceholderNode::new_for(state, node.clone(), weak)); parent.cnode_replace_child(node.tl_as_node(), placeholder.clone()); let mut kb_foci = Default::default(); if ws.visible.get() { @@ -599,6 +614,12 @@ impl ToplevelData { } } +impl Drop for ToplevelData { + fn drop(&mut self) { + self.state.toplevels.remove(&self.identifier.get()); + } +} + pub struct TileDragDestination { pub highlight: Rect, pub ty: TddType, From 8d6aaf79a79459b39b3c9654abc53d01f165068c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 17:01:27 +0200 Subject: [PATCH 03/10] util: optimize opaque string formatting --- src/utils/activation_token.rs | 9 ++++++++- src/utils/opaque.rs | 18 ++++++++++++++---- src/utils/toplevel_identifier.rs | 9 ++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/utils/activation_token.rs b/src/utils/activation_token.rs index 117ab480..1e96b4ef 100644 --- a/src/utils/activation_token.rs +++ b/src/utils/activation_token.rs @@ -1,5 +1,6 @@ use { - crate::utils::opaque::{opaque, Opaque, OpaqueError}, + crate::utils::opaque::{opaque, Opaque, OpaqueError, OPAQUE_LEN}, + arrayvec::ArrayString, std::{ fmt::{Display, Formatter}, str::FromStr, @@ -13,6 +14,12 @@ pub fn activation_token() -> ActivationToken { ActivationToken(opaque()) } +impl ActivationToken { + pub fn to_string(self) -> ArrayString { + self.0.to_string() + } +} + impl Display for ActivationToken { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) diff --git a/src/utils/opaque.rs b/src/utils/opaque.rs index 53fb6e6c..82e9744d 100644 --- a/src/utils/opaque.rs +++ b/src/utils/opaque.rs @@ -1,4 +1,5 @@ use { + arrayvec::ArrayString, rand::{thread_rng, Rng}, std::{ fmt::{Debug, Display, Formatter}, @@ -22,6 +23,15 @@ pub fn opaque() -> Opaque { } } +impl Opaque { + pub fn to_string(self) -> ArrayString { + use std::fmt::Write; + let mut s = ArrayString::new(); + write!(s, "{}", self).unwrap(); + s + } +} + impl Display for Opaque { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:016x}", self.hi)?; @@ -40,20 +50,20 @@ impl FromStr for Opaque { type Err = OpaqueError; fn from_str(s: &str) -> Result { - if s.len() != LEN { + if s.len() != OPAQUE_LEN { return Err(OpaqueError::InvalidLength); } - if !s.is_char_boundary(LEN / 2) { + if !s.is_char_boundary(OPAQUE_LEN / 2) { return Err(OpaqueError::NotAscii); } - let (hi, lo) = s.split_at(LEN / 2); + let (hi, lo) = s.split_at(OPAQUE_LEN / 2); let hi = u64::from_str_radix(hi, 16).map_err(OpaqueError::Parse)?; let lo = u64::from_str_radix(lo, 16).map_err(OpaqueError::Parse)?; Ok(Self { lo, hi }) } } -const LEN: usize = 32; +pub const OPAQUE_LEN: usize = 32; #[derive(Debug, Error)] pub enum OpaqueError { diff --git a/src/utils/toplevel_identifier.rs b/src/utils/toplevel_identifier.rs index 6e6a5b43..61f44f2f 100644 --- a/src/utils/toplevel_identifier.rs +++ b/src/utils/toplevel_identifier.rs @@ -1,5 +1,6 @@ use { - crate::utils::opaque::{opaque, Opaque, OpaqueError}, + crate::utils::opaque::{opaque, Opaque, OpaqueError, OPAQUE_LEN}, + arrayvec::ArrayString, std::{ fmt::{Display, Formatter}, str::FromStr, @@ -13,6 +14,12 @@ pub fn toplevel_identifier() -> ToplevelIdentifier { ToplevelIdentifier(opaque()) } +impl ToplevelIdentifier { + pub fn to_string(self) -> ArrayString { + self.0.to_string() + } +} + impl Display for ToplevelIdentifier { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) From d4c0fb29ba0b5685dfa8854516a8c1c1e0e5fda3 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 12:57:27 +0200 Subject: [PATCH 04/10] portal: send toplevel identifier in jay_toplevel --- src/ifs/jay_compositor.rs | 3 +- src/ifs/jay_select_toplevel.rs | 24 ++++++++--- src/ifs/jay_toplevel.rs | 15 +++++++ src/portal/ptl_display.rs | 2 +- src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs | 40 +++++++++++++++---- src/wl_usr/usr_ifs/usr_jay_toplevel.rs | 16 +++++++- wire/jay_toplevel.txt | 7 ++++ 7 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index a80a6814..6afb5e1c 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -71,7 +71,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 11 + 12 } fn required_caps(&self) -> ClientCaps { @@ -369,6 +369,7 @@ impl JayCompositorRequestHandler for JayCompositor { client: self.client.clone(), tracker: Default::default(), destroyed: Cell::new(false), + version: self.version, }); track!(self.client, obj); self.client.add_client_obj(&obj)?; diff --git a/src/ifs/jay_select_toplevel.rs b/src/ifs/jay_select_toplevel.rs index 417805e6..634d8c0c 100644 --- a/src/ifs/jay_select_toplevel.rs +++ b/src/ifs/jay_select_toplevel.rs @@ -1,7 +1,10 @@ use { crate::{ client::{Client, ClientError}, - ifs::{jay_toplevel::JayToplevel, wl_seat::ToplevelSelector}, + ifs::{ + jay_toplevel::{JayToplevel, ID_SINCE}, + wl_seat::ToplevelSelector, + }, leaks::Tracker, object::{Object, Version}, tree::ToplevelNode, @@ -17,6 +20,7 @@ pub struct JaySelectToplevel { pub client: Rc, pub tracker: Tracker, pub destroyed: Cell, + pub version: Version, } pub struct JayToplevelSelector { @@ -35,8 +39,8 @@ impl Drop for JayToplevelSelector { if self.jst.destroyed.get() { return; } - let id = match self.tl.take() { - None => JayToplevelId::NONE, + let jtl = match self.tl.take() { + None => None, Some(toplevel) => { let id = match self.jst.client.new_id() { Ok(id) => id, @@ -51,6 +55,7 @@ impl Drop for JayToplevelSelector { tracker: Default::default(), toplevel, destroyed: Cell::new(false), + version: self.jst.version, }); track!(self.jst.client, jtl); self.jst.client.add_server_obj(&jtl); @@ -58,10 +63,19 @@ impl Drop for JayToplevelSelector { .tl_data() .jay_toplevels .set((jtl.client.id, jtl.id), jtl.clone()); - jtl.id + Some(jtl) } }; - self.jst.send_done(id); + match jtl { + None => self.jst.send_done(JayToplevelId::NONE), + Some(jtl) => { + self.jst.send_done(jtl.id); + if jtl.version >= ID_SINCE { + jtl.send_id(); + jtl.send_done(); + } + } + } let _ = self.jst.client.remove_obj(&*self.jst); } } diff --git a/src/ifs/jay_toplevel.rs b/src/ifs/jay_toplevel.rs index 15ac9729..30c83d3c 100644 --- a/src/ifs/jay_toplevel.rs +++ b/src/ifs/jay_toplevel.rs @@ -10,12 +10,15 @@ use { thiserror::Error, }; +pub const ID_SINCE: Version = Version(12); + pub struct JayToplevel { pub id: JayToplevelId, pub client: Rc, pub tracker: Tracker, pub toplevel: Rc, pub destroyed: Cell, + pub version: Version, } impl JayToplevel { @@ -35,6 +38,18 @@ impl JayToplevel { fn send_destroyed(&self) { self.client.event(Destroyed { self_id: self.id }); } + + pub fn send_id(&self) { + let s = self.toplevel.tl_data().identifier.get().to_string(); + self.client.event(Id { + self_id: self.id, + id: &s, + }) + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }) + } } impl JayToplevelRequestHandler for JayToplevel { diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index 16a42cc5..3e07e0a4 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -323,7 +323,7 @@ fn finish_display_connect(dpy: Rc) { con: dpy.con.clone(), owner: Default::default(), caps: Default::default(), - version: Version(version.min(9)), + version: Version(version.min(12)), }); dpy.con.add_object(jc.clone()); dpy.registry.request_bind(name, jc.version.0, jc.deref()); diff --git a/src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs b/src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs index 381d59fe..2d10ba32 100644 --- a/src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs +++ b/src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs @@ -1,9 +1,14 @@ use { crate::{ + ifs::jay_toplevel::ID_SINCE, object::Version, utils::clonecell::CloneCell, wire::{jay_select_toplevel::*, JaySelectToplevelId}, - wl_usr::{usr_ifs::usr_jay_toplevel::UsrJayToplevel, usr_object::UsrObject, UsrCon}, + wl_usr::{ + usr_ifs::usr_jay_toplevel::{UsrJayToplevel, UsrJayToplevelOwner}, + usr_object::UsrObject, + UsrCon, + }, }, std::{convert::Infallible, rc::Rc}, }; @@ -15,6 +20,19 @@ pub struct UsrJaySelectToplevel { pub version: Version, } +impl UsrJaySelectToplevel { + fn send(&self, tl: Option>) { + if let Some(owner) = self.owner.get() { + owner.done(tl); + } else { + if let Some(tl) = tl { + self.con.remove_obj(&*tl); + } + } + self.con.remove_obj(self); + } +} + pub trait UsrJaySelectToplevelOwner { fn done(&self, toplevel: Option>); } @@ -22,7 +40,7 @@ pub trait UsrJaySelectToplevelOwner { impl JaySelectToplevelEventHandler for UsrJaySelectToplevel { type Error = Infallible; - fn done(&self, ev: Done, _slf: &Rc) -> Result<(), Self::Error> { + fn done(&self, ev: Done, slf: &Rc) -> Result<(), Self::Error> { let tl = if ev.id.is_none() { None } else { @@ -31,23 +49,31 @@ impl JaySelectToplevelEventHandler for UsrJaySelectToplevel { con: self.con.clone(), owner: Default::default(), version: self.version, + toplevel_id: Default::default(), }); self.con.add_object(tl.clone()); Some(tl) }; - match self.owner.get() { - Some(owner) => owner.done(tl), - _ => { + 'send: { + if self.version >= ID_SINCE { if let Some(tl) = tl { - self.con.remove_obj(&*tl); + tl.owner.set(Some(slf.clone())); + break 'send; } } + self.send(tl); } - self.con.remove_obj(self); Ok(()) } } +impl UsrJayToplevelOwner for UsrJaySelectToplevel { + fn done(&self, tl: &Rc) { + tl.owner.take(); + self.send(Some(tl.clone())); + } +} + usr_object_base! { self = UsrJaySelectToplevel = JaySelectToplevel; version = self.version; diff --git a/src/wl_usr/usr_ifs/usr_jay_toplevel.rs b/src/wl_usr/usr_ifs/usr_jay_toplevel.rs index e1a31604..11b9900c 100644 --- a/src/wl_usr/usr_ifs/usr_jay_toplevel.rs +++ b/src/wl_usr/usr_ifs/usr_jay_toplevel.rs @@ -5,7 +5,7 @@ use { wire::{jay_toplevel::*, JayToplevelId}, wl_usr::{usr_object::UsrObject, UsrCon}, }, - std::{convert::Infallible, rc::Rc}, + std::{cell::RefCell, convert::Infallible, rc::Rc}, }; pub struct UsrJayToplevel { @@ -13,10 +13,12 @@ pub struct UsrJayToplevel { pub con: Rc, pub owner: CloneCell>>, pub version: Version, + pub toplevel_id: RefCell>, } pub trait UsrJayToplevelOwner { fn destroyed(&self) {} + fn done(&self, tl: &Rc); } impl JayToplevelEventHandler for UsrJayToplevel { @@ -28,6 +30,18 @@ impl JayToplevelEventHandler for UsrJayToplevel { } Ok(()) } + + fn id_(&self, ev: Id<'_>, _slf: &Rc) -> Result<(), Self::Error> { + *self.toplevel_id.borrow_mut() = Some(ev.id.to_string()); + Ok(()) + } + + fn done(&self, _ev: Done, slf: &Rc) -> Result<(), Self::Error> { + if let Some(owner) = self.owner.get() { + owner.done(slf); + } + Ok(()) + } } usr_object_base! { diff --git a/wire/jay_toplevel.txt b/wire/jay_toplevel.txt index 5d5e4505..2cb50c06 100644 --- a/wire/jay_toplevel.txt +++ b/wire/jay_toplevel.txt @@ -3,3 +3,10 @@ request destroy { event destroyed { } + +event id (since = 12) { + id: str, +} + +event done (since = 12) { +} From 4f431eec5c490e7f1ead794d8346c684715d0d3b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 13:51:01 +0200 Subject: [PATCH 05/10] jay_compositior: add get_toplevel request --- src/ifs/jay_compositor.rs | 32 ++++++++++++++++++++-------- src/ifs/jay_select_toplevel.rs | 38 +++++++++++++++++++++++----------- wire/jay_compositor.txt | 5 +++++ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 6afb5e1c..df3e1f7b 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -23,12 +23,12 @@ use { leaks::Tracker, object::{Object, Version}, screenshoter::take_screenshot, - utils::errorfmt::ErrorFmt, + utils::{errorfmt::ErrorFmt, toplevel_identifier::ToplevelIdentifier}, wire::{jay_compositor::*, JayCompositorId, JayScreenshotId}, }, bstr::ByteSlice, log::Level, - std::{cell::Cell, ops::Deref, rc::Rc}, + std::{cell::Cell, ops::Deref, rc::Rc, str::FromStr}, thiserror::Error, }; @@ -364,13 +364,7 @@ impl JayCompositorRequestHandler for JayCompositor { fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc) -> Result<(), Self::Error> { let seat = self.client.lookup(req.seat)?; - let obj = Rc::new(JaySelectToplevel { - id: req.id, - client: self.client.clone(), - tracker: Default::default(), - destroyed: Cell::new(false), - version: self.version, - }); + let obj = JaySelectToplevel::new(&self.client, req.id, self.version); track!(self.client, obj); self.client.add_client_obj(&obj)?; let selector = JayToplevelSelector { @@ -423,6 +417,26 @@ impl JayCompositorRequestHandler for JayCompositor { self.client.add_client_obj(&obj)?; Ok(()) } + + fn get_toplevel(&self, req: GetToplevel<'_>, _slf: &Rc) -> Result<(), Self::Error> { + let obj = JaySelectToplevel::new(&self.client, req.id, self.version); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + let tl = match ToplevelIdentifier::from_str(req.toplevel_id) { + Ok(id) => self + .client + .state + .toplevels + .get(&id) + .and_then(|w| w.upgrade()), + Err(e) => { + log::error!("Could not parse toplevel id: {}", ErrorFmt(e)); + None + } + }; + obj.done(tl); + Ok(()) + } } object_base! { diff --git a/src/ifs/jay_select_toplevel.rs b/src/ifs/jay_select_toplevel.rs index 634d8c0c..6eb83067 100644 --- a/src/ifs/jay_select_toplevel.rs +++ b/src/ifs/jay_select_toplevel.rs @@ -39,26 +39,32 @@ impl Drop for JayToplevelSelector { if self.jst.destroyed.get() { return; } - let jtl = match self.tl.take() { + self.jst.done(self.tl.take()); + } +} + +impl JaySelectToplevel { + pub fn done(&self, tl: Option>) { + let jtl = match tl { None => None, Some(toplevel) => { - let id = match self.jst.client.new_id() { + let id = match self.client.new_id() { Ok(id) => id, Err(e) => { - self.jst.client.error(e); + self.client.error(e); return; } }; let jtl = Rc::new(JayToplevel { id, - client: self.jst.client.clone(), + client: self.client.clone(), tracker: Default::default(), toplevel, destroyed: Cell::new(false), - version: self.jst.version, + version: self.version, }); - track!(self.jst.client, jtl); - self.jst.client.add_server_obj(&jtl); + track!(self.client, jtl); + self.client.add_server_obj(&jtl); jtl.toplevel .tl_data() .jay_toplevels @@ -67,20 +73,28 @@ impl Drop for JayToplevelSelector { } }; match jtl { - None => self.jst.send_done(JayToplevelId::NONE), + None => self.send_done(JayToplevelId::NONE), Some(jtl) => { - self.jst.send_done(jtl.id); + self.send_done(jtl.id); if jtl.version >= ID_SINCE { jtl.send_id(); jtl.send_done(); } } } - let _ = self.jst.client.remove_obj(&*self.jst); + let _ = self.client.remove_obj(self); + } + + pub fn new(client: &Rc, id: JaySelectToplevelId, version: Version) -> Rc { + Rc::new(JaySelectToplevel { + id, + client: client.clone(), + tracker: Default::default(), + destroyed: Cell::new(false), + version, + }) } -} -impl JaySelectToplevel { fn send_done(&self, id: JayToplevelId) { self.client.event(Done { self_id: self.id, diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index 5116178f..84f70406 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -96,6 +96,11 @@ request get_xwayland (since = 11) { id: id(jay_xwayland), } +request get_toplevel (since = 12) { + id: id(jay_select_toplevel), + toplevel_id: str, +} + # events event client_id { From 3e3532574b7772f4f0c39d2a33d66b2670a999cc Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 16:48:46 +0200 Subject: [PATCH 06/10] portal: implement session restoration --- Cargo.lock | 6 +- Cargo.toml | 1 + release-notes.md | 1 + src/ifs/jay_compositor.rs | 1 + src/portal/ptl_display.rs | 34 +- src/portal/ptl_screencast.rs | 293 ++++++++++++++++-- src/portal/ptl_screencast/screencast_gui.rs | 45 ++- src/utils/opaque.rs | 21 ++ src/wl_usr/usr_ifs/usr_jay_compositor.rs | 17 +- .../usr_ifs/usr_jay_select_workspace.rs | 24 +- src/wl_usr/usr_ifs/usr_jay_workspace.rs | 38 +-- .../usr_ifs/usr_jay_workspace_watcher.rs | 3 + src/wl_usr/usr_ifs/usr_wl_output.rs | 4 +- 13 files changed, 433 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bb184ba..a44b308d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,6 +586,7 @@ dependencies = [ "repc", "rustc-demangle", "serde", + "serde_json", "shaderc", "smallvec", "thiserror", @@ -1067,12 +1068,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap", "itoa", + "memchr", "ryu", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index cb0d0ef1..faccd080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ ash = "0.38.0" gpu-alloc = "0.6.0" gpu-alloc-ash = "0.7.0" serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.128" enum-map = "2.7.3" png = "0.17.13" rustc-demangle = { version = "0.1.24", optional = true } diff --git a/release-notes.md b/release-notes.md index fd89deb5..5c433219 100644 --- a/release-notes.md +++ b/release-notes.md @@ -7,6 +7,7 @@ - Allow X windows to scale themselves. - Implement ext-image-capture-source-v1. - Implement ext-image-copy-capture-v1. +- Implement screencast session restoration. # 1.6.0 (2024-09-25) diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index df3e1f7b..28eab913 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -34,6 +34,7 @@ use { pub const CREATE_EI_SESSION_SINCE: Version = Version(5); pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6); +pub const GET_TOPLEVEL_SINCE: Version = Version(12); pub struct JayCompositorGlobal { name: GlobalName, diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index 3e07e0a4..7561cc37 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -12,8 +12,13 @@ use { PortalState, }, utils::{ - bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, hash_map_ext::HashMapExt, oserror::OsError, + bitflags::BitflagsExt, + clonecell::CloneCell, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + hash_map_ext::HashMapExt, + opaque::{opaque, Opaque}, + oserror::OsError, }, video::drm::Drm, wire::{ @@ -26,6 +31,8 @@ use { usr_jay_output::{UsrJayOutput, UsrJayOutputOwner}, usr_jay_pointer::UsrJayPointer, usr_jay_render_ctx::UsrJayRenderCtxOwner, + usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner}, + usr_jay_workspace_watcher::{UsrJayWorkspaceWatcher, UsrJayWorkspaceWatcherOwner}, usr_linux_dmabuf::UsrLinuxDmabuf, usr_wl_compositor::UsrWlCompositor, usr_wl_output::{UsrWlOutput, UsrWlOutputOwner}, @@ -61,9 +68,11 @@ struct PortalDisplayPrelude { shared_ids!(PortalDisplayId); pub struct PortalDisplay { pub id: PortalDisplayId, + pub unique_id: Opaque, pub con: Rc, pub(super) state: Rc, registry: Rc, + _workspace_watcher: Rc, pub dmabuf: CloneCell>>, pub jc: Rc, @@ -75,6 +84,7 @@ pub struct PortalDisplay { pub outputs: CopyHashMap>, pub seats: CopyHashMap>, + pub workspaces: CopyHashMap>, pub windows: CopyHashMap>, pub screencasts: CopyHashMap>, @@ -243,6 +253,20 @@ impl UsrWlRegistryOwner for PortalDisplay { } } +impl UsrJayWorkspaceWatcherOwner for PortalDisplay { + fn new(self: Rc, ev: Rc, linear_id: u32) { + ev.owner.set(Some(self.clone())); + self.workspaces.set(linear_id, ev); + } +} + +impl UsrJayWorkspaceOwner for PortalDisplay { + fn destroyed(&self, ws: &UsrJayWorkspace) { + self.workspaces.remove(&ws.linear_id.get()); + self.con.remove_obj(ws); + } +} + impl UsrJayOutputOwner for PortalOutput { fn destroyed(&self) { log::info!( @@ -398,12 +422,15 @@ fn finish_display_connect(dpy: Rc) { let comp = get!(comp_opt, WlCompositor); let fsm = get!(fsm_opt, WpFractionalScaleManagerV1); let vp = get!(vp_opt, WpViewporter); + let ww = jc.watch_workspaces(); let dpy = Rc::new(PortalDisplay { id: dpy.state.id(), + unique_id: opaque(), con: dpy.con.clone(), state: dpy.state.clone(), registry: dpy.registry.clone(), + _workspace_watcher: ww.clone(), dmabuf: CloneCell::new(dmabuf_opt), jc, outputs: Default::default(), @@ -416,11 +443,13 @@ fn finish_display_connect(dpy: Rc) { windows: Default::default(), screencasts: Default::default(), remote_desktop_sessions: Default::default(), + workspaces: Default::default(), }); dpy.state.displays.set(dpy.id, dpy.clone()); dpy.con.owner.set(Some(dpy.clone())); dpy.registry.owner.set(Some(dpy.clone())); + ww.owner.set(Some(dpy.clone())); let jrc = dpy.jc.get_render_context(); jrc.owner.set(Some(dpy.clone())); @@ -464,6 +493,7 @@ fn add_output(dpy: &Rc, name: u32, version: u32) { con: dpy.con.clone(), owner: Default::default(), version: Version(version.min(4)), + name: Default::default(), }); dpy.con.add_object(wl.clone()); dpy.registry.request_bind(name, wl.version.0, wl.deref()); diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index 886e7812..605531a3 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -5,7 +5,7 @@ use { allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING}, dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply}, format::{Format, XRGB8888}, - ifs::jay_screencast::CLIENT_BUFFERS_SINCE, + ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE}, pipewire::{ pw_con::PwCon, pw_ifs::pw_client_node::{ @@ -29,6 +29,7 @@ use { copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, + opaque::Opaque, }, video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER}, wire::jay_screencast::Ready, @@ -54,6 +55,7 @@ use { usr_wl_buffer::UsrWlBuffer, }, }, + serde::{Deserialize, Serialize}, std::{ borrow::Cow, cell::{Cell, RefCell}, @@ -77,7 +79,7 @@ pub struct ScreencastSession { #[derive(Clone)] pub enum ScreencastPhase { Init, - SourcesSelected, + SourcesSelected(Rc), Selecting(Rc), SelectingWindow(Rc), SelectingWorkspace(Rc), @@ -88,6 +90,10 @@ pub enum ScreencastPhase { unsafe impl UnsafeCellCloneSafe for ScreencastPhase {} +pub struct SourcesSelectedScreencast { + pub restore_data: Cell>>, +} + #[derive(Clone)] pub struct SelectingScreencastCore { pub session: Rc, @@ -98,12 +104,14 @@ pub struct SelectingScreencastCore { pub struct SelectingScreencast { pub core: SelectingScreencastCore, pub guis: CopyHashMap>, + pub restore_data: Cell>, } pub struct SelectingWindowScreencast { pub core: SelectingScreencastCore, pub dpy: Rc, pub selector: Rc, + pub restoring: bool, } pub struct SelectingWorkspaceScreencast { @@ -123,7 +131,7 @@ pub struct StartingScreencast { pub enum ScreencastTarget { Output(Rc), - Workspace(Rc, Rc), + Workspace(Rc, Rc, bool), Toplevel(Rc), } @@ -171,16 +179,22 @@ impl PwClientNodeOwner for StartingScreencast { DynamicType::U32, DynamicType::Array(Box::new(inner_type.clone())), ]); - let variants = &[DictEntry { + 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::Borrowed(variants), + results: Cow::Owned(variants), }); } let mut supported_formats = PwClientNodePortSupportedFormats { @@ -221,7 +235,7 @@ impl PwClientNodeOwner for StartingScreencast { jsc.set_output(&o.jay); jsc.set_allow_all_workspaces(true); } - ScreencastTarget::Workspace(o, ws) => { + ScreencastTarget::Workspace(o, ws, _) => { jsc.set_output(&o.jay); jsc.allow_workspace(ws); } @@ -231,9 +245,10 @@ impl PwClientNodeOwner for StartingScreencast { jsc.configure(); match &self.target { ScreencastTarget::Output(_) => {} - ScreencastTarget::Workspace(_, w) => { + ScreencastTarget::Workspace(_, w, true) => { self.dpy.con.remove_obj(&**w); } + ScreencastTarget::Workspace(_, _, false) => {} ScreencastTarget::Toplevel(t) => { self.dpy.con.remove_obj(&**t); } @@ -439,7 +454,7 @@ impl ScreencastSession { self.state.screencasts.remove(self.session_obj.path()); match self.phase.set(ScreencastPhase::Terminated) { ScreencastPhase::Init => {} - ScreencastPhase::SourcesSelected => {} + ScreencastPhase::SourcesSelected(_) => {} ScreencastPhase::Terminated => {} ScreencastPhase::Selecting(s) => { s.core.reply.err("Session has been terminated"); @@ -461,9 +476,10 @@ impl ScreencastSession { s.dpy.screencasts.remove(self.session_obj.path()); match &s.target { ScreencastTarget::Output(_) => {} - ScreencastTarget::Workspace(_, w) => { + ScreencastTarget::Workspace(_, w, true) => { s.dpy.con.remove_obj(&**w); } + ScreencastTarget::Workspace(_, _, false) => {} ScreencastTarget::Toplevel(t) => { s.dpy.con.remove_obj(&**t); } @@ -482,7 +498,7 @@ impl ScreencastSession { fn dbus_select_sources( self: &Rc, - _req: SelectSources, + req: SelectSources, reply: PendingReply>, ) { match self.phase.get() { @@ -493,7 +509,11 @@ impl ScreencastSession { return; } } - self.phase.set(ScreencastPhase::SourcesSelected); + self.phase.set(ScreencastPhase::SourcesSelected(Rc::new( + SourcesSelectedScreencast { + restore_data: Cell::new(get_restore_data(&req)), + }, + ))); reply.ok(&SelectSourcesReply { response: PORTAL_SUCCESS, results: Default::default(), @@ -501,16 +521,16 @@ impl ScreencastSession { } fn dbus_start(self: &Rc, req: Start<'_>, reply: PendingReply>) { - match self.phase.get() { - ScreencastPhase::SourcesSelected => {} + let restore_data = match self.phase.get() { + ScreencastPhase::SourcesSelected(s) => s.restore_data.take(), _ => { self.kill(); reply.err("Session is not in the correct phase for starting"); return; } - } + }; let request_obj = match self.state.dbus.add_object(req.handle.to_string()) { - Ok(r) => r, + Ok(r) => Rc::new(r), Err(_) => { self.kill(); reply.err("Request handle is not unique"); @@ -527,10 +547,20 @@ impl ScreencastSession { } }); } + let reply = Rc::new(reply); + self.restore(&request_obj, &reply, restore_data, None); + } + + fn start_interactive_selection( + self: &Rc, + request_obj: &Rc, + reply: &Rc>>, + restore_data: Option, + ) { let guis = CopyHashMap::new(); for dpy in self.state.displays.lock().values() { if dpy.outputs.len() > 0 { - guis.set(dpy.id, SelectionGui::new(self, dpy)); + guis.set(dpy.id, SelectionGui::new(self, dpy, restore_data.is_some())); } } if guis.is_empty() { @@ -542,12 +572,120 @@ impl ScreencastSession { .set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast { core: SelectingScreencastCore { session: self.clone(), - request_obj: Rc::new(request_obj), - reply: Rc::new(reply), + request_obj: request_obj.clone(), + reply: reply.clone(), }, guis, + restore_data: Cell::new(restore_data), }))); } + + fn 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) { + log::error!("Could not restore session: {}", ErrorFmt(e)); + } else { + return; + } + } + self.start_interactive_selection(&request_obj, &reply, None); + } + + fn try_restore( + self: &Rc, + request_obj: &Rc, + reply: &Rc>>, + restore_data: Result, + display: Option>, + ) -> Result<(), RestoreError> { + let rd = restore_data?; + let dpy = if let Some(dpy) = display { + dpy + } else { + let dpy = self + .state + .displays + .lock() + .values() + .find(|d| d.unique_id == rd.display) + .cloned(); + match dpy { + Some(dpy) => dpy, + _ => { + if self.state.displays.len() == 0 { + return Err(RestoreError::UnknownDisplay); + } 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)); + return Ok(()); + } + } + } + }; + let start = |target: ScreencastTarget| { + SelectingScreencastCore { + session: self.clone(), + request_obj: request_obj.clone(), + reply: reply.clone(), + } + .starting(&dpy, target); + }; + match &rd.ty { + RestoreDataType::Output(d) => { + let output = dpy + .outputs + .lock() + .values() + .find(|o| o.wl.name.borrow().as_ref() == Some(&d.name)) + .cloned(); + let Some(output) = output else { + return Err(RestoreError::UnknownOutput); + }; + start(ScreencastTarget::Output(output)); + } + RestoreDataType::Workspace(ws) => { + let ws = dpy + .workspaces + .lock() + .values() + .find(|w| w.name.borrow().as_ref() == Some(&ws.name)) + .cloned(); + let Some(ws) = ws else { + return Err(RestoreError::UnknownWorkspace); + }; + let Some(output) = dpy.outputs.get(&ws.output.get()) else { + return Err(RestoreError::UnknownOutput); + }; + start(ScreencastTarget::Workspace(output, ws, false)); + } + RestoreDataType::Toplevel(d) => { + if dpy.jc.version < GET_TOPLEVEL_SINCE { + return Err(RestoreError::GetToplevel); + } + let selector = dpy.jc.get_toplevel(&d.id); + let selecting = Rc::new(SelectingWindowScreencast { + 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)); + } + } + Ok(()) + } } impl UsrJayScreencastOwner for StartedScreencast { @@ -760,3 +898,122 @@ fn get_session( } res } + +fn create_restore_data(dpy: &PortalDisplay, rd: &ScreencastTarget) -> Option> { + let rd = RestoreData { + display: dpy.unique_id, + ty: match rd { + ScreencastTarget::Output(o) => RestoreDataType::Output(RestoreDataOutput { + name: o.wl.name.borrow().clone()?, + }), + ScreencastTarget::Workspace(_, w, _) => { + RestoreDataType::Workspace(RestoreDataWorkspace { + name: w.name.borrow().clone()?, + }) + } + ScreencastTarget::Toplevel(tl) => RestoreDataType::Toplevel(RestoreDataToplevel { + id: tl.toplevel_id.borrow().clone()?, + }), + }, + }; + Some(Variant::Struct(vec![ + Variant::String("Jay".into()), + Variant::U32(1), + Variant::Variant(Box::new(Variant::String( + serde_json::to_string(&rd).unwrap().into(), + ))), + ])) +} + +#[derive(Debug, Error)] +pub enum RestoreError { + #[error("DBus restore data is not a struct")] + NotAStruct, + #[error("DBus restore data is not a struct with 3 fields")] + NotLen3, + #[error("DBus restore data first field is not a string")] + FirstNotString, + #[error("DBus restore data second field is not a u32")] + SecondNotU32, + #[error("DBus restore data third field is not a variant")] + ThirdNotVariant, + #[error("DBus restore data third field is not a string")] + ThirdNotString, + #[error("DBus restore data is not for Jay")] + NotJay, + #[error("DBus restore data is not version 1")] + NotVersion1, + #[error("DBus restore data could not be deserialized")] + Parse(#[source] serde_json::Error), + #[error("The display no longer exists")] + UnknownDisplay, + #[error("The output no longer exists")] + UnknownOutput, + #[error("The workspace no longer exists")] + UnknownWorkspace, + #[error("The display does not support toplevel restoration")] + GetToplevel, +} + +fn get_restore_data(req: &SelectSources) -> Option> { + let restore_data = req.options.iter().find(|n| n.key == "restore_data")?; + Some(get_restore_data_(restore_data)) +} + +fn get_restore_data_( + restore_data: &DictEntry, Variant>, +) -> Result { + let Variant::Struct(s) = &restore_data.value else { + return Err(RestoreError::NotAStruct); + }; + if s.len() != 3 { + return Err(RestoreError::NotLen3); + } + let Variant::String(compositor) = &s[0] else { + return Err(RestoreError::FirstNotString); + }; + let Variant::U32(version) = &s[1] else { + return Err(RestoreError::SecondNotU32); + }; + let Variant::Variant(restore_data) = &s[2] else { + return Err(RestoreError::ThirdNotVariant); + }; + let Variant::String(restore_data) = &**restore_data else { + return Err(RestoreError::ThirdNotString); + }; + if compositor != "Jay" { + return Err(RestoreError::NotJay); + } + if *version != 1 { + return Err(RestoreError::NotVersion1); + } + serde_json::from_str(restore_data).map_err(RestoreError::Parse) +} + +#[derive(Serialize, Deserialize)] +pub struct RestoreData { + display: Opaque, + ty: RestoreDataType, +} + +#[derive(Serialize, Deserialize)] +enum RestoreDataType { + Output(RestoreDataOutput), + Workspace(RestoreDataWorkspace), + Toplevel(RestoreDataToplevel), +} + +#[derive(Serialize, Deserialize)] +struct RestoreDataOutput { + name: String, +} + +#[derive(Serialize, Deserialize)] +struct RestoreDataWorkspace { + name: String, +} + +#[derive(Serialize, Deserialize)] +struct RestoreDataToplevel { + id: String, +} diff --git a/src/portal/ptl_screencast/screencast_gui.rs b/src/portal/ptl_screencast/screencast_gui.rs index 9dbf2dbb..290bd6ca 100644 --- a/src/portal/ptl_screencast/screencast_gui.rs +++ b/src/portal/ptl_screencast/screencast_gui.rs @@ -45,6 +45,7 @@ struct StaticButton { #[derive(Copy, Clone, Eq, PartialEq)] enum ButtonRole { + Restore, Accept, SelectWorkspace, SelectWindow, @@ -65,7 +66,7 @@ impl SelectionGui { } } -fn create_accept_gui(surface: &Rc) -> Rc { +fn create_accept_gui(surface: &Rc, for_restore: bool) -> Rc { let app = &surface.gui.screencast_session.app; let text = if app.is_empty() { format!("An application wants to capture the screen") @@ -74,11 +75,13 @@ fn create_accept_gui(surface: &Rc) -> Rc { }; let label = Rc::new(Label::default()); *label.text.borrow_mut() = text; + let restore_button = static_button(surface, ButtonRole::Restore, "Restore Session"); let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output"); let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace"); let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window"); let reject_button = static_button(surface, ButtonRole::Reject, "Reject"); for button in [ + &restore_button, &accept_button, &workspace_button, &window_button, @@ -88,6 +91,10 @@ fn create_accept_gui(surface: &Rc) -> Rc { button.border.set(2.0); button.padding.set(5.0); } + restore_button.bg_color.set(Color::from_rgb(170, 170, 200)); + restore_button + .bg_hover_color + .set(Color::from_rgb(170, 170, 255)); for button in [&accept_button, &workspace_button, &window_button] { button.bg_color.set(Color::from_rgb(170, 200, 170)); button.bg_hover_color.set(Color::from_rgb(170, 255, 170)); @@ -101,7 +108,11 @@ fn create_accept_gui(surface: &Rc) -> Rc { flow.cross_align.set(Align::Center); flow.in_margin.set(V_MARGIN); flow.cross_margin.set(H_MARGIN); - let mut elements: Vec> = vec![label, accept_button]; + let mut elements: Vec> = vec![label]; + if for_restore { + elements.push(restore_button); + } + elements.push(accept_button); if surface.gui.dpy.jc.caps.select_workspace.get() { elements.push(workspace_button); } @@ -124,7 +135,7 @@ impl OverlayWindowOwner for SelectionGuiSurface { } impl SelectionGui { - pub fn new(ss: &Rc, dpy: &Rc) -> Rc { + pub fn new(ss: &Rc, dpy: &Rc, for_restore: bool) -> Rc { let gui = Rc::new(SelectionGui { screencast_session: ss.clone(), dpy: dpy.clone(), @@ -136,7 +147,7 @@ impl SelectionGui { output: output.clone(), overlay: OverlayWindow::new(output), }); - let element = create_accept_gui(&sgs); + let element = create_accept_gui(&sgs, for_restore); sgs.overlay.data.content.set(Some(element)); gui.dpy .windows @@ -153,7 +164,10 @@ impl ButtonOwner for StaticButton { return; } match self.role { - ButtonRole::Accept | ButtonRole::SelectWorkspace | ButtonRole::SelectWindow => { + ButtonRole::Restore + | ButtonRole::Accept + | ButtonRole::SelectWorkspace + | ButtonRole::SelectWindow => { log::info!("User has accepted the request"); let selecting = match self.surface.gui.screencast_session.phase.get() { ScreencastPhase::Selecting(selecting) => selecting, @@ -163,7 +177,14 @@ impl ButtonOwner for StaticButton { gui.kill(false); } let dpy = &self.surface.output.dpy; - if self.role == ButtonRole::Accept { + if self.role == ButtonRole::Restore { + selecting.core.session.restore( + &selecting.core.request_obj, + &selecting.core.reply, + selecting.restore_data.take().map(Ok), + Some(self.surface.gui.dpy.clone()), + ); + } else if self.role == ButtonRole::Accept { selecting .core .starting(dpy, ScreencastTarget::Output(self.surface.output.clone())); @@ -186,6 +207,7 @@ impl ButtonOwner for StaticButton { core: selecting.core.clone(), dpy: dpy.clone(), selector: selector.clone(), + restoring: false, }); selector.owner.set(Some(selecting.clone())); self.surface @@ -206,6 +228,15 @@ impl ButtonOwner for StaticButton { impl UsrJaySelectToplevelOwner for SelectingWindowScreencast { fn done(&self, tl: Option>) { 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, + ); + return; + } log::info!("User has aborted the selection"); self.core.session.kill(); return; @@ -252,7 +283,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast { } }; self.core - .starting(&self.dpy, ScreencastTarget::Workspace(output, ws)); + .starting(&self.dpy, ScreencastTarget::Workspace(output, ws, true)); } } diff --git a/src/utils/opaque.rs b/src/utils/opaque.rs index 82e9744d..6468a8c5 100644 --- a/src/utils/opaque.rs +++ b/src/utils/opaque.rs @@ -1,6 +1,7 @@ use { arrayvec::ArrayString, rand::{thread_rng, Rng}, + serde::{de, Deserialize, Deserializer, Serialize, Serializer}, std::{ fmt::{Debug, Display, Formatter}, num::ParseIntError, @@ -46,6 +47,26 @@ impl Debug for Opaque { } } +impl Serialize for Opaque { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = self.to_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for Opaque { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = <&str>::deserialize(deserializer)?; + Opaque::from_str(s).map_err(de::Error::custom) + } +} + impl FromStr for Opaque { type Err = OpaqueError; diff --git a/src/wl_usr/usr_ifs/usr_jay_compositor.rs b/src/wl_usr/usr_ifs/usr_jay_compositor.rs index 6aaab0b3..8642e533 100644 --- a/src/wl_usr/usr_ifs/usr_jay_compositor.rs +++ b/src/wl_usr/usr_ifs/usr_jay_compositor.rs @@ -96,7 +96,6 @@ impl UsrJayCompositor { jo } - #[expect(dead_code)] pub fn watch_workspaces(&self) -> Rc { let ww = Rc::new(UsrJayWorkspaceWatcher { id: self.con.id(), @@ -143,6 +142,22 @@ impl UsrJayCompositor { sc } + pub fn get_toplevel(&self, id: &str) -> Rc { + let sc = Rc::new(UsrJaySelectToplevel { + id: self.con.id(), + con: self.con.clone(), + owner: Default::default(), + version: self.version, + }); + self.con.request(GetToplevel { + self_id: self.id, + id: sc.id, + toplevel_id: id, + }); + self.con.add_object(sc.clone()); + sc + } + pub fn select_workspace(&self, seat: &UsrWlSeat) -> Rc { let sc = Rc::new(UsrJaySelectWorkspace { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs b/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs index 07153faa..5d59ecd6 100644 --- a/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs +++ b/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs @@ -3,7 +3,11 @@ use { object::Version, utils::clonecell::CloneCell, wire::{jay_select_workspace::*, JaySelectWorkspaceId}, - wl_usr::{usr_ifs::usr_jay_workspace::UsrJayWorkspace, usr_object::UsrObject, UsrCon}, + wl_usr::{ + usr_ifs::usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner}, + usr_object::UsrObject, + UsrCon, + }, }, std::{convert::Infallible, rc::Rc}, }; @@ -30,20 +34,30 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace { Ok(()) } - fn selected(&self, ev: Selected, _slf: &Rc) -> Result<(), Self::Error> { + fn selected(&self, ev: Selected, slf: &Rc) -> Result<(), Self::Error> { let tl = Rc::new(UsrJayWorkspace { id: ev.id, con: self.con.clone(), owner: Default::default(), version: self.version, + linear_id: Default::default(), + output: Default::default(), + name: Default::default(), }); self.con.add_object(tl.clone()); + tl.owner.set(Some(slf.clone())); + Ok(()) + } +} + +impl UsrJayWorkspaceOwner for UsrJaySelectWorkspace { + fn done(&self, ws: &Rc) { + ws.owner.take(); match self.owner.get() { - Some(owner) => owner.done(ev.output, Some(tl)), - _ => self.con.remove_obj(&*tl), + Some(owner) => owner.done(ws.output.get(), Some(ws.clone())), + _ => self.con.remove_obj(&**ws), } self.con.remove_obj(self); - Ok(()) } } diff --git a/src/wl_usr/usr_ifs/usr_jay_workspace.rs b/src/wl_usr/usr_ifs/usr_jay_workspace.rs index d3d183a3..1e88c196 100644 --- a/src/wl_usr/usr_ifs/usr_jay_workspace.rs +++ b/src/wl_usr/usr_ifs/usr_jay_workspace.rs @@ -5,7 +5,11 @@ use { wire::{jay_workspace::*, JayWorkspaceId}, wl_usr::{usr_object::UsrObject, UsrCon}, }, - std::{convert::Infallible, rc::Rc}, + std::{ + cell::{Cell, RefCell}, + convert::Infallible, + rc::Rc, + }, }; pub struct UsrJayWorkspace { @@ -13,21 +17,20 @@ pub struct UsrJayWorkspace { pub con: Rc, pub owner: CloneCell>>, pub version: Version, + pub linear_id: Cell, + pub output: Cell, + pub name: RefCell>, } pub trait UsrJayWorkspaceOwner { - fn linear_id(self: Rc, ev: &LinearId) { - let _ = ev; + fn destroyed(&self, ws: &UsrJayWorkspace) { + let _ = ws; } - fn name(&self, ev: &Name) { - let _ = ev; + fn done(&self, ws: &Rc) { + let _ = ws; } - fn destroyed(&self) {} - - fn done(&self) {} - fn output(self: Rc, ev: &Output) { let _ = ev; } @@ -41,34 +44,31 @@ impl JayWorkspaceEventHandler for UsrJayWorkspace { type Error = Infallible; fn linear_id(&self, ev: LinearId, _slf: &Rc) -> Result<(), Self::Error> { - if let Some(owner) = self.owner.get() { - owner.linear_id(&ev); - } + self.linear_id.set(ev.linear_id); Ok(()) } fn name(&self, ev: Name<'_>, _slf: &Rc) -> Result<(), Self::Error> { - if let Some(owner) = self.owner.get() { - owner.name(&ev); - } + *self.name.borrow_mut() = Some(ev.name.to_string()); Ok(()) } - fn destroyed(&self, _ev: Destroyed, _slf: &Rc) -> Result<(), Self::Error> { + fn destroyed(&self, _ev: Destroyed, slf: &Rc) -> Result<(), Self::Error> { if let Some(owner) = self.owner.get() { - owner.destroyed(); + owner.destroyed(slf); } Ok(()) } - fn done(&self, _ev: Done, _slf: &Rc) -> Result<(), Self::Error> { + fn done(&self, _ev: Done, slf: &Rc) -> Result<(), Self::Error> { if let Some(owner) = self.owner.get() { - owner.done(); + owner.done(slf); } Ok(()) } fn output(&self, ev: Output, _slf: &Rc) -> Result<(), Self::Error> { + self.output.set(ev.global_name); if let Some(owner) = self.owner.get() { owner.output(&ev); } diff --git a/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs b/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs index 495de4f0..e7f5894e 100644 --- a/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs +++ b/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs @@ -31,6 +31,9 @@ impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher { con: self.con.clone(), owner: Default::default(), version: self.version, + linear_id: Default::default(), + output: Default::default(), + name: Default::default(), }); self.con.add_object(jw.clone()); if let Some(owner) = self.owner.get() { diff --git a/src/wl_usr/usr_ifs/usr_wl_output.rs b/src/wl_usr/usr_ifs/usr_wl_output.rs index c5ad9f60..f4d1ae20 100644 --- a/src/wl_usr/usr_ifs/usr_wl_output.rs +++ b/src/wl_usr/usr_ifs/usr_wl_output.rs @@ -5,7 +5,7 @@ use { wire::{wl_output::*, WlOutputId}, wl_usr::{usr_object::UsrObject, UsrCon}, }, - std::{convert::Infallible, rc::Rc}, + std::{cell::RefCell, convert::Infallible, rc::Rc}, }; pub struct UsrWlOutput { @@ -13,6 +13,7 @@ pub struct UsrWlOutput { pub con: Rc, pub owner: CloneCell>>, pub version: Version, + pub name: RefCell>, } pub trait UsrWlOutputOwner { @@ -71,6 +72,7 @@ impl WlOutputEventHandler for UsrWlOutput { } fn name(&self, ev: Name<'_>, _slf: &Rc) -> Result<(), Self::Error> { + *self.name.borrow_mut() = Some(ev.name.to_string()); if let Some(owner) = self.owner.get() { owner.name(&ev); } From 260d241f79c521493362e0c6a8c4344bf6fba69f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 21:14:05 +0200 Subject: [PATCH 07/10] portal: unify remote desktop and screencast sessions --- release-notes.md | 1 + src/portal.rs | 16 +- src/portal/ptl_display.rs | 11 +- src/portal/ptl_remote_desktop.rs | 154 +++++--------- .../ptl_remote_desktop/remote_desktop_gui.rs | 10 +- src/portal/ptl_screencast.rs | 200 ++++++------------ src/portal/ptl_screencast/screencast_gui.rs | 29 ++- src/portal/ptl_session.rs | 162 ++++++++++++++ 8 files changed, 304 insertions(+), 279 deletions(-) create mode 100644 src/portal/ptl_session.rs 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), + } + } + } +} From edafe16742dfe8af478df1740b276383d9386555 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Oct 2024 21:19:33 +0200 Subject: [PATCH 08/10] portal: damage buffer before committing --- src/portal/ptr_gui.rs | 1 + src/wl_usr/usr_ifs/usr_wl_surface.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index fd5a1510..246f455b 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -661,6 +661,7 @@ impl WindowData { buf.free.set(false); self.surface.attach(&buf.wl); + self.surface.damage(); self.surface.commit(); } diff --git a/src/wl_usr/usr_ifs/usr_wl_surface.rs b/src/wl_usr/usr_ifs/usr_wl_surface.rs index 3a3fc616..7d72e3eb 100644 --- a/src/wl_usr/usr_ifs/usr_wl_surface.rs +++ b/src/wl_usr/usr_ifs/usr_wl_surface.rs @@ -27,6 +27,16 @@ impl UsrWlSurface { }); } + pub fn damage(&self) { + self.con.request(DamageBuffer { + self_id: self.id, + x: 0, + y: 0, + width: i32::MAX, + height: i32::MAX, + }); + } + pub fn frame(&self, f: F) where F: FnOnce() + 'static, From 5df018046c42c413282a90eb308e17c34c73eaf0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 11 Oct 2024 13:23:54 +0200 Subject: [PATCH 09/10] portal: include mapping_id in screencasts --- src/ei/ei_ifs/ei_device.rs | 11 +++++++++-- src/ei/ei_ifs/ei_seat.rs | 1 + src/portal/ptl_remote_desktop.rs | 2 +- src/portal/ptl_screencast.rs | 15 +++++++++++++-- src/portal/ptl_session.rs | 7 +++++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/ei/ei_ifs/ei_device.rs b/src/ei/ei_ifs/ei_device.rs index 7f87fbae..e3e6cc6a 100644 --- a/src/ei/ei_ifs/ei_device.rs +++ b/src/ei/ei_ifs/ei_device.rs @@ -15,8 +15,8 @@ use { wire_ei::{ ei_device::{ ClientFrame, ClientStartEmulating, ClientStopEmulating, Destroyed, DeviceType, - Done, EiDeviceRequestHandler, Interface, Paused, Region, Release, Resumed, - ServerFrame, ServerStartEmulating, + Done, EiDeviceRequestHandler, Interface, Paused, Region, RegionMappingId, Release, + Resumed, ServerFrame, ServerStartEmulating, }, EiDeviceId, }, @@ -100,6 +100,13 @@ impl EiDevice { }); } + pub fn send_region_mapping_id(&self, mapping_id: &str) { + self.client.event(RegionMappingId { + self_id: self.id, + mapping_id, + }); + } + #[expect(dead_code)] pub fn send_paused(&self, serial: u32) { self.client.event(Paused { diff --git a/src/ei/ei_ifs/ei_seat.rs b/src/ei/ei_ifs/ei_seat.rs index 40430299..d8469b40 100644 --- a/src/ei/ei_ifs/ei_seat.rs +++ b/src/ei/ei_ifs/ei_seat.rs @@ -346,6 +346,7 @@ impl EiSeat { apply!(EI_CAP_KEYBOARD, create_keyboard); apply!(EI_CAP_TOUCHSCREEN, create_touchscreen); for output in self.client.state.root.outputs.lock().values() { + device.send_region_mapping_id(&output.global.connector.name); device.send_region( output.node_absolute_position(), output.global.persistent.scale.get(), diff --git a/src/portal/ptl_remote_desktop.rs b/src/portal/ptl_remote_desktop.rs index 166b0592..2c0f8634 100644 --- a/src/portal/ptl_remote_desktop.rs +++ b/src/portal/ptl_remote_desktop.rs @@ -90,7 +90,7 @@ impl UsrJayEiSessionOwner for StartingRemoteDesktop { Some(self.dpy.clone()), ); } else { - self.session.send_start_reply(None, None); + self.session.send_start_reply(None, None, None); } } diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index f13b9087..567813ab 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -159,8 +159,19 @@ bitflags! { impl PwClientNodeOwner for StartingScreencast { fn bound_id(&self, node_id: u32) { - self.session - .send_start_reply(Some(node_id), create_restore_data(&self.dpy, &self.target)); + { + let output = match &self.target { + ScreencastTarget::Output(o) => Some(o), + ScreencastTarget::Workspace(o, _, _) => Some(o), + ScreencastTarget::Toplevel(_) => None, + }; + let mapping_id = output.and_then(|o| o.wl.name.borrow().clone()); + self.session.send_start_reply( + Some(node_id), + create_restore_data(&self.dpy, &self.target), + mapping_id.as_deref(), + ); + } let mut supported_formats = PwClientNodePortSupportedFormats { media_type: Some(SPA_MEDIA_TYPE_video), media_sub_type: Some(SPA_MEDIA_SUBTYPE_raw), diff --git a/src/portal/ptl_session.rs b/src/portal/ptl_session.rs index e0d897fb..5a94df5a 100644 --- a/src/portal/ptl_session.rs +++ b/src/portal/ptl_session.rs @@ -100,6 +100,7 @@ impl PortalSession { &self, pw_node_id: Option, restore_data: Option>, + mapping_id: Option<&str>, ) { let inner_type = DynamicType::DictEntry( Box::new(DynamicType::String), @@ -133,6 +134,12 @@ impl PortalSession { value: rd, }); } + if let Some(mapping_id) = mapping_id { + variants.push(DictEntry { + key: "mapping_id".into(), + value: Variant::String(mapping_id.into()), + }); + } if let Some(reply) = self.start_reply.take() { match reply { PortalSessionReply::RemoteDesktop(reply) => { From 2b552784209cd6eb2ae8fab1d016d985ed2af430 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 11 Oct 2024 14:21:18 +0200 Subject: [PATCH 10/10] xwayland: respect input region --- src/ifs/wl_surface/x_surface/xwindow.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index b5208c5d..b558ebe3 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -347,12 +347,7 @@ impl Node for Xwindow { } let rect = self.x.surface.buffer_abs_pos.get(); if x < rect.width() && y < rect.height() { - tree.push(FoundNode { - node: self.x.surface.clone(), - x, - y, - }); - return FindTreeResult::AcceptsInput; + return self.x.surface.find_tree_at_(x, y, tree); } FindTreeResult::Other }