Keyboard: rework key processing semantic

The old eat/pass-on point of view was good when there was only the
focused client to send the key to. But where does the input method
stand? Instead, we now want to know where the key goes, treating river
and clients all equally.

Thanks to ifreund for pointing me to std.BoundedArray which simplifies
some of the logic.
This commit is contained in:
tiosgz 2024-02-18 07:37:28 +00:00
parent a1ce53a998
commit bd52c155ef
2 changed files with 56 additions and 63 deletions

View File

@ -31,63 +31,52 @@ const InputDevice = @import("InputDevice.zig");
const log = std.log.scoped(.keyboard); const log = std.log.scoped(.keyboard);
const EatReason = enum { const KeyConsumer = enum {
/// Not eaten
none,
mapping, mapping,
im_grab, im_grab,
/// Seat's focused client (xdg or layer shell)
focus,
}; };
pub const KeycodeSet = struct { const KeySet = struct {
items: [32]u32 = undefined, const Key = struct {
reason: [32]EatReason = undefined, code: u32,
len: usize = 0, consumer: KeyConsumer,
};
pub fn add(set: *KeycodeSet, new: u32, reason: EatReason) EatReason { items: std.BoundedArray(Key, 32) = .{},
for (set.items[0..set.len]) |item| assert(new != item);
comptime assert(@typeInfo(std.meta.fieldInfo(KeycodeSet, .items).type).Array.len == comptime {
// The same magic number is also used in Seat.keyboardNotifyEnter()
assert(@typeInfo(std.meta.fieldInfo(
std.meta.fieldInfo(KeySet, .items).type,
.buffer,
).type).Array.len ==
@typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).Array.len); @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).Array.len);
if (set.len == set.items.len) {
log.err("KeycodeSet limit reached, code {d} omitted", .{new});
// We can't eat the release, don't eat the press
return .none;
}
set.items[set.len] = new;
set.reason[set.len] = reason;
set.len += 1;
return reason;
} }
pub fn remove(set: *KeycodeSet, old: u32) EatReason { pub fn add(keyset: *KeySet, new: Key) error{Overflow}!void {
for (set.items[0..set.len], set.reason[0..set.len], 0..) |item, reason, idx| { for (keyset.items.constSlice()) |item| assert(new.code != item.code);
if (old == item) {
set.len -= 1;
if (set.len > 0) {
set.items[idx] = set.items[set.len];
set.reason[idx] = set.reason[set.len];
}
return reason; keyset.items.append(new) catch |err| {
} log.err("KeySet limit reached, code {d} omitted", .{new.code});
} return err;
};
return .none;
} }
/// Removes other's contents from set (if present), regardless of reason pub fn remove(keyset: *KeySet, code: u32) ?KeyConsumer {
pub fn subtract(set: *KeycodeSet, other: KeycodeSet) void { for (keyset.items.constSlice(), 0..) |item, idx| {
for (other.items[0..other.len]) |item| _ = set.remove(item); if (item.code == code) return keyset.items.swapRemove(idx).consumer;
}
return null;
} }
}; };
device: InputDevice, device: InputDevice,
/// Pressed keys for which a mapping was triggered on press /// Pressed keys along with where they've been sent
eaten_keycodes: KeycodeSet = .{}, keycodes: KeySet = .{},
key: wl.Listener(*wlr.Keyboard.event.Key) = wl.Listener(*wlr.Keyboard.event.Key).init(handleKey), key: wl.Listener(*wlr.Keyboard.event.Key) = wl.Listener(*wlr.Keyboard.event.Key).init(handleKey),
modifiers: wl.Listener(*wlr.Keyboard) = wl.Listener(*wlr.Keyboard).init(handleModifiers), modifiers: wl.Listener(*wlr.Keyboard) = wl.Listener(*wlr.Keyboard).init(handleModifiers),
@ -191,28 +180,29 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
// the corresponding release event sent to the same client. // the corresponding release event sent to the same client.
// Similarly, no press event means no release event. // Similarly, no press event means no release event.
const eat_reason = blk: { const consumer: KeyConsumer = blk: {
// We can only eat a key on press; never on release // Decision is made on press; release only follows it
if (released) break :blk self.eaten_keycodes.remove(event.keycode); if (released) break :blk self.keycodes.remove(event.keycode) orelse unreachable;
if (self.device.seat.hasMapping(keycode, modifiers, released, xkb_state)) { if (self.device.seat.hasMapping(keycode, modifiers, released, xkb_state)) {
// The key needs to be eaten before the mapping is run // We cannot handle the mapping before we know if the key gets saved,
// Otherwise the mapping may e.g. trigger a focus change which sends an incorrect // in order to pair press/release mappings as expected (and for
// wl_keyboard.enter event since eaten_keycodes has not yet been updated. // consistency and code simplicity)
break :blk self.eaten_keycodes.add(event.keycode, .mapping); break :blk .mapping;
} else if (self.getInputMethodGrab() != null) { } else if (self.getInputMethodGrab() != null) {
break :blk self.eaten_keycodes.add(event.keycode, .im_grab); break :blk .im_grab;
} }
break :blk .none; break :blk .focus;
}; };
switch (eat_reason) { if (!released) self.keycodes.add(.{ .code = event.keycode, .consumer = consumer }) catch {
.none => { // We've run out of capacity and cannot process the key correctly.
const wlr_seat = self.device.seat.wlr_seat; // If wlroots hasn't failed on a similar assertion, this logic needs to be updated.
wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard()); unreachable;
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state); };
},
switch (consumer) {
.mapping => if (!released) { .mapping => if (!released) {
// We handle release mappings separately, regardless of whether press mapping exists // We handle release mappings separately, regardless of whether press mapping exists
const handled = self.device.seat.handleMapping(keycode, modifiers, released, xkb_state); const handled = self.device.seat.handleMapping(keycode, modifiers, released, xkb_state);
@ -222,6 +212,11 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
keyboard_grab.setKeyboard(keyboard_grab.keyboard); keyboard_grab.setKeyboard(keyboard_grab.keyboard);
keyboard_grab.sendKey(event.time_msec, event.keycode, event.state); keyboard_grab.sendKey(event.time_msec, event.keycode, event.state);
}, },
.focus => {
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);
},
} }
// Release mappings don't interact with anything // Release mappings don't interact with anything

