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:
parent
69a51cadb4
commit
66f1881a72
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user