portal: implement session restoration
This commit is contained in:
parent
4f431eec5c
commit
3e3532574b
13 changed files with 436 additions and 58 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
- 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.
|
||||||
|
|
||||||
# 1.6.0 (2024-09-25)
|
# 1.6.0 (2024-09-25)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ use {
|
||||||
|
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,13 @@ use {
|
||||||
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 +31,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 +68,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,6 +84,7 @@ 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 screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
||||||
|
|
@ -243,6 +253,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!(
|
||||||
|
|
@ -398,12 +422,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(),
|
||||||
|
|
@ -416,11 +443,13 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
screencasts: Default::default(),
|
screencasts: Default::default(),
|
||||||
remote_desktop_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 +493,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());
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use {
|
||||||
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, DynamicType, 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::{
|
||||||
|
|
@ -29,6 +29,7 @@ use {
|
||||||
copyhashmap::CopyHashMap,
|
copyhashmap::CopyHashMap,
|
||||||
errorfmt::ErrorFmt,
|
errorfmt::ErrorFmt,
|
||||||
hash_map_ext::HashMapExt,
|
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,
|
||||||
|
|
@ -54,6 +55,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},
|
||||||
|
|
@ -77,7 +79,7 @@ pub struct ScreencastSession {
|
||||||
#[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,6 +90,10 @@ 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<ScreencastSession>,
|
||||||
|
|
@ -98,12 +104,14 @@ pub struct SelectingScreencastCore {
|
||||||
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 {
|
||||||
|
|
@ -123,7 +131,7 @@ 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>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,16 +179,22 @@ impl PwClientNodeOwner for StartingScreencast {
|
||||||
DynamicType::U32,
|
DynamicType::U32,
|
||||||
DynamicType::Array(Box::new(inner_type.clone())),
|
DynamicType::Array(Box::new(inner_type.clone())),
|
||||||
]);
|
]);
|
||||||
let variants = &[DictEntry {
|
let mut variants = vec![DictEntry {
|
||||||
key: "streams".into(),
|
key: "streams".into(),
|
||||||
value: Variant::Array(
|
value: Variant::Array(
|
||||||
kt,
|
kt,
|
||||||
vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])],
|
vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])],
|
||||||
),
|
),
|
||||||
}];
|
}];
|
||||||
|
if let Some(rd) = create_restore_data(&self.dpy, &self.target) {
|
||||||
|
variants.push(DictEntry {
|
||||||
|
key: "restore_data".into(),
|
||||||
|
value: rd,
|
||||||
|
});
|
||||||
|
}
|
||||||
self.reply.ok(&StartReply {
|
self.reply.ok(&StartReply {
|
||||||
response: PORTAL_SUCCESS,
|
response: PORTAL_SUCCESS,
|
||||||
results: Cow::Borrowed(variants),
|
results: Cow::Owned(variants),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut supported_formats = PwClientNodePortSupportedFormats {
|
let mut supported_formats = PwClientNodePortSupportedFormats {
|
||||||
|
|
@ -221,7 +235,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 +245,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);
|
||||||
}
|
}
|
||||||
|
|
@ -439,7 +454,7 @@ impl ScreencastSession {
|
||||||
self.state.screencasts.remove(self.session_obj.path());
|
self.state.screencasts.remove(self.session_obj.path());
|
||||||
match self.phase.set(ScreencastPhase::Terminated) {
|
match self.phase.set(ScreencastPhase::Terminated) {
|
||||||
ScreencastPhase::Init => {}
|
ScreencastPhase::Init => {}
|
||||||
ScreencastPhase::SourcesSelected => {}
|
ScreencastPhase::SourcesSelected(_) => {}
|
||||||
ScreencastPhase::Terminated => {}
|
ScreencastPhase::Terminated => {}
|
||||||
ScreencastPhase::Selecting(s) => {
|
ScreencastPhase::Selecting(s) => {
|
||||||
s.core.reply.err("Session has been terminated");
|
s.core.reply.err("Session has been terminated");
|
||||||
|
|
@ -461,9 +476,10 @@ impl ScreencastSession {
|
||||||
s.dpy.screencasts.remove(self.session_obj.path());
|
s.dpy.screencasts.remove(self.session_obj.path());
|
||||||
match &s.target {
|
match &s.target {
|
||||||
ScreencastTarget::Output(_) => {}
|
ScreencastTarget::Output(_) => {}
|
||||||
ScreencastTarget::Workspace(_, w) => {
|
ScreencastTarget::Workspace(_, w, true) => {
|
||||||
s.dpy.con.remove_obj(&**w);
|
s.dpy.con.remove_obj(&**w);
|
||||||
}
|
}
|
||||||
|
ScreencastTarget::Workspace(_, _, false) => {}
|
||||||
ScreencastTarget::Toplevel(t) => {
|
ScreencastTarget::Toplevel(t) => {
|
||||||
s.dpy.con.remove_obj(&**t);
|
s.dpy.con.remove_obj(&**t);
|
||||||
}
|
}
|
||||||
|
|
@ -482,7 +498,7 @@ impl ScreencastSession {
|
||||||
|
|
||||||
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.phase.get() {
|
||||||
|
|
@ -493,7 +509,11 @@ impl ScreencastSession {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.phase.set(ScreencastPhase::SourcesSelected);
|
self.phase.set(ScreencastPhase::SourcesSelected(Rc::new(
|
||||||
|
SourcesSelectedScreencast {
|
||||||
|
restore_data: Cell::new(get_restore_data(&req)),
|
||||||
|
},
|
||||||
|
)));
|
||||||
reply.ok(&SelectSourcesReply {
|
reply.ok(&SelectSourcesReply {
|
||||||
response: PORTAL_SUCCESS,
|
response: PORTAL_SUCCESS,
|
||||||
results: Default::default(),
|
results: Default::default(),
|
||||||
|
|
@ -501,16 +521,16 @@ impl ScreencastSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
|
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
|
||||||
match self.phase.get() {
|
let restore_data = match self.phase.get() {
|
||||||
ScreencastPhase::SourcesSelected => {}
|
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,10 +547,20 @@ impl ScreencastSession {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let reply = Rc::new(reply);
|
||||||
|
self.restore(&request_obj, &reply, restore_data, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_interactive_selection(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
request_obj: &Rc<DbusObject>,
|
||||||
|
reply: &Rc<PendingReply<StartReply<'static>>>,
|
||||||
|
restore_data: Option<RestoreData>,
|
||||||
|
) {
|
||||||
let guis = CopyHashMap::new();
|
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() {
|
||||||
|
|
@ -542,12 +572,120 @@ impl ScreencastSession {
|
||||||
.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),
|
reply: reply.clone(),
|
||||||
},
|
},
|
||||||
guis,
|
guis,
|
||||||
|
restore_data: Cell::new(restore_data),
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn restore(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
request_obj: &Rc<DbusObject>,
|
||||||
|
reply: &Rc<PendingReply<StartReply<'static>>>,
|
||||||
|
restore_data: Option<Result<RestoreData, RestoreError>>,
|
||||||
|
display: Option<Rc<PortalDisplay>>,
|
||||||
|
) {
|
||||||
|
if let Some(rd) = restore_data {
|
||||||
|
if let Err(e) = self.try_restore(&request_obj, &reply, rd, display) {
|
||||||
|
log::error!("Could not restore session: {}", ErrorFmt(e));
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.start_interactive_selection(&request_obj, &reply, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_restore(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
request_obj: &Rc<DbusObject>,
|
||||||
|
reply: &Rc<PendingReply<StartReply<'static>>>,
|
||||||
|
restore_data: Result<RestoreData, RestoreError>,
|
||||||
|
display: Option<Rc<PortalDisplay>>,
|
||||||
|
) -> Result<(), RestoreError> {
|
||||||
|
let rd = restore_data?;
|
||||||
|
let dpy = if let Some(dpy) = display {
|
||||||
|
dpy
|
||||||
|
} else {
|
||||||
|
let dpy = self
|
||||||
|
.state
|
||||||
|
.displays
|
||||||
|
.lock()
|
||||||
|
.values()
|
||||||
|
.find(|d| d.unique_id == rd.display)
|
||||||
|
.cloned();
|
||||||
|
match dpy {
|
||||||
|
Some(dpy) => dpy,
|
||||||
|
_ => {
|
||||||
|
if self.state.displays.len() == 0 {
|
||||||
|
return Err(RestoreError::UnknownDisplay);
|
||||||
|
} else if self.state.displays.len() == 1 {
|
||||||
|
self.state.displays.lock().values().next().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
self.start_interactive_selection(&request_obj, &reply, Some(rd));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let start = |target: ScreencastTarget| {
|
||||||
|
SelectingScreencastCore {
|
||||||
|
session: self.clone(),
|
||||||
|
request_obj: request_obj.clone(),
|
||||||
|
reply: reply.clone(),
|
||||||
|
}
|
||||||
|
.starting(&dpy, target);
|
||||||
|
};
|
||||||
|
match &rd.ty {
|
||||||
|
RestoreDataType::Output(d) => {
|
||||||
|
let output = dpy
|
||||||
|
.outputs
|
||||||
|
.lock()
|
||||||
|
.values()
|
||||||
|
.find(|o| o.wl.name.borrow().as_ref() == Some(&d.name))
|
||||||
|
.cloned();
|
||||||
|
let Some(output) = output else {
|
||||||
|
return Err(RestoreError::UnknownOutput);
|
||||||
|
};
|
||||||
|
start(ScreencastTarget::Output(output));
|
||||||
|
}
|
||||||
|
RestoreDataType::Workspace(ws) => {
|
||||||
|
let ws = dpy
|
||||||
|
.workspaces
|
||||||
|
.lock()
|
||||||
|
.values()
|
||||||
|
.find(|w| w.name.borrow().as_ref() == Some(&ws.name))
|
||||||
|
.cloned();
|
||||||
|
let Some(ws) = ws else {
|
||||||
|
return Err(RestoreError::UnknownWorkspace);
|
||||||
|
};
|
||||||
|
let Some(output) = dpy.outputs.get(&ws.output.get()) else {
|
||||||
|
return Err(RestoreError::UnknownOutput);
|
||||||
|
};
|
||||||
|
start(ScreencastTarget::Workspace(output, ws, false));
|
||||||
|
}
|
||||||
|
RestoreDataType::Toplevel(d) => {
|
||||||
|
if dpy.jc.version < GET_TOPLEVEL_SINCE {
|
||||||
|
return Err(RestoreError::GetToplevel);
|
||||||
|
}
|
||||||
|
let selector = dpy.jc.get_toplevel(&d.id);
|
||||||
|
let selecting = Rc::new(SelectingWindowScreencast {
|
||||||
|
core: SelectingScreencastCore {
|
||||||
|
session: self.clone(),
|
||||||
|
request_obj: request_obj.clone(),
|
||||||
|
reply: reply.clone(),
|
||||||
|
},
|
||||||
|
dpy: dpy.clone(),
|
||||||
|
selector: selector.clone(),
|
||||||
|
restoring: true,
|
||||||
|
});
|
||||||
|
selector.owner.set(Some(selecting.clone()));
|
||||||
|
self.phase.set(ScreencastPhase::SelectingWindow(selecting));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrJayScreencastOwner for StartedScreencast {
|
impl UsrJayScreencastOwner for StartedScreencast {
|
||||||
|
|
@ -760,3 +898,122 @@ fn get_session<T>(
|
||||||
}
|
}
|
||||||
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,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -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<ScreencastSession>, 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,7 +164,10 @@ 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.phase.get() {
|
||||||
ScreencastPhase::Selecting(selecting) => selecting,
|
ScreencastPhase::Selecting(selecting) => selecting,
|
||||||
|
|
@ -163,7 +177,14 @@ 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.restore(
|
||||||
|
&selecting.core.request_obj,
|
||||||
|
&selecting.core.reply,
|
||||||
|
selecting.restore_data.take().map(Ok),
|
||||||
|
Some(self.surface.gui.dpy.clone()),
|
||||||
|
);
|
||||||
|
} else if self.role == ButtonRole::Accept {
|
||||||
selecting
|
selecting
|
||||||
.core
|
.core
|
||||||
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
|
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
|
||||||
|
|
@ -186,6 +207,7 @@ 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
|
||||||
|
|
@ -206,6 +228,15 @@ 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,
|
||||||
|
&self.core.reply,
|
||||||
|
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;
|
||||||
|
|
@ -252,7 +283,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.core
|
self.core
|
||||||
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws));
|
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use {
|
use {
|
||||||
arrayvec::ArrayString,
|
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,
|
||||||
|
|
@ -46,6 +47,26 @@ 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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue