From bd52c155ef2bc3c4ce4f8d2969570aa9800d608d Mon Sep 17 00:00:00 2001 From: tiosgz Date: Sun, 18 Feb 2024 07:37:28 +0000 Subject: [PATCH] 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. --- river/Keyboard.zig | 105 +++++++++++++++++++++------------------------ river/Seat.zig | 14 +++--- 2 files changed, 56 insertions(+), 63 deletions(-) diff --git a/river/Keyboard.zig b/river/Keyboard.zig index 946523b..ae4beb2 100644 --- a/river/Keyboard.zig +++ b/river/Keyboard.zig @@ -31,63 +31,52 @@ const InputDevice = @import("InputDevice.zig"); const log = std.log.scoped(.keyboard); -const EatReason = enum { - /// Not eaten - none, +const KeyConsumer = enum { mapping, im_grab, + /// Seat's focused client (xdg or layer shell) + focus, }; -pub const KeycodeSet = struct { - items: [32]u32 = undefined, - reason: [32]EatReason = undefined, - len: usize = 0, +const KeySet = struct { + const Key = struct { + code: u32, + consumer: KeyConsumer, + }; - pub fn add(set: *KeycodeSet, new: u32, reason: EatReason) EatReason { - for (set.items[0..set.len]) |item| assert(new != item); + items: std.BoundedArray(Key, 32) = .{}, - 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); - - 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 { - for (set.items[0..set.len], set.reason[0..set.len], 0..) |item, reason, idx| { - if (old == item) { - set.len -= 1; - if (set.len > 0) { - set.items[idx] = set.items[set.len]; - set.reason[idx] = set.reason[set.len]; - } + pub fn add(keyset: *KeySet, new: Key) error{Overflow}!void { + for (keyset.items.constSlice()) |item| assert(new.code != item.code); - return reason; - } - } - - return .none; + keyset.items.append(new) catch |err| { + log.err("KeySet limit reached, code {d} omitted", .{new.code}); + return err; + }; } - /// Removes other's contents from set (if present), regardless of reason - pub fn subtract(set: *KeycodeSet, other: KeycodeSet) void { - for (other.items[0..other.len]) |item| _ = set.remove(item); + pub fn remove(keyset: *KeySet, code: u32) ?KeyConsumer { + for (keyset.items.constSlice(), 0..) |item, idx| { + if (item.code == code) return keyset.items.swapRemove(idx).consumer; + } + + return null; } }; device: InputDevice, -/// Pressed keys for which a mapping was triggered on press -eaten_keycodes: KeycodeSet = .{}, +/// Pressed keys along with where they've been sent +keycodes: KeySet = .{}, 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), @@ -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. // 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); + const consumer: KeyConsumer = blk: { + // Decision is made on press; release only follows it + if (released) break :blk self.keycodes.remove(event.keycode) orelse unreachable; if (self.device.seat.hasMapping(keycode, modifiers, released, xkb_state)) { - // The key needs to be eaten before the mapping is run - // Otherwise the mapping may e.g. trigger a focus change which sends an incorrect - // wl_keyboard.enter event since eaten_keycodes has not yet been updated. - break :blk self.eaten_keycodes.add(event.keycode, .mapping); + // We cannot handle the mapping before we know if the key gets saved, + // in order to pair press/release mappings as expected (and for + // consistency and code simplicity) + break :blk .mapping; } 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) { - .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); - }, + if (!released) self.keycodes.add(.{ .code = event.keycode, .consumer = consumer }) catch { + // We've run out of capacity and cannot process the key correctly. + // If wlroots hasn't failed on a similar assertion, this logic needs to be updated. + unreachable; + }; + + switch (consumer) { .mapping => if (!released) { // We handle release mappings separately, regardless of whether press mapping exists 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.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 diff --git a/river/Seat.zig b/river/Seat.zig index d83f7af..df9a336 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -304,18 +304,16 @@ pub fn keyboardEnterOrLeave(self: *Self, target_surface: ?*wlr.Surface) void { fn keyboardNotifyEnter(self: *Self, wlr_surface: *wlr.Surface) void { 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); - 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( wlr_surface, - &keycodes.items, + &keycodes.buffer, keycodes.len, &wlr_keyboard.modifiers, );