1
0
Fork 0
forked from wry/wry

Merge pull request #291 from mahkoh/jorth/session-restore

portal: implement session restoration
This commit is contained in:
mahkoh 2024-10-11 17:02:12 +02:00 committed by GitHub
commit 06d7fff905
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 987 additions and 380 deletions

6
Cargo.lock generated
View file

@ -586,6 +586,7 @@ dependencies = [
"repc", "repc",
"rustc-demangle", "rustc-demangle",
"serde", "serde",
"serde_json",
"shaderc", "shaderc",
"smallvec", "smallvec",
"thiserror", "thiserror",
@ -1067,12 +1068,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.114" version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]

View file

@ -56,6 +56,7 @@ ash = "0.38.0"
gpu-alloc = "0.6.0" gpu-alloc = "0.6.0"
gpu-alloc-ash = "0.7.0" gpu-alloc-ash = "0.7.0"
serde = { version = "1.0.196", features = ["derive"] } serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.128"
enum-map = "2.7.3" enum-map = "2.7.3"
png = "0.17.13" png = "0.17.13"
rustc-demangle = { version = "0.1.24", optional = true } rustc-demangle = { version = "0.1.24", optional = true }

View file

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

View file

@ -271,6 +271,7 @@ fn start_compositor2(
cpu_worker, cpu_worker,
ui_drag_enabled: Cell::new(true), ui_drag_enabled: Cell::new(true),
ui_drag_threshold_squared: Cell::new(10), ui_drag_threshold_squared: Cell::new(10),
toplevels: Default::default(),
}); });
state.tracker.register(ClientId::from_raw(0)); state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state); create_dummy_output(&state);

View file

