srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/state/toplevel.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-07-30 22:10:00 +0200
committersrdusr <[email protected]>2024-07-30 22:10:00 +0200
commit38683f069a039f9da6d7ddc64167850560fd07ed (patch)
treec9c240dfb91639c59e27779cd654c96202b25688 /crates/wayland/src/state/toplevel.rs
parent5fe599437ab9d1d9cfa749e861ce2f3acc02aa3c (diff)
downloadsrdwm-38683f069a039f9da6d7ddc64167850560fd07ed.tar.gz
srdwm-38683f069a039f9da6d7ddc64167850560fd07ed.zip
Split crates/wayland/src/state.rs (1276 lines) into state/
Pure reorganization, no behavior change - verified by diffing the function-name and struct-name sets before/after (both identical) plus a full cargo test pass. mod.rs keeps ClientState/OutputEntry/CompState/ WindowAnim/RepeatState's definitions, the key-repeat impl, and the output-lookup impl (all small and tightly coupled to the type definitions), plus mod declarations. The one large impl CompState block (previously ~600 lines) splits by concern: - lifecycle.rs: new_managed_window, set_decorated_from_mode, redraw_decoration_buffer, remove_window. - layers.rs: ensure_layer_initial_configure. - focus.rs: set_keyboard_focus, set_window_activated. - menu.rs: open/close/run_context_menu_action, is_double_click. - geometry.rs: raise_pinned, sync_geometry. - tick.rs: tick_dirty_broadcasts, tick_animations, resync_stacking_order. - toplevel.rs: the with_toplevel_title/app_id/sync_toplevel_metadata free functions. - tests.rs: unsplit, same reasoning as every other split this pass. CompState's fields were already pub(crate) (this crate's existing convention, unlike core's/config's plain-private), so no field- visibility changes were needed - only resync_stacking_order (called from geometry.rs, defined in tick.rs) needed bumping from private to pub(crate), matching that same convention.
Diffstat (limited to 'crates/wayland/src/state/toplevel.rs')
-rw-r--r--crates/wayland/src/state/toplevel.rs82
1 files changed, 82 insertions, 0 deletions
diff --git a/crates/wayland/src/state/toplevel.rs b/crates/wayland/src/state/toplevel.rs
new file mode 100644
index 0000000..2504ad2
--- /dev/null
+++ b/crates/wayland/src/state/toplevel.rs
@@ -0,0 +1,82 @@
+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.
+ if state.wm.borrow_mut().reapply_rules_if_pending(id) {
+ state.redraw_decoration_buffer(id);
+ state.sync_geometry(id);
+ }
+ crate::foreign_toplevel::send_state(state, id);
+ }
+}