srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/state/toplevel.rs
blob: 90cf688122670d953714fa28957c3bc5e14e7959 (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
use super::*;


pub(crate) fn with_toplevel_title(surface: &WlSurface) -> Option<String> {
    smithay::wayland::compositor::with_states(surface, |states| {
        states.data_map.get::<XdgToplevelSurfaceData>().map(|d| d.lock().unwrap().title.clone().unwrap_or_default())
    })
}

/// Same pattern as `with_toplevel_title`, for `app_id` - the xdg-shell
/// equivalent of `WM_CLASS`, and what `srd.rule({ class = ... })` matches
/// against (`crates/core/src/rules.rs`).
///
/// Nothing read this before: `new_managed_window` populated `Window.title`
/// from `with_toplevel_title` but never touched `Window.app_id` at all, so
/// every native Wayland window had an *empty* app_id the entire time rules
/// are evaluated (`WindowManager::add_window` matches rules once, at
/// creation). Every `class`-based rule - including the Firefox
/// `decorated = false` one meant to stop srdwm drawing a second titlebar
/// on top of Firefox's own - could therefore never match a native Wayland
/// client, only an XWayland one (`map_window_request` in xwayland.rs does
/// set `app_id` correctly, from `X11Surface::class()`). Reported live as
/// Firefox showing two titlebars, "same for other applications" - exactly
/// what this predicts, since it silently breaks every class-matched rule
/// for every native Wayland app, not just Firefox's.
pub(crate) fn with_toplevel_app_id(surface: &WlSurface) -> Option<String> {
    smithay::wayland::compositor::with_states(surface, |states| {
        states.data_map.get::<XdgToplevelSurfaceData>().map(|d| d.lock().unwrap().app_id.clone().unwrap_or_default())
    })
}

/// Re-reads title/app_id from the surface's own xdg-shell state and updates
/// `Window`/foreign-toplevel listeners if either changed since last read.
///
/// Needed because a fresh toplevel's `new_toplevel` fires at
/// `xdg_surface.get_toplevel()` - role assignment - which for essentially
/// every real client happens *before* the `set_title`/`set_app_id`/first
/// `commit()` sequence that actually supplies them. `new_managed_window`
/// reading those fields at that moment (see its own comment) reliably got
/// nothing: not a race, a fixed ordering every client hits, confirmed live
/// by a peer session capturing the raw `zwlr_foreign_toplevel_handle_v1`
/// wire output and finding `app_id`/`title` empty on every window. Unlike
/// `Window.geometry`/state (double-buffered per xdg-shell semantics),
/// title and app_id are plain immediate-apply requests in smithay's own
/// `XdgToplevelSurfaceData`, so re-reading them on every commit - cheap,
/// and this is already a per-commit hook - keeps `Window.title`/`app_id`
/// (and anything, like `srd.rule`, that reads them) correct from the first
/// real commit onward instead of frozen at an empty initial snapshot.
pub(crate) fn sync_toplevel_metadata(state: &mut CompState, id: WindowId, surface: &WlSurface) {
    let title = with_toplevel_title(surface).unwrap_or_default();
    let app_id = with_toplevel_app_id(surface).unwrap_or_default();
    let changed = {
        let mut wm = state.wm.borrow_mut();
        let Some(w) = wm.window_mut(id) else { return };
        let changed = w.title != title || w.app_id != app_id;
        w.title = title;
        w.app_id = app_id;
        changed
    };
    if changed {
        // Now that `title`/`app_id` are real, give `add_window`'s rule
        // match (which ran before either was set - see `Window::
        // rules_applied`'s doc comment) a real chance. Only the *first*
        // successful evaluation (rule actually matched, `Some` returned)
        // warrants `redraw_decoration_buffer`/`sync_geometry` - unlike
        // `set_decorated_from_mode`, this fires on every subsequent title
        // change too (a page finishing loading, long after the window's
        // own creation), and `sync_geometry` re-stacks the window to the
        // top of smithay's `Space` as a side effect of `map_element`
        // (true regardless of its `activate` argument - there's no
        // "move without restacking" in this smithay version). Calling it
        // unconditionally here silently yanked an unrelated, unfocused
        // window back to the front any time its title happened to
        // update - reported live as an older window jumping in front of
        // a newer, focused one with no user action to explain it.
        let reapplied = state.wm.borrow_mut().reapply_rules_if_pending(id);
        log::warn!("DECO-DIAG sync_toplevel_metadata id={id} changed=true reapplied={reapplied}");
        if reapplied {
            state.redraw_decoration_buffer(id);
            state.sync_geometry(id);
        }
        crate::foreign_toplevel::send_state(state, id);
    }
}