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:
parent
a1ce53a998
commit
bd52c155ef
@ -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
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user