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 (!released and handleBuiltinMapping(sym)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we sent a pressed event to the client we should send the matching release event as well,
|
// Every sent press event, to a regular client or the IM, should have
|
||||||
// even if the release event triggers a mapping or would otherwise be sent to an input method.
|
// the corresponding release event sent to the same client.
|
||||||
const sent_to_client = blk: {
|
// Similarly, no press event means no release event.
|
||||||
if (released and !self.eaten_keycodes.remove(event.keycode)) {
|
|
||||||
const wlr_seat = self.device.seat.wlr_seat;
|
const eat_reason = blk: {
|
||||||
wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard());
|
// We can only eat a key on press; never on release
|
||||||
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
|
if (released) break :blk self.eaten_keycodes.remove(event.keycode);
|
||||||
break :blk true;
|
|
||||||
} else {
|
|
||||||
break :blk false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (self.device.seat.handleMapping(keycode, modifiers, released, xkb_state)) {
|
if (self.device.seat.handleMapping(keycode, modifiers, released, xkb_state)) {
|
||||||
if (!released) self.eaten_keycodes.add(event.keycode);
|
break :blk self.eaten_keycodes.add(event.keycode, .mapping);
|
||||||
} else if (self.getInputMethodGrab()) |keyboard_grab| {
|
} else if (self.getInputMethodGrab() != null) {
|
||||||
if (!released) self.eaten_keycodes.add(event.keycode);
|
break :blk self.eaten_keycodes.add(event.keycode, .im_grab);
|
||||||
|
|
||||||
if (!sent_to_client) {
|
|
||||||
keyboard_grab.setKeyboard(keyboard_grab.keyboard);
|
|
||||||
keyboard_grab.sendKey(event.time_msec, event.keycode, event.state);
|
|
||||||
}
|
}
|
||||||
} else if (!sent_to_client) {
|
|
||||||
|
break :blk .none;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (eat_reason) {
|
||||||
|
.none => {
|
||||||
const wlr_seat = self.device.seat.wlr_seat;
|
const wlr_seat = self.device.seat.wlr_seat;
|
||||||
wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard());
|
wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard());
|
||||||
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
|
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 {
|
fn isModifier(keysym: xkb.Keysym) bool {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// This file is part of river, a dynamic tiling wayland compositor.
|
// 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
|
// 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
|
// 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 wlr = @import("wlroots");
|
||||||
|
|
||||||
|
const EatReason = enum {
|
||||||
|
/// Not eaten
|
||||||
|
none,
|
||||||
|
mapping,
|
||||||
|
im_grab,
|
||||||
|
};
|
||||||
|
|
||||||
items: [32]u32 = undefined,
|
items: [32]u32 = undefined,
|
||||||
|
reason: [32]EatReason = undefined,
|
||||||
len: usize = 0,
|
len: usize = 0,
|
||||||
|
|
||||||
pub fn add(self: *Self, new: u32) void {
|
pub fn add(self: *Self, new: u32, reason: EatReason) EatReason {
|
||||||
for (self.items[0..self.len]) |item| if (new == item) return;
|
for (self.items[0..self.len]) |item| assert(new != item);
|
||||||
|
|
||||||
comptime assert(@typeInfo(std.meta.fieldInfo(Self, .items).type).Array.len ==
|
comptime assert(@typeInfo(std.meta.fieldInfo(Self, .items).type).Array.len ==
|
||||||
@typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).Array.len);
|
@typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).Array.len);
|
||||||
|
|
||||||
if (self.len == self.items.len) {
|
if (self.len == self.items.len) {
|
||||||
log.err("KeycodeSet limit reached, code {d} omitted", .{new});
|
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.items[self.len] = new;
|
||||||
|
self.reason[self.len] = reason;
|
||||||
self.len += 1;
|
self.len += 1;
|
||||||
|
|
||||||
|
return reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(self: *Self, old: u32) bool {
|
pub fn remove(self: *Self, old: u32) EatReason {
|
||||||
for (self.items[0..self.len], 0..) |item, idx| if (old == item) {
|
for (self.items[0..self.len], self.reason[0..self.len], 0..) |item, reason, idx| {
|
||||||
|
if (old == item) {
|
||||||
self.len -= 1;
|
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];
|
||||||
return true;
|
self.reason[idx] = self.reason[self.len];
|
||||||
};
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes other's contents from self (if present)
|
return reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes other's contents from self (if present), regardless of reason
|
||||||
pub fn subtract(self: *Self, other: Self) void {
|
pub fn subtract(self: *Self, other: Self) void {
|
||||||
for (other.items[0..other.len]) |item| _ = self.remove(item);
|
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| {
|
if (self.wlr_seat.getKeyboard()) |wlr_keyboard| {
|
||||||
var keycodes = KeycodeSet{
|
var keycodes = KeycodeSet{
|
||||||
.items = wlr_keyboard.keycodes,
|
.items = wlr_keyboard.keycodes,
|
||||||
|
.reason = .{.none} ** 32,
|
||||||
.len = wlr_keyboard.num_keycodes,
|
.len = wlr_keyboard.num_keycodes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user