diff options
| author | srdusr <[email protected]> | 2025-02-15 14:56:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-02-15 14:56:00 +0200 |
| commit | 0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd (patch) | |
| tree | 7672d0af277664f457c6c9462925c0005fe35dcf /crates/wayland/src/protocols/layer_shell.rs | |
| parent | 413daa7ba2ea0ebd1424c024fd0566423aaea3f8 (diff) | |
| download | srdwm-0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd.tar.gz srdwm-0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd.zip | |
Checkpoint: preserve all uncommitted rust-rewrite worktree work
Safety commit before reconciling this worktree with main, which has
diverged with its own separate fixes today. Nothing here is reviewed
or curated yet - this exists purely so none of this work can be lost
to a git operation, disk issue, or worktree cleanup while that
reconciliation happens.
Diffstat (limited to 'crates/wayland/src/protocols/layer_shell.rs')
| -rw-r--r-- | crates/wayland/src/protocols/layer_shell.rs | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/crates/wayland/src/protocols/layer_shell.rs b/crates/wayland/src/protocols/layer_shell.rs new file mode 100644 index 0000000..931ddb4 --- /dev/null +++ b/crates/wayland/src/protocols/layer_shell.rs @@ -0,0 +1,117 @@ +//! `zwlr_layer_shell_v1`: panels, bars, launchers, and other output-anchored +//! shell surfaces (AGS's own bar and popups, notably). + +use smithay::desktop::{layer_map_for_output, LayerSurface as DesktopLayerSurface}; +use smithay::reexports::wayland_server::protocol::wl_output::WlOutput; +use smithay::reexports::wayland_server::Resource; +use smithay::wayland::shell::wlr_layer::{Layer, LayerSurface as WlrLayerSurface, WlrLayerShellHandler, WlrLayerShellState}; + +use crate::state::CompState; + +impl WlrLayerShellHandler for CompState { + fn shell_state(&mut self) -> &mut WlrLayerShellState { + &mut self.layer_shell_state + } + + fn new_layer_surface(&mut self, surface: WlrLayerSurface, wl_output: Option<WlOutput>, _layer: Layer, namespace: String) { + // Logged before anything else can early-return or panic: the + // question this answers (see docs/PANEL_SUPPORT_TODO.md) is + // whether this handler is reached AT ALL for a later + // `get_layer_surface` request in a create -> commit -> destroy -> + // commit-again -> create sequence, or whether the client's + // dispatch is already dead by then and this never runs. + log::debug!("layer-shell: new_layer_surface entered, surface={:?} namespace={namespace:?} output_named={}", surface.wl_surface().id(), wl_output.is_some()); + // A client may name the output it wants (a bar on a specific + // monitor); if it doesn't, or names one we don't drive, it lands on + // the primary output. + let output = wl_output + .as_ref() + .and_then(|wl| self.output_for_wl(wl)) + .map(|e| e.output.clone()) + .or_else(|| self.primary_output().cloned()); + let Some(output) = output else { + log::warn!("wayland: layer surface requested but no output exists yet"); + return; + }; + // Paired with the debug log in `ensure_layer_initial_configure`'s + // early return - see docs/PANEL_SUPPORT_TODO.md's P0. This is the + // other half of "did map_layer actually succeed, and on which + // output": logged unconditionally (not just on the error paths + // that already existed) so a real reproduction shows both sides of + // the handoff instead of just the failure. + let surface_id = surface.wl_surface().id(); + let layer_surface = DesktopLayerSurface::new(surface, namespace); + let result = layer_map_for_output(&output).map_layer(&layer_surface); + match &result { + Ok(()) => log::debug!("layer-shell: mapped surface {surface_id:?} onto output {}", output.name()), + Err(e) => log::warn!("wayland: failed to map layer surface {surface_id:?}: {e}"), + } + } + + fn layer_destroyed(&mut self, surface: WlrLayerSurface) { + // See the matching top-of-function log in `new_layer_surface`. + log::debug!("layer-shell: layer_destroyed entered, surface={:?}", surface.wl_surface().id()); + // Marks this surface for the pre-commit-hook workaround in + // `new_surface` - see that function's doc comment for the bug + // this exists to route around. + self.dead_layer_surfaces.insert(surface.wl_surface().clone()); + // GTK (confirmed live via an AGS peer session's WAYLAND_DEBUG trace) + // reuses the same `wl_surface` for the next `get_layer_surface` role + // rather than creating a fresh one - so without this, a "shown at + // least once" flag from *this* role would leak onto the next one + // and make `sync_layer_visibility` treat that new role's own + // ack-configure commit as eligible to hide again, the same bug + // `layer_surfaces_shown_once` exists to prevent, just reintroduced + // for exactly the reused-surface case that matters here. + self.layer_surfaces_shown_once.remove(surface.wl_surface()); + // The surface belongs to exactly one output's map, but which one is + // the client's choice, so unmap from whichever holds it. + for output in self.outputs().cloned().collect::<Vec<_>>() { + let mut map = layer_map_for_output(&output); + let found = map.layers().find(|l| l.layer_surface() == &surface).cloned(); + if let Some(layer) = found { + // Same zone-change recompute `ensure_layer_initial_configure` + // already does on every commit that changes a layer's + // exclusive zone (state/layers.rs) - but this is the *only* place + // that ever runs for a surface that goes away without one + // last commit. `unmap_layer` alone doesn't trigger it: + // reported live (by the AGS peer session) as a bar unmapping + // for fullscreen yet `srd monitors` still reporting the + // bar's old reserved_top for as long as fullscreen lasted -- + // harmless there only because fullscreen targets + // `full_geometry`, which ignores the reservation anyway, but + // wrong for anything that reads `usable`/`geometry` while a + // bar is unmapped without exiting cleanly (a crash, not just + // AGS's cooperative fullscreen hide). + let zone_before = map.non_exclusive_zone(); + map.unmap_layer(&layer); + let zone_after = map.non_exclusive_zone(); + if zone_after != zone_before { + self.pending.borrow_mut().push(srdwm_core::Event::MonitorAdded(srdwm_core::Monitor::new(0, "", srdwm_core::Rect::new(0, 0, 0, 0)))); + } + break; + } + } + // A lock/launcher surface holding exclusive keyboard focus just + // vanished (crash, or a normal close) - don't leave focus dangling + // on a dead surface. + // + // `sync_keyboard_focus`, not a bare `set_keyboard_focus(None)`: an + // `OnDemand` layer surface (a launcher/quicksettings/datemenu + // popup, per `wlr-layer-shell`) claiming focus on click + // (`input.rs`'s `on_demand` branch) goes straight through + // `set_keyboard_focus` without ever touching `WindowManager:: + // focused` - core has no concept of a layer surface to focus, so + // it still correctly points at whatever real toplevel was focused + // before the popup opened. Hardcoding `None` here threw that away + // regardless, leaving nothing focused until the user happened to + // click a window again - reported live (an AGS peer session's + // user) as "focus never returns after using the bar". `sync_ + // keyboard_focus` reads that still-correct core state and restores + // real Wayland focus to it, falling through to `None` only if core + // genuinely has nothing focused either. + if self.seat.get_keyboard().and_then(|k| k.current_focus()).as_ref() == Some(surface.wl_surface()) { + crate::input::sync_keyboard_focus(self); + } + } +} |