diff --git a/Cargo.lock b/Cargo.lock index 1bb184ba..a44b308d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,6 +586,7 @@ dependencies = [ "repc", "rustc-demangle", "serde", + "serde_json", "shaderc", "smallvec", "thiserror", @@ -1067,12 +1068,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap", "itoa", + "memchr", "ryu", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index cb0d0ef1..faccd080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ ash = "0.38.0" gpu-alloc = "0.6.0" gpu-alloc-ash = "0.7.0" serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.128" enum-map = "2.7.3" png = "0.17.13" rustc-demangle = { version = "0.1.24", optional = true } diff --git a/release-notes.md b/release-notes.md index fd89deb5..5c433219 100644 --- a/release-notes.md +++ b/release-notes.md @@ -7,6 +7,7 @@ - Allow X windows to scale themselves. - Implement ext-image-capture-source-v1. - Implement ext-image-copy-capture-v1. +- Implement screencast session restoration. # 1.6.0 (2024-09-25) diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index df3e1f7b..28eab913 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -34,6 +34,7 @@ use { pub const CREATE_EI_SESSION_SINCE: Version = Version(5); pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6); +pub const GET_TOPLEVEL_SINCE: Version = Version(12); pub struct JayCompositorGlobal { name: GlobalName, diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index 3e07e0a4..7561cc37 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -12,8 +12,13 @@ use { PortalState, }, utils::{ - bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, hash_map_ext::HashMapExt, oserror::OsError, + bitflags::BitflagsExt, + clonecell::CloneCell, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + hash_map_ext::HashMapExt, + opaque::{opaque, Opaque}, + oserror::OsError, }, video::drm::Drm, wire::{ @@ -26,6 +31,8 @@ use { usr_jay_output::{UsrJayOutput, UsrJayOutputOwner}, usr_jay_pointer::UsrJayPointer, usr_jay_render_ctx::UsrJayRenderCtxOwner, + usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner}, + usr_jay_workspace_watcher::{UsrJayWorkspaceWatcher, UsrJayWorkspaceWatcherOwner}, usr_linux_dmabuf::UsrLinuxDmabuf, usr_wl_compositor::UsrWlCompositor, usr_wl_output::{UsrWlOutput, UsrWlOutputOwner}, @@ -61,9 +68,11 @@ struct PortalDisplayPrelude { shared_ids!(PortalDisplayId); pub struct PortalDisplay { pub id: PortalDisplayId, + pub unique_id: Opaque, pub con: Rc, pub(super) state: Rc, registry: Rc, + _workspace_watcher: Rc, pub dmabuf: CloneCell>>, pub jc: Rc, @@ -75,6 +84,7 @@ pub struct PortalDisplay { pub outputs: CopyHashMap>, pub seats: CopyHashMap>, + pub workspaces: CopyHashMap>, pub windows: CopyHashMap>, pub screencasts: CopyHashMap>, @@ -243,6 +253,20 @@ impl UsrWlRegistryOwner for PortalDisplay { } } +impl UsrJayWorkspaceWatcherOwner for PortalDisplay { + fn new(self: Rc, ev: Rc, linear_id: u32) { + ev.owner.set(Some(self.clone())); + self.workspaces.set(linear_id, ev); + } +} + +impl UsrJayWorkspaceOwner for PortalDisplay { + fn destroyed(&self, ws: &UsrJayWorkspace) { + self.workspaces.remove(&ws.linear_id.get()); + self.con.remove_obj(ws); + } +} + impl UsrJayOutputOwner for PortalOutput { fn destroyed(&self) { log::info!( @@ -398,12 +422,15 @@ fn finish_display_connect(dpy: Rc) { let comp = get!(comp_opt, WlCompositor); let fsm = get!(fsm_opt, WpFractionalScaleManagerV1); let vp = get!(vp_opt, WpViewporter); + let ww = jc.watch_workspaces(); let dpy = Rc::new(PortalDisplay { id: dpy.state.id(), + unique_id: opaque(), con: dpy.con.clone(), state: dpy.state.clone(), registry: dpy.registry.clone(), + _workspace_watcher: ww.clone(), dmabuf: CloneCell::new(dmabuf_opt), jc, outputs: Default::default(), @@ -416,11 +443,13 @@ fn finish_display_connect(dpy: Rc) { windows: Default::default(), screencasts: Default::default(), remote_desktop_sessions: Default::default(), + workspaces: Default::default(), }); dpy.state.displays.set(dpy.id, dpy.clone()); dpy.con.owner.set(Some(dpy.clone())); dpy.registry.owner.set(Some(dpy.clone())); + ww.owner.set(Some(dpy.clone())); let jrc = dpy.jc.get_render_context(); jrc.owner.set(Some(dpy.clone())); @@ -464,6 +493,7 @@ fn add_output(dpy: &Rc, name: u32, version: u32) { con: dpy.con.clone(), owner: Default::default(), version: Version(version.min(4)), + name: Default::default(), }); dpy.con.add_object(wl.clone()); dpy.registry.request_bind(name, wl.version.0, wl.deref()); diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index 886e7812..605531a3 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -5,7 +5,7 @@ use { allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING}, dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply}, format::{Format, XRGB8888}, - ifs::jay_screencast::CLIENT_BUFFERS_SINCE, + ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE}, pipewire::{ pw_con::PwCon, pw_ifs::pw_client_node::{ @@ -29,6 +29,7 @@ use { copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, + opaque::Opaque, }, video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER}, wire::jay_screencast::Ready, @@ -54,6 +55,7 @@ use { usr_wl_buffer::UsrWlBuffer, }, }, + serde::{Deserialize, Serialize}, std::{ borrow::Cow, cell::{Cell, RefCell}, @@ -77,7 +79,7 @@ pub struct ScreencastSession { #[derive(Clone)] pub enum ScreencastPhase { Init, - SourcesSelected, + SourcesSelected(Rc), Selecting(Rc), SelectingWindow(Rc), SelectingWorkspace(Rc), @@ -88,6 +90,10 @@ pub enum ScreencastPhase { unsafe impl UnsafeCellCloneSafe for ScreencastPhase {} +pub struct SourcesSelectedScreencast { + pub restore_data: Cell>>, +} + #[derive(Clone)] pub struct SelectingScreencastCore { pub session: Rc, @@ -98,12 +104,14 @@ pub struct SelectingScreencastCore { pub struct SelectingScreencast { pub core: SelectingScreencastCore, pub guis: CopyHashMap>, + pub restore_data: Cell>, } pub struct SelectingWindowScreencast { pub core: SelectingScreencastCore, pub dpy: Rc, pub selector: Rc, + pub restoring: bool, } pub struct SelectingWorkspaceScreencast { @@ -123,7 +131,7 @@ pub struct StartingScreencast { pub enum ScreencastTarget { Output(Rc), - Workspace(Rc, Rc), + Workspace(Rc, Rc, bool), Toplevel(Rc), } @@ -171,16 +179,22 @@ impl PwClientNodeOwner for StartingScreencast { DynamicType::U32, DynamicType::Array(Box::new(inner_type.clone())), ]); - let variants = &[DictEntry { + let mut variants = vec![DictEntry { key: "streams".into(), value: Variant::Array( kt, vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])], ), }]; + if let Some(rd) = create_restore_data(&self.dpy, &self.target) { + variants.push(DictEntry { + key: "restore_data".into(), + value: rd, + }); + } self.reply.ok(&StartReply { response: PORTAL_SUCCESS, - results: Cow::Borrowed(variants), + results: Cow::Owned(variants), }); } let mut supported_formats = PwClientNodePortSupportedFormats { @@ -221,7 +235,7 @@ impl PwClientNodeOwner for StartingScreencast { jsc.set_output(&o.jay); jsc.set_allow_all_workspaces(true); } - ScreencastTarget::Workspace(o, ws) => { + ScreencastTarget::Workspace(o, ws, _) => { jsc.set_output(&o.jay); jsc.allow_workspace(ws); } @@ -231,9 +245,10 @@ impl PwClientNodeOwner for StartingScreencast { jsc.configure(); match &self.target { ScreencastTarget::Output(_) => {} - ScreencastTarget::Workspace(_, w) => { + ScreencastTarget::Workspace(_, w, true) => { self.dpy.con.remove_obj(&**w); } + ScreencastTarget::Workspace(_, _, false) => {} ScreencastTarget::Toplevel(t) => { self.dpy.con.remove_obj(&**t); } @@ -439,7 +454,7 @@ impl ScreencastSession { self.state.screencasts.remove(self.session_obj.path()); match self.phase.set(ScreencastPhase::Terminated) { ScreencastPhase::Init => {} - ScreencastPhase::SourcesSelected => {} + ScreencastPhase::SourcesSelected(_) => {} ScreencastPhase::Terminated => {} ScreencastPhase::Selecting(s) => { s.core.reply.err("Session has been terminated"); @@ -461,9 +476,10 @@ impl ScreencastSession { s.dpy.screencasts.remove(self.session_obj.path()); match &s.target { ScreencastTarget::Output(_) => {} - ScreencastTarget::Workspace(_, w) => { + ScreencastTarget::Workspace(_, w, true) => { s.dpy.con.remove_obj(&**w); } + ScreencastTarget::Workspace(_, _, false) => {} ScreencastTarget::Toplevel(t) => { s.dpy.con.remove_obj(&**t); } @@ -482,7 +498,7 @@ impl ScreencastSession { fn dbus_select_sources( self: &Rc, - _req: SelectSources, + req: SelectSources, reply: PendingReply>, ) { match self.phase.get() { @@ -493,7 +509,11 @@ impl ScreencastSession { return; } } - self.phase.set(ScreencastPhase::SourcesSelected); + self.phase.set(ScreencastPhase::SourcesSelected(Rc::new( + SourcesSelectedScreencast { + restore_data: Cell::new(get_restore_data(&req)), + }, + ))); reply.ok(&SelectSourcesReply { response: PORTAL_SUCCESS, results: Default::default(), @@ -501,16 +521,16 @@ impl ScreencastSession { } fn dbus_start(self: &Rc, req: Start<'_>, reply: PendingReply>) { - match self.phase.get() { - ScreencastPhase::SourcesSelected => {} + let restore_data = match self.phase.get() { + ScreencastPhase::SourcesSelected(s) => s.restore_data.take(), _ => { self.kill(); reply.err("Session is not in the correct phase for starting"); return; } - } + }; let request_obj = match self.state.dbus.add_object(req.handle.to_string()) { - Ok(r) => r, + Ok(r) => Rc::new(r), Err(_) => { self.kill(); reply.err("Request handle is not unique"); @@ -527,10 +547,20 @@ impl ScreencastSession { } }); } + let reply = Rc::new(reply); + self.restore(&request_obj, &reply, restore_data, None); + } + + fn start_interactive_selection( + self: &Rc, + request_obj: &Rc, + reply: &Rc>>, + restore_data: Option, + ) { let guis = CopyHashMap::new(); for dpy in self.state.displays.lock().values() { if dpy.outputs.len() > 0 { - guis.set(dpy.id, SelectionGui::new(self, dpy)); + guis.set(dpy.id, SelectionGui::new(self, dpy, restore_data.is_some())); } } if guis.is_empty() { @@ -542,12 +572,120 @@ impl ScreencastSession { .set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast { core: SelectingScreencastCore { session: self.clone(), - request_obj: Rc::new(request_obj), - reply: Rc::new(reply), + request_obj: request_obj.clone(), + reply: reply.clone(), }, guis, + restore_data: Cell::new(restore_data), }))); } + + fn restore( + self: &Rc, + request_obj: &Rc, + reply: &Rc>>, + restore_data: Option>, + display: Option>, + ) { + if let Some(rd) = restore_data { + if let Err(e) = self.try_restore(&request_obj, &reply, rd, display) { + log::error!("Could not restore session: {}", ErrorFmt(e)); + } else { + return; + } + } + self.start_interactive_selection(&request_obj, &reply, None); + } + + fn try_restore( + self: &Rc, + request_obj: &Rc, + reply: &Rc>>, + restore_data: Result, + display: Option>, + ) -> Result<(), RestoreError> { + let rd = restore_data?; + let dpy = if let Some(dpy) = display { + dpy + } else { + let dpy = self + .state + .displays + .lock() + .values() + .find(|d| d.unique_id == rd.display) + .cloned(); + match dpy { + Some(dpy) => dpy, + _ => { + if self.state.displays.len() == 0 { + return Err(RestoreError::UnknownDisplay); + } else if self.state.displays.len() == 1 { + self.state.displays.lock().values().next().unwrap().clone() + } else { + self.start_interactive_selection(&request_obj, &reply, Some(rd)); + return Ok(()); + } + } + } + }; + let start = |target: ScreencastTarget| { + SelectingScreencastCore { + session: self.clone(), + request_obj: request_obj.clone(), + reply: reply.clone(), + } + .starting(&dpy, target); + }; + match &rd.ty { + RestoreDataType::Output(d) => { + let output = dpy + .outputs + .lock() + .values() + .find(|o| o.wl.name.borrow().as_ref() == Some(&d.name)) + .cloned(); + let Some(output) = output else { + return Err(RestoreError::UnknownOutput); + }; + start(ScreencastTarget::Output(output)); + } + RestoreDataType::Workspace(ws) => { + let ws = dpy + .workspaces + .lock() + .values() + .find(|w| w.name.borrow().as_ref() == Some(&ws.name)) + .cloned(); + let Some(ws) = ws else { + return Err(RestoreError::UnknownWorkspace); + }; + let Some(output) = dpy.outputs.get(&ws.output.get()) else { + return Err(RestoreError::UnknownOutput); + }; + start(ScreencastTarget::Workspace(output, ws, false)); + } + RestoreDataType::Toplevel(d) => { + if dpy.jc.version < GET_TOPLEVEL_SINCE { + return Err(RestoreError::GetToplevel); + } + let selector = dpy.jc.get_toplevel(&d.id); + let selecting = Rc::new(SelectingWindowScreencast { + core: SelectingScreencastCore { + session: self.clone(), + request_obj: request_obj.clone(), + reply: reply.clone(), + }, + dpy: dpy.clone(), + selector: selector.clone(), + restoring: true, + }); + selector.owner.set(Some(selecting.clone())); + self.phase.set(ScreencastPhase::SelectingWindow(selecting)); + } + } + Ok(()) + } } impl UsrJayScreencastOwner for StartedScreencast { @@ -760,3 +898,122 @@ fn get_session( } res } + +fn create_restore_data(dpy: &PortalDisplay, rd: &ScreencastTarget) -> Option> { + let rd = RestoreData { + display: dpy.unique_id, + ty: match rd { + ScreencastTarget::Output(o) => RestoreDataType::Output(RestoreDataOutput { + name: o.wl.name.borrow().clone()?, + }), + ScreencastTarget::Workspace(_, w, _) => { + RestoreDataType::Workspace(RestoreDataWorkspace { + name: w.name.borrow().clone()?, + }) + } + ScreencastTarget::Toplevel(tl) => RestoreDataType::Toplevel(RestoreDataToplevel { + id: tl.toplevel_id.borrow().clone()?, + }), + }, + }; + Some(Variant::Struct(vec![ + Variant::String("Jay".into()), + Variant::U32(1), + Variant::Variant(Box::new(Variant::String( + serde_json::to_string(&rd).unwrap().into(), + ))), + ])) +} + +#[derive(Debug, Error)] +pub enum RestoreError { + #[error("DBus restore data is not a struct")] + NotAStruct, + #[error("DBus restore data is not a struct with 3 fields")] + NotLen3, + #[error("DBus restore data first field is not a string")] + FirstNotString, + #[error("DBus restore data second field is not a u32")] + SecondNotU32, + #[error("DBus restore data third field is not a variant")] + ThirdNotVariant, + #[error("DBus restore data third field is not a string")] + ThirdNotString, + #[error("DBus restore data is not for Jay")] + NotJay, + #[error("DBus restore data is not version 1")] + NotVersion1, + #[error("DBus restore data could not be deserialized")] + Parse(#[source] serde_json::Error), + #[error("The display no longer exists")] + UnknownDisplay, + #[error("The output no longer exists")] + UnknownOutput, + #[error("The workspace no longer exists")] + UnknownWorkspace, + #[error("The display does not support toplevel restoration")] + GetToplevel, +} + +fn get_restore_data(req: &SelectSources) -> Option> { + let restore_data = req.options.iter().find(|n| n.key == "restore_data")?; + Some(get_restore_data_(restore_data)) +} + +fn get_restore_data_( + restore_data: &DictEntry, Variant>, +) -> Result { + let Variant::Struct(s) = &restore_data.value else { + return Err(RestoreError::NotAStruct); + }; + if s.len() != 3 { + return Err(RestoreError::NotLen3); + } + let Variant::String(compositor) = &s[0] else { + return Err(RestoreError::FirstNotString); + }; + let Variant::U32(version) = &s[1] else { + return Err(RestoreError::SecondNotU32); + }; + let Variant::Variant(restore_data) = &s[2] else { + return Err(RestoreError::ThirdNotVariant); + }; + let Variant::String(restore_data) = &**restore_data else { + return Err(RestoreError::ThirdNotString); + }; + if compositor != "Jay" { + return Err(RestoreError::NotJay); + } + if *version != 1 { + return Err(RestoreError::NotVersion1); + } + serde_json::from_str(restore_data).map_err(RestoreError::Parse) +} + +#[derive(Serialize, Deserialize)] +pub struct RestoreData { + display: Opaque, + ty: RestoreDataType, +} + +#[derive(Serialize, Deserialize)] +enum RestoreDataType { + Output(RestoreDataOutput), + Workspace(RestoreDataWorkspace), + Toplevel(RestoreDataToplevel), +} + +#[derive(Serialize, Deserialize)] +struct RestoreDataOutput { + name: String, +} + +#[derive(Serialize, Deserialize)] +struct RestoreDataWorkspace { + name: String, +} + +#[derive(Serialize, Deserialize)] +struct RestoreDataToplevel { + id: String, +} diff --git a/src/portal/ptl_screencast/screencast_gui.rs b/src/portal/ptl_screencast/screencast_gui.rs index 9dbf2dbb..290bd6ca 100644 --- a/src/portal/ptl_screencast/screencast_gui.rs +++ b/src/portal/ptl_screencast/screencast_gui.rs @@ -45,6 +45,7 @@ struct StaticButton { #[derive(Copy, Clone, Eq, PartialEq)] enum ButtonRole { + Restore, Accept, SelectWorkspace, SelectWindow, @@ -65,7 +66,7 @@ impl SelectionGui { } } -fn create_accept_gui(surface: &Rc) -> Rc { +fn create_accept_gui(surface: &Rc, for_restore: bool) -> Rc { let app = &surface.gui.screencast_session.app; let text = if app.is_empty() { format!("An application wants to capture the screen") @@ -74,11 +75,13 @@ fn create_accept_gui(surface: &Rc) -> Rc { }; let label = Rc::new(Label::default()); *label.text.borrow_mut() = text; + let restore_button = static_button(surface, ButtonRole::Restore, "Restore Session"); let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output"); let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace"); let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window"); let reject_button = static_button(surface, ButtonRole::Reject, "Reject"); for button in [ + &restore_button, &accept_button, &workspace_button, &window_button, @@ -88,6 +91,10 @@ fn create_accept_gui(surface: &Rc) -> Rc { button.border.set(2.0); button.padding.set(5.0); } + restore_button.bg_color.set(Color::from_rgb(170, 170, 200)); + restore_button + .bg_hover_color + .set(Color::from_rgb(170, 170, 255)); for button in [&accept_button, &workspace_button, &window_button] { button.bg_color.set(Color::from_rgb(170, 200, 170)); button.bg_hover_color.set(Color::from_rgb(170, 255, 170)); @@ -101,7 +108,11 @@ fn create_accept_gui(surface: &Rc) -> Rc { flow.cross_align.set(Align::Center); flow.in_margin.set(V_MARGIN); flow.cross_margin.set(H_MARGIN); - let mut elements: Vec> = vec![label, accept_button]; + let mut elements: Vec> = vec![label]; + if for_restore { + elements.push(restore_button); + } + elements.push(accept_button); if surface.gui.dpy.jc.caps.select_workspace.get() { elements.push(workspace_button); } @@ -124,7 +135,7 @@ impl OverlayWindowOwner for SelectionGuiSurface { } impl SelectionGui { - pub fn new(ss: &Rc, dpy: &Rc) -> Rc { + pub fn new(ss: &Rc, dpy: &Rc, for_restore: bool) -> Rc { let gui = Rc::new(SelectionGui { screencast_session: ss.clone(), dpy: dpy.clone(), @@ -136,7 +147,7 @@ impl SelectionGui { output: output.clone(), overlay: OverlayWindow::new(output), }); - let element = create_accept_gui(&sgs); + let element = create_accept_gui(&sgs, for_restore); sgs.overlay.data.content.set(Some(element)); gui.dpy .windows @@ -153,7 +164,10 @@ impl ButtonOwner for StaticButton { return; } match self.role { - ButtonRole::Accept | ButtonRole::SelectWorkspace | ButtonRole::SelectWindow => { + ButtonRole::Restore + | ButtonRole::Accept + | ButtonRole::SelectWorkspace + | ButtonRole::SelectWindow => { log::info!("User has accepted the request"); let selecting = match self.surface.gui.screencast_session.phase.get() { ScreencastPhase::Selecting(selecting) => selecting, @@ -163,7 +177,14 @@ impl ButtonOwner for StaticButton { gui.kill(false); } let dpy = &self.surface.output.dpy; - if self.role == ButtonRole::Accept { + if self.role == ButtonRole::Restore { + selecting.core.session.restore( + &selecting.core.request_obj, + &selecting.core.reply, + selecting.restore_data.take().map(Ok), + Some(self.surface.gui.dpy.clone()), + ); + } else if self.role == ButtonRole::Accept { selecting .core .starting(dpy, ScreencastTarget::Output(self.surface.output.clone())); @@ -186,6 +207,7 @@ impl ButtonOwner for StaticButton { core: selecting.core.clone(), dpy: dpy.clone(), selector: selector.clone(), + restoring: false, }); selector.owner.set(Some(selecting.clone())); self.surface @@ -206,6 +228,15 @@ impl ButtonOwner for StaticButton { impl UsrJaySelectToplevelOwner for SelectingWindowScreencast { fn done(&self, tl: Option>) { let Some(tl) = tl else { + if self.restoring { + log::warn!("Could not restore session because toplevel no longer exists"); + self.core.session.start_interactive_selection( + &self.core.request_obj, + &self.core.reply, + None, + ); + return; + } log::info!("User has aborted the selection"); self.core.session.kill(); return; @@ -252,7 +283,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast { } }; self.core - .starting(&self.dpy, ScreencastTarget::Workspace(output, ws)); + .starting(&self.dpy, ScreencastTarget::Workspace(output, ws, true)); } } diff --git a/src/utils/opaque.rs b/src/utils/opaque.rs index 82e9744d..6468a8c5 100644 --- a/src/utils/opaque.rs +++ b/src/utils/opaque.rs @@ -1,6 +1,7 @@ use { arrayvec::ArrayString, rand::{thread_rng, Rng}, + serde::{de, Deserialize, Deserializer, Serialize, Serializer}, std::{ fmt::{Debug, Display, Formatter}, num::ParseIntError, @@ -46,6 +47,26 @@ impl Debug for Opaque { } } +impl Serialize for Opaque { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = self.to_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for Opaque { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = <&str>::deserialize(deserializer)?; + Opaque::from_str(s).map_err(de::Error::custom) + } +} + impl FromStr for Opaque { type Err = OpaqueError; diff --git a/src/wl_usr/usr_ifs/usr_jay_compositor.rs b/src/wl_usr/usr_ifs/usr_jay_compositor.rs index 6aaab0b3..8642e533 100644 --- a/src/wl_usr/usr_ifs/usr_jay_compositor.rs +++ b/src/wl_usr/usr_ifs/usr_jay_compositor.rs @@ -96,7 +96,6 @@ impl UsrJayCompositor { jo } - #[expect(dead_code)] pub fn watch_workspaces(&self) -> Rc { let ww = Rc::new(UsrJayWorkspaceWatcher { id: self.con.id(), @@ -143,6 +142,22 @@ impl UsrJayCompositor { sc } + pub fn get_toplevel(&self, id: &str) -> Rc { + let sc = Rc::new(UsrJaySelectToplevel { + id: self.con.id(), + con: self.con.clone(), + owner: Default::default(), + version: self.version, + }); + self.con.request(GetToplevel { + self_id: self.id, + id: sc.id, + toplevel_id: id, + }); + self.con.add_object(sc.clone()); + sc + } + pub fn select_workspace(&self, seat: &UsrWlSeat) -> Rc { let sc = Rc::new(UsrJaySelectWorkspace { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs b/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs index 07153faa..5d59ecd6 100644 --- a/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs +++ b/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs @@ -3,7 +3,11 @@ use { object::Version, utils::clonecell::CloneCell, wire::{jay_select_workspace::*, JaySelectWorkspaceId}, - wl_usr::{usr_ifs::usr_jay_workspace::UsrJayWorkspace, usr_object::UsrObject, UsrCon}, + wl_usr::{ + usr_ifs::usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner}, + usr_object::UsrObject, + UsrCon, + }, }, std::{convert::Infallible, rc::Rc}, }; @@ -30,20 +34,30 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace { Ok(()) } - fn selected(&self, ev: Selected, _slf: &Rc) -> Result<(), Self::Error> { + fn selected(&self, ev: Selected, slf: &Rc) -> Result<(), Self::Error> { let tl = Rc::new(UsrJayWorkspace { id: ev.id, con: self.con.clone(), owner: Default::default(), version: self.version, + linear_id: Default::default(), + output: Default::default(), + name: Default::default(), }); self.con.add_object(tl.clone()); + tl.owner.set(Some(slf.clone())); + Ok(()) + } +} + +impl UsrJayWorkspaceOwner for UsrJaySelectWorkspace { + fn done(&self, ws: &Rc) { + ws.owner.take(); match self.owner.get() { - Some(owner) => owner.done(ev.output, Some(tl)), - _ => self.con.remove_obj(&*tl), + Some(owner) => owner.done(ws.output.get(), Some(ws.clone())), + _ => self.con.remove_obj(&**ws), } self.con.remove_obj(self); - Ok(()) } } diff --git a/src/wl_usr/usr_ifs/usr_jay_workspace.rs b/src/wl_usr/usr_ifs/usr_jay_workspace.rs index d3d183a3..1e88c196 100644 --- a/src/wl_usr/usr_ifs/usr_jay_workspace.rs +++ b/src/wl_usr/usr_ifs/usr_jay_workspace.rs @@ -5,7 +5,11 @@ use { wire::{jay_workspace::*, JayWorkspaceId}, wl_usr::{usr_object::UsrObject, UsrCon}, }, - std::{convert::Infallible, rc::Rc}, + std::{ + cell::{Cell, RefCell}, + convert::Infallible, + rc::Rc, + }, }; pub struct UsrJayWorkspace { @@ -13,21 +17,20 @@ pub struct UsrJayWorkspace { pub con: Rc, pub owner: CloneCell>>, pub version: Version, + pub linear_id: Cell, + pub output: Cell, + pub name: RefCell>, } pub trait UsrJayWorkspaceOwner { - fn linear_id(self: Rc, ev: &LinearId) { - let _ = ev; + fn destroyed(&self, ws: &UsrJayWorkspace) { + let _ = ws; } - fn name(&self, ev: &Name) { - let _ = ev; + fn done(&self, ws: &Rc) { + let _ = ws; } - fn destroyed(&self) {} - - fn done(&self) {} - fn output(self: Rc, ev: &Output) { let _ = ev; } @@ -41,34 +44,31 @@ impl JayWorkspaceEventHandler for UsrJayWorkspace { type Error = Infallible; fn linear_id(&self, ev: LinearId, _slf: &Rc) -> Result<(), Self::Error> { - if let Some(owner) = self.owner.get() { - owner.linear_id(&ev); - } + self.linear_id.set(ev.linear_id); Ok(()) } fn name(&self, ev: Name<'_>, _slf: &Rc) -> Result<(), Self::Error> { + *self.name.borrow_mut() = Some(ev.name.to_string()); + Ok(()) + } + + fn destroyed(&self, _ev: Destroyed, slf: &Rc) -> Result<(), Self::Error> { if let Some(owner) = self.owner.get() { - owner.name(&ev); + owner.destroyed(slf); } Ok(()) } - fn destroyed(&self, _ev: Destroyed, _slf: &Rc) -> Result<(), Self::Error> { + fn done(&self, _ev: Done, slf: &Rc) -> Result<(), Self::Error> { if let Some(owner) = self.owner.get() { - owner.destroyed(); - } - Ok(()) - } - - fn done(&self, _ev: Done, _slf: &Rc) -> Result<(), Self::Error> { - if let Some(owner) = self.owner.get() { - owner.done(); + owner.done(slf); } Ok(()) } fn output(&self, ev: Output, _slf: &Rc) -> Result<(), Self::Error> { + self.output.set(ev.global_name); if let Some(owner) = self.owner.get() { owner.output(&ev); } diff --git a/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs b/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs index 495de4f0..e7f5894e 100644 --- a/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs +++ b/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs @@ -31,6 +31,9 @@ impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher { con: self.con.clone(), owner: Default::default(), version: self.version, + linear_id: Default::default(), + output: Default::default(), + name: Default::default(), }); self.con.add_object(jw.clone()); if let Some(owner) = self.owner.get() { diff --git a/src/wl_usr/usr_ifs/usr_wl_output.rs b/src/wl_usr/usr_ifs/usr_wl_output.rs index c5ad9f60..f4d1ae20 100644 --- a/src/wl_usr/usr_ifs/usr_wl_output.rs +++ b/src/wl_usr/usr_ifs/usr_wl_output.rs @@ -5,7 +5,7 @@ use { wire::{wl_output::*, WlOutputId}, wl_usr::{usr_object::UsrObject, UsrCon}, }, - std::{convert::Infallible, rc::Rc}, + std::{cell::RefCell, convert::Infallible, rc::Rc}, }; pub struct UsrWlOutput { @@ -13,6 +13,7 @@ pub struct UsrWlOutput { pub con: Rc, pub owner: CloneCell>>, pub version: Version, + pub name: RefCell>, } pub trait UsrWlOutputOwner { @@ -71,6 +72,7 @@ impl WlOutputEventHandler for UsrWlOutput { } fn name(&self, ev: Name<'_>, _slf: &Rc) -> Result<(), Self::Error> { + *self.name.borrow_mut() = Some(ev.name.to_string()); if let Some(owner) = self.owner.get() { owner.name(&ev); }