@ -15,8 +15,8 @@ use {
wire_ei::{ wire_ei::{
ei_device::{ ei_device::{
ClientFrame, ClientStartEmulating, ClientStopEmulating, Destroyed, DeviceType, ClientFrame, ClientStartEmulating, ClientStopEmulating, Destroyed, DeviceType,
Done, EiDeviceRequestHandler, Interface, Paused, Region, Release, Resumed, Done, EiDeviceRequestHandler, Interface, Paused, Region, RegionMappingId, Release,
ServerFrame, ServerStartEmulating, Resumed, ServerFrame, ServerStartEmulating,
}, },
EiDeviceId, 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)] #[expect(dead_code)]
pub fn send_paused(&self, serial: u32) { pub fn send_paused(&self, serial: u32) {
self.client.event(Paused { self.client.event(Paused {

View file

@ -346,6 +346,7 @@ impl EiSeat {
apply!(EI_CAP_KEYBOARD, create_keyboard); apply!(EI_CAP_KEYBOARD, create_keyboard);
apply!(EI_CAP_TOUCHSCREEN, create_touchscreen); apply!(EI_CAP_TOUCHSCREEN, create_touchscreen);
for output in self.client.state.root.outputs.lock().values() { for output in self.client.state.root.outputs.lock().values() {
device.send_region_mapping_id(&output.global.connector.name);
device.send_region( device.send_region(
output.node_absolute_position(), output.node_absolute_position(),
output.global.persistent.scale.get(), output.global.persistent.scale.get(),

View file

@ -23,17 +23,18 @@ use {
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
screenshoter::take_screenshot, screenshoter::take_screenshot,
utils::errorfmt::ErrorFmt, utils::{errorfmt::ErrorFmt, toplevel_identifier::ToplevelIdentifier},
wire::{jay_compositor::*, JayCompositorId, JayScreenshotId}, wire::{jay_compositor::*, JayCompositorId, JayScreenshotId},
}, },
bstr::ByteSlice, bstr::ByteSlice,
log::Level, log::Level,
std::{cell::Cell, ops::Deref, rc::Rc}, std::{cell::Cell, ops::Deref, rc::Rc, str::FromStr},
thiserror::Error, thiserror::Error,
}; };
pub const CREATE_EI_SESSION_SINCE: Version = Version(5); pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6); pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
pub const GET_TOPLEVEL_SINCE: Version = Version(12);
pub struct JayCompositorGlobal { pub struct JayCompositorGlobal {
name: GlobalName, name: GlobalName,
@ -71,7 +72,7 @@ impl Global for JayCompositorGlobal {
} }
fn version(&self) -> u32 { fn version(&self) -> u32 {
11 12
} }
fn required_caps(&self) -> ClientCaps { fn required_caps(&self) -> ClientCaps {
@ -364,12 +365,7 @@ impl JayCompositorRequestHandler for JayCompositor {
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let seat = self.client.lookup(req.seat)?; let seat = self.client.lookup(req.seat)?;
let obj = Rc::new(JaySelectToplevel { let obj = JaySelectToplevel::new(&self.client, req.id, self.version);
id: req.id,
client: self.client.clone(),
tracker: Default::default(),
destroyed: Cell::new(false),
});
track!(self.client, obj); track!(self.client, obj);
self.client.add_client_obj(&obj)?; self.client.add_client_obj(&obj)?;
let selector = JayToplevelSelector { let selector = JayToplevelSelector {
@ -422,6 +418,26 @@ impl JayCompositorRequestHandler for JayCompositor {
self.client.add_client_obj(&obj)?; self.client.add_client_obj(&obj)?;
Ok(()) Ok(())
} }
fn get_toplevel(&self, req: GetToplevel<'_>, _slf: &Rc<Self>) -> 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! { object_base! {

View file

@ -1,7 +1,10 @@
use { use {
crate::{ crate::{
client::{Client, ClientError}, client::{Client, ClientError},
ifs::{jay_toplevel::JayToplevel, wl_seat::ToplevelSelector}, ifs::{
jay_toplevel::{JayToplevel, ID_SINCE},
wl_seat::ToplevelSelector,
},
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
tree::ToplevelNode, tree::ToplevelNode,
@ -17,6 +20,7 @@ pub struct JaySelectToplevel {
pub client: Rc<Client>, pub client: Rc<Client>,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
pub destroyed: Cell<bool>, pub destroyed: Cell<bool>,
pub version: Version,
} }
pub struct JayToplevelSelector { pub struct JayToplevelSelector {
@ -35,38 +39,62 @@ impl Drop for JayToplevelSelector {
if self.jst.destroyed.get() { if self.jst.destroyed.get() {
return; return;
} }
let id = match self.tl.take() { self.jst.done(self.tl.take());
None => JayToplevelId::NONE, }
}
impl JaySelectToplevel {
pub fn done(&self, tl: Option<Rc<dyn ToplevelNode>>) {
let jtl = match tl {
None => None,
Some(toplevel) => { Some(toplevel) => {
let id = match self.jst.client.new_id() { let id = match self.client.new_id() {
Ok(id) => id, Ok(id) => id,
Err(e) => { Err(e) => {
self.jst.client.error(e); self.client.error(e);
return; return;
} }
}; };
let jtl = Rc::new(JayToplevel { let jtl = Rc::new(JayToplevel {
id, id,
client: self.jst.client.clone(), client: self.client.clone(),
tracker: Default::default(), tracker: Default::default(),
toplevel, toplevel,
destroyed: Cell::new(false), destroyed: Cell::new(false),
version: self.version,
}); });
track!(self.jst.client, jtl); track!(self.client, jtl);
self.jst.client.add_server_obj(&jtl); self.client.add_server_obj(&jtl);
jtl.toplevel jtl.toplevel
.tl_data() .tl_data()
.jay_toplevels .jay_toplevels
.set((jtl.client.id, jtl.id), jtl.clone()); .set((jtl.client.id, jtl.id), jtl.clone());
jtl.id Some(jtl)
} }
}; };
self.jst.send_done(id); match jtl {
let _ = self.jst.client.remove_obj(&*self.jst); None => self.send_done(JayToplevelId::NONE),
Some(jtl) => {
self.send_done(jtl.id);
if jtl.version >= ID_SINCE {
jtl.send_id();
jtl.send_done();
}
}
}
let _ = self.client.remove_obj(self);
}
pub fn new(client: &Rc<Client>, id: JaySelectToplevelId, version: Version) -> Rc<Self> {
Rc::new(JaySelectToplevel {
id,
client: client.clone(),
tracker: Default::default(),
destroyed: Cell::new(false),
version,
})
} }
}
impl JaySelectToplevel {
fn send_done(&self, id: JayToplevelId) { fn send_done(&self, id: JayToplevelId) {
self.client.event(Done { self.client.event(Done {
self_id: self.id, self_id: self.id,

View file

@ -10,12 +10,15 @@ use {
thiserror::Error, thiserror::Error,
}; };
pub const ID_SINCE: Version = Version(12);
pub struct JayToplevel { pub struct JayToplevel {
pub id: JayToplevelId, pub id: JayToplevelId,
pub client: Rc<Client>, pub client: Rc<Client>,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
pub toplevel: Rc<dyn ToplevelNode>, pub toplevel: Rc<dyn ToplevelNode>,
pub destroyed: Cell<bool>, pub destroyed: Cell<bool>,
pub version: Version,
} }
impl JayToplevel { impl JayToplevel {
@ -35,6 +38,18 @@ impl JayToplevel {
fn send_destroyed(&self) { fn send_destroyed(&self) {
self.client.event(Destroyed { self_id: self.id }); 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 { impl JayToplevelRequestHandler for JayToplevel {

View file

@ -1227,7 +1227,7 @@ impl UiDragUsecase for TileDragUsecase {
return; return;
}; };
let detach = || { 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 src_parent
.clone() .clone()
.cnode_replace_child(src.tl_as_node(), placeholder.clone()); .cnode_replace_child(src.tl_as_node(), placeholder.clone());

View file

@ -205,18 +205,21 @@ impl Xwindow {
if xsurface.xwindow.is_some() { if xsurface.xwindow.is_some() {
return Err(XWindowError::AlreadyAttached); return Err(XWindowError::AlreadyAttached);
} }
let tld = ToplevelData::new( let slf = Rc::new_cyclic(|weak| {
&data.state, let tld = ToplevelData::new(
data.info.title.borrow_mut().clone().unwrap_or_default(), &data.state,
Some(surface.client.clone()), data.info.title.borrow_mut().clone().unwrap_or_default(),
); Some(surface.client.clone()),
tld.pos.set(surface.extents.get()); weak,
let slf = Rc::new(Self { );
id: data.state.node_ids.next(), tld.pos.set(surface.extents.get());
data: data.clone(), Self {
display_link: Default::default(), id: data.state.node_ids.next(),
toplevel_data: tld, data: data.clone(),
x: xsurface, display_link: Default::default(),
toplevel_data: tld,
x: xsurface,
}
}); });
slf.x.xwindow.set(Some(slf.clone())); slf.x.xwindow.set(Some(slf.clone()));
slf.x.surface.set_toplevel(Some(slf.clone())); slf.x.surface.set_toplevel(Some(slf.clone()));
@ -344,12 +347,7 @@ impl Node for Xwindow {
} }
let rect = self.x.surface.buffer_abs_pos.get(); let rect = self.x.surface.buffer_abs_pos.get();
if x < rect.width() && y < rect.height() { if x < rect.width() && y < rect.height() {
tree.push(FoundNode { return self.x.surface.find_tree_at_(x, y, tree);
node: self.x.surface.clone(),
x,
y,
});
return FindTreeResult::AcceptsInput;
} }
FindTreeResult::Other FindTreeResult::Other
} }

View file

@ -347,7 +347,7 @@ impl XdgSurfaceRequestHandler for XdgSurface {
); );
return Err(XdgSurfaceError::AlreadyConstructed); 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); track!(self.surface.client, toplevel);
self.surface.client.add_client_obj(&toplevel)?; self.surface.client.add_client_obj(&toplevel)?;
self.ext.set(Some(toplevel.clone())); self.ext.set(Some(toplevel.clone()));

View file

@ -38,7 +38,7 @@ use {
cell::{Cell, RefCell}, cell::{Cell, RefCell},
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
mem, mem,
rc::Rc, rc::{Rc, Weak},
}, },
thiserror::Error, thiserror::Error,
}; };
@ -115,7 +115,7 @@ impl Debug for XdgToplevel {
} }
impl XdgToplevel { impl XdgToplevel {
pub fn new(id: XdgToplevelId, surface: &Rc<XdgSurface>) -> Self { pub fn new(id: XdgToplevelId, surface: &Rc<XdgSurface>, slf: &Weak<Self>) -> Self {
let mut states = AHashSet::new(); let mut states = AHashSet::new();
states.insert(STATE_TILED_LEFT); states.insert(STATE_TILED_LEFT);
states.insert(STATE_TILED_RIGHT); states.insert(STATE_TILED_RIGHT);
@ -141,6 +141,7 @@ impl XdgToplevel {
state, state,
String::new(), String::new(),
Some(surface.surface.client.clone()), Some(surface.surface.client.clone()),
slf,
), ),
drag: Default::default(), drag: Default::default(),
is_mapped: Cell::new(false), is_mapped: Cell::new(false),

View file

@ -740,20 +740,28 @@ macro_rules! ei_object_base {
macro_rules! logical_to_client_wire_scale { macro_rules! logical_to_client_wire_scale {
($client:expr, $($field:expr),+ $(,)?) => { ($client:expr, $($field:expr),+ $(,)?) => {
if let Some(scale) = $client.wire_scale.get() { #[expect(clippy::allow_attributes)]
$( {
$field = $field * scale; #[allow(clippy::assign_op_pattern)]
)+ if let Some(scale) = $client.wire_scale.get() {
$(
$field = $field * scale;
)+
}
} }
}; };
} }
macro_rules! client_wire_scale_to_logical { macro_rules! client_wire_scale_to_logical {
($client:expr, $($field:expr),+ $(,)?) => { ($client:expr, $($field:expr),+ $(,)?) => {
if let Some(scale) = $client.wire_scale.get() { #[expect(clippy::allow_attributes)]
$( {
$field = $field / scale; #[allow(clippy::assign_op_pattern)]
)+ if let Some(scale) = $client.wire_scale.get() {
$(
$field = $field / scale;
)+
}
} }
}; };
} }

View file

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

View file

@ -5,15 +5,19 @@ use {
ifs::wl_seat::POINTER, ifs::wl_seat::POINTER,
object::Version, object::Version,
portal::{ portal::{
ptl_remote_desktop::RemoteDesktopSession,
ptl_render_ctx::{PortalRenderCtx, PortalServerRenderCtx}, ptl_render_ctx::{PortalRenderCtx, PortalServerRenderCtx},
ptl_screencast::ScreencastSession, ptl_session::PortalSession,
ptr_gui::WindowData, ptr_gui::WindowData,
PortalState, PortalState,
}, },
utils::{ utils::{
bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap, bitflags::BitflagsExt,
errorfmt::ErrorFmt, hash_map_ext::HashMapExt, oserror::OsError, clonecell::CloneCell,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
hash_map_ext::HashMapExt,
opaque::{opaque, Opaque},
oserror::OsError,
}, },
video::drm::Drm, video::drm::Drm,
wire::{ wire::{
@ -26,6 +30,8 @@ use {
usr_jay_output::{UsrJayOutput, UsrJayOutputOwner}, usr_jay_output::{UsrJayOutput, UsrJayOutputOwner},
usr_jay_pointer::UsrJayPointer, usr_jay_pointer::UsrJayPointer,
usr_jay_render_ctx::UsrJayRenderCtxOwner, usr_jay_render_ctx::UsrJayRenderCtxOwner,
usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner},
usr_jay_workspace_watcher::{UsrJayWorkspaceWatcher, UsrJayWorkspaceWatcherOwner},
usr_linux_dmabuf::UsrLinuxDmabuf, usr_linux_dmabuf::UsrLinuxDmabuf,
usr_wl_compositor::UsrWlCompositor, usr_wl_compositor::UsrWlCompositor,
usr_wl_output::{UsrWlOutput, UsrWlOutputOwner}, usr_wl_output::{UsrWlOutput, UsrWlOutputOwner},
@ -61,9 +67,11 @@ struct PortalDisplayPrelude {
shared_ids!(PortalDisplayId); shared_ids!(PortalDisplayId);
pub struct PortalDisplay { pub struct PortalDisplay {
pub id: PortalDisplayId, pub id: PortalDisplayId,
pub unique_id: Opaque,
pub con: Rc<UsrCon>, pub con: Rc<UsrCon>,
pub(super) state: Rc<PortalState>, pub(super) state: Rc<PortalState>,
registry: Rc<UsrWlRegistry>, registry: Rc<UsrWlRegistry>,
_workspace_watcher: Rc<UsrJayWorkspaceWatcher>,
pub dmabuf: CloneCell<Option<Rc<UsrLinuxDmabuf>>>, pub dmabuf: CloneCell<Option<Rc<UsrLinuxDmabuf>>>,
pub jc: Rc<UsrJayCompositor>, pub jc: Rc<UsrJayCompositor>,
@ -75,10 +83,10 @@ pub struct PortalDisplay {
pub outputs: CopyHashMap<u32, Rc<PortalOutput>>, pub outputs: CopyHashMap<u32, Rc<PortalOutput>>,
pub seats: CopyHashMap<u32, Rc<PortalSeat>>, pub seats: CopyHashMap<u32, Rc<PortalSeat>>,
pub workspaces: CopyHashMap<u32, Rc<UsrJayWorkspace>>,
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>, pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
pub screencasts: CopyHashMap<String, Rc<ScreencastSession>>, pub sessions: CopyHashMap<String, Rc<PortalSession>>,
pub remote_desktop_sessions: CopyHashMap<String, Rc<RemoteDesktopSession>>,
} }
pub struct PortalOutput { pub struct PortalOutput {
@ -215,7 +223,7 @@ impl UsrJayRenderCtxOwner for PortalDisplay {
impl UsrConOwner for PortalDisplay { impl UsrConOwner for PortalDisplay {
fn killed(&self) { fn killed(&self) {
log::info!("Removing display {}", self.id); log::info!("Removing display {}", self.id);
for sc in self.screencasts.lock().drain_values() { for sc in self.sessions.lock().drain_values() {
sc.kill(); sc.kill();
} }
self.windows.clear(); self.windows.clear();
@ -243,6 +251,20 @@ impl UsrWlRegistryOwner for PortalDisplay {
} }
} }
impl UsrJayWorkspaceWatcherOwner for PortalDisplay {
fn new(self: Rc<Self>, ev: Rc<UsrJayWorkspace>, 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 { impl UsrJayOutputOwner for PortalOutput {
fn destroyed(&self) { fn destroyed(&self) {
log::info!( log::info!(
@ -323,7 +345,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
con: dpy.con.clone(), con: dpy.con.clone(),
owner: Default::default(), owner: Default::default(),
caps: Default::default(), caps: Default::default(),
version: Version(version.min(9)), version: Version(version.min(12)),
}); });
dpy.con.add_object(jc.clone()); dpy.con.add_object(jc.clone());
dpy.registry.request_bind(name, jc.version.0, jc.deref()); dpy.registry.request_bind(name, jc.version.0, jc.deref());
@ -398,12 +420,15 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
let comp = get!(comp_opt, WlCompositor); let comp = get!(comp_opt, WlCompositor);
let fsm = get!(fsm_opt, WpFractionalScaleManagerV1); let fsm = get!(fsm_opt, WpFractionalScaleManagerV1);
let vp = get!(vp_opt, WpViewporter); let vp = get!(vp_opt, WpViewporter);
let ww = jc.watch_workspaces();
let dpy = Rc::new(PortalDisplay { let dpy = Rc::new(PortalDisplay {
id: dpy.state.id(), id: dpy.state.id(),
unique_id: opaque(),
con: dpy.con.clone(), con: dpy.con.clone(),
state: dpy.state.clone(), state: dpy.state.clone(),
registry: dpy.registry.clone(), registry: dpy.registry.clone(),
_workspace_watcher: ww.clone(),
dmabuf: CloneCell::new(dmabuf_opt), dmabuf: CloneCell::new(dmabuf_opt),
jc, jc,
outputs: Default::default(), outputs: Default::default(),
@ -414,13 +439,14 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
fsm, fsm,
vp, vp,
windows: Default::default(), windows: Default::default(),
screencasts: Default::default(), sessions: Default::default(),
remote_desktop_sessions: Default::default(), workspaces: Default::default(),
}); });
dpy.state.displays.set(dpy.id, dpy.clone()); dpy.state.displays.set(dpy.id, dpy.clone());
dpy.con.owner.set(Some(dpy.clone())); dpy.con.owner.set(Some(dpy.clone()));
dpy.registry.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(); let jrc = dpy.jc.get_render_context();
jrc.owner.set(Some(dpy.clone())); jrc.owner.set(Some(dpy.clone()));
@ -464,6 +490,7 @@ fn add_output(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
con: dpy.con.clone(), con: dpy.con.clone(),
owner: Default::default(), owner: Default::default(),
version: Version(version.min(4)), version: Version(version.min(4)),
name: Default::default(),
}); });
dpy.con.add_object(wl.clone()); dpy.con.add_object(wl.clone());
dpy.registry.request_bind(name, wl.version.0, wl.deref()); dpy.registry.request_bind(name, wl.version.0, wl.deref());

View file

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

View file

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

View file

@ -3,9 +3,9 @@ mod screencast_gui;
use { use {
crate::{ crate::{
allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING}, allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING},
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply}, dbus::{prelude::Variant, DbusObject, DictEntry, PendingReply},
format::{Format, XRGB8888}, format::{Format, XRGB8888},
ifs::jay_screencast::CLIENT_BUFFERS_SINCE, ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE},
pipewire::{ pipewire::{
pw_con::PwCon, pw_con::PwCon,
pw_ifs::pw_client_node::{ pw_ifs::pw_client_node::{
@ -21,14 +21,16 @@ use {
}, },
portal::{ portal::{
ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput}, ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput},
ptl_remote_desktop::RemoteDesktopPhase,
ptl_screencast::screencast_gui::SelectionGui, ptl_screencast::screencast_gui::SelectionGui,
ptl_session::{PortalSession, PortalSessionReply},
PortalState, PORTAL_SUCCESS, PortalState, PORTAL_SUCCESS,
}, },
utils::{ utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe}, clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, errorfmt::ErrorFmt,
hash_map_ext::HashMapExt, opaque::Opaque,
}, },
video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER}, video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER},
wire::jay_screencast::Ready, wire::jay_screencast::Ready,
@ -39,7 +41,7 @@ use {
CreateSession, CreateSessionReply, SelectSources, SelectSourcesReply, Start, CreateSession, CreateSessionReply, SelectSources, SelectSourcesReply, Start,
StartReply, StartReply,
}, },
session::{CloseReply as SessionCloseReply, Closed}, session::CloseReply as SessionCloseReply,
}, },
}, },
wl_usr::usr_ifs::{ wl_usr::usr_ifs::{
@ -54,6 +56,7 @@ use {
usr_wl_buffer::UsrWlBuffer, usr_wl_buffer::UsrWlBuffer,
}, },
}, },
serde::{Deserialize, Serialize},
std::{ std::{
borrow::Cow, borrow::Cow,
cell::{Cell, RefCell}, cell::{Cell, RefCell},
@ -64,20 +67,10 @@ use {
thiserror::Error, thiserror::Error,
}; };
shared_ids!(ScreencastSessionId);
pub struct ScreencastSession {
_id: ScreencastSessionId,
state: Rc<PortalState>,
pw_con: Rc<PwCon>,
pub app: String,
session_obj: DbusObject,
pub phase: CloneCell<ScreencastPhase>,
}
#[derive(Clone)] #[derive(Clone)]
pub enum ScreencastPhase { pub enum ScreencastPhase {
Init, Init,
SourcesSelected, SourcesSelected(Rc<SourcesSelectedScreencast>),
Selecting(Rc<SelectingScreencast>), Selecting(Rc<SelectingScreencast>),
SelectingWindow(Rc<SelectingWindowScreencast>), SelectingWindow(Rc<SelectingWindowScreencast>),
SelectingWorkspace(Rc<SelectingWorkspaceScreencast>), SelectingWorkspace(Rc<SelectingWorkspaceScreencast>),
@ -88,22 +81,27 @@ pub enum ScreencastPhase {
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {} unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
pub struct SourcesSelectedScreencast {
pub restore_data: Cell<Option<Result<RestoreData, RestoreError>>>,
}
#[derive(Clone)] #[derive(Clone)]
pub struct SelectingScreencastCore { pub struct SelectingScreencastCore {
pub session: Rc<ScreencastSession>, pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>, pub request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
} }
pub struct SelectingScreencast { pub struct SelectingScreencast {
pub core: SelectingScreencastCore, pub core: SelectingScreencastCore,
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>, pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
pub restore_data: Cell<Option<RestoreData>>,
} }
pub struct SelectingWindowScreencast { pub struct SelectingWindowScreencast {
pub core: SelectingScreencastCore, pub core: SelectingScreencastCore,
pub dpy: Rc<PortalDisplay>, pub dpy: Rc<PortalDisplay>,
pub selector: Rc<UsrJaySelectToplevel>, pub selector: Rc<UsrJaySelectToplevel>,
pub restoring: bool,
} }
pub struct SelectingWorkspaceScreencast { pub struct SelectingWorkspaceScreencast {
@ -113,9 +111,8 @@ pub struct SelectingWorkspaceScreencast {
} }
pub struct StartingScreencast { pub struct StartingScreencast {
pub session: Rc<ScreencastSession>, pub session: Rc<PortalSession>,
pub _request_obj: Rc<DbusObject>, pub _request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
pub node: Rc<PwClientNode>, pub node: Rc<PwClientNode>,
pub dpy: Rc<PortalDisplay>, pub dpy: Rc<PortalDisplay>,
pub target: ScreencastTarget, pub target: ScreencastTarget,
@ -123,26 +120,26 @@ pub struct StartingScreencast {
pub enum ScreencastTarget { pub enum ScreencastTarget {
Output(Rc<PortalOutput>), Output(Rc<PortalOutput>),
Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>), Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>, bool),
Toplevel(Rc<UsrJayToplevel>), Toplevel(Rc<UsrJayToplevel>),
} }
pub struct StartedScreencast { pub struct StartedScreencast {
session: Rc<ScreencastSession>, pub session: Rc<PortalSession>,
node: Rc<PwClientNode>, pub node: Rc<PwClientNode>,
port: Rc<PwClientNodePort>, pub port: Rc<PwClientNodePort>,
buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>, pub buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>,
buffers: RefCell<Vec<DmaBuf>>, pub buffers: RefCell<Vec<DmaBuf>>,
pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>, pub pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>,
buffers_valid: Cell<bool>, pub buffers_valid: Cell<bool>,
dpy: Rc<PortalDisplay>, pub dpy: Rc<PortalDisplay>,
jay_screencast: Rc<UsrJayScreencast>, pub jay_screencast: Rc<UsrJayScreencast>,
port_buffer_valid: Cell<bool>, pub port_buffer_valid: Cell<bool>,
fixated: Cell<bool>, pub fixated: Cell<bool>,
format: Cell<&'static Format>, pub format: Cell<&'static Format>,
modifier: Cell<Modifier>, pub modifier: Cell<Modifier>,
width: Cell<i32>, pub width: Cell<i32>,
height: Cell<i32>, pub height: Cell<i32>,
} }
bitflags! { bitflags! {
@ -163,25 +160,17 @@ bitflags! {
impl PwClientNodeOwner for StartingScreencast { impl PwClientNodeOwner for StartingScreencast {
fn bound_id(&self, node_id: u32) { fn bound_id(&self, node_id: u32) {
{ {
let inner_type = DynamicType::DictEntry( let output = match &self.target {
Box::new(DynamicType::String), ScreencastTarget::Output(o) => Some(o),
Box::new(DynamicType::Variant), 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 kt = DynamicType::Struct(vec![
DynamicType::U32,
DynamicType::Array(Box::new(inner_type.clone())),
]);
let variants = &[DictEntry {
key: "streams".into(),
value: Variant::Array(
kt,
vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])],
),
}];
self.reply.ok(&StartReply {
response: PORTAL_SUCCESS,
results: Cow::Borrowed(variants),
});
} }
let mut supported_formats = PwClientNodePortSupportedFormats { let mut supported_formats = PwClientNodePortSupportedFormats {
media_type: Some(SPA_MEDIA_TYPE_video), media_type: Some(SPA_MEDIA_TYPE_video),
@ -221,7 +210,7 @@ impl PwClientNodeOwner for StartingScreencast {
jsc.set_output(&o.jay); jsc.set_output(&o.jay);
jsc.set_allow_all_workspaces(true); jsc.set_allow_all_workspaces(true);
} }
ScreencastTarget::Workspace(o, ws) => { ScreencastTarget::Workspace(o, ws, _) => {
jsc.set_output(&o.jay); jsc.set_output(&o.jay);
jsc.allow_workspace(ws); jsc.allow_workspace(ws);
} }
@ -231,9 +220,10 @@ impl PwClientNodeOwner for StartingScreencast {
jsc.configure(); jsc.configure();
match &self.target { match &self.target {
ScreencastTarget::Output(_) => {} ScreencastTarget::Output(_) => {}
ScreencastTarget::Workspace(_, w) => { ScreencastTarget::Workspace(_, w, true) => {
self.dpy.con.remove_obj(&**w); self.dpy.con.remove_obj(&**w);
} }
ScreencastTarget::Workspace(_, _, false) => {}
ScreencastTarget::Toplevel(t) => { ScreencastTarget::Toplevel(t) => {
self.dpy.con.remove_obj(&**t); self.dpy.con.remove_obj(&**t);
} }
@ -256,7 +246,7 @@ impl PwClientNodeOwner for StartingScreencast {
height: Cell::new(1), height: Cell::new(1),
}); });
self.session self.session
.phase .sc_phase
.set(ScreencastPhase::Started(started.clone())); .set(ScreencastPhase::Started(started.clone()));
started.jay_screencast.owner.set(Some(started.clone())); started.jay_screencast.owner.set(Some(started.clone()));
self.node.owner.set(Some(started.clone())); self.node.owner.set(Some(started.clone()));
@ -409,7 +399,11 @@ impl StartedScreencast {
impl SelectingScreencastCore { impl SelectingScreencastCore {
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) { pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
let node = self.session.pw_con.create_client_node(&[ let Some(pw_con) = &self.session.pw_con else {
self.session.kill();
return;
};
let node = pw_con.create_client_node(&[
("media.class".to_string(), "Video/Source".to_string()), ("media.class".to_string(), "Video/Source".to_string()),
("node.name".to_string(), "jay-desktop-portal".to_string()), ("node.name".to_string(), "jay-desktop-portal".to_string()),
("node.driver".to_string(), "true".to_string()), ("node.driver".to_string(), "true".to_string()),
@ -417,75 +411,28 @@ impl SelectingScreencastCore {
let starting = Rc::new(StartingScreencast { let starting = Rc::new(StartingScreencast {
session: self.session.clone(), session: self.session.clone(),
_request_obj: self.request_obj.clone(), _request_obj: self.request_obj.clone(),
reply: self.reply.clone(),
node, node,
dpy: dpy.clone(), dpy: dpy.clone(),
target, target,
}); });
self.session self.session
.phase .sc_phase
.set(ScreencastPhase::Starting(starting.clone())); .set(ScreencastPhase::Starting(starting.clone()));
starting.node.owner.set(Some(starting.clone())); starting.node.owner.set(Some(starting.clone()));
dpy.screencasts.set( dpy.sessions.set(
self.session.session_obj.path().to_owned(), self.session.session_obj.path().to_owned(),
self.session.clone(), self.session.clone(),
); );
} }
} }
impl ScreencastSession { impl PortalSession {
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) => {
s.dpy.con.remove_obj(&**w);
}
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);
}
}
}
}
fn dbus_select_sources( fn dbus_select_sources(
self: &Rc<Self>, self: &Rc<Self>,
_req: SelectSources, req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>, reply: PendingReply<SelectSourcesReply<'static>>,
) { ) {
match self.phase.get() { match self.sc_phase.get() {
ScreencastPhase::Init => {} ScreencastPhase::Init => {}
_ => { _ => {
self.kill(); self.kill();
@ -493,24 +440,32 @@ impl ScreencastSession {
return; return;
} }
} }
self.phase.set(ScreencastPhase::SourcesSelected); self.sc_phase.set(ScreencastPhase::SourcesSelected(Rc::new(
SourcesSelectedScreencast {
restore_data: Cell::new(get_restore_data(&req)),
},
)));
reply.ok(&SelectSourcesReply { reply.ok(&SelectSourcesReply {
response: PORTAL_SUCCESS, response: PORTAL_SUCCESS,
results: Default::default(), results: Default::default(),
}); });
} }
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) { fn dbus_start_screencast(
match self.phase.get() { self: &Rc<Self>,
ScreencastPhase::SourcesSelected => {} req: Start<'_>,
reply: PendingReply<StartReply<'static>>,
) {
let restore_data = match self.sc_phase.get() {
ScreencastPhase::SourcesSelected(s) => s.restore_data.take(),
_ => { _ => {
self.kill(); self.kill();
reply.err("Session is not in the correct phase for starting"); reply.err("Session is not in the correct phase for starting");
return; return;
} }
} };
let request_obj = match self.state.dbus.add_object(req.handle.to_string()) { let request_obj = match self.state.dbus.add_object(req.handle.to_string()) {
Ok(r) => r, Ok(r) => Rc::new(r),
Err(_) => { Err(_) => {
self.kill(); self.kill();
reply.err("Request handle is not unique"); reply.err("Request handle is not unique");
@ -527,27 +482,141 @@ impl ScreencastSession {
} }
}); });
} }
self.start_reply
.set(Some(PortalSessionReply::ScreenCast(reply)));
self.screencast_restore(&request_obj, restore_data, None);
}
fn start_interactive_selection(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
restore_data: Option<RestoreData>,
) {
let guis = CopyHashMap::new(); let guis = CopyHashMap::new();
for dpy in self.state.displays.lock().values() { for dpy in self.state.displays.lock().values() {
if dpy.outputs.len() > 0 { 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() { if guis.is_empty() {
self.kill(); self.kill();
reply.err("There are no running displays"); self.reply_err("There are no running displays");
return; return;
} }
self.phase self.sc_phase
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast { .set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
core: SelectingScreencastCore { core: SelectingScreencastCore {
session: self.clone(), session: self.clone(),
request_obj: Rc::new(request_obj), request_obj: request_obj.clone(),
reply: Rc::new(reply),
}, },
guis, guis,
restore_data: Cell::new(restore_data),
}))); })));
} }
pub fn screencast_restore(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
restore_data: Option<Result<RestoreData, RestoreError>>,
display: Option<Rc<PortalDisplay>>,
) {
if let Some(rd) = restore_data {
if let Err(e) = self.try_restore(&request_obj, rd, display) {
log::error!("Could not restore session: {}", ErrorFmt(e));
} else {
return;
}
}
self.start_interactive_selection(&request_obj, None);
}
fn try_restore(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
restore_data: Result<RestoreData, RestoreError>,
display: Option<Rc<PortalDisplay>>,
) -> 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, Some(rd));
return Ok(());
}
}
}
};
let start = |target: ScreencastTarget| {
SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.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(),
},
dpy: dpy.clone(),
selector: selector.clone(),
restoring: true,
});
selector.owner.set(Some(selecting.clone()));
self.sc_phase
.set(ScreencastPhase::SelectingWindow(selecting));
}
}
Ok(())
}
} }
impl UsrJayScreencastOwner for StartedScreencast { impl UsrJayScreencastOwner for StartedScreencast {
@ -695,7 +764,7 @@ fn dbus_create_session(
reply: PendingReply<CreateSessionReply<'static>>, reply: PendingReply<CreateSessionReply<'static>>,
) { ) {
log::info!("Create Session {:#?}", req); 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"); reply.err("Session already exists");
return; return;
} }
@ -706,13 +775,15 @@ fn dbus_create_session(
return; return;
} }
}; };
let session = Rc::new(ScreencastSession { let session = Rc::new(PortalSession {
_id: state.id(), _id: state.id(),
state: state.clone(), state: state.clone(),
pw_con: pw_con.clone(), pw_con: Some(pw_con.clone()),
app: req.app_id.to_string(), app: req.app_id.to_string(),
session_obj: obj, 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::*; use org::freedesktop::impl_::portal::session::*;
@ -724,7 +795,7 @@ fn dbus_create_session(
session.session_obj.set_property::<version>(Variant::U32(4)); session.session_obj.set_property::<version>(Variant::U32(4));
} }
state state
.screencasts .sessions
.set(req.session_handle.0.to_string(), session); .set(req.session_handle.0.to_string(), session);
reply.ok(&CreateSessionReply { reply.ok(&CreateSessionReply {
response: PORTAL_SUCCESS, response: PORTAL_SUCCESS,
@ -744,7 +815,7 @@ fn dbus_select_sources(
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) { fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) { if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_start(req, reply); s.dbus_start_screencast(req, reply);
} }
} }
@ -752,11 +823,130 @@ fn get_session<T>(
state: &Rc<PortalState>, state: &Rc<PortalState>,
reply: &PendingReply<T>, reply: &PendingReply<T>,
handle: &str, handle: &str,
) -> Option<Rc<ScreencastSession>> { ) -> Option<Rc<PortalSession>> {
let res = state.screencasts.get(handle); let res = state.sessions.get(handle);
if res.is_none() { if res.is_none() {
let msg = format!("Screencast session `{}` does not exist", handle); let msg = format!("Screencast session `{}` does not exist", handle);
reply.err(&msg); reply.err(&msg);
} }
res res
} }
fn create_restore_data(dpy: &PortalDisplay, rd: &ScreencastTarget) -> Option<Variant<'static>> {
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<Result<RestoreData, RestoreError>> {
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<Cow<str>, Variant>,
) -> Result<RestoreData, RestoreError> {
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,
}

View file

@ -4,7 +4,7 @@ use {
portal::{ portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
ptl_screencast::{ ptl_screencast::{
ScreencastPhase, ScreencastSession, ScreencastTarget, SelectingWindowScreencast, PortalSession, ScreencastPhase, ScreencastTarget, SelectingWindowScreencast,
SelectingWorkspaceScreencast, SelectingWorkspaceScreencast,
}, },
ptr_gui::{ ptr_gui::{
@ -27,7 +27,7 @@ const H_MARGIN: f32 = 30.0;
const V_MARGIN: f32 = 20.0; const V_MARGIN: f32 = 20.0;
pub struct SelectionGui { pub struct SelectionGui {
screencast_session: Rc<ScreencastSession>, screencast_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>, dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>, surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
} }
@ -45,6 +45,7 @@ struct StaticButton {
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
enum ButtonRole { enum ButtonRole {
Restore,
Accept, Accept,
SelectWorkspace, SelectWorkspace,
SelectWindow, SelectWindow,
@ -56,7 +57,7 @@ impl SelectionGui {
for surface in self.surfaces.lock().drain_values() { for surface in self.surfaces.lock().drain_values() {
surface.overlay.data.kill(false); 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); s.guis.remove(&self.dpy.id);
if upwards && s.guis.is_empty() { if upwards && s.guis.is_empty() {
self.screencast_session.kill(); self.screencast_session.kill();
@ -65,7 +66,7 @@ impl SelectionGui {
} }
} }
fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> { fn create_accept_gui(surface: &Rc<SelectionGuiSurface>, for_restore: bool) -> Rc<dyn GuiElement> {
let app = &surface.gui.screencast_session.app; let app = &surface.gui.screencast_session.app;
let text = if app.is_empty() { let text = if app.is_empty() {
format!("An application wants to capture the screen") format!("An application wants to capture the screen")
@ -74,11 +75,13 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
}; };
let label = Rc::new(Label::default()); let label = Rc::new(Label::default());
*label.text.borrow_mut() = text; *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 accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace"); let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace");
let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window"); let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject"); let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
for button in [ for button in [
&restore_button,
&accept_button, &accept_button,
&workspace_button, &workspace_button,
&window_button, &window_button,
@ -88,6 +91,10 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
button.border.set(2.0); button.border.set(2.0);
button.padding.set(5.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] { for button in [&accept_button, &workspace_button, &window_button] {
button.bg_color.set(Color::from_rgb(170, 200, 170)); button.bg_color.set(Color::from_rgb(170, 200, 170));
button.bg_hover_color.set(Color::from_rgb(170, 255, 170)); button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
@ -101,7 +108,11 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
flow.cross_align.set(Align::Center); flow.cross_align.set(Align::Center);
flow.in_margin.set(V_MARGIN); flow.in_margin.set(V_MARGIN);
flow.cross_margin.set(H_MARGIN); flow.cross_margin.set(H_MARGIN);
let mut elements: Vec<Rc<dyn GuiElement>> = vec![label, accept_button]; let mut elements: Vec<Rc<dyn GuiElement>> = vec![label];
if for_restore {
elements.push(restore_button);
}
elements.push(accept_button);
if surface.gui.dpy.jc.caps.select_workspace.get() { if surface.gui.dpy.jc.caps.select_workspace.get() {
elements.push(workspace_button); elements.push(workspace_button);
} }
@ -124,7 +135,7 @@ impl OverlayWindowOwner for SelectionGuiSurface {
} }
impl SelectionGui { impl SelectionGui {
pub fn new(ss: &Rc<ScreencastSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> { pub fn new(ss: &Rc<PortalSession>, dpy: &Rc<PortalDisplay>, for_restore: bool) -> Rc<Self> {
let gui = Rc::new(SelectionGui { let gui = Rc::new(SelectionGui {
screencast_session: ss.clone(), screencast_session: ss.clone(),
dpy: dpy.clone(), dpy: dpy.clone(),
@ -136,7 +147,7 @@ impl SelectionGui {
output: output.clone(), output: output.clone(),
overlay: OverlayWindow::new(output), 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)); sgs.overlay.data.content.set(Some(element));
gui.dpy gui.dpy
.windows .windows
@ -153,9 +164,12 @@ impl ButtonOwner for StaticButton {
return; return;
} }
match self.role { match self.role {
ButtonRole::Accept | ButtonRole::SelectWorkspace | ButtonRole::SelectWindow => { ButtonRole::Restore
| ButtonRole::Accept
| ButtonRole::SelectWorkspace
| ButtonRole::SelectWindow => {
log::info!("User has accepted the request"); 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, ScreencastPhase::Selecting(selecting) => selecting,
_ => return, _ => return,
}; };
@ -163,7 +177,13 @@ impl ButtonOwner for StaticButton {
gui.kill(false); gui.kill(false);
} }
let dpy = &self.surface.output.dpy; let dpy = &self.surface.output.dpy;
if self.role == ButtonRole::Accept { if self.role == ButtonRole::Restore {
selecting.core.session.screencast_restore(
&selecting.core.request_obj,
selecting.restore_data.take().map(Ok),
Some(self.surface.gui.dpy.clone()),
);
} else if self.role == ButtonRole::Accept {
selecting selecting
.core .core
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone())); .starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
@ -178,7 +198,7 @@ impl ButtonOwner for StaticButton {
self.surface self.surface
.gui .gui
.screencast_session .screencast_session
.phase .sc_phase
.set(ScreencastPhase::SelectingWorkspace(selecting)); .set(ScreencastPhase::SelectingWorkspace(selecting));
} else { } else {
let selector = dpy.jc.select_toplevel(&seat.wl); let selector = dpy.jc.select_toplevel(&seat.wl);
@ -186,12 +206,13 @@ impl ButtonOwner for StaticButton {
core: selecting.core.clone(), core: selecting.core.clone(),
dpy: dpy.clone(), dpy: dpy.clone(),
selector: selector.clone(), selector: selector.clone(),
restoring: false,
}); });
selector.owner.set(Some(selecting.clone())); selector.owner.set(Some(selecting.clone()));
self.surface self.surface
.gui .gui
.screencast_session .screencast_session
.phase .sc_phase
.set(ScreencastPhase::SelectingWindow(selecting)); .set(ScreencastPhase::SelectingWindow(selecting));
} }
} }
@ -206,11 +227,18 @@ impl ButtonOwner for StaticButton {
impl UsrJaySelectToplevelOwner for SelectingWindowScreencast { impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
fn done(&self, tl: Option<Rc<UsrJayToplevel>>) { fn done(&self, tl: Option<Rc<UsrJayToplevel>>) {
let Some(tl) = tl else { 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, None);
return;
}
log::info!("User has aborted the selection"); log::info!("User has aborted the selection");
self.core.session.kill(); self.core.session.kill();
return; return;
}; };
match self.core.session.phase.get() { match self.core.session.sc_phase.get() {
ScreencastPhase::SelectingWindow(s) => { ScreencastPhase::SelectingWindow(s) => {
self.dpy.con.remove_obj(&*s.selector); self.dpy.con.remove_obj(&*s.selector);
} }
@ -232,7 +260,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
self.core.session.kill(); self.core.session.kill();
return; return;
}; };
match self.core.session.phase.get() { match self.core.session.sc_phase.get() {
ScreencastPhase::SelectingWorkspace(s) => { ScreencastPhase::SelectingWorkspace(s) => {
self.dpy.con.remove_obj(&*s.selector); self.dpy.con.remove_obj(&*s.selector);
} }
@ -252,7 +280,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
} }
}; };
self.core self.core
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws)); .starting(&self.dpy, ScreencastTarget::Workspace(output, ws, true));
} }
} }

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

@ -0,0 +1,169 @@
use {
crate::{
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply, FALSE},
pipewire::pw_con::PwCon,
portal::{
ptl_remote_desktop::{DeviceTypes, RemoteDesktopPhase},
ptl_screencast::{ScreencastPhase, ScreencastTarget},
PortalState, PORTAL_SUCCESS,
},
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
wire_dbus::org::freedesktop::impl_::portal::{
remote_desktop::StartReply as RdStartReply, screen_cast::StartReply as ScStartReply,
session::Closed,
},
},
std::{borrow::Cow, cell::Cell, ops::Deref, rc::Rc},
};
shared_ids!(SessionId);
pub struct PortalSession {
pub _id: SessionId,
pub state: Rc<PortalState>,
pub pw_con: Option<Rc<PwCon>>,
pub app: String,
pub session_obj: DbusObject,
pub sc_phase: CloneCell<ScreencastPhase>,
pub rd_phase: CloneCell<RemoteDesktopPhase>,
pub start_reply: Cell<Option<PortalSessionReply>>,
}
pub enum PortalSessionReply {
RemoteDesktop(PendingReply<RdStartReply<'static>>),
ScreenCast(PendingReply<ScStartReply<'static>>),
}
impl PortalSession {
pub(super) fn kill(&self) {
self.session_obj.emit_signal(&Closed);
self.state.sessions.remove(self.session_obj.path());
self.reply_err("Session has been terminated");
match self.rd_phase.set(RemoteDesktopPhase::Terminated) {
RemoteDesktopPhase::Init => {}
RemoteDesktopPhase::DevicesSelected => {}
RemoteDesktopPhase::Terminated => {}
RemoteDesktopPhase::Selecting(s) => {
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
RemoteDesktopPhase::Starting(s) => {
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy.sessions.remove(self.session_obj.path());
}
RemoteDesktopPhase::Started(s) => {
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy.sessions.remove(self.session_obj.path());
}
}
match self.sc_phase.set(ScreencastPhase::Terminated) {
ScreencastPhase::Init => {}
ScreencastPhase::SourcesSelected(_) => {}
ScreencastPhase::Terminated => {}
ScreencastPhase::Selecting(s) => {
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
ScreencastPhase::SelectingWindow(s) => {
s.dpy.con.remove_obj(&*s.selector);
}
ScreencastPhase::SelectingWorkspace(s) => {
s.dpy.con.remove_obj(&*s.selector);
}
ScreencastPhase::Starting(s) => {
s.node.con.destroy_obj(s.node.deref());
s.dpy.sessions.remove(self.session_obj.path());
match &s.target {
ScreencastTarget::Output(_) => {}
ScreencastTarget::Workspace(_, w, true) => {
s.dpy.con.remove_obj(&**w);
}
ScreencastTarget::Workspace(_, _, false) => {}
ScreencastTarget::Toplevel(t) => {
s.dpy.con.remove_obj(&**t);
}
}
}
ScreencastPhase::Started(s) => {
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
s.node.con.destroy_obj(s.node.deref());
s.dpy.sessions.remove(self.session_obj.path());
for buffer in s.pending_buffers.borrow_mut().drain(..) {
s.dpy.con.remove_obj(&*buffer);
}
}
}
}
pub(super) fn send_start_reply(
&self,
pw_node_id: Option<u32>,
restore_data: Option<Variant<'static>>,
mapping_id: Option<&str>,
) {
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(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) => {
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),
}
}
}
}

View file

@ -661,6 +661,7 @@ impl WindowData {
buf.free.set(false); buf.free.set(false);
self.surface.attach(&buf.wl); self.surface.attach(&buf.wl);
self.surface.damage();
self.surface.commit(); self.surface.commit();
} }

View file

@ -78,6 +78,7 @@ use {
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser,
hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue,
refcounted::RefCounted, run_toplevel::RunToplevel, refcounted::RefCounted, run_toplevel::RunToplevel,
toplevel_identifier::ToplevelIdentifier,
}, },
video::{ video::{
dmabuf::DmaBufIds, dmabuf::DmaBufIds,
@ -107,7 +108,7 @@ use {
mem, mem,
num::Wrapping, num::Wrapping,
ops::DerefMut, ops::DerefMut,
rc::Rc, rc::{Rc, Weak},
sync::Arc, sync::Arc,
time::Duration, time::Duration,
}, },
@ -220,6 +221,7 @@ pub struct State {
pub cpu_worker: Rc<CpuWorker>, pub cpu_worker: Rc<CpuWorker>,
pub ui_drag_enabled: Cell<bool>, pub ui_drag_enabled: Cell<bool>,
pub ui_drag_threshold_squared: Cell<i32>, pub ui_drag_threshold_squared: Cell<i32>,
pub toplevels: CopyHashMap<ToplevelIdentifier, Weak<dyn ToplevelNode>>,
} }
// impl Drop for State { // impl Drop for State {
@ -875,6 +877,7 @@ impl State {
self.ei_acceptor_future.take(); self.ei_acceptor_future.take();
self.ei_clients.clear(); self.ei_clients.clear();
self.slow_ei_clients.clear(); self.slow_ei_clients.clear();
self.toplevels.clear();
} }
pub fn damage_hardware_cursors(&self, render: bool) { pub fn damage_hardware_cursors(&self, render: bool) {

View file

@ -213,7 +213,7 @@ impl ContainerNode {
let child_node_ref = child_node.clone(); let child_node_ref = child_node.clone();
let mut child_nodes = AHashMap::new(); let mut child_nodes = AHashMap::new();
child_nodes.insert(child.node_id(), child_node); 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(), id: state.node_ids.next(),
split: Cell::new(split), split: Cell::new(split),
mono_child: CloneCell::new(None), mono_child: CloneCell::new(None),
@ -238,7 +238,7 @@ impl ContainerNode {
state: state.clone(), state: state.clone(),
render_data: Default::default(), render_data: Default::default(),
scroller: 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(), attention_requests: Default::default(),
}); });
child.tl_set_parent(slf.clone()); child.tl_set_parent(slf.clone());

View file

@ -22,7 +22,7 @@ use {
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
ops::Deref, ops::Deref,
rc::Rc, rc::{Rc, Weak},
sync::Arc, sync::Arc,
}, },
}; };
@ -48,13 +48,14 @@ pub async fn placeholder_render_textures(state: Rc<State>) {
} }
impl PlaceholderNode { impl PlaceholderNode {
pub fn new_for(state: &Rc<State>, node: Rc<dyn ToplevelNode>) -> Self { pub fn new_for(state: &Rc<State>, node: Rc<dyn ToplevelNode>, slf: &Weak<Self>) -> Self {
Self { Self {
id: state.node_ids.next(), id: state.node_ids.next(),
toplevel: ToplevelData::new( toplevel: ToplevelData::new(
state, state,
node.tl_data().title.borrow().clone(), node.tl_data().title.borrow().clone(),
node.node_client(), node.node_client(),
slf,
), ),
destroyed: Default::default(), destroyed: Default::default(),
update_textures_scheduled: Cell::new(false), update_textures_scheduled: Cell::new(false),
@ -63,10 +64,10 @@ impl PlaceholderNode {
} }
} }
pub fn new_empty(state: &Rc<State>) -> Self { pub fn new_empty(state: &Rc<State>, slf: &Weak<Self>) -> Self {
Self { Self {
id: state.node_ids.next(), id: state.node_ids.next(),
toplevel: ToplevelData::new(state, String::new(), None), toplevel: ToplevelData::new(state, String::new(), None, slf),
destroyed: Default::default(), destroyed: Default::default(),
update_textures_scheduled: Default::default(), update_textures_scheduled: Default::default(),
state: state.clone(), state: state.clone(),

View file

@ -282,10 +282,18 @@ pub struct ToplevelData {
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>, pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
pub ext_copy_sessions: pub ext_copy_sessions:
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>, CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
pub slf: Weak<dyn ToplevelNode>,
} }
impl ToplevelData { impl ToplevelData {
pub fn new(state: &Rc<State>, title: String, client: Option<Rc<Client>>) -> Self { pub fn new<T: ToplevelNode>(
state: &Rc<State>,
title: String,
client: Option<Rc<Client>>,
slf: &Weak<T>,
) -> Self {
let id = toplevel_identifier();
state.toplevels.set(id, slf.clone());
Self { Self {
self_active: Cell::new(false), self_active: Cell::new(false),
client, client,
@ -307,12 +315,13 @@ impl ToplevelData {
wants_attention: Cell::new(false), wants_attention: Cell::new(false),
requested_attention: Cell::new(false), requested_attention: Cell::new(false),
app_id: Default::default(), app_id: Default::default(),
identifier: Cell::new(toplevel_identifier()), identifier: Cell::new(id),
handles: Default::default(), handles: Default::default(),
render_highlight: Default::default(), render_highlight: Default::default(),
jay_toplevels: Default::default(), jay_toplevels: Default::default(),
jay_screencasts: Default::default(), jay_screencasts: Default::default(),
ext_copy_sessions: 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() { for screencast in self.ext_copy_sessions.lock().drain_values() {
screencast.stop(); 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(); let mut handles = self.handles.lock();
for handle in handles.drain_values() { for handle in handles.drain_values() {
@ -476,7 +490,8 @@ impl ToplevelData {
log::warn!("Cannot fullscreen root container in a workspace"); log::warn!("Cannot fullscreen root container in a workspace");
return; 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()); parent.cnode_replace_child(node.tl_as_node(), placeholder.clone());
let mut kb_foci = Default::default(); let mut kb_foci = Default::default();
if ws.visible.get() { 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 struct TileDragDestination {
pub highlight: Rect, pub highlight: Rect,
pub ty: TddType, pub ty: TddType,

View file

@ -1,5 +1,6 @@
use { use {
crate::utils::opaque::{opaque, Opaque, OpaqueError}, crate::utils::opaque::{opaque, Opaque, OpaqueError, OPAQUE_LEN},
arrayvec::ArrayString,
std::{ std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
str::FromStr, str::FromStr,
@ -13,6 +14,12 @@ pub fn activation_token() -> ActivationToken {
ActivationToken(opaque()) ActivationToken(opaque())
} }
impl ActivationToken {
pub fn to_string(self) -> ArrayString<OPAQUE_LEN> {
self.0.to_string()
}
}
impl Display for ActivationToken { impl Display for ActivationToken {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f) self.0.fmt(f)

View file

@ -1,5 +1,7 @@
use { use {
arrayvec::ArrayString,
rand::{thread_rng, Rng}, rand::{thread_rng, Rng},
serde::{de, Deserialize, Deserializer, Serialize, Serializer},
std::{ std::{
fmt::{Debug, Display, Formatter}, fmt::{Debug, Display, Formatter},
num::ParseIntError, num::ParseIntError,
@ -22,6 +24,15 @@ pub fn opaque() -> Opaque {
} }
} }
impl Opaque {
pub fn to_string(self) -> ArrayString<OPAQUE_LEN> {
use std::fmt::Write;
let mut s = ArrayString::new();
write!(s, "{}", self).unwrap();
s
}
}
impl Display for Opaque { impl Display for Opaque {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:016x}", self.hi)?; write!(f, "{:016x}", self.hi)?;
@ -36,24 +47,44 @@ impl Debug for Opaque {
} }
} }
impl Serialize for Opaque {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = self.to_string();
serializer.serialize_str(&s)
}
}
impl<'de> Deserialize<'de> for Opaque {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
Opaque::from_str(s).map_err(de::Error::custom)
}
}
impl FromStr for Opaque { impl FromStr for Opaque {
type Err = OpaqueError; type Err = OpaqueError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != LEN { if s.len() != OPAQUE_LEN {
return Err(OpaqueError::InvalidLength); return Err(OpaqueError::InvalidLength);
} }
if !s.is_char_boundary(LEN / 2) { if !s.is_char_boundary(OPAQUE_LEN / 2) {
return Err(OpaqueError::NotAscii); 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 hi = u64::from_str_radix(hi, 16).map_err(OpaqueError::Parse)?;
let lo = u64::from_str_radix(lo, 16).map_err(OpaqueError::Parse)?; let lo = u64::from_str_radix(lo, 16).map_err(OpaqueError::Parse)?;
Ok(Self { lo, hi }) Ok(Self { lo, hi })
} }
} }
const LEN: usize = 32; pub const OPAQUE_LEN: usize = 32;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum OpaqueError { pub enum OpaqueError {

View file

@ -1,5 +1,6 @@
use { use {
crate::utils::opaque::{opaque, Opaque, OpaqueError}, crate::utils::opaque::{opaque, Opaque, OpaqueError, OPAQUE_LEN},
arrayvec::ArrayString,
std::{ std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
str::FromStr, str::FromStr,
@ -13,6 +14,12 @@ pub fn toplevel_identifier() -> ToplevelIdentifier {
ToplevelIdentifier(opaque()) ToplevelIdentifier(opaque())
} }
impl ToplevelIdentifier {
pub fn to_string(self) -> ArrayString<OPAQUE_LEN> {
self.0.to_string()
}
}
impl Display for ToplevelIdentifier { impl Display for ToplevelIdentifier {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f) self.0.fmt(f)

View file

@ -96,7 +96,6 @@ impl UsrJayCompositor {
jo jo
} }
#[expect(dead_code)]
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> { pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
let ww = Rc::new(UsrJayWorkspaceWatcher { let ww = Rc::new(UsrJayWorkspaceWatcher {
id: self.con.id(), id: self.con.id(),
@ -143,6 +142,22 @@ impl UsrJayCompositor {
sc sc
} }
pub fn get_toplevel(&self, id: &str) -> Rc<UsrJaySelectToplevel> {
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<UsrJaySelectWorkspace> { pub fn select_workspace(&self, seat: &UsrWlSeat) -> Rc<UsrJaySelectWorkspace> {
let sc = Rc::new(UsrJaySelectWorkspace { let sc = Rc::new(UsrJaySelectWorkspace {
id: self.con.id(), id: self.con.id(),

View file

@ -1,9 +1,14 @@
use { use {
crate::{ crate::{
ifs::jay_toplevel::ID_SINCE,
object::Version, object::Version,
utils::clonecell::CloneCell, utils::clonecell::CloneCell,
wire::{jay_select_toplevel::*, JaySelectToplevelId}, 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}, std::{convert::Infallible, rc::Rc},
}; };
@ -15,6 +20,19 @@ pub struct UsrJaySelectToplevel {
pub version: Version, pub version: Version,
} }
impl UsrJaySelectToplevel {
fn send(&self, tl: Option<Rc<UsrJayToplevel>>) {
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 { pub trait UsrJaySelectToplevelOwner {
fn done(&self, toplevel: Option<Rc<UsrJayToplevel>>); fn done(&self, toplevel: Option<Rc<UsrJayToplevel>>);
} }
@ -22,7 +40,7 @@ pub trait UsrJaySelectToplevelOwner {
impl JaySelectToplevelEventHandler for UsrJaySelectToplevel { impl JaySelectToplevelEventHandler for UsrJaySelectToplevel {
type Error = Infallible; type Error = Infallible;
fn done(&self, ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn done(&self, ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
let tl = if ev.id.is_none() { let tl = if ev.id.is_none() {
None None
} else { } else {
@ -31,23 +49,31 @@ impl JaySelectToplevelEventHandler for UsrJaySelectToplevel {
con: self.con.clone(), con: self.con.clone(),
owner: Default::default(), owner: Default::default(),
version: self.version, version: self.version,
toplevel_id: Default::default(),
}); });
self.con.add_object(tl.clone()); self.con.add_object(tl.clone());
Some(tl) Some(tl)
}; };
match self.owner.get() { 'send: {
Some(owner) => owner.done(tl), if self.version >= ID_SINCE {
_ => {
if let Some(tl) = tl { 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(()) Ok(())
} }
} }
impl UsrJayToplevelOwner for UsrJaySelectToplevel {
fn done(&self, tl: &Rc<UsrJayToplevel>) {
tl.owner.take();
self.send(Some(tl.clone()));
}
}
usr_object_base! { usr_object_base! {
self = UsrJaySelectToplevel = JaySelectToplevel; self = UsrJaySelectToplevel = JaySelectToplevel;
version = self.version; version = self.version;

View file

@ -3,7 +3,11 @@ use {
object::Version, object::Version,
utils::clonecell::CloneCell, utils::clonecell::CloneCell,
wire::{jay_select_workspace::*, JaySelectWorkspaceId}, 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}, std::{convert::Infallible, rc::Rc},
}; };
@ -30,20 +34,30 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace {
Ok(()) Ok(())
} }
fn selected(&self, ev: Selected, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn selected(&self, ev: Selected, slf: &Rc<Self>) -> Result<(), Self::Error> {
let tl = Rc::new(UsrJayWorkspace { let tl = Rc::new(UsrJayWorkspace {
id: ev.id, id: ev.id,
con: self.con.clone(), con: self.con.clone(),
owner: Default::default(), owner: Default::default(),
version: self.version, version: self.version,
linear_id: Default::default(),
output: Default::default(),
name: Default::default(),
}); });
self.con.add_object(tl.clone()); self.con.add_object(tl.clone());
tl.owner.set(Some(slf.clone()));
Ok(())
}
}
impl UsrJayWorkspaceOwner for UsrJaySelectWorkspace {
fn done(&self, ws: &Rc<UsrJayWorkspace>) {
ws.owner.take();
match self.owner.get() { match self.owner.get() {
Some(owner) => owner.done(ev.output, Some(tl)), Some(owner) => owner.done(ws.output.get(), Some(ws.clone())),
_ => self.con.remove_obj(&*tl), _ => self.con.remove_obj(&**ws),
} }
self.con.remove_obj(self); self.con.remove_obj(self);
Ok(())
} }
} }

View file

@ -5,7 +5,7 @@ use {
wire::{jay_toplevel::*, JayToplevelId}, wire::{jay_toplevel::*, JayToplevelId},
wl_usr::{usr_object::UsrObject, UsrCon}, wl_usr::{usr_object::UsrObject, UsrCon},
}, },
std::{convert::Infallible, rc::Rc}, std::{cell::RefCell, convert::Infallible, rc::Rc},
}; };
pub struct UsrJayToplevel { pub struct UsrJayToplevel {
@ -13,10 +13,12 @@ pub struct UsrJayToplevel {
pub con: Rc<UsrCon>, pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayToplevelOwner>>>, pub owner: CloneCell<Option<Rc<dyn UsrJayToplevelOwner>>>,
pub version: Version, pub version: Version,
pub toplevel_id: RefCell<Option<String>>,
} }
pub trait UsrJayToplevelOwner { pub trait UsrJayToplevelOwner {
fn destroyed(&self) {} fn destroyed(&self) {}
fn done(&self, tl: &Rc<UsrJayToplevel>);
} }
impl JayToplevelEventHandler for UsrJayToplevel { impl JayToplevelEventHandler for UsrJayToplevel {
@ -28,6 +30,18 @@ impl JayToplevelEventHandler for UsrJayToplevel {
} }
Ok(()) Ok(())
} }
fn id_(&self, ev: Id<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
*self.toplevel_id.borrow_mut() = Some(ev.id.to_string());
Ok(())
}
fn done(&self, _ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.done(slf);
}
Ok(())
}
} }
usr_object_base! { usr_object_base! {

View file

@ -5,7 +5,11 @@ use {
wire::{jay_workspace::*, JayWorkspaceId}, wire::{jay_workspace::*, JayWorkspaceId},
wl_usr::{usr_object::UsrObject, UsrCon}, wl_usr::{usr_object::UsrObject, UsrCon},
}, },
std::{convert::Infallible, rc::Rc}, std::{
cell::{Cell, RefCell},
convert::Infallible,
rc::Rc,
},
}; };
pub struct UsrJayWorkspace { pub struct UsrJayWorkspace {
@ -13,21 +17,20 @@ pub struct UsrJayWorkspace {
pub con: Rc<UsrCon>, pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceOwner>>>, pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceOwner>>>,
pub version: Version, pub version: Version,
pub linear_id: Cell<u32>,
pub output: Cell<u32>,
pub name: RefCell<Option<String>>,
} }
pub trait UsrJayWorkspaceOwner { pub trait UsrJayWorkspaceOwner {
fn linear_id(self: Rc<Self>, ev: &LinearId) { fn destroyed(&self, ws: &UsrJayWorkspace) {
let _ = ev; let _ = ws;
} }
fn name(&self, ev: &Name) { fn done(&self, ws: &Rc<UsrJayWorkspace>) {
let _ = ev; let _ = ws;
} }
fn destroyed(&self) {}
fn done(&self) {}
fn output(self: Rc<Self>, ev: &Output) { fn output(self: Rc<Self>, ev: &Output) {
let _ = ev; let _ = ev;
} }
@ -41,34 +44,31 @@ impl JayWorkspaceEventHandler for UsrJayWorkspace {
type Error = Infallible; type Error = Infallible;
fn linear_id(&self, ev: LinearId, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn linear_id(&self, ev: LinearId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() { self.linear_id.set(ev.linear_id);
owner.linear_id(&ev);
}
Ok(()) Ok(())
} }
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
*self.name.borrow_mut() = Some(ev.name.to_string());
Ok(())
}
fn destroyed(&self, _ev: Destroyed, slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() { if let Some(owner) = self.owner.get() {
owner.name(&ev); owner.destroyed(slf);
} }
Ok(()) Ok(())
} }
fn destroyed(&self, _ev: Destroyed, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn done(&self, _ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() { if let Some(owner) = self.owner.get() {
owner.destroyed(); owner.done(slf);
}
Ok(())
}
fn done(&self, _ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.done();
} }
Ok(()) Ok(())
} }
fn output(&self, ev: Output, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn output(&self, ev: Output, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.output.set(ev.global_name);
if let Some(owner) = self.owner.get() { if let Some(owner) = self.owner.get() {
owner.output(&ev); owner.output(&ev);
} }

View file

@ -31,6 +31,9 @@ impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher {
con: self.con.clone(), con: self.con.clone(),
owner: Default::default(), owner: Default::default(),
version: self.version, version: self.version,
linear_id: Default::default(),
output: Default::default(),
name: Default::default(),
}); });
self.con.add_object(jw.clone()); self.con.add_object(jw.clone());
if let Some(owner) = self.owner.get() { if let Some(owner) = self.owner.get() {

View file

@ -5,7 +5,7 @@ use {
wire::{wl_output::*, WlOutputId}, wire::{wl_output::*, WlOutputId},
wl_usr::{usr_object::UsrObject, UsrCon}, wl_usr::{usr_object::UsrObject, UsrCon},
}, },
std::{convert::Infallible, rc::Rc}, std::{cell::RefCell, convert::Infallible, rc::Rc},
}; };
pub struct UsrWlOutput { pub struct UsrWlOutput {
@ -13,6 +13,7 @@ pub struct UsrWlOutput {
pub con: Rc<UsrCon>, pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlOutputOwner>>>, pub owner: CloneCell<Option<Rc<dyn UsrWlOutputOwner>>>,
pub version: Version, pub version: Version,
pub name: RefCell<Option<String>>,
} }
pub trait UsrWlOutputOwner { pub trait UsrWlOutputOwner {
@ -71,6 +72,7 @@ impl WlOutputEventHandler for UsrWlOutput {
} }
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
*self.name.borrow_mut() = Some(ev.name.to_string());
if let Some(owner) = self.owner.get() { if let Some(owner) = self.owner.get() {
owner.name(&ev); owner.name(&ev);
} }

View file

@ -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<F>(&self, f: F) pub fn frame<F>(&self, f: F)
where where
F: FnOnce() + 'static, F: FnOnce() + 'static,

View file

@ -96,6 +96,11 @@ request get_xwayland (since = 11) {
id: id(jay_xwayland), id: id(jay_xwayland),
} }
request get_toplevel (since = 12) {
id: id(jay_select_toplevel),
toplevel_id: str,
}
# events # events
event client_id { event client_id {

View file

@ -3,3 +3,10 @@ request destroy {
event destroyed { event destroyed {
} }
event id (since = 12) {
id: str,
}
event done (since = 12) {
}