summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPsilocybin78 <101070914+Psilocybin78@users.noreply.github.com>2026-04-27 17:39:39 -0400
committerGitHub <noreply@github.com>2026-04-27 22:39:39 +0100
commitc8c66642e42cc5223196a57626b3434bd15cce12 (patch)
treea585aaceea2fd1c4cb1deb0d3fb94dc54ab09642
parent5caf1df1af51255fdd114b842c9bcecebcf963b6 (diff)
seat: fix dropped wl_keyboard.enter after stale keyboardFocusResource (#14143)
* seat: fix dropped wl_keyboard.enter after stale keyboardFocusResource CSeatManager::setKeyboardFocus's leave loop was gated on m_state.keyboardFocusResource (a WP<CWLSeatResource>). That weak pointer can expire between focus cycles — most reliably via the explicit `.reset()` in KeybindManager::passKeys for XWayland targets, but also in practice after certain wl_seat rebind sequences from toolkit-level churn. When the WP is expired, the leave loop's `if (m_state.keyboardFocusResource)` is false, the loop is skipped, and no wl_keyboard.leave is sent for the previous focus. The old client's CWLKeyboardResource keeps m_currentSurface pointing at its old surface. On the next focus back to that surface, CWLKeyboardResource::sendEnter's per-keyboard dedup fires (`if (m_currentSurface == surface) return;`) and the enter is silently dropped. Net result: client believes it's still focused, compositor routes keys via m_state.keyboardFocusResource which now points somewhere else, typed keystrokes silently go to a different window. A mouse drag resyncs because InputManager::processMouseDownNormal → refocus() → mouseMoveUnified(refocus=true) → rawWindowFocus(…, foundSurface) takes a different path that forces the leave/enter pair through. Reported and reproducible in discussion #14141. Fix: iterate PROTO::seat->m_keyboards (the global owning list) in the leave loop and let sendLeave()'s own m_currentSurface gate decide whether each keyboard needs the event. sendLeave() and the sendMods(0,…) that precedes it are already gated on m_currentSurface being set, so iterating every keyboard is a no-op for any keyboard that wasn't on the previous focus — identical semantics to the old code in the healthy case, correct in the broken case. Enter loop unchanged: it's driven by surf->client() which is a cached wl_client* on the surface resource, stable across cycles. Verified against the repro in #14141 (two wev instances cycled via `hyprctl dispatch focuswindow`). Before the fix both windows get stuck in a "I am focused" state after ~3 cycles. After the fix leave/enter pair each dispatch cleanly. * seat: apply same leave-loop fix to setPointerFocus Identical failure mode to the keyboard case in the previous commit: setPointerFocus's leave loop is gated on m_state.pointerFocusResource, which can expire between focus changes. When it does, the leave is skipped, the previously-focused client's wl_pointer m_currentSurface stays stuck, and the next sendEnter's per-pointer dedup silently drops the enter. sendPointerButton also early-returns on `if (!m_state.pointerFocusResource)`, so the visible symptom is "a click on a taskbar / dock / menu button doesn't register after a layer-shell Overlay surface appeared or disappeared, until the user moves the mouse." Motion re-syncs focus via InputManager::mouseMoveUnified → setPointerFocus with a fresh resource, at which point clicks work again. Fix is the same: iterate PROTO::seat->m_pointers and let sendLeave's m_currentSurface gate decide. No-op for pointers not on the old surface.
-rw-r--r--src/managers/SeatManager.cpp36
1 files changed, 11 insertions, 25 deletions
diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp
index 46ad79e0..f3c485f8 100644
--- a/src/managers/SeatManager.cpp
+++ b/src/managers/SeatManager.cpp
@@ -120,20 +120,14 @@ void CSeatManager::setKeyboardFocus(SP<CWLSurfaceResource> surf) {
m_listeners.keyboardSurfaceDestroy.reset();
- if (m_state.keyboardFocusResource) {
- auto client = m_state.keyboardFocusResource->client();
- for (auto const& s : m_seatResources) {
- if (s->resource->client() != client)
- continue;
-
- for (auto const& k : s->resource->m_keyboards) {
- if (!k)
- continue;
+ // Don't gate leave on m_state.keyboardFocusResource — the WP can
+ // be stale. sendLeave no-ops on keyboards without m_currentSurface.
+ for (auto const& k : PROTO::seat->m_keyboards) {
+ if (!k)
+ continue;
- k->sendMods(0, m_keyboard->m_modifiersState.latched, m_keyboard->m_modifiersState.locked, m_keyboard->m_modifiersState.group);
- k->sendLeave();
- }
- }
+ k->sendMods(0, m_keyboard->m_modifiersState.latched, m_keyboard->m_modifiersState.locked, m_keyboard->m_modifiersState.group);
+ k->sendLeave();
}
m_state.keyboardFocusResource.reset();
@@ -232,19 +226,11 @@ void CSeatManager::setPointerFocus(SP<CWLSurfaceResource> surf, const Vector2D&
m_listeners.pointerSurfaceDestroy.reset();
- if (m_state.pointerFocusResource) {
- auto client = m_state.pointerFocusResource->client();
- for (auto const& s : m_seatResources) {
- if (s->resource->client() != client)
- continue;
-
- for (auto const& p : s->resource->m_pointers) {
- if (!p)
- continue;
+ for (auto const& p : PROTO::seat->m_pointers) {
+ if (!p)
+ continue;
- p->sendLeave();
- }
- }
+ p->sendLeave();
}
auto lastPointerFocusResource = m_state.pointerFocusResource;