diff --git a/src/compositor.rs b/src/compositor.rs index 8ceeb423..4a46c67e 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -200,6 +200,7 @@ fn start_compositor2( default_workspace_capture: Cell::new(true), default_gfx_api: Cell::new(GfxApi::OpenGl), activation_tokens: Default::default(), + toplevel_lists: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/globals.rs b/src/globals.rs index 3e2a4156..93430b82 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -3,6 +3,7 @@ use { backend::Backend, client::Client, ifs::{ + ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, ipc::{ wl_data_device_manager::WlDataDeviceManagerGlobal, @@ -164,6 +165,7 @@ impl Globals { add_singleton!(WpCursorShapeManagerV1Global); add_singleton!(WpContentTypeManagerV1Global); add_singleton!(XdgActivationV1Global); + add_singleton!(ExtForeignToplevelListV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index bfe9ad91..9c3d565f 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -1,3 +1,5 @@ +pub mod ext_foreign_toplevel_handle_v1; +pub mod ext_foreign_toplevel_list_v1; pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; pub mod ipc; diff --git a/src/ifs/ext_foreign_toplevel_handle_v1.rs b/src/ifs/ext_foreign_toplevel_handle_v1.rs new file mode 100644 index 00000000..a679bcb1 --- /dev/null +++ b/src/ifs/ext_foreign_toplevel_handle_v1.rs @@ -0,0 +1,88 @@ +use { + crate::{ + client::{Client, ClientError}, + leaks::Tracker, + object::Object, + tree::ToplevelNode, + utils::buffd::{MsgParser, MsgParserError}, + wire::{ext_foreign_toplevel_handle_v1::*, ExtForeignToplevelHandleV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtForeignToplevelHandleV1 { + pub id: ExtForeignToplevelHandleV1Id, + pub client: Rc, + pub tracker: Tracker, + pub toplevel: Rc, +} + +impl ExtForeignToplevelHandleV1 { + fn detach(&self) { + self.toplevel + .tl_data() + .handles + .remove(&(self.client.id, self.id)); + } + + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ExtSessionLockV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } + + pub fn send_closed(&self) { + self.client.event(Closed { self_id: self.id }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_title(&self, title: &str) { + self.client.event(Title { + self_id: self.id, + title, + }); + } + + pub fn send_app_id(&self, app_id: &str) { + self.client.event(AppId { + self_id: self.id, + app_id, + }); + } + + pub fn send_identifier(&self, identifier: &str) { + self.client.event(Identifier { + self_id: self.id, + identifier, + }); + } +} + +object_base! { + self = ExtForeignToplevelHandleV1; + + DESTROY => destroy, +} + +impl Object for ExtForeignToplevelHandleV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ExtForeignToplevelHandleV1); + +#[derive(Debug, Error)] +pub enum ExtSessionLockV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtSessionLockV1Error, MsgParserError); +efrom!(ExtSessionLockV1Error, ClientError); diff --git a/src/ifs/ext_foreign_toplevel_list_v1.rs b/src/ifs/ext_foreign_toplevel_list_v1.rs new file mode 100644 index 00000000..09c9d26e --- /dev/null +++ b/src/ifs/ext_foreign_toplevel_list_v1.rs @@ -0,0 +1,172 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::{ + ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + wl_surface::{x_surface::xwindow::Xwindow, xdg_surface::xdg_toplevel::XdgToplevel}, + }, + leaks::Tracker, + object::Object, + tree::{NodeVisitorBase, ToplevelNode}, + utils::buffd::{MsgParser, MsgParserError}, + wire::{ + ext_foreign_toplevel_list_v1::*, ExtForeignToplevelHandleV1Id, + ExtForeignToplevelListV1Id, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtForeignToplevelListV1Global { + pub name: GlobalName, +} + +impl ExtForeignToplevelListV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ExtForeignToplevelListV1Id, + client: &Rc, + _version: u32, + ) -> Result<(), ExtForeignToplevelListV1Error> { + let obj = Rc::new(ExtForeignToplevelListV1 { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + ToplevelVisitor { list: &obj }.visit_display(&client.state.root); + client.state.toplevel_lists.set((client.id, id), obj); + Ok(()) + } +} + +struct ToplevelVisitor<'a> { + list: &'a ExtForeignToplevelListV1, +} + +impl NodeVisitorBase for ToplevelVisitor<'_> { + fn visit_toplevel(&mut self, node: &Rc) { + node.send_to(self.list); + } + + fn visit_xwindow(&mut self, node: &Rc) { + node.toplevel_data.send(node.clone(), self.list); + } +} + +pub struct ExtForeignToplevelListV1 { + pub id: ExtForeignToplevelListV1Id, + pub client: Rc, + pub tracker: Tracker, +} + +impl ExtForeignToplevelListV1 { + fn detach(&self) { + self.client + .state + .toplevel_lists + .remove(&(self.client.id, self.id)); + } + + fn stop(&self, msg: MsgParser<'_, '_>) -> Result<(), ExtForeignToplevelListV1Error> { + let _req: Stop = self.client.parse(self, msg)?; + self.detach(); + self.send_finished(); + Ok(()) + } + + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ExtForeignToplevelListV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } + + fn send_finished(&self) { + self.client.event(Finished { self_id: self.id }) + } + + fn send_handle(&self, handle: &ExtForeignToplevelHandleV1) { + self.client.event(Toplevel { + self_id: self.id, + toplevel: handle.id, + }); + } + + pub fn publish_toplevel( + &self, + tl: &Rc, + ) -> Option> { + let id: ExtForeignToplevelHandleV1Id = match self.client.new_id() { + Ok(i) => i, + Err(e) => { + self.client.error(e); + return None; + } + }; + let handle = Rc::new(ExtForeignToplevelHandleV1 { + id, + client: self.client.clone(), + tracker: Default::default(), + toplevel: tl.clone(), + }); + track!(self.client, handle); + self.client.add_server_obj(&handle); + self.send_handle(&handle); + Some(handle) + } +} + +global_base!( + ExtForeignToplevelListV1Global, + ExtForeignToplevelListV1, + ExtForeignToplevelListV1Error +); + +impl Global for ExtForeignToplevelListV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn secure(&self) -> bool { + true + } +} + +simple_add_global!(ExtForeignToplevelListV1Global); + +object_base! { + self = ExtForeignToplevelListV1; + + STOP => stop, + DESTROY => destroy, +} + +impl Object for ExtForeignToplevelListV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ExtForeignToplevelListV1); + +#[derive(Debug, Error)] +pub enum ExtForeignToplevelListV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtForeignToplevelListV1Error, MsgParserError); +efrom!(ExtForeignToplevelListV1Error, ClientError); diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index b13f044b..12e45b56 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -283,7 +283,10 @@ impl Xwindow { } match map_change { Change::Unmap => self.tl_set_visible(false), - Change::Map => self.tl_set_visible(true), + Change::Map => { + self.tl_set_visible(true); + self.toplevel_data.broadcast(self.clone()); + } Change::None => {} } self.data.state.tree_changed(); diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index eee6daea..5249b715 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -6,6 +6,7 @@ use { cursor::KnownCursor, fixed::Fixed, ifs::{ + ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, wl_seat::{NodeSeatState, SeatId, WlSeatGlobal}, wl_surface::xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt}, }, @@ -134,6 +135,10 @@ impl XdgToplevel { } } + pub fn send_to(self: &Rc, list: &ExtForeignToplevelListV1) { + self.toplevel_data.send(self.clone(), list); + } + pub fn send_current_configure(&self) { let rect = self.xdg.absolute_desired_extents.get(); self.send_configure_checked(rect.width(), rect.height()); @@ -222,13 +227,14 @@ impl XdgToplevel { fn set_title(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgToplevelError> { let req: SetTitle = self.xdg.surface.client.parse(self, parser)?; - *self.toplevel_data.title.borrow_mut() = req.title.to_string(); + self.toplevel_data.set_title(req.title); self.tl_title_changed(); Ok(()) } fn set_app_id(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgToplevelError> { let req: SetAppId = self.xdg.surface.client.parse(self, parser)?; + self.toplevel_data.set_app_id(req.app_id); self.bugs.set(bugs::get(req.app_id)); Ok(()) } @@ -585,6 +591,7 @@ impl XdgSurfaceExt for XdgToplevel { // } // } self.state.tree_changed(); + self.toplevel_data.broadcast(self.clone()); } } diff --git a/src/state.rs b/src/state.rs index 49cfb508..e2c466c0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -18,6 +18,7 @@ use { gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ + ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_session_lock_v1::ExtSessionLockV1, jay_render_ctx::JayRenderCtx, jay_seat_events::JaySeatEvents, @@ -50,7 +51,8 @@ use { video::drm::Drm, wheel::Wheel, wire::{ - JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwpLinuxDmabufFeedbackV1Id, + ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, + ZwpLinuxDmabufFeedbackV1Id, }, xkbcommon::{XkbContext, XkbKeymap}, xwayland::{self, XWaylandEvent}, @@ -136,6 +138,8 @@ pub struct State { pub default_workspace_capture: Cell, pub default_gfx_api: Cell, pub activation_tokens: CopyHashMap, + pub toplevel_lists: + CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc>, } // impl Drop for State { @@ -670,6 +674,7 @@ impl State { self.pending_float_titles.clear(); self.render_ctx_watchers.clear(); self.workspace_watchers.clear(); + self.toplevel_lists.clear(); self.slow_clients.clear(); for (_, h) in self.input_device_handlers.borrow_mut().drain() { h.async_event.clear(); diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index c5bdcceb..d7a7516f 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -33,7 +33,7 @@ impl PlaceholderNode { id: state.node_ids.next(), toplevel: ToplevelData::new( state, - node.tl_data().title.borrow_mut().clone(), + node.tl_data().title.borrow().clone(), node.node_client(), ), destroyed: Default::default(), diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 44c0fddc..f0e36fb4 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -1,11 +1,22 @@ use { crate::{ - client::Client, - ifs::wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId}, + client::{Client, ClientId}, + ifs::{ + ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, + wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId}, + }, rect::Rect, state::State, tree::{ContainingNode, Direction, Node, OutputNode, PlaceholderNode, WorkspaceNode}, - utils::{clonecell::CloneCell, smallmap::SmallMap, threshold_counter::ThresholdCounter}, + utils::{ + clonecell::CloneCell, + copyhashmap::CopyHashMap, + smallmap::SmallMap, + threshold_counter::ThresholdCounter, + toplevel_identifier::{toplevel_identifier, ToplevelIdentifier}, + }, + wire::ExtForeignToplevelHandleV1Id, }, std::{ cell::{Cell, RefCell}, @@ -176,6 +187,10 @@ pub struct ToplevelData { pub seat_state: NodeSeatState, pub wants_attention: Cell, pub requested_attention: Cell, + pub app_id: RefCell, + pub identifier: Cell, + pub handles: + CopyHashMap<(ClientId, ExtForeignToplevelHandleV1Id), Rc>, } impl ToplevelData { @@ -199,6 +214,9 @@ impl ToplevelData { seat_state: Default::default(), wants_attention: Cell::new(false), requested_attention: Cell::new(false), + app_id: Default::default(), + identifier: Cell::new(toplevel_identifier()), + handles: Default::default(), } } @@ -216,6 +234,13 @@ impl ToplevelData { } pub fn destroy_node(&self, node: &dyn Node) { + self.identifier.set(toplevel_identifier()); + { + let mut handles = self.handles.lock(); + for (_, handle) in handles.drain() { + handle.send_closed(); + } + } if let Some(fd) = self.fullscrceen_data.borrow_mut().take() { fd.placeholder.tl_destroy(); } @@ -227,6 +252,58 @@ impl ToplevelData { self.focus_node.clear(); } + pub fn broadcast(&self, toplevel: Rc) { + let id = self.identifier.get().to_string(); + let title = self.title.borrow(); + let app_id = self.app_id.borrow(); + for list in self.state.toplevel_lists.lock().values() { + self.send_once(&toplevel, list, &id, &title, &app_id); + } + } + + pub fn send(&self, toplevel: Rc, list: &ExtForeignToplevelListV1) { + let id = self.identifier.get().to_string(); + let title = self.title.borrow(); + let app_id = self.app_id.borrow(); + self.send_once(&toplevel, list, &id, &title, &app_id); + } + + fn send_once( + &self, + toplevel: &Rc, + list: &ExtForeignToplevelListV1, + id: &str, + title: &str, + app_id: &str, + ) { + let handle = match list.publish_toplevel(toplevel) { + None => return, + Some(handle) => handle, + }; + handle.send_identifier(id); + handle.send_title(title); + handle.send_app_id(app_id); + handle.send_done(); + self.handles + .set((handle.client.id, handle.id), handle.clone()); + } + + pub fn set_title(&self, title: &str) { + *self.title.borrow_mut() = title.to_string(); + for handle in self.handles.lock().values() { + handle.send_title(title); + handle.send_done(); + } + } + + pub fn set_app_id(&self, app_id: &str) { + *self.app_id.borrow_mut() = app_id.to_string(); + for handle in self.handles.lock().values() { + handle.send_app_id(app_id); + handle.send_done(); + } + } + pub fn set_fullscreen( &self, state: &Rc, diff --git a/src/utils.rs b/src/utils.rs index 6f5435fa..aacfb54a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -34,6 +34,7 @@ pub mod stack; pub mod syncqueue; pub mod threshold_counter; pub mod timer; +pub mod toplevel_identifier; pub mod tri; pub mod trim; pub mod unlink_on_drop; diff --git a/src/utils/toplevel_identifier.rs b/src/utils/toplevel_identifier.rs new file mode 100644 index 00000000..6e6a5b43 --- /dev/null +++ b/src/utils/toplevel_identifier.rs @@ -0,0 +1,28 @@ +use { + crate::utils::opaque::{opaque, Opaque, OpaqueError}, + std::{ + fmt::{Display, Formatter}, + str::FromStr, + }, +}; + +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub struct ToplevelIdentifier(Opaque); + +pub fn toplevel_identifier() -> ToplevelIdentifier { + ToplevelIdentifier(opaque()) +} + +impl Display for ToplevelIdentifier { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for ToplevelIdentifier { + type Err = OpaqueError; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse()?)) + } +} diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 84539399..39052f8c 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -1102,7 +1102,7 @@ impl Wm { } let title = buf.as_bstr().to_string(); if let Some(window) = data.window.get() { - *window.toplevel_data.title.borrow_mut() = title.clone(); + window.toplevel_data.set_title(&title); window.tl_title_changed(); } *data.info.title.borrow_mut() = Some(title); diff --git a/wire/ext_foreign_toplevel_handle_v1.txt b/wire/ext_foreign_toplevel_handle_v1.txt new file mode 100644 index 00000000..423b2de9 --- /dev/null +++ b/wire/ext_foreign_toplevel_handle_v1.txt @@ -0,0 +1,24 @@ +# requests + +msg destroy = 0 { +} + +# events + +msg closed = 0 { +} + +msg done = 1 { +} + +msg title = 2 { + title: str, +} + +msg app_id = 3 { + app_id: str, +} + +msg identifier = 4 { + identifier: str, +} diff --git a/wire/ext_foreign_toplevel_list_v1.txt b/wire/ext_foreign_toplevel_list_v1.txt new file mode 100644 index 00000000..45b260b0 --- /dev/null +++ b/wire/ext_foreign_toplevel_list_v1.txt @@ -0,0 +1,16 @@ +# requests + +msg stop = 0 { +} + +msg destroy = 1 { +} + +# events + +msg toplevel = 0 { + toplevel: id(ext_foreign_toplevel_handle_v1), +} + +msg finished = 1 { +}