diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 3d23df0e94..df7973909c 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -800,6 +800,9 @@ pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn show_window_menu(&self, _position: Point) {} fn start_window_move(&self) {} fn start_window_resize(&self, _edge: ResizeEdge) {} + fn set_exclusive_zone(&self, _zone: Pixels) {} + #[cfg(all(target_os = "linux", feature = "wayland"))] + fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {} fn set_input_region(&self, _region: Option<&[Bounds]>) {} fn window_decorations(&self) -> Decorations { Decorations::Server diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 15193b3c78..4c8b4651c8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1997,6 +1997,24 @@ impl Window { self.platform_window.request_decorations(decorations); } + /// Set the exclusive zone for a layer-shell surface: how much screen space it + /// reserves so other surfaces avoid occluding it (e.g. a panel reserving space). + /// Positive values reserve that distance from the anchored edge, 0 lets the + /// surface be moved out of others' exclusive zones, and -1 ignores reserved + /// space and may extend under other surfaces. (Wayland layer-shell windows only) + pub fn set_exclusive_zone(&self, zone: Pixels) { + self.platform_window.set_exclusive_zone(zone); + } + + /// Set which anchored edge a layer-shell surface's exclusive zone applies to. + /// This is only needed to disambiguate a corner-anchored surface; otherwise the + /// edge is deduced from the anchor. The edge must be a single edge the surface + /// is anchored to, or it is ignored. (Wayland layer-shell windows only) + #[cfg(all(target_os = "linux", feature = "wayland"))] + pub fn set_exclusive_edge(&self, edge: crate::layer_shell::Anchor) { + self.platform_window.set_exclusive_edge(edge); + } + /// Start a window resize operation (Wayland) pub fn start_window_resize(&self, edge: ResizeEdge) { self.platform_window.start_window_resize(edge); diff --git a/crates/gpui_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index 6d2753e131..993b2ff3fa 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -36,8 +36,10 @@ use gpui::{ PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, Scene, Size, Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls, - WindowDecorations, WindowKind, WindowParams, layer_shell::LayerShellNotSupportedError, - popup::PopupOptions, px, size, + WindowDecorations, WindowKind, WindowParams, + layer_shell::{Anchor, LayerShellNotSupportedError}, + popup::PopupOptions, + px, size, }; use gpui_wgpu::{CompositorGpuHint, WgpuRenderer, WgpuSurfaceConfig, wgpu}; @@ -183,12 +185,12 @@ impl WaylandSurfaceState { } if let Some(exclusive_edge) = options.exclusive_edge { - layer_surface - .set_exclusive_edge(super::layer_shell::wayland_anchor(exclusive_edge)); + Self::apply_exclusive_edge(&layer_surface, options.anchor, exclusive_edge); } return Ok(WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, + anchor: options.anchor, })); } @@ -298,6 +300,7 @@ pub struct WaylandXdgSurfaceState { pub struct WaylandLayerSurfaceState { layer_surface: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + anchor: Anchor, } pub struct WaylandPopupSurfaceState { @@ -452,6 +455,49 @@ impl WaylandSurfaceState { } } + fn set_exclusive_zone(&self, zone: i32) -> bool { + if let WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, .. }) = + self + { + layer_surface.set_exclusive_zone(zone); + true + } else { + false + } + } + + /// An exclusive edge must be a single edge that the surface is anchored to, + /// otherwise the compositor raises a fatal `invalid_exclusive_edge` protocol + /// error. An invalid edge is logged and ignored. Returns whether it applied. + fn apply_exclusive_edge( + layer_surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + anchor: Anchor, + edge: Anchor, + ) -> bool { + if edge.bits().count_ones() == 1 && anchor.contains(edge) { + layer_surface.set_exclusive_edge(super::layer_shell::wayland_anchor(edge)); + true + } else { + log::warn!( + "ignoring exclusive edge {edge:?}: must be a single edge of the surface anchor {anchor:?}" + ); + false + } + } + + fn set_exclusive_edge(&self, edge: Anchor) -> bool { + if let WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { + layer_surface, + anchor, + .. + }) = self + { + Self::apply_exclusive_edge(layer_surface, *anchor, edge) + } else { + false + } + } + fn destroy(&mut self) { match self { WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { @@ -470,7 +516,7 @@ impl WaylandSurfaceState { toplevel.destroy(); xdg_surface.destroy(); } - WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface }) => { + WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, .. }) => { layer_surface.destroy(); } WaylandSurfaceState::Popup(WaylandPopupSurfaceState { @@ -1739,6 +1785,27 @@ impl PlatformWindow for WaylandWindow { } } + fn set_exclusive_zone(&self, zone: Pixels) { + let state = self.borrow(); + if state + .surface_state + .set_exclusive_zone(f32::from(zone) as i32) + { + // Commit to apply it immediately, otherwise it only takes effect + // on the next frame. + state.surface.commit(); + } + } + + fn set_exclusive_edge(&self, edge: Anchor) { + let state = self.borrow(); + if state.surface_state.set_exclusive_edge(edge) { + // Commit to apply it immediately, otherwise it only takes effect + // on the next frame. + state.surface.commit(); + } + } + fn set_input_region(&self, region: Option<&[Bounds]>) { let state = self.borrow(); match region {