Seat: avoid leaking eaten keys to client on focus

Until now, Seat.setFocusRaw sent all pressed keys to the client,
including ones that should be eaten. (Try e.g. changing focus to a
nested wlroots compositor with a terminal open to easily see it.)

However, only filtering out the eaten keys is not enough; they were
eaten only once all mappings had been executed. Therefore, the original
function had to be split into one looking up mappings and another
executing them.
This commit is contained in:
tiosgz 2022-05-29 19:59:41 +00:00 committed by Isaac Freund
parent 6d6646febe
commit 1e3b8ed161
3 changed files with 47 additions and 9 deletions

View File

@ -17,6 +17,7 @@
const Self = @This();
const std = @import("std");
const assert = std.debug.assert;
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const xkb = @import("xkbcommon");
@ -61,6 +62,7 @@ pub fn init(self: *Self, seat: *Seat, input_device: *wlr.InputDevice) !void {
defer keymap.unref();
const wlr_keyboard = self.input_device.device.keyboard;
wlr_keyboard.data = @ptrToInt(self);
if (!wlr_keyboard.setKeymap(keymap)) return error.SetKeymapFailed;
@ -111,9 +113,14 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
if (!released and handleBuiltinMapping(sym)) return;
}
// Handle user-defined mapping
const mapped = self.seat.handleMapping(keycode, modifiers, released, xkb_state);
if (mapped and !released) self.eaten_keycodes.add(event.keycode);
// Handle user-defined mappings
const mapped = self.seat.hasMapping(keycode, modifiers, released, xkb_state);
if (mapped) {
if (!released) self.eaten_keycodes.add(event.keycode);
const handled = self.seat.handleMapping(keycode, modifiers, released, xkb_state);
assert(handled);
}
const eaten = if (released) self.eaten_keycodes.remove(event.keycode) else mapped;

View File

@ -50,3 +50,8 @@ pub fn remove(self: *Self, old: u32) bool {
return false;
}
/// Removes other's contents from self (if present)
pub fn subtract(self: *Self, other: Self) void {
for (other.items[0..other.len]) |item| _ = self.remove(item);
}

View File

@ -31,6 +31,7 @@ const DragIcon = @import("DragIcon.zig");
const Cursor = @import("Cursor.zig");
const InputManager = @import("InputManager.zig");
const Keyboard = @import("Keyboard.zig");
const KeycodeSet = @import("KeycodeSet.zig");
const Switch = @import("Switch.zig");
const Mapping = @import("Mapping.zig");
const LayerSurface = @import("LayerSurface.zig");
@ -253,12 +254,20 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// Send keyboard enter/leave events and handle pointer constraints
if (target_surface) |wlr_surface| {
if (self.wlr_seat.getKeyboard()) |keyboard| {
if (self.wlr_seat.getKeyboard()) |wlr_keyboard| {
var keycodes = KeycodeSet{
.items = wlr_keyboard.keycodes,
.len = wlr_keyboard.num_keycodes,
};
const keyboard = @intToPtr(*Keyboard, wlr_keyboard.data);
keycodes.subtract(keyboard.eaten_keycodes);
self.wlr_seat.keyboardNotifyEnter(
wlr_surface,
&keyboard.keycodes,
keyboard.num_keycodes,
&keyboard.modifiers,
&keycodes.items,
keycodes.len,
&wlr_keyboard.modifiers,
);
} else {
self.wlr_seat.keyboardNotifyEnter(wlr_surface, null, 0, null);
@ -349,8 +358,25 @@ pub fn handleViewUnmap(self: *Self, view: *View) void {
if (self.focused == .view and self.focused.view == view) self.focus(null);
}
/// Is there a user-defined mapping for passed keycode, modifiers and keyboard state?
pub fn hasMapping(
self: *Self,
keycode: xkb.Keycode,
modifiers: wlr.Keyboard.ModifierMask,
released: bool,
xkb_state: *xkb.State,
) bool {
const modes = &server.config.modes;
for (modes.items[self.mode_id].mappings.items) |*mapping| {
if (mapping.match(keycode, modifiers, released, xkb_state)) {
return true;
}
}
return false;
}
/// Handle any user-defined mapping for passed keycode, modifiers and keyboard state
/// Returns true if the key was handled
/// Returns true if at least one mapping was run
pub fn handleMapping(
self: *Self,
keycode: xkb.Keycode,
@ -359,7 +385,7 @@ pub fn handleMapping(
xkb_state: *xkb.State,
) bool {
const modes = &server.config.modes;
// In case more than on mapping matches, all of them are activated
// In case more than one mapping matches, all of them are activated
var handled = false;
for (modes.items[self.mode_id].mappings.items) |*mapping| {
if (mapping.match(keycode, modifiers, released, xkb_state)) {