From 0930f0035699e2e504e0d12fd5ec7d00e41f7c98 Mon Sep 17 00:00:00 2001 From: Mostafa Ibrahim <138681028+disluckyguy@users.noreply.github.com> Date: Wed, 28 May 2025 14:53:29 +0300 Subject: [PATCH] implement zwlr_foreign_toplevel_management protocol (#452) * implement zwlr_foreign_toplevel_management protocol * check if initial id is empty --- docs/features.md | 1 + release-notes.md | 1 + src/client.rs | 1 + src/compositor.rs | 1 + src/config/handler.rs | 2 +- src/globals.rs | 2 + src/ifs.rs | 2 + src/ifs/wl_seat.rs | 2 +- src/ifs/wl_surface/x_surface/xwindow.rs | 2 +- .../wl_surface/xdg_surface/xdg_toplevel.rs | 5 + src/ifs/zwlr_foreign_toplevel_handle_v1.rs | 207 ++++++++++++++++++ src/ifs/zwlr_foreign_toplevel_manager_v1.rs | 162 ++++++++++++++ src/state.rs | 5 +- src/tree/toplevel.rs | 142 +++++++++++- src/tree/workspace.rs | 24 +- src/xwayland/xwm.rs | 7 +- wire/zwlr_foreign_toplevel_handle_v1.txt | 79 +++++++ wire/zwlr_foreign_toplevel_manager_v1.txt | 15 ++ 18 files changed, 636 insertions(+), 24 deletions(-) create mode 100644 src/ifs/zwlr_foreign_toplevel_handle_v1.rs create mode 100644 src/ifs/zwlr_foreign_toplevel_manager_v1.rs create mode 100644 wire/zwlr_foreign_toplevel_handle_v1.txt create mode 100644 wire/zwlr_foreign_toplevel_manager_v1.txt diff --git a/docs/features.md b/docs/features.md index 51924ba8..528cd70d 100644 --- a/docs/features.md +++ b/docs/features.md @@ -193,6 +193,7 @@ Jay supports the following wayland protocols: | xdg_wm_base | 7 | | | xdg_wm_dialog_v1 | 1 | | | zwlr_data_control_manager_v1 | 2 | Yes | +| zwlr_foreign_toplevel_manager_v1 | 3 | Yes | | zwlr_layer_shell_v1 | 5 | No[^lsaccess] | | zwlr_screencopy_manager_v1 | 3 | Yes | | zwp_idle_inhibit_manager_v1 | 1 | | diff --git a/release-notes.md b/release-notes.md index 3dcafb8c..8c6b135a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -34,6 +34,7 @@ - Jay now supports being started with CAP_SYS_NICE capabilities to improve responsiveness under high system load. This is described in detail in [setup.md](docs/setup.md). +- Implement wlr-foreign-toplevel-management-v1. # 1.10.0 (2025-04-22) diff --git a/src/client.rs b/src/client.rs index 5fdc960d..89699125 100644 --- a/src/client.rs +++ b/src/client.rs @@ -63,6 +63,7 @@ bitflags! { CAP_DRM_LEASE = 1 << 9, CAP_INPUT_METHOD = 1 << 10, CAP_WORKSPACE = 1 << 11, + CAP_FOREIGN_TOPLEVEL_MANAGER = 1 << 12, } pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0); diff --git a/src/compositor.rs b/src/compositor.rs index c220f868..37c2b001 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -333,6 +333,7 @@ fn start_compositor2( cl_matcher_manager: ClMatcherManager::new(&crit_ids), tl_matcher_manager: TlMatcherManager::new(&crit_ids), caps_thread, + toplevel_managers: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index fba4db9e..4824e0ca 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -415,7 +415,7 @@ impl ConfigProxyHandler { fullscreen: bool, ) -> Result<(), CphError> { let tl = self.get_window(window)?; - tl.tl_set_fullscreen(fullscreen); + tl.tl_set_fullscreen(fullscreen, None); Ok(()) } diff --git a/src/globals.rs b/src/globals.rs index e590ee46..6ce60fa6 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -58,6 +58,7 @@ use { xdg_toplevel_tag_manager_v1::XdgToplevelTagManagerV1Global, xdg_wm_base::XdgWmBaseGlobal, xdg_wm_dialog_v1::XdgWmDialogV1Global, + zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1Global, zwlr_layer_shell_v1::ZwlrLayerShellV1Global, zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1Global, zwp_idle_inhibit_manager_v1::ZwpIdleInhibitManagerV1Global, @@ -202,6 +203,7 @@ impl Globals { add_singleton!(ZwpIdleInhibitManagerV1Global); add_singleton!(ExtIdleNotifierV1Global); add_singleton!(XdgToplevelDragManagerV1Global); + add_singleton!(ZwlrForeignToplevelManagerV1Global); add_singleton!(ZwlrDataControlManagerV1Global); add_singleton!(WpAlphaModifierV1Global); add_singleton!(ZwpVirtualKeyboardManagerV1Global); diff --git a/src/ifs.rs b/src/ifs.rs index 1d50c259..d6af56ed 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -81,6 +81,8 @@ pub mod xdg_toplevel_drag_v1; pub mod xdg_toplevel_tag_manager_v1; pub mod xdg_wm_base; pub mod xdg_wm_dialog_v1; +pub mod zwlr_foreign_toplevel_handle_v1; +pub mod zwlr_foreign_toplevel_manager_v1; pub mod zwlr_layer_shell_v1; pub mod zwlr_screencopy_frame_v1; pub mod zwlr_screencopy_manager_v1; diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 89356b56..9ebf00e1 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -474,7 +474,7 @@ impl WlSeatGlobal { pub fn set_fullscreen(&self, fullscreen: bool) { if let Some(tl) = self.keyboard_node.get().node_toplevel() { - tl.tl_set_fullscreen(fullscreen); + tl.tl_set_fullscreen(fullscreen, None); } } diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index 46b950fb..c454c14f 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -302,7 +302,7 @@ impl Xwindow { Change::Map => { self.data.state.map_tiled(self.clone()); if self.data.info.fullscreen.get() { - self.clone().tl_set_fullscreen(true); + self.clone().tl_set_fullscreen(true, None); } self.data.title_changed(); } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 9a824027..b3562d64 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -18,6 +18,7 @@ use { }, }, xdg_toplevel_drag_v1::XdgToplevelDragV1, + zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, }, leaks::Tracker, object::{Object, Version}, @@ -180,6 +181,10 @@ impl XdgToplevel { self.toplevel_data.send(self.clone(), list); } + pub fn manager_send_to(self: &Rc, manager: &ZwlrForeignToplevelManagerV1) { + self.toplevel_data.manager_send(self.clone(), manager); + } + pub fn send_current_configure(&self) { if self.drag.is_none() { let rect = self.xdg.absolute_desired_extents.get(); diff --git a/src/ifs/zwlr_foreign_toplevel_handle_v1.rs b/src/ifs/zwlr_foreign_toplevel_handle_v1.rs new file mode 100644 index 00000000..d08ff50e --- /dev/null +++ b/src/ifs/zwlr_foreign_toplevel_handle_v1.rs @@ -0,0 +1,207 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_output::WlOutput, + leaks::Tracker, + object::{Object, Version}, + tree::{Direction, OutputNode, ToplevelOpt}, + wire::{ZwlrForeignToplevelHandleV1Id, zwlr_foreign_toplevel_handle_v1::*}, + }, + arrayvec::ArrayVec, + std::rc::Rc, + thiserror::Error, +}; + +const STATE_ACTIVATED: u32 = 2; +const STATE_FULLSCREEN: u32 = 3; + +const FULLSCREEN_SINCE: Version = Version(2); + +pub struct ZwlrForeignToplevelHandleV1 { + pub id: ZwlrForeignToplevelHandleV1Id, + pub client: Rc, + pub tracker: Tracker, + pub toplevel: ToplevelOpt, + pub version: Version, +} + +impl ZwlrForeignToplevelHandleV1 { + fn detach(&self) { + if let Some(tl) = self.toplevel.get() { + tl.tl_data() + .manager_handles + .remove(&(self.client.id, self.id)); + } + } +} + +impl ZwlrForeignToplevelHandleV1RequestHandler for ZwlrForeignToplevelHandleV1 { + type Error = ZwlrForeignToplevelHandleV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_maximized(&self, _req: SetMaximized, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn unset_maximized(&self, _req: UnsetMaximized, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn set_minimized(&self, _req: SetMinimized, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn unset_minimized(&self, _req: UnsetMinimized, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn activate(&self, req: Activate, _slf: &Rc) -> Result<(), Self::Error> { + if let Some(toplevel) = self.toplevel.get() { + if !toplevel.node_visible() { + return Ok(()); + } + let seat = self.client.lookup(req.seat)?; + toplevel.node_do_focus(&seat.global, Direction::Unspecified); + } + Ok(()) + } + + fn close(&self, _req: Close, _slf: &Rc) -> Result<(), Self::Error> { + if let Some(toplevel) = self.toplevel.get() { + toplevel.tl_close(); + } + Ok(()) + } + + fn set_rectangle(&self, _req: SetRectangle, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn set_fullscreen(&self, req: SetFullscreen, _slf: &Rc) -> Result<(), Self::Error> { + if let Some(toplevel) = self.toplevel.get() { + let ws = if req.output.is_some() { + self.client + .lookup(req.output)? + .global + .node() + .map(|node| node.ensure_workspace()) + } else { + None + }; + toplevel.tl_set_fullscreen(true, ws); + } + Ok(()) + } + + fn unset_fullscreen(&self, _req: UnsetFullscreen, _slf: &Rc) -> Result<(), Self::Error> { + if let Some(toplevel) = self.toplevel.get() { + toplevel.tl_set_fullscreen(false, None); + } + Ok(()) + } +} + +impl ZwlrForeignToplevelHandleV1 { + pub fn leave_output(&self, output: &Rc) { + let bindings = output.global.bindings.borrow(); + if let Some(bindings) = bindings.get(&self.client.id) { + for binding in bindings.values() { + self.send_output_leave(binding); + } + } + } + + pub fn enter_output(&self, output: &Rc) { + let bindings = output.global.bindings.borrow(); + if let Some(bindings) = bindings.get(&self.client.id) { + for binding in bindings.values() { + self.send_output_enter(binding); + } + } + } + + 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_state(&self, activated: bool, fullscreen: bool) { + let mut state: ArrayVec = ArrayVec::new(); + if activated { + state.push(STATE_ACTIVATED); + } + if self.version >= FULLSCREEN_SINCE { + if fullscreen { + state.push(STATE_FULLSCREEN); + } + } + self.client.event(State { + self_id: self.id, + state: &state, + }); + } + + #[expect(dead_code)] + pub fn send_parent(&self, parent: &Rc) { + self.client.event(Parent { + self_id: self.id, + parent: parent.id, + }); + } + + pub fn send_output_enter(&self, output: &Rc) { + self.client.event(OutputEnter { + self_id: self.id, + output: output.id, + }); + } + + pub fn send_output_leave(&self, output: &Rc) { + self.client.event(OutputLeave { + self_id: self.id, + output: output.id, + }); + } +} + +object_base! { + self = ZwlrForeignToplevelHandleV1; + version = self.version; +} + +impl Object for ZwlrForeignToplevelHandleV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwlrForeignToplevelHandleV1); + +#[derive(Debug, Error)] +pub enum ZwlrForeignToplevelHandleV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwlrForeignToplevelHandleV1Error, ClientError); diff --git a/src/ifs/zwlr_foreign_toplevel_manager_v1.rs b/src/ifs/zwlr_foreign_toplevel_manager_v1.rs new file mode 100644 index 00000000..3c040f8c --- /dev/null +++ b/src/ifs/zwlr_foreign_toplevel_manager_v1.rs @@ -0,0 +1,162 @@ +use { + crate::{ + client::{CAP_FOREIGN_TOPLEVEL_MANAGER, Client, ClientCaps, ClientError}, + globals::{Global, GlobalName}, + ifs::{ + wl_surface::{x_surface::xwindow::Xwindow, xdg_surface::xdg_toplevel::XdgToplevel}, + zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, + }, + leaks::Tracker, + object::{Object, Version}, + tree::{NodeVisitorBase, ToplevelOpt}, + wire::{ZwlrForeignToplevelManagerV1Id, zwlr_foreign_toplevel_manager_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwlrForeignToplevelManagerV1Global { + name: GlobalName, +} + +impl ZwlrForeignToplevelManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ZwlrForeignToplevelManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), ZwlrForeignToplevelManagerV1Error> { + let obj = Rc::new(ZwlrForeignToplevelManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + ZwlrToplevelVisitor { manager: &obj }.visit_display(&client.state.root); + client.state.toplevel_managers.set((client.id, id), obj); + Ok(()) + } +} + +struct ZwlrToplevelVisitor<'a> { + manager: &'a ZwlrForeignToplevelManagerV1, +} + +impl NodeVisitorBase for ZwlrToplevelVisitor<'_> { + fn visit_toplevel(&mut self, node: &Rc) { + node.manager_send_to(self.manager); + } + + fn visit_xwindow(&mut self, node: &Rc) { + node.toplevel_data.manager_send(node.clone(), self.manager); + } +} + +pub struct ZwlrForeignToplevelManagerV1 { + pub id: ZwlrForeignToplevelManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl ZwlrForeignToplevelManagerV1 { + pub fn detach(&self) { + self.client + .state + .toplevel_managers + .remove(&(self.client.id, self.id)); + } +} + +impl ZwlrForeignToplevelManagerV1RequestHandler for ZwlrForeignToplevelManagerV1 { + type Error = ZwlrForeignToplevelManagerV1Error; + + fn stop(&self, _req: Stop, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.send_finished(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +impl ZwlrForeignToplevelManagerV1 { + pub fn send_finished(&self) { + self.client.event(Finished { self_id: self.id }); + } + + pub fn send_handle(&self, handle: &ZwlrForeignToplevelHandleV1) { + self.client.event(Toplevel { + self_id: self.id, + toplevel: handle.id, + }); + } + + pub fn publish_toplevel(&self, tl: ToplevelOpt) -> Option> { + let id = match self.client.new_id() { + Ok(id) => id, + Err(e) => { + self.client.error(e); + return None; + } + }; + let handle = Rc::new(ZwlrForeignToplevelHandleV1 { + id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + toplevel: tl, + }); + track!(self.client, handle); + self.client.add_server_obj(&handle); + self.send_handle(&handle); + Some(handle) + } +} + +global_base!( + ZwlrForeignToplevelManagerV1Global, + ZwlrForeignToplevelManagerV1, + ZwlrForeignToplevelManagerV1Error +); + +impl Global for ZwlrForeignToplevelManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 3 + } + + fn required_caps(&self) -> ClientCaps { + CAP_FOREIGN_TOPLEVEL_MANAGER + } +} + +simple_add_global!(ZwlrForeignToplevelManagerV1Global); + +object_base! { + self = ZwlrForeignToplevelManagerV1; + version = self.version; +} + +impl Object for ZwlrForeignToplevelManagerV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwlrForeignToplevelManagerV1); + +#[derive(Debug, Error)] +pub enum ZwlrForeignToplevelManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwlrForeignToplevelManagerV1Error, ClientError); diff --git a/src/state.rs b/src/state.rs index cfd3f16b..8dc58b59 100644 --- a/src/state.rs +++ b/src/state.rs @@ -65,6 +65,7 @@ use { wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global, wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global, + zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1Global, @@ -104,7 +105,7 @@ use { wheel::Wheel, wire::{ ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayRenderCtxId, JaySeatEventsId, - JayWorkspaceWatcherId, ZwpLinuxDmabufFeedbackV1Id, + JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id, }, xwayland::{self, XWaylandEvent}, }, @@ -240,6 +241,8 @@ pub struct State { pub tray_item_ids: TrayItemIds, pub data_control_device_ids: DataControlDeviceIds, pub workspace_managers: WorkspaceManagerState, + pub toplevel_managers: + CopyHashMap<(ClientId, ZwlrForeignToplevelManagerV1Id), Rc>, pub color_management_enabled: Cell, pub color_manager: Rc, pub float_above_fullscreen: Cell, diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 161dc3e5..56a072e6 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -20,6 +20,8 @@ use { WlSurface, x_surface::xwindow::XwindowData, xdg_surface::xdg_toplevel::XdgToplevelToplevelData, }, + zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, + zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, }, rect::Rect, state::State, @@ -38,11 +40,12 @@ use { }, wire::{ ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayScreencastId, - JayToplevelId, + JayToplevelId, ZwlrForeignToplevelHandleV1Id, }, }, jay_config::{window, window::WindowType}, std::{ + borrow::Borrow, cell::{Cell, RefCell}, ops::Deref, rc::{Rc, Weak}, @@ -53,12 +56,12 @@ tree_id!(ToplevelNodeId); pub trait ToplevelNode: ToplevelNodeBase { fn tl_surface_active_changed(&self, active: bool); - fn tl_set_fullscreen(self: Rc, fullscreen: bool); + fn tl_set_fullscreen(self: Rc, fullscreen: bool, ws: Option>); fn tl_title_changed(&self); fn tl_set_parent(&self, parent: Rc); fn tl_extents_changed(&self); fn tl_set_workspace(&self, ws: &Rc); - fn tl_workspace_output_changed(&self); + fn tl_workspace_output_changed(&self, prev: &Rc, new: &Rc); fn tl_change_extents(self: Rc, rect: &Rect); fn tl_set_visible(&self, visible: bool); fn tl_destroy(&self); @@ -74,10 +77,10 @@ impl ToplevelNode for T { }); } - fn tl_set_fullscreen(self: Rc, fullscreen: bool) { + fn tl_set_fullscreen(self: Rc, fullscreen: bool, ws: Option>) { let data = self.tl_data(); if fullscreen { - if let Some(ws) = data.workspace.get() { + if let Some(ws) = ws.or_else(|| data.workspace.get()) { data.set_fullscreen2(&data.state, self.clone(), &ws); } } else { @@ -132,14 +135,17 @@ impl ToplevelNode for T { let prev = data.workspace.set(Some(ws.clone())); self.tl_set_workspace_ext(ws); self.tl_data().property_changed(TL_CHANGED_WORKSPACE); - let prev_id = prev.map(|p| p.output.get().id); - let new_id = Some(ws.output.get().id); - if prev_id != new_id { - self.tl_workspace_output_changed(); + let prev_output = match &prev { + Some(n) => n.output.get(), + _ => ws.state.dummy_output.get().unwrap(), + }; + let new_output = ws.output.get(); + if prev.is_none() || prev_output.id != new_output.id { + self.tl_workspace_output_changed(&prev_output, &new_output); } } - fn tl_workspace_output_changed(&self) { + fn tl_workspace_output_changed(&self, prev: &Rc, new: &Rc) { let data = self.tl_data(); for sc in data.jay_screencasts.lock().values() { sc.update_latch_listener(); @@ -147,6 +153,13 @@ impl ToplevelNode for T { for sc in data.ext_copy_sessions.lock().values() { sc.update_latch_listener(); } + if prev.id != new.id { + for handle in data.manager_handles.borrow().lock().values() { + handle.leave_output(prev); + handle.enter_output(new); + handle.send_done(); + } + } } fn tl_change_extents(self: Rc, rect: &Rect) { @@ -322,6 +335,8 @@ pub struct ToplevelData { pub identifier: Cell, pub handles: CopyHashMap<(ClientId, ExtForeignToplevelHandleV1Id), Rc>, + pub manager_handles: + CopyHashMap<(ClientId, ZwlrForeignToplevelHandleV1Id), Rc>, pub render_highlight: NumCell, pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc>, pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc>, @@ -372,6 +387,7 @@ impl ToplevelData { app_id: Default::default(), identifier: Cell::new(id), handles: Default::default(), + manager_handles: Default::default(), render_highlight: Default::default(), jay_toplevels: Default::default(), jay_screencasts: Default::default(), @@ -397,6 +413,10 @@ impl ToplevelData { if let Some(parent) = self.parent.get() { parent.node_child_active_changed(tl, active_new, 1); } + for handle in self.manager_handles.borrow().lock().values() { + handle.send_state(active_new, self.is_fullscreen.get()); + handle.send_done(); + } } } @@ -453,6 +473,12 @@ impl ToplevelData { handle.send_closed(); } } + { + let mut manager_handles = self.manager_handles.lock(); + for handle in manager_handles.drain_values() { + handle.send_closed(); + } + } self.detach_node(node); self.property_changed(TL_CHANGED_DESTROYED); } @@ -472,9 +498,29 @@ impl ToplevelData { let id = self.identifier.get().to_string(); let title = self.title.borrow(); let app_id = self.app_id.borrow(); + let activated = self.active(); + let fullscreen = self.is_fullscreen.get(); + let class; + let manager_app_id = match &self.kind { + ToplevelType::XWindow(w) => { + class = w.info.class.borrow(); + class.as_deref().unwrap_or_default() + } + _ => &app_id, + }; for list in self.state.toplevel_lists.lock().values() { self.send_once(&toplevel, list, &id, &title, &app_id); } + for manager in self.state.toplevel_managers.lock().values() { + self.manager_send_once( + &toplevel, + manager, + &title, + manager_app_id, + activated, + fullscreen, + ); + } } pub fn send(&self, toplevel: Rc, list: &ExtForeignToplevelListV1) { @@ -508,12 +554,76 @@ impl ToplevelData { .set((handle.client.id, handle.id), handle.clone()); } + pub fn manager_send( + &self, + toplevel: Rc, + manager: &ZwlrForeignToplevelManagerV1, + ) { + let title = self.title.borrow(); + let activated = self.active(); + let fullscreen = self.is_fullscreen.get(); + let app_id; + let class; + let manager_app_id = match &self.kind { + ToplevelType::XWindow(w) => { + class = w.info.class.borrow(); + class.as_deref().unwrap_or_default() + } + _ => { + app_id = self.app_id.borrow(); + &app_id + } + }; + self.manager_send_once( + &toplevel, + manager, + &title, + manager_app_id, + activated, + fullscreen, + ); + } + + fn manager_send_once( + &self, + toplevel: &Rc, + manager: &ZwlrForeignToplevelManagerV1, + title: &str, + app_id: &str, + activated: bool, + fullscreen: bool, + ) { + let opt = ToplevelOpt { + toplevel: Rc::downgrade(toplevel), + identifier: self.identifier.get(), + }; + let handle = match manager.publish_toplevel(opt) { + None => return, + Some(handle) => handle, + }; + if !app_id.is_empty() { + handle.send_app_id(app_id); + } + if !title.is_empty() { + handle.send_title(title); + } + handle.enter_output(&self.output()); + handle.send_state(activated, fullscreen); + handle.send_done(); + self.manager_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(); } + for handle in self.manager_handles.lock().values() { + handle.send_title(title); + handle.send_done(); + } } pub fn set_app_id(&self, app_id: &str) { @@ -526,6 +636,10 @@ impl ToplevelData { handle.send_app_id(app_id); handle.send_done(); } + for handle in self.manager_handles.lock().values() { + handle.send_app_id(app_id); + handle.send_done(); + } self.property_changed(TL_CHANGED_APP_ID) } @@ -596,6 +710,10 @@ impl ToplevelData { for seat in kb_foci { node.clone().node_do_focus(&seat, Direction::Unspecified); } + for handle in self.manager_handles.lock().values() { + handle.send_state(self.active(), true); + handle.send_done(); + } } pub fn unset_fullscreen(&self, state: &Rc, node: Rc) { @@ -641,6 +759,10 @@ impl ToplevelData { } } fd.placeholder.tl_destroy(); + for handle in self.manager_handles.lock().values() { + handle.send_state(self.active(), false); + handle.send_done(); + } } pub fn set_visible(&self, node: &dyn Node, visible: bool) { diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index e694e5ec..d69a46a9 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -103,7 +103,7 @@ impl WorkspaceNode { } pub fn set_output(&self, output: &Rc) { - self.output.set(output.clone()); + let old = self.output.set(output.clone()); for wh in self.ext_workspaces.lock().values() { wh.handle_new_output(output); } @@ -111,38 +111,44 @@ impl WorkspaceNode { jw.send_output(output); } self.update_has_captures(); - struct OutputSetter<'a>(&'a Rc); + struct OutputSetter<'a> { + old: &'a Rc, + new: &'a Rc, + } impl NodeVisitorBase for OutputSetter<'_> { fn visit_surface(&mut self, node: &Rc) { - node.set_output(self.0); + node.set_output(self.new); } fn visit_container(&mut self, node: &Rc) { - node.tl_workspace_output_changed(); + node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } fn visit_toplevel(&mut self, node: &Rc) { - node.tl_workspace_output_changed(); + node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.after_ws_move(self.0); + node.after_ws_move(self.new); node.node_visit_children(self); } fn visit_xwindow(&mut self, node: &Rc) { - node.tl_workspace_output_changed(); + node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { - node.tl_workspace_output_changed(); + node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } } - let mut visitor = OutputSetter(output); + let mut visitor = OutputSetter { + old: &old, + new: output, + }; self.node_visit_children(&mut visitor); for stacked in self.stacked.iter() { stacked.deref().clone().node_visit(&mut visitor); diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 7d22e3e3..5eb550c8 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -1126,6 +1126,11 @@ impl Wm { let mut buf = vec![]; let property_changed = || { if let Some(window) = data.window.get() { + let class = data.info.class.borrow(); + for handle in window.toplevel_data.manager_handles.lock().values() { + handle.send_app_id(class.as_deref().unwrap_or_default()); + handle.send_done(); + } window.toplevel_data.property_changed(TL_CHANGED_CLASS_INST); } }; @@ -2458,7 +2463,7 @@ impl Wm { } if fullscreen != data.info.fullscreen.get() { if let Some(w) = data.window.get() { - w.tl_set_fullscreen(fullscreen); + w.tl_set_fullscreen(fullscreen, None); } } data.info.fullscreen.set(fullscreen); diff --git a/wire/zwlr_foreign_toplevel_handle_v1.txt b/wire/zwlr_foreign_toplevel_handle_v1.txt new file mode 100644 index 00000000..19d55f5a --- /dev/null +++ b/wire/zwlr_foreign_toplevel_handle_v1.txt @@ -0,0 +1,79 @@ +# requests + +request set_maximized { + +} + +request unset_maximized { + +} + +request set_minimized { + +} + +request unset_minimized { + +} + +request activate { + seat: id(wl_seat) +} + +request close { + +} + +request set_rectangle { + surface: id(wl_surface), + x: i32, + y: i32, + width: i32, + height: i32, +} + +request destroy { + +} + +request set_fullscreen (since = 2) { + output: id(wl_output) +} + +request unset_fullscreen (since = 2) { + +} + +# events + +event title { + title: str, +} + +event app_id { + app_id: str, +} + +event output_enter { + output: id(wl_output), +} + +event output_leave { + output: id(wl_output), +} + +event state { + state: array(u32), +} + +event done { + +} + +event closed { + +} + +event parent (since = 3) { + parent: id(zwlr_foreign_toplevel_handle_v1) +} diff --git a/wire/zwlr_foreign_toplevel_manager_v1.txt b/wire/zwlr_foreign_toplevel_manager_v1.txt new file mode 100644 index 00000000..2a84e47e --- /dev/null +++ b/wire/zwlr_foreign_toplevel_manager_v1.txt @@ -0,0 +1,15 @@ +# requests + +request stop { + +} + +# events + +event toplevel { + toplevel: id(zwlr_foreign_toplevel_handle_v1) +} + +event finished { + +}