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:
parent
2b6f90b12c
commit
bfc5e108e5
2 changed files with 34 additions and 13 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ export namespace Crafter {
|
|||
inline static xkb_keymap* xkb_keymap;
|
||||
inline static wl_compositor* compositor = nullptr;
|
||||
inline static Window* focusedWindow = nullptr;
|
||||
// The window with keyboard focus (wl_keyboard.enter/leave), apart from
|
||||
// the pointer's: a key arrives without the pointer ever having entered
|
||||
// (a touch phone, a key typed while the pointer hovers elsewhere), so the
|
||||
// keyboard handlers route by this and never by focusedWindow.
|
||||
inline static Window* keyboardWindow = nullptr;
|
||||
inline static wl_shm* shm = nullptr;
|
||||
inline static wp_viewporter* wpViewporter = nullptr;
|
||||
inline static xkb_context* xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
|
|
|
|||
Loading…
Reference in a new issue