1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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);
}
}
}
|