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",
|
||||
"rustc-demangle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shaderc",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
|
|
@ -1067,12 +1068,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.114"
|
||||
version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ ash = "0.38.0"
|
|||
gpu-alloc = "0.6.0"
|
||||
gpu-alloc-ash = "0.7.0"
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
enum-map = "2.7.3"
|
||||
png = "0.17.13"
|
||||
rustc-demangle = { version = "0.1.24", optional = true }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
- Allow X windows to scale themselves.
|
||||
- Implement ext-image-capture-source-v1.
|
||||
- Implement ext-image-copy-capture-v1.
|
||||
- Implement screencast session restoration.
|
||||
|
||||
# 1.6.0 (2024-09-25)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ use {
|
|||
|
||||
pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
|
||||
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
|
||||
pub const GET_TOPLEVEL_SINCE: Version = Version(12);
|
||||
|
||||
pub struct JayCompositorGlobal {
|
||||
name: GlobalName,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ use {
|
|||
PortalState,
|
||||
},
|
||||
utils::{
|
||||
bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||
errorfmt::ErrorFmt, hash_map_ext::HashMapExt, oserror::OsError,
|
||||
bitflags::BitflagsExt,
|
||||
clonecell::CloneCell,
|
||||
copyhashmap::CopyHashMap,
|
||||
errorfmt::ErrorFmt,
|
||||
hash_map_ext::HashMapExt,
|
||||
opaque::{opaque, Opaque},
|
||||
oserror::OsError,
|
||||
},
|
||||
video::drm::Drm,
|
||||
wire::{
|
||||
|
|
@ -26,6 +31,8 @@ use {
|
|||
usr_jay_output::{UsrJayOutput, UsrJayOutputOwner},
|
||||
usr_jay_pointer::UsrJayPointer,
|
||||
usr_jay_render_ctx::UsrJayRenderCtxOwner,
|
||||
usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner},
|
||||
usr_jay_workspace_watcher::{UsrJayWorkspaceWatcher, UsrJayWorkspaceWatcherOwner},
|
||||
usr_linux_dmabuf::UsrLinuxDmabuf,
|
||||
usr_wl_compositor::UsrWlCompositor,
|
||||
usr_wl_output::{UsrWlOutput, UsrWlOutputOwner},
|
||||
|
|
@ -61,9 +68,11 @@ struct PortalDisplayPrelude {
|
|||
shared_ids!(PortalDisplayId);
|
||||
pub struct PortalDisplay {
|
||||
pub id: PortalDisplayId,
|
||||
pub unique_id: Opaque,
|
||||
pub con: Rc<UsrCon>,
|
||||
pub(super) state: Rc<PortalState>,
|
||||
registry: Rc<UsrWlRegistry>,
|
||||
_workspace_watcher: Rc<UsrJayWorkspaceWatcher>,
|
||||
pub dmabuf: CloneCell<Option<Rc<UsrLinuxDmabuf>>>,
|
||||
|
||||
pub jc: Rc<UsrJayCompositor>,
|
||||
|
|
@ -75,6 +84,7 @@ pub struct PortalDisplay {
|
|||
|
||||
pub outputs: CopyHashMap<u32, Rc<PortalOutput>>,
|
||||
pub seats: CopyHashMap<u32, Rc<PortalSeat>>,
|
||||
pub workspaces: CopyHashMap<u32, Rc<UsrJayWorkspace>>,
|
||||
|
||||
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
|
||||
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 {
|
||||
fn destroyed(&self) {
|
||||
log::info!(
|
||||
|
|
@ -398,12 +422,15 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
|||
let comp = get!(comp_opt, WlCompositor);
|
||||
let fsm = get!(fsm_opt, WpFractionalScaleManagerV1);
|
||||
let vp = get!(vp_opt, WpViewporter);
|
||||
let ww = jc.watch_workspaces();
|
||||
|
||||
let dpy = Rc::new(PortalDisplay {
|
||||
id: dpy.state.id(),
|
||||
unique_id: opaque(),
|
||||
con: dpy.con.clone(),
|
||||
state: dpy.state.clone(),
|
||||
registry: dpy.registry.clone(),
|
||||
_workspace_watcher: ww.clone(),
|
||||
dmabuf: CloneCell::new(dmabuf_opt),
|
||||
jc,
|
||||
outputs: Default::default(),
|
||||
|
|
@ -416,11 +443,13 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
|||
windows: Default::default(),
|
||||
screencasts: Default::default(),
|
||||
remote_desktop_sessions: Default::default(),
|
||||
workspaces: Default::default(),
|
||||
});
|
||||
|
||||
dpy.state.displays.set(dpy.id, dpy.clone());
|
||||
dpy.con.owner.set(Some(dpy.clone()));
|
||||
dpy.registry.owner.set(Some(dpy.clone()));
|
||||
ww.owner.set(Some(dpy.clone()));
|
||||
|
||||
let jrc = dpy.jc.get_render_context();
|
||||
jrc.owner.set(Some(dpy.clone()));
|
||||
|
|
@ -464,6 +493,7 @@ fn add_output(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
|
|||
con: dpy.con.clone(),
|
||||
owner: Default::default(),
|
||||
version: Version(version.min(4)),
|
||||
name: Default::default(),
|
||||
});
|
||||
dpy.con.add_object(wl.clone());
|
||||
dpy.registry.request_bind(name, wl.version.0, wl.deref());
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use {
|
|||
allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING},
|
||||
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply},
|
||||
format::{Format, XRGB8888},
|
||||
ifs::jay_screencast::CLIENT_BUFFERS_SINCE,
|
||||
ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE},
|
||||
pipewire::{
|
||||
pw_con::PwCon,
|
||||
pw_ifs::pw_client_node::{
|
||||
|
|
@ -29,6 +29,7 @@ use {
|
|||
copyhashmap::CopyHashMap,
|
||||
errorfmt::ErrorFmt,
|
||||
hash_map_ext::HashMapExt,
|
||||
opaque::Opaque,
|
||||
},
|
||||
video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER},
|
||||
wire::jay_screencast::Ready,
|
||||
|
|
@ -54,6 +55,7 @@ use {
|
|||
usr_wl_buffer::UsrWlBuffer,
|
||||
},
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
|
|
@ -77,7 +79,7 @@ pub struct ScreencastSession {
|
|||
#[derive(Clone)]
|
||||
pub enum ScreencastPhase {
|
||||
Init,
|
||||
SourcesSelected,
|
||||
SourcesSelected(Rc<SourcesSelectedScreencast>),
|
||||
Selecting(Rc<SelectingScreencast>),
|
||||
SelectingWindow(Rc<SelectingWindowScreencast>),
|
||||
SelectingWorkspace(Rc<SelectingWorkspaceScreencast>),
|
||||
|
|
@ -88,6 +90,10 @@ pub enum ScreencastPhase {
|
|||
|
||||
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
|
||||
|
||||
pub struct SourcesSelectedScreencast {
|
||||
pub restore_data: Cell<Option<Result<RestoreData, RestoreError>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SelectingScreencastCore {
|
||||
pub session: Rc<ScreencastSession>,
|
||||
|
|
@ -98,12 +104,14 @@ pub struct SelectingScreencastCore {
|
|||
pub struct SelectingScreencast {
|
||||
pub core: SelectingScreencastCore,
|
||||
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
||||
pub restore_data: Cell<Option<RestoreData>>,
|
||||
}
|
||||
|
||||
pub struct SelectingWindowScreencast {
|
||||
pub core: SelectingScreencastCore,
|
||||
pub dpy: Rc<PortalDisplay>,
|
||||
pub selector: Rc<UsrJaySelectToplevel>,
|
||||
pub restoring: bool,
|
||||
}
|
||||
|
||||
pub struct SelectingWorkspaceScreencast {
|
||||
|
|
@ -123,7 +131,7 @@ pub struct StartingScreencast {
|
|||
|
||||
pub enum ScreencastTarget {
|
||||
Output(Rc<PortalOutput>),
|
||||
Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>),
|
||||
Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>, bool),
|
||||
Toplevel(Rc<UsrJayToplevel>),
|
||||
}
|
||||
|
||||
|
|
@ -171,16 +179,22 @@ impl PwClientNodeOwner for StartingScreencast {
|
|||
DynamicType::U32,
|
||||
DynamicType::Array(Box::new(inner_type.clone())),
|
||||
]);
|
||||
let variants = &[DictEntry {
|
||||
let mut variants = vec![DictEntry {
|
||||
key: "streams".into(),
|
||||
value: Variant::Array(
|
||||
kt,
|
||||
vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])],
|
||||
),
|
||||
}];
|
||||
if let Some(rd) = create_restore_data(&self.dpy, &self.target) {
|
||||
variants.push(DictEntry {
|
||||
key: "restore_data".into(),
|
||||
value: rd,
|
||||
});
|
||||
}
|
||||
self.reply.ok(&StartReply {
|
||||
response: PORTAL_SUCCESS,
|
||||
results: Cow::Borrowed(variants),
|
||||
results: Cow::Owned(variants),
|
||||
});
|
||||
}
|
||||
let mut supported_formats = PwClientNodePortSupportedFormats {
|
||||
|
|
@ -221,7 +235,7 @@ impl PwClientNodeOwner for StartingScreencast {
|
|||
jsc.set_output(&o.jay);
|
||||
jsc.set_allow_all_workspaces(true);
|
||||
}
|
||||
ScreencastTarget::Workspace(o, ws) => {
|
||||
ScreencastTarget::Workspace(o, ws, _) => {
|
||||
jsc.set_output(&o.jay);
|
||||
jsc.allow_workspace(ws);
|
||||
}
|
||||
|
|
@ -231,9 +245,10 @@ impl PwClientNodeOwner for StartingScreencast {
|
|||
jsc.configure();
|
||||
match &self.target {
|
||||
ScreencastTarget::Output(_) => {}
|
||||
ScreencastTarget::Workspace(_, w) => {
|
||||
ScreencastTarget::Workspace(_, w, true) => {
|
||||
self.dpy.con.remove_obj(&**w);
|
||||
}
|
||||
ScreencastTarget::Workspace(_, _, false) => {}
|
||||
ScreencastTarget::Toplevel(t) => {
|
||||
self.dpy.con.remove_obj(&**t);
|
||||
}
|
||||
|
|
@ -439,7 +454,7 @@ impl ScreencastSession {
|
|||
self.state.screencasts.remove(self.session_obj.path());
|
||||
match self.phase.set(ScreencastPhase::Terminated) {
|
||||
ScreencastPhase::Init => {}
|
||||
ScreencastPhase::SourcesSelected => {}
|
||||
ScreencastPhase::SourcesSelected(_) => {}
|
||||
ScreencastPhase::Terminated => {}
|
||||
ScreencastPhase::Selecting(s) => {
|
||||
s.core.reply.err("Session has been terminated");
|
||||
|
|
@ -461,9 +476,10 @@ impl ScreencastSession {
|
|||
s.dpy.screencasts.remove(self.session_obj.path());
|
||||
match &s.target {
|
||||
ScreencastTarget::Output(_) => {}
|
||||
ScreencastTarget::Workspace(_, w) => {
|
||||
ScreencastTarget::Workspace(_, w, true) => {
|
||||
s.dpy.con.remove_obj(&**w);
|
||||
}
|
||||
ScreencastTarget::Workspace(_, _, false) => {}
|
||||
ScreencastTarget::Toplevel(t) => {
|
||||
s.dpy.con.remove_obj(&**t);
|
||||
}
|
||||
|
|
@ -482,7 +498,7 @@ impl ScreencastSession {
|
|||
|
||||
fn dbus_select_sources(
|
||||
self: &Rc<Self>,
|
||||
_req: SelectSources,
|
||||
req: SelectSources,
|
||||
reply: PendingReply<SelectSourcesReply<'static>>,
|
||||
) {
|
||||
match self.phase.get() {
|
||||
|
|
@ -493,7 +509,11 @@ impl ScreencastSession {
|
|||
return;
|
||||
}
|
||||
}
|
||||
self.phase.set(ScreencastPhase::SourcesSelected);
|
||||
self.phase.set(ScreencastPhase::SourcesSelected(Rc::new(
|
||||
SourcesSelectedScreencast {
|
||||
restore_data: Cell::new(get_restore_data(&req)),
|
||||
},
|
||||
)));
|
||||
reply.ok(&SelectSourcesReply {
|
||||
response: PORTAL_SUCCESS,
|
||||
results: Default::default(),
|
||||
|
|
@ -501,16 +521,16 @@ impl ScreencastSession {
|
|||
}
|
||||
|
||||
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
|
||||
match self.phase.get() {
|
||||
ScreencastPhase::SourcesSelected => {}
|
||||
let restore_data = match self.phase.get() {
|
||||
ScreencastPhase::SourcesSelected(s) => s.restore_data.take(),
|
||||
_ => {
|
||||
self.kill();
|
||||
reply.err("Session is not in the correct phase for starting");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
let request_obj = match self.state.dbus.add_object(req.handle.to_string()) {
|
||||
Ok(r) => r,
|
||||
Ok(r) => Rc::new(r),
|
||||
Err(_) => {
|
||||
self.kill();
|
||||
reply.err("Request handle is not unique");
|
||||
|
|
@ -527,10 +547,20 @@ impl ScreencastSession {
|
|||
}
|
||||
});
|
||||
}
|
||||
let reply = Rc::new(reply);
|
||||
self.restore(&request_obj, &reply, restore_data, None);
|
||||
}
|
||||
|
||||
fn start_interactive_selection(
|
||||
self: &Rc<Self>,
|
||||
request_obj: &Rc<DbusObject>,
|
||||
reply: &Rc<PendingReply<StartReply<'static>>>,
|
||||
restore_data: Option<RestoreData>,
|
||||
) {
|
||||
let guis = CopyHashMap::new();
|
||||
for dpy in self.state.displays.lock().values() {
|
||||
if dpy.outputs.len() > 0 {
|
||||
guis.set(dpy.id, SelectionGui::new(self, dpy));
|
||||
guis.set(dpy.id, SelectionGui::new(self, dpy, restore_data.is_some()));
|
||||
}
|
||||
}
|
||||
if guis.is_empty() {
|
||||
|
|
@ -542,12 +572,120 @@ impl ScreencastSession {
|
|||
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
|
||||
core: SelectingScreencastCore {
|
||||
session: self.clone(),
|
||||
request_obj: Rc::new(request_obj),
|
||||
reply: Rc::new(reply),
|
||||
request_obj: request_obj.clone(),
|
||||
reply: reply.clone(),
|
||||
},
|
||||
guis,
|
||||
restore_data: Cell::new(restore_data),
|
||||
})));
|
||||
}
|
||||
|
||||
fn restore(
|
||||
self: &Rc<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 {
|
||||
|
|
@ -760,3 +898,122 @@ fn get_session<T>(
|
|||
}
|
||||
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)]
|
||||
enum ButtonRole {
|
||||
Restore,
|
||||
Accept,
|
||||
SelectWorkspace,
|
||||
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 text = if app.is_empty() {
|
||||
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());
|
||||
*label.text.borrow_mut() = text;
|
||||
let restore_button = static_button(surface, ButtonRole::Restore, "Restore Session");
|
||||
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
|
||||
let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace");
|
||||
let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window");
|
||||
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
||||
for button in [
|
||||
&restore_button,
|
||||
&accept_button,
|
||||
&workspace_button,
|
||||
&window_button,
|
||||
|
|
@ -88,6 +91,10 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
|||
button.border.set(2.0);
|
||||
button.padding.set(5.0);
|
||||
}
|
||||
restore_button.bg_color.set(Color::from_rgb(170, 170, 200));
|
||||
restore_button
|
||||
.bg_hover_color
|
||||
.set(Color::from_rgb(170, 170, 255));
|
||||
for button in [&accept_button, &workspace_button, &window_button] {
|
||||
button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
|
||||
|
|
@ -101,7 +108,11 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
|||
flow.cross_align.set(Align::Center);
|
||||
flow.in_margin.set(V_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() {
|
||||
elements.push(workspace_button);
|
||||
}
|
||||
|
|
@ -124,7 +135,7 @@ impl OverlayWindowOwner for SelectionGuiSurface {
|
|||
}
|
||||
|
||||
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 {
|
||||
screencast_session: ss.clone(),
|
||||
dpy: dpy.clone(),
|
||||
|
|
@ -136,7 +147,7 @@ impl SelectionGui {
|
|||
output: output.clone(),
|
||||
overlay: OverlayWindow::new(output),
|
||||
});
|
||||
let element = create_accept_gui(&sgs);
|
||||
let element = create_accept_gui(&sgs, for_restore);
|
||||
sgs.overlay.data.content.set(Some(element));
|
||||
gui.dpy
|
||||
.windows
|
||||
|
|
@ -153,7 +164,10 @@ impl ButtonOwner for StaticButton {
|
|||
return;
|
||||
}
|
||||
match self.role {
|
||||
ButtonRole::Accept | ButtonRole::SelectWorkspace | ButtonRole::SelectWindow => {
|
||||
ButtonRole::Restore
|
||||
| ButtonRole::Accept
|
||||
| ButtonRole::SelectWorkspace
|
||||
| ButtonRole::SelectWindow => {
|
||||
log::info!("User has accepted the request");
|
||||
let selecting = match self.surface.gui.screencast_session.phase.get() {
|
||||
ScreencastPhase::Selecting(selecting) => selecting,
|
||||
|
|
@ -163,7 +177,14 @@ impl ButtonOwner for StaticButton {
|
|||
gui.kill(false);
|
||||
}
|
||||
let dpy = &self.surface.output.dpy;
|
||||
if self.role == ButtonRole::Accept {
|
||||
if self.role == ButtonRole::Restore {
|
||||
selecting.core.session.restore(
|
||||
&selecting.core.request_obj,
|
||||
&selecting.core.reply,
|
||||
selecting.restore_data.take().map(Ok),
|
||||
Some(self.surface.gui.dpy.clone()),
|
||||
);
|
||||
} else if self.role == ButtonRole::Accept {
|
||||
selecting
|
||||
.core
|
||||
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
|
||||
|
|
@ -186,6 +207,7 @@ impl ButtonOwner for StaticButton {
|
|||
core: selecting.core.clone(),
|
||||
dpy: dpy.clone(),
|
||||
selector: selector.clone(),
|
||||
restoring: false,
|
||||
});
|
||||
selector.owner.set(Some(selecting.clone()));
|
||||
self.surface
|
||||
|
|
@ -206,6 +228,15 @@ impl ButtonOwner for StaticButton {
|
|||
impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
|
||||
fn done(&self, tl: Option<Rc<UsrJayToplevel>>) {
|
||||
let Some(tl) = tl else {
|
||||
if self.restoring {
|
||||
log::warn!("Could not restore session because toplevel no longer exists");
|
||||
self.core.session.start_interactive_selection(
|
||||
&self.core.request_obj,
|
||||
&self.core.reply,
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
log::info!("User has aborted the selection");
|
||||
self.core.session.kill();
|
||||
return;
|
||||
|
|
@ -252,7 +283,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
|
|||
}
|
||||
};
|
||||
self.core
|
||||
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws));
|
||||
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
arrayvec::ArrayString,
|
||||
rand::{thread_rng, Rng},
|
||||
serde::{de, Deserialize, Deserializer, Serialize, Serializer},
|
||||
std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
num::ParseIntError,
|
||||
|
|
@ -46,6 +47,26 @@ impl Debug for Opaque {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Opaque {
|
||||
fn serialize<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 {
|
||||
type Err = OpaqueError;
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ impl UsrJayCompositor {
|
|||
jo
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
|
||||
let ww = Rc::new(UsrJayWorkspaceWatcher {
|
||||
id: self.con.id(),
|
||||
|
|
@ -143,6 +142,22 @@ impl UsrJayCompositor {
|
|||
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> {
|
||||
let sc = Rc::new(UsrJaySelectWorkspace {
|
||||
id: self.con.id(),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ use {
|
|||
object::Version,
|
||||
utils::clonecell::CloneCell,
|
||||
wire::{jay_select_workspace::*, JaySelectWorkspaceId},
|
||||
wl_usr::{usr_ifs::usr_jay_workspace::UsrJayWorkspace, usr_object::UsrObject, UsrCon},
|
||||
wl_usr::{
|
||||
usr_ifs::usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner},
|
||||
usr_object::UsrObject,
|
||||
UsrCon,
|
||||
},
|
||||
},
|
||||
std::{convert::Infallible, rc::Rc},
|
||||
};
|
||||
|
|
@ -30,20 +34,30 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn selected(&self, ev: Selected, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
fn selected(&self, ev: Selected, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let tl = Rc::new(UsrJayWorkspace {
|
||||
id: ev.id,
|
||||
con: self.con.clone(),
|
||||
owner: Default::default(),
|
||||
version: self.version,
|
||||
linear_id: Default::default(),
|
||||
output: Default::default(),
|
||||
name: Default::default(),
|
||||
});
|
||||
self.con.add_object(tl.clone());
|
||||
tl.owner.set(Some(slf.clone()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl UsrJayWorkspaceOwner for UsrJaySelectWorkspace {
|
||||
fn done(&self, ws: &Rc<UsrJayWorkspace>) {
|
||||
ws.owner.take();
|
||||
match self.owner.get() {
|
||||
Some(owner) => owner.done(ev.output, Some(tl)),
|
||||
_ => self.con.remove_obj(&*tl),
|
||||
Some(owner) => owner.done(ws.output.get(), Some(ws.clone())),
|
||||
_ => self.con.remove_obj(&**ws),
|
||||
}
|
||||
self.con.remove_obj(self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ use {
|
|||
wire::{jay_workspace::*, JayWorkspaceId},
|
||||
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||
},
|
||||
std::{convert::Infallible, rc::Rc},
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
convert::Infallible,
|
||||
rc::Rc,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct UsrJayWorkspace {
|
||||
|
|
@ -13,21 +17,20 @@ pub struct UsrJayWorkspace {
|
|||
pub con: Rc<UsrCon>,
|
||||
pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceOwner>>>,
|
||||
pub version: Version,
|
||||
pub linear_id: Cell<u32>,
|
||||
pub output: Cell<u32>,
|
||||
pub name: RefCell<Option<String>>,
|
||||
}
|
||||
|
||||
pub trait UsrJayWorkspaceOwner {
|
||||
fn linear_id(self: Rc<Self>, ev: &LinearId) {
|
||||
let _ = ev;
|
||||
fn destroyed(&self, ws: &UsrJayWorkspace) {
|
||||
let _ = ws;
|
||||
}
|
||||
|
||||
fn name(&self, ev: &Name) {
|
||||
let _ = ev;
|
||||
fn done(&self, ws: &Rc<UsrJayWorkspace>) {
|
||||
let _ = ws;
|
||||
}
|
||||
|
||||
fn destroyed(&self) {}
|
||||
|
||||
fn done(&self) {}
|
||||
|
||||
fn output(self: Rc<Self>, ev: &Output) {
|
||||
let _ = ev;
|
||||
}
|
||||
|
|
@ -41,34 +44,31 @@ impl JayWorkspaceEventHandler for UsrJayWorkspace {
|
|||
type Error = Infallible;
|
||||
|
||||
fn linear_id(&self, ev: LinearId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if let Some(owner) = self.owner.get() {
|
||||
owner.linear_id(&ev);
|
||||
}
|
||||
self.linear_id.set(ev.linear_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self, ev: Name<'_>, _slf: &Rc<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() {
|
||||
owner.name(&ev);
|
||||
owner.destroyed(slf);
|
||||
}
|
||||
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() {
|
||||
owner.destroyed();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn done(&self, _ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if let Some(owner) = self.owner.get() {
|
||||
owner.done();
|
||||
owner.done(slf);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output(&self, ev: Output, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.output.set(ev.global_name);
|
||||
if let Some(owner) = self.owner.get() {
|
||||
owner.output(&ev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher {
|
|||
con: self.con.clone(),
|
||||
owner: Default::default(),
|
||||
version: self.version,
|
||||
linear_id: Default::default(),
|
||||
output: Default::default(),
|
||||
name: Default::default(),
|
||||
});
|
||||
self.con.add_object(jw.clone());
|
||||
if let Some(owner) = self.owner.get() {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use {
|
|||
wire::{wl_output::*, WlOutputId},
|
||||
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||
},
|
||||
std::{convert::Infallible, rc::Rc},
|
||||
std::{cell::RefCell, convert::Infallible, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct UsrWlOutput {
|
||||
|
|
@ -13,6 +13,7 @@ pub struct UsrWlOutput {
|
|||
pub con: Rc<UsrCon>,
|
||||
pub owner: CloneCell<Option<Rc<dyn UsrWlOutputOwner>>>,
|
||||
pub version: Version,
|
||||
pub name: RefCell<Option<String>>,
|
||||
}
|
||||
|
||||
pub trait UsrWlOutputOwner {
|
||||
|
|
@ -71,6 +72,7 @@ impl WlOutputEventHandler for UsrWlOutput {
|
|||
}
|
||||
|
||||
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() {
|
||||
owner.name(&ev);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue