fix(wayland): route keys by keyboard focus, not pointer focus

keyboard_enter and keyboard_leave were empty and keyboard_key dereferenced
Device::focusedWindow, which only a pointer-enter sets: the first key
event in a window the pointer never entered — every key on a touch phone,
or a key typed while the pointer hovers another window — dereferenced a
null pointer, and keys typed after the pointer left went nowhere.

Keyboard focus is its own thing in Wayland. wl_keyboard.enter now resolves
its surface to the Window into Device::keyboardWindow, leave clears it
together with the held keys (their releases happen elsewhere) and stops
the repeat, and keyboard_key and TickKeyRepeats deliver to that window and
drop a key that has none.
This commit is contained in:
Jorijn van der Graaf 2026-10-17 23:48:01 +02:00
commit bfc5e108e5
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
2 changed files with 34 additions and 13 deletions

View file

@ -441,11 +441,23 @@ void Device::keyboard_keymap(void *data, wl_keyboard *keyboard, uint32_t format,
}
void Device::keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys) {
for (Window* window : windows) {
if (window->surface == surface) {
keyboardWindow = window;
return;
}
}
keyboardWindow = nullptr;
}
void Device::keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) {
// Releases happen elsewhere while the focus is away: nothing we still
// hold is held, and a repeat must not keep firing into a window that
// lost the keyboard.
if (keyboardWindow != nullptr) keyboardWindow->heldKeys.clear();
keyboardWindow = nullptr;
keyRepeat.active = false;
keyRepeat.utf8.clear();
}
void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
@ -456,12 +468,16 @@ void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, ui
KeyCode code = key;
xkb_keycode_t xkbKeycode = key + 8;
// No enter for any of our surfaces (a compositor quirk, or a key for a
// window already gone): nowhere to deliver it.
if (keyboardWindow == nullptr) return;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if (focusedWindow->heldKeys.contains(code)) {
focusedWindow->onRawKeyHold.Invoke(code);
if (keyboardWindow->heldKeys.contains(code)) {
keyboardWindow->onRawKeyHold.Invoke(code);
} else {
focusedWindow->heldKeys.insert(code);
focusedWindow->onRawKeyDown.Invoke(code);
keyboardWindow->heldKeys.insert(code);
keyboardWindow->onRawKeyDown.Invoke(code);
}
std::string buf;
@ -471,7 +487,7 @@ void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, ui
if (n > 0 && (unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) {
buf.resize(n);
utf8 = buf;
focusedWindow->onTextInput.Invoke(utf8);
keyboardWindow->onTextInput.Invoke(utf8);
}
// Replace the active repeat with this key — most recent press wins,
@ -482,8 +498,8 @@ void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, ui
keyRepeat.pressTime = std::chrono::steady_clock::now();
keyRepeat.lastFireTime = keyRepeat.pressTime;
} else {
focusedWindow->heldKeys.erase(code);
focusedWindow->onRawKeyUp.Invoke(code);
keyboardWindow->heldKeys.erase(code);
keyboardWindow->onRawKeyUp.Invoke(code);
// If the released key was the one repeating, stop. Otherwise leave
// the existing repeat alone (user pressed/released a modifier
@ -506,7 +522,7 @@ void Device::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rat
}
void Device::TickKeyRepeats() {
if (!keyRepeat.active || !focusedWindow) return;
if (!keyRepeat.active || !keyboardWindow) return;
if (keyRepeat.rate <= 0) return;
auto now = std::chrono::steady_clock::now();
@ -521,10 +537,10 @@ void Device::TickKeyRepeats() {
// Catch up — emit one event per missed period so a paused frame doesn't
// make the repeat permanently lag behind.
while (now - keyRepeat.lastFireTime >= period) {
focusedWindow->onRawKeyDown.Invoke(keyRepeat.key);
focusedWindow->onRawKeyHold.Invoke(keyRepeat.key);
keyboardWindow->onRawKeyDown.Invoke(keyRepeat.key);
keyboardWindow->onRawKeyHold.Invoke(keyRepeat.key);
if (!keyRepeat.utf8.empty()) {
focusedWindow->onTextInput.Invoke(keyRepeat.utf8);
keyboardWindow->onTextInput.Invoke(keyRepeat.utf8);
}
keyRepeat.lastFireTime += period;
}