From 52afd94f6e8f4fb95cdbfd6f97ef107fa0bcf8ff Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 6 May 2024 20:41:10 +0200 Subject: [PATCH] layer-shell: implement popups --- deploy-notes.md | 2 + release-notes.md | 2 + src/client/objects.rs | 9 +- src/ifs/wl_surface/xdg_surface.rs | 40 ++++++- src/ifs/wl_surface/xdg_surface/xdg_popup.rs | 4 +- src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 116 ++++++++++++++++++-- src/renderer.rs | 22 ++-- src/tree/display.rs | 4 +- src/tree/output.rs | 81 +++++++++----- 9 files changed, 224 insertions(+), 56 deletions(-) diff --git a/deploy-notes.md b/deploy-notes.md index 5d11b72b..b2c0fa6b 100644 --- a/deploy-notes.md +++ b/deploy-notes.md @@ -1,5 +1,7 @@ # Unreleased +- Needs jay-compositor release. + # 1.2.0 - Needs jay-config release. diff --git a/release-notes.md b/release-notes.md index 54972082..ea2793e5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,7 @@ # Unreleased +- Add remaining layer-shell features. + # 1.2.0 (2024-05-05) - Add support for wp-security-manager-v1. diff --git a/src/client/objects.rs b/src/client/objects.rs index 4616e014..599fafdf 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -17,7 +17,7 @@ use { wl_registry::WlRegistry, wl_seat::{tablet::zwp_tablet_tool_v2::ZwpTabletToolV2, wl_pointer::WlPointer, WlSeat}, wl_surface::{ - xdg_surface::{xdg_toplevel::XdgToplevel, XdgSurface}, + xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel, XdgSurface}, WlSurface, }, wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, @@ -33,8 +33,8 @@ use { wire::{ JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, - WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, - XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, + WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, + XdgPositionerId, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, }, }, @@ -66,6 +66,7 @@ pub struct Objects { pub jay_toplevels: CopyHashMap>, pub drm_lease_outputs: CopyHashMap>, pub tablet_tools: CopyHashMap>, + pub xdg_popups: CopyHashMap>, ids: RefCell>, } @@ -98,6 +99,7 @@ impl Objects { jay_toplevels: Default::default(), drm_lease_outputs: Default::default(), tablet_tools: Default::default(), + xdg_popups: Default::default(), ids: RefCell::new(vec![]), } } @@ -134,6 +136,7 @@ impl Objects { self.jay_toplevels.clear(); self.drm_lease_outputs.clear(); self.tablet_tools.clear(); + self.xdg_popups.clear(); } pub fn id(&self, client_data: &Client) -> Result diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 5117b6a9..b88c0d9a 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -19,8 +19,13 @@ use { rect::Rect, tree::{FindTreeResult, FoundNode, OutputNode, StackedNode, WorkspaceNode}, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, - linkedlist::LinkedNode, numcell::NumCell, option_ext::OptionExt, + clonecell::CloneCell, + copyhashmap::CopyHashMap, + hash_map_ext::HashMapExt, + linkedlist::{LinkedList, LinkedNode}, + numcell::NumCell, + option_ext::OptionExt, + rc_eq::rc_eq, }, wire::{xdg_surface::*, WlSurfaceId, XdgPopupId, XdgSurfaceId}, }, @@ -66,6 +71,7 @@ pub struct XdgSurface { extents: Cell, pub absolute_desired_extents: Cell, ext: CloneCell>>, + popup_display_stack: CloneCell>>>, popups: CopyHashMap>, pub workspace: CloneCell>>, pub tracker: Tracker, @@ -111,7 +117,12 @@ impl XdgPopupParent for Popup { if wl.is_none() { self.popup.xdg.set_workspace(&ws); *wl = Some(ws.stacked.add_last(self.popup.clone())); - *dl = Some(state.root.stacked.add_last(self.popup.clone())); + *dl = Some( + self.parent + .popup_display_stack + .get() + .add_last(self.popup.clone()), + ); state.tree_changed(); self.popup.set_visible(self.parent.surface.visible.get()); } @@ -171,6 +182,7 @@ impl XdgSurface { extents: Cell::new(Default::default()), absolute_desired_extents: Cell::new(Default::default()), ext: Default::default(), + popup_display_stack: CloneCell::new(surface.client.state.root.stacked.clone()), popups: Default::default(), workspace: Default::default(), tracker: Default::default(), @@ -272,6 +284,19 @@ impl XdgSurface { p.xdg_surface.get_or_insert_default_ext() }) } + + pub fn set_popup_stack(&self, stack: &Rc>>) { + let prev = self.popup_display_stack.set(stack.clone()); + if rc_eq(&prev, stack) { + return; + } + for popup in self.popups.lock().values() { + if let Some(dl) = &*popup.display_link.borrow() { + stack.add_last_existing(dl); + } + popup.popup.xdg.set_popup_stack(stack); + } + } } impl XdgSurfaceRequestHandler for XdgSurface { @@ -416,14 +441,17 @@ impl XdgSurface { } fn restack_popups(&self) { - let state = &self.surface.client.state; + if self.popups.is_empty() { + return; + } + let stack = self.popup_display_stack.get(); for popup in self.popups.lock().values() { if let Some(dl) = &*popup.display_link.borrow() { - state.root.stacked.add_last_existing(dl); + stack.add_last_existing(dl); } popup.popup.xdg.restack_popups(); } - state.tree_changed(); + self.surface.client.state.tree_changed(); } } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 7e716bf1..05d58c8a 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -47,7 +47,7 @@ pub struct XdgPopup { pub id: XdgPopupId, node_id: PopupId, pub xdg: Rc, - pub(super) parent: CloneCell>>, + pub(in super::super) parent: CloneCell>>, relative_position: Cell, pos: RefCell, pub tracker: Tracker, @@ -273,7 +273,7 @@ impl Object for XdgPopup { } } -simple_add_obj!(XdgPopup); +dedicated_add_obj!(XdgPopup, XdgPopupId, xdg_popups); impl Node for XdgPopup { fn node_id(&self) -> NodeId { diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index cd531698..08f4547e 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -4,21 +4,32 @@ use { ifs::{ wl_output::OutputGlobalOpt, wl_seat::NodeSeatState, - wl_surface::{PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError}, + wl_surface::{ + xdg_surface::xdg_popup::{XdgPopup, XdgPopupParent}, + PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError, + }, zwlr_layer_shell_v1::{ZwlrLayerShellV1, OVERLAY}, }, leaks::Tracker, object::Object, rect::Rect, renderer::Renderer, - tree::{FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor}, + tree::{ + FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode, + StackedNode, + }, utils::{ - bitflags::BitflagsExt, linkedlist::LinkedNode, numcell::NumCell, option_ext::OptionExt, + bitflags::BitflagsExt, + copyhashmap::CopyHashMap, + hash_map_ext::HashMapExt, + linkedlist::{LinkedList, LinkedNode}, + numcell::NumCell, + option_ext::OptionExt, }, - wire::{zwlr_layer_surface_v1::*, WlSurfaceId, ZwlrLayerSurfaceV1Id}, + wire::{zwlr_layer_surface_v1::*, WlSurfaceId, XdgPopupId, ZwlrLayerSurfaceV1Id}, }, std::{ - cell::{Cell, RefMut}, + cell::{Cell, RefCell, RefMut}, ops::Deref, rc::Rc, }, @@ -60,6 +71,7 @@ pub struct ZwlrLayerSurfaceV1 { last_configure: Cell<(i32, i32)>, exclusive_edge: Cell>, exclusive_size: Cell, + popups: CopyHashMap>, } #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] @@ -96,6 +108,13 @@ pub enum ExclusiveZone { Acquire(i32), } +struct Popup { + parent: Rc, + popup: Rc, + stack: Rc>>, + stack_link: RefCell>>>, +} + #[derive(Default)] pub struct PendingLayerSurfaceData { size: Option<(i32, i32)>, @@ -158,6 +177,7 @@ impl ZwlrLayerSurfaceV1 { last_configure: Default::default(), exclusive_edge: Default::default(), exclusive_size: Default::default(), + popups: Default::default(), } } @@ -259,7 +279,21 @@ impl ZwlrLayerSurfaceV1RequestHandler for ZwlrLayerSurfaceV1 { Ok(()) } - fn get_popup(&self, _req: GetPopup, _slf: &Rc) -> Result<(), Self::Error> { + fn get_popup(&self, req: GetPopup, slf: &Rc) -> Result<(), Self::Error> { + let popup = self.client.lookup(req.popup)?; + if popup.parent.is_some() { + return Err(ZwlrLayerSurfaceV1Error::PopupHasParent); + } + let stack = self.client.state.root.stacked_above_layers.clone(); + popup.xdg.set_popup_stack(&stack); + let user = Rc::new(Popup { + parent: slf.clone(), + popup: popup.clone(), + stack, + stack_link: Default::default(), + }); + popup.parent.set(Some(user.clone())); + self.popups.set(popup.id, user); Ok(()) } @@ -268,6 +302,9 @@ impl ZwlrLayerSurfaceV1RequestHandler for ZwlrLayerSurfaceV1 { } fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + if self.popups.is_not_empty() { + return Err(ZwlrLayerSurfaceV1Error::HasPopups); + } self.destroy_node(); self.client.remove_obj(self)?; self.surface.unset_ext(); @@ -466,10 +503,15 @@ impl ZwlrLayerSurfaceV1 { let a_rect = Rect::new_sized(x1 + rect.x1(), y1 + rect.y1(), width, height).unwrap(); let o_rect = a_rect.move_(-opos.x1(), -opos.y1()); self.output_extents.set(o_rect); - self.pos.set(a_rect); + let a_rect_old = self.pos.replace(a_rect); let abs_x = a_rect.x1() - extents.x1(); let abs_y = a_rect.y1() - extents.y1(); self.surface.set_absolute_position(abs_x, abs_y); + if a_rect_old != a_rect { + for popup in self.popups.lock().values() { + popup.popup.update_absolute_position(); + } + } self.client.state.tree_changed(); } @@ -497,6 +539,19 @@ impl ZwlrLayerSurfaceV1 { node.update_exclusive_zones(); } } + for popup in self.popups.lock().drain_values() { + popup.popup.destroy_node(); + } + } + + pub fn set_visible(&self, visible: bool) { + self.surface.set_visible(visible); + if !visible { + for popup in self.popups.lock().drain_values() { + popup.popup.set_visible(false); + popup.popup.destroy_node(); + } + } } } @@ -605,6 +660,49 @@ impl Node for ZwlrLayerSurfaceV1 { } } +impl XdgPopupParent for Popup { + fn position(&self) -> Rect { + self.parent.pos.get() + } + + fn remove_popup(&self) { + self.parent.popups.remove(&self.popup.id); + } + + fn output(&self) -> Rc { + self.parent.surface.output.get() + } + + fn has_workspace_link(&self) -> bool { + false + } + + fn post_commit(&self) { + let mut dl = self.stack_link.borrow_mut(); + let output = self.output(); + let surface = &self.popup.xdg.surface; + let state = &surface.client.state; + if surface.buffer.is_some() { + if dl.is_none() { + if self.parent.surface.visible.get() { + self.popup.xdg.set_output(&output); + *dl = Some(self.stack.add_last(self.popup.clone())); + state.tree_changed(); + self.popup.set_visible(self.parent.surface.visible.get()); + } else { + self.popup.destroy_node(); + } + } + } else { + if dl.take().is_some() { + drop(dl); + self.popup.set_visible(false); + self.popup.destroy_node(); + } + } + } +} + object_base! { self = ZwlrLayerSurfaceV1; version = self.shell.version; @@ -647,6 +745,10 @@ pub enum ZwlrLayerSurfaceV1Error { TooManyExclusiveEdges, #[error("Exclusive zone not be larger than 65535")] ExcessiveExclusive, + #[error("Popup already has a parent")] + PopupHasParent, + #[error("Surface still has popups")] + HasPopups, } efrom!(ZwlrLayerSurfaceV1Error, WlSurfaceError); efrom!(ZwlrLayerSurfaceV1Error, ClientError); diff --git a/src/renderer.rs b/src/renderer.rs index 80bf94cc..b6901070 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -189,18 +189,24 @@ impl Renderer<'_> { self.render_workspace(&ws, x, y + th + 1); } } - for stacked in self.state.root.stacked.iter() { - if stacked.node_visible() { - self.base.ops.push(GfxApiOpt::Sync); - let pos = stacked.node_absolute_position(); - if pos.intersects(&opos) { - let (x, y) = opos.translate(pos.x1(), pos.y1()); - stacked.node_render(self, x, y, None); + macro_rules! render_stacked { + ($stack:expr) => { + for stacked in $stack.iter() { + if stacked.node_visible() { + self.base.ops.push(GfxApiOpt::Sync); + let pos = stacked.node_absolute_position(); + if pos.intersects(&opos) { + let (x, y) = opos.translate(pos.x1(), pos.y1()); + stacked.node_render(self, x, y, None); + } + } } - } + }; } + render_stacked!(self.state.root.stacked); render_layer!(output.layers[2]); render_layer!(output.layers[3]); + render_stacked!(self.state.root.stacked_above_layers); if let Some(ws) = output.workspace.get() { if ws.render_highlight.get() > 0 { let color = self.state.theme.colors.highlight.get(); diff --git a/src/tree/display.rs b/src/tree/display.rs index 4cd2039f..87c3cf61 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -20,7 +20,8 @@ pub struct DisplayNode { pub id: NodeId, pub extents: Cell, pub outputs: CopyHashMap>, - pub stacked: LinkedList>, + pub stacked: Rc>>, + pub stacked_above_layers: Rc>>, pub seat_state: NodeSeatState, } @@ -31,6 +32,7 @@ impl DisplayNode { extents: Default::default(), outputs: Default::default(), stacked: Default::default(), + stacked_above_layers: Default::default(), seat_state: Default::default(), } } diff --git a/src/tree/output.rs b/src/tree/output.rs index cc6b76fc..93dec03e 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -32,7 +32,7 @@ use { time::Time, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, - NodeId, WorkspaceNode, + NodeId, StackedNode, WorkspaceNode, }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, @@ -605,6 +605,46 @@ impl OutputNode { self.state.tree_changed(); } + fn find_stacked_at( + &self, + stack: &LinkedList>, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { + if stack.is_empty() { + return FindTreeResult::Other; + } + let (x_abs, y_abs) = self.global.pos.get().translate_inv(x, y); + for stacked in stack.rev_iter() { + let ext = stacked.node_absolute_position(); + if !stacked.node_visible() { + continue; + } + if stacked.stacked_absolute_position_constrains_input() && !ext.contains(x_abs, y_abs) { + // TODO: make constrain always true + continue; + } + let (x, y) = ext.translate(x_abs, y_abs); + let idx = tree.len(); + tree.push(FoundNode { + node: stacked.deref().clone().stacked_into_node(), + x, + y, + }); + match stacked.node_find_tree_at(x, y, tree, usecase) { + FindTreeResult::AcceptsInput => { + return FindTreeResult::AcceptsInput; + } + FindTreeResult::Other => { + tree.truncate(idx); + } + } + } + FindTreeResult::Other + } + pub fn find_layer_surface_at( &self, x: i32, @@ -671,7 +711,7 @@ impl OutputNode { macro_rules! set_layer_visible { ($layer:expr, $visible:expr) => { for ls in $layer.iter() { - ls.surface.set_visible($visible); + ls.set_visible($visible); } }; } @@ -833,6 +873,13 @@ impl Node for OutputNode { } } } + { + let res = + self.find_stacked_at(&self.state.root.stacked_above_layers, x, y, tree, usecase); + if res.accepts_input() { + return res; + } + } { let res = self.find_layer_surface_at(x, y, &[OVERLAY, TOP], tree, usecase); if res.accepts_input() { @@ -840,33 +887,9 @@ impl Node for OutputNode { } } { - let (x_abs, y_abs) = self.global.pos.get().translate_inv(x, y); - for stacked in self.state.root.stacked.rev_iter() { - let ext = stacked.node_absolute_position(); - if !stacked.node_visible() { - continue; - } - if stacked.stacked_absolute_position_constrains_input() - && !ext.contains(x_abs, y_abs) - { - // TODO: make constrain always true - continue; - } - let (x, y) = ext.translate(x_abs, y_abs); - let idx = tree.len(); - tree.push(FoundNode { - node: stacked.deref().clone().stacked_into_node(), - x, - y, - }); - match stacked.node_find_tree_at(x, y, tree, usecase) { - FindTreeResult::AcceptsInput => { - return FindTreeResult::AcceptsInput; - } - FindTreeResult::Other => { - tree.truncate(idx); - } - } + let res = self.find_stacked_at(&self.state.root.stacked, x, y, tree, usecase); + if res.accepts_input() { + return res; } } let mut fullscreen = None;