1
0
Fork 0
forked from wry/wry

portal: implement session restoration

This commit is contained in:
Julian Orth 2024-10-10 16:48:46 +02:00
parent 4f431eec5c
commit 3e3532574b
13 changed files with 436 additions and 58 deletions

6
Cargo.lock generated
View file

@ -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",
]

View file

@ -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 }

View file

@ -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)

View file

@ -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,

View file

@ -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());

View file

@ -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,
}

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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(),

View file

@ -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(())
}
}

View file

@ -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);
}

View file

@ -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() {

View file

@ -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);
}