Keyboard: fix key handling/eating logic

As noticed by leon-p, last refactorings made river send a release event
to the client even if the press event has been eaten. In addition, the
introduction of input method support means that we need to remember
*why* we've eaten the key.

Also make KeycodeSet more strict: i am not aware of any case when a
keyboard could have the same key pressed twice (specifically, keyboard
groups have this handled in wlroots), so make the behavior follow a
smaller set of possible scenarios.
This commit is contained in:
tiosgz 2024-01-27 11:43:40 +00:00
parent 69a51cadb4
commit 66f1881a72
3 changed files with 56 additions and 33 deletions

View File

@ -135,33 +135,38 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
if (!released and handleBuiltinMapping(sym)) return;
}
// If we sent a pressed event to the client we should send the matching release event as well,
// even if the release event triggers a mapping or would otherwise be sent to an input method.
const sent_to_client = blk: {
if (released and !self.eaten_keycodes.remove(event.keycode)) {
const wlr_seat = self.device.seat.wlr_seat;
wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard());
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
break :blk true;
} else {
break :blk false;
}
};
// Every sent press event, to a regular client or the IM, should have
// the corresponding release event sent to the same client.
// Similarly, no press event means no release event.
const eat_reason = blk: {
// We can only eat a key on press; never on release
if (released) break :blk self.eaten_keycodes.remove(event.keycode);
if (self.device.seat.handleMapping(keycode, modifiers, released, xkb_state)) {
if (!released) self.eaten_keycodes.add(event.keycode);
} else if (self.getInputMethodGrab()) |keyboard_grab| {
if (!released) self.eaten_keycodes.add(event.keycode);
if (!sent_to_client) {
keyboard_grab.setKeyboard(keyboard_grab.keyboard);
keyboard_grab.sendKey(event.time_msec, event.keycode, event.state);
break :blk self.eaten_keycodes.add(event.keycode, .mapping);
} else if (self.getInputMethodGrab() != null) {
break :blk self.eaten_keycodes.add(event.keycode, .im_grab);
}
} else if (!sent_to_client) {
break :blk .none;
};
switch (eat_reason) {
.none => {
const wlr_seat = self.device.seat.wlr_seat;
wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard());
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
},
.mapping => {},
.im_grab => if (self.getInputMethodGrab()) |keyboard_grab| {
keyboard_grab.setKeyboard(keyboard_grab.keyboard);
keyboard_grab.sendKey(event.time_msec, event.keycode, event.state);
},
}
// Release mappings don't interact with anything
if (released) _ = self.device.seat.handleMapping(keycode, modifiers, released, xkb_state);
}
fn isModifier(keysym: xkb.Keysym) bool {

View File

@ -1,6 +1,6 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2022 The River Developers
// Copyright 2022 - 2024 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -22,36 +22,53 @@ const log = std.log.scoped(.keyboard);
const wlr = @import("wlroots");
const EatReason = enum {
/// Not eaten
none,
mapping,
im_grab,
};
items: [32]u32 = undefined,
reason: [32]EatReason = undefined,
len: usize = 0,
pub fn add(self: *Self, new: u32) void {
for (self.items[0..self.len]) |item| if (new == item) return;
pub fn add(self: *Self, new: u32, reason: EatReason) EatReason {
for (self.items[0..self.len]) |item| assert(new != item);
comptime assert(@typeInfo(std.meta.fieldInfo(Self, .items).type).Array.len ==
@typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).Array.len);
if (self.len == self.items.len) {
log.err("KeycodeSet limit reached, code {d} omitted", .{new});
return;
// We can't eat the release, don't eat the press
return .none;
}
self.items[self.len] = new;
self.reason[self.len] = reason;
self.len += 1;
return reason;
}
pub fn remove(self: *Self, old: u32) bool {
for (self.items[0..self.len], 0..) |item, idx| if (old == item) {
pub fn remove(self: *Self, old: u32) EatReason {
for (self.items[0..self.len], self.reason[0..self.len], 0..) |item, reason, idx| {
if (old == item) {
self.len -= 1;
if (self.len > 0) self.items[idx] = self.items[self.len];
if (self.len > 0) {
self.items[idx] = self.items[self.len];
self.reason[idx] = self.reason[self.len];
}
return true;
};
return reason;
}
}
return false;
return .none;
}
/// Removes other's contents from self (if present)
/// Removes other's contents from self (if present), regardless of reason
pub fn subtract(self: *Self, other: Self) void {
for (other.items[0..other.len]) |item| _ = self.remove(item);
}

View File

@ -307,6 +307,7 @@ fn keyboardNotifyEnter(self: *Self, wlr_surface: *wlr.Surface) void {
if (self.wlr_seat.getKeyboard()) |wlr_keyboard| {
var keycodes = KeycodeSet{
.items = wlr_keyboard.keycodes,
.reason = .{.none} ** 32,
.len = wlr_keyboard.num_keycodes,
};