srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/state/focus.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/focus.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/focus.rs')
-rw-r--r--crates/wayland/src/state/focus.rs90
1 files changed, 90 insertions, 0 deletions
diff --git a/crates/wayland/src/state/focus.rs b/crates/wayland/src/state/focus.rs
new file mode 100644
index 0000000..523c68d
--- /dev/null
+++ b/crates/wayland/src/state/focus.rs
@@ -0,0 +1,90 @@
+use super::*;
+
+impl CompState {
+
+ /// Sets keyboard focus *and* selection (clipboard/primary) focus to the
+ /// same surface's client. These have to move together: the data-device
+ /// protocols only ever offer the current selection to the client that
+ /// holds selection focus, and only accept `set_selection` from it, so a
+ /// window that has keyboard focus but not data-device focus can neither
+ /// paste nor copy.
+ pub(crate) fn set_keyboard_focus(&mut self, surface: Option<WlSurface>) {
+ // While the session is locked, only the lock surface may hold focus.
+ // This is the single chokepoint that enforces it: without the guard,
+ // any path that focuses a window - notably `new_managed_window`,
+ // i.e. *a client simply opening a window* - would hand keyboard
+ // focus to a normal client at a locked screen. (Caught by an A/B
+ // test that counted `wl_keyboard.enter` events delivered to a client
+ // launched while locked; it was 1 before this guard, 0 after.)
+ if self.lock.locked {
+ // With multiple outputs there is a lock surface per output, and
+ // any of them is a legitimate focus target.
+ let is_lock_surface = surface
+ .as_ref()
+ .is_some_and(|s| self.lock.surfaces.values().any(|lock| lock.wl_surface() == s));
+ if surface.is_some() && !is_lock_surface {
+ return;
+ }
+ }
+ let Some(keyboard) = self.seat.get_keyboard() else { return };
+ let old_focus = keyboard.current_focus();
+ if old_focus == surface {
+ return;
+ }
+ let client = surface.as_ref().and_then(|s| self.dh.get_client(s.id()).ok());
+ set_data_device_focus(&self.dh.clone(), &self.seat.clone(), client.clone());
+ set_primary_focus(&self.dh.clone(), &self.seat.clone(), client);
+ self.update_net_active_window(surface.as_ref());
+ foreign_toplevel::update_activated(self, old_focus.clone(), surface.as_ref());
+ // `xdg_toplevel`'s own `Activated` state - never sent anywhere
+ // before this. `keyboard.set_focus` below only delivers
+ // `wl_keyboard.enter`/`leave`; it says nothing about `xdg_toplevel`
+ // state, which is the signal GTK4/libadwaita's `:backdrop` CSS
+ // pseudo-class (and equivalents elsewhere) actually key off to
+ // decide whether to paint their *own* titlebar as focused. Without
+ // this, no window - including the only one open, with nothing else
+ // it could be losing focus to - ever received it, so any client
+ // that draws its own focus indicator this way looked permanently
+ // unfocused no matter how many other windows existed, even though
+ // real keyboard input (`wl_keyboard.enter`/`keyboard.set_focus`
+ // below) was unaffected and reached the right window regardless.
+ self.set_window_activated(old_focus.as_ref(), false);
+ self.set_window_activated(surface.as_ref(), true);
+ let serial = SERIAL_COUNTER.next_serial();
+ keyboard.set_focus(self, surface, serial);
+ }
+
+ /// Sets `xdg_toplevel`'s `Activated` state (or the X11 equivalent) for
+ /// whichever window owns `surface`, flushing a configure for the native
+ /// case - `DWindow::set_activated` alone only queues the pending state
+ /// for an xdg-shell window; nothing sends it to the client without an
+ /// explicit `send_configure` (the X11 case has no such split, its own
+ /// `set_activated` talks to the X connection directly). A no-op if
+ /// `surface` has no window (e.g. `None`, or a layer surface/popup,
+ /// neither of which are `xdg_toplevel`s) or the state didn't actually
+ /// change.
+ fn set_window_activated(&mut self, surface: Option<&WlSurface>, active: bool) {
+ let Some(id) = surface.and_then(|s| self.surface_to_id.get(s)).copied() else { return };
+ let changed = match self.id_to_window.get(&id) {
+ Some(w) if w.set_activated(active) => {
+ if let Some(toplevel) = w.toplevel() {
+ toplevel.send_configure();
+ }
+ true
+ }
+ _ => false,
+ };
+ // Titlebar text and border colour both dim/brighten on focus (see
+ // `redraw_decoration_buffer`'s `fg`/`effective_border_color`), but
+ // neither was ever actually re-rasterized here before - this call
+ // only ever ran for other reasons (creation, a resize, a rule
+ // re-applying) that happened to also be roughly when focus
+ // changed, in practice, close enough that the gap went unnoticed.
+ // Gated on `changed` so a redundant `set_window_activated(_, false)`
+ // for a surface that was never activated (the common `old_focus ==
+ // None` case) doesn't force a pointless redraw.
+ if changed {
+ self.redraw_decoration_buffer(id);
+ }
+ }
+}