View File

@ -304,18 +304,16 @@ pub fn keyboardEnterOrLeave(self: *Self, target_surface: ?*wlr.Surface) void {
fn keyboardNotifyEnter(self: *Self, wlr_surface: *wlr.Surface) void { fn keyboardNotifyEnter(self: *Self, wlr_surface: *wlr.Surface) void {
if (self.wlr_seat.getKeyboard()) |wlr_keyboard| { if (self.wlr_seat.getKeyboard()) |wlr_keyboard| {
var keycodes = Keyboard.KeycodeSet{
.items = wlr_keyboard.keycodes,
.reason = .{.none} ** 32,
.len = wlr_keyboard.num_keycodes,
};
const keyboard: *Keyboard = @ptrFromInt(wlr_keyboard.data); const keyboard: *Keyboard = @ptrFromInt(wlr_keyboard.data);
keycodes.subtract(keyboard.eaten_keycodes);
var keycodes: std.BoundedArray(u32, 32) = .{};
for (keyboard.keycodes.items.constSlice()) |item| {
if (item.consumer == .focus) keycodes.appendAssumeCapacity(item.code);
}
self.wlr_seat.keyboardNotifyEnter( self.wlr_seat.keyboardNotifyEnter(
wlr_surface, wlr_surface,
&keycodes.items, &keycodes.buffer,
keycodes.len, keycodes.len,
&wlr_keyboard.modifiers, &wlr_keyboard.modifiers,
); );