srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/state/layers.rs
blob: 1a02858bd19934acecf674c482af52496f01a7e0 (plain) (blame)
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
use super::*;

impl CompState {

    /// Layer surfaces need a configure sent in direct response to their
    /// first commit (sending it any earlier violates the protocol - see
    /// `smithay::desktop::LayerMap::arrange`'s doc comment on why `arrange`
    /// itself deliberately won't send one). Also the point at which an
    /// `Exclusive`-interactivity layer (e.g. a lock screen, or a launcher
    /// configured to grab all keyboard input) claims keyboard focus, since
    /// its `keyboard_interactivity` isn't reliably known until the client's
    /// state has actually committed.
    pub(crate) fn ensure_layer_initial_configure(&mut self, surface: &WlSurface) {
        // Called unconditionally from `commit()` for every surface in the
        // whole desktop, on every single commit - so before doing anything
        // that scales with output/layer count, a cheap O(1) check: has this
        // surface ever gone through `get_layer_surface` at all? Only that
        // request ever inserts `LayerSurfaceData` into a surface's
        // `data_map` (smithay's own `handlers.rs`), so this is `None` for
        // every ordinary xdg-toplevel/subsurface commit - the overwhelming
        // majority of commits on any real desktop. Skipping straight past
        // the per-output `layer_for_surface` surface-tree walk for all of
        // those is the difference between this function costing something
        // on every single frame any window renders versus only on commits
        // from the handful of surfaces that were ever layer surfaces.
        if with_states(surface, |states| states.data_map.get::<LayerSurfaceData>().is_none()) {
            return;
        }
        // A layer surface lives in exactly one output's `LayerMap` (whichever
        // one `new_layer_surface` mapped it into), so find that output rather
        // than assuming a single global one.
        let found = self.outputs().find_map(|output| {
            let layer = layer_map_for_output(output).layer_for_surface(surface, WindowSurfaceType::TOPLEVEL).cloned();
            layer.map(|l| (output.clone(), l))
        });
        // Not a layer surface (or a destroyed one - see `new_surface`'s
        // pre-commit-hook workaround, which is what stops this from being
        // a protocol error). Every ordinary commit from every window in
        // the desktop passes through here and takes this branch, so this
        // used to log unconditionally during the P0 investigation
        // (docs/PANEL_SUPPORT_TODO.md) - diagnostic purpose long since
        // served, and left running it logged upwards of 15k lines in a few
        // minutes of normal use (every Firefox frame, every terminal
        // redraw, ...), which is real wasted I/O, not just noise.
        let Some((output, layer)) = found else {
            return;
        };

        // Recompute geometry from whatever the client just committed
        // (`set_size`/`set_anchor`/`set_margin`/`set_exclusive_zone` are all
        // double-buffered, applied on this commit) *before* looking at
        // `initial_configure_sent` - `map_layer`'s own `arrange()` call ran
        // before the client had sent any of that, so without this, the
        // first configure would carry stale, pre-request-processed geometry
        // (verified live: wofi's `set_size(420, 550)` was otherwise ignored
        // and it got stuck at the half-output fallback size instead). Every
        // later commit needs the same treatment for live resizes/anchor
        // changes; `arrange()` only actually sends a configure when
        // something changed, so this is a no-op on a commit that didn't
        // touch layer-shell state.
        let zone_before = layer_map_for_output(&output).non_exclusive_zone();
        layer_map_for_output(&output).arrange();
        let zone_after = layer_map_for_output(&output).non_exclusive_zone();
        if zone_after != zone_before {
            // A bar/dock claiming (or releasing) an exclusive zone changes
            // the area core's placement/tiling should actually use --
            // without this, `WindowManager`'s notion of the monitor rect
            // is whatever `Platform::monitors()` returned once at startup
            // (before any layer-shell client had connected and set a real
            // exclusive zone), so every window keeps being placed across
            // the *whole* output including the strip a bar now occupies:
            // new windows spawn with their titlebar directly under the bar,
            // rendered beneath it and unreachable to drag. Reusing
            // `MonitorAdded` here rather than a new event type: main.rs's
            // handler for it already re-queries the full monitor list from
            // the platform rather than trusting the event's payload (see
            // its own comment on why), which is exactly "go recompute the
            // usable area" - the placeholder `Monitor` below is discarded
            // unread on that path.
            self.pending.borrow_mut().push(CoreEvent::MonitorAdded(srdwm_core::Monitor::new(0, "", srdwm_core::Rect::new(0, 0, 0, 0))));
        }

        let initial_configure_sent = with_states(surface, |states| {
            states
                .data_map
                .get::<LayerSurfaceData>()
                .map(|d| d.lock().unwrap().initial_configure_sent)
                .unwrap_or(false)
        });
        if !initial_configure_sent {
            layer.layer_surface().send_configure();
            log::debug!("layer-shell: sent initial configure for surface {:?}", surface.id());
        }

        // Checked on every commit, not just the first: a client can flip
        // `keyboard_interactivity` to `Exclusive` after already being
        // mapped (and this is also, in practice, where a freshly-mapped
        // `Exclusive` surface - e.g. wofi, which requests it from the very
        // first commit - actually gets focus, since `set_keyboard_focus`
        // is idempotent against a surface that's already focused).
        if layer.cached_state().keyboard_interactivity == KeyboardInteractivity::Exclusive {
            self.set_keyboard_focus(Some(surface.clone()));
        }
    }
}