diff options
| author | Psilocybin78 <101070914+Psilocybin78@users.noreply.github.com> | 2026-04-27 17:39:39 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-27 22:39:39 +0100 |
| commit | c8c66642e42cc5223196a57626b3434bd15cce12 (patch) | |
| tree | a585aaceea2fd1c4cb1deb0d3fb94dc54ab09642 | |
| parent | 5caf1df1af51255fdd114b842c9bcecebcf963b6 (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.cpp | 36 |
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; |
