From f639a2f94ea9af6ae27afa7da78c6f75b65ac866 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 5 Feb 2026 17:17:36 -0500 Subject: [PATCH] keyboard repeat for compositor keybinds --- src/Cursor.zig | 4 +-- src/Keyboard.zig | 62 +++++++++++++++++++++++++++++++----------- src/KeyboardGroup.zig | 63 +++++++++++++++++++++++++++++++++++++++++++ src/Seat.zig | 11 ++++---- src/View.zig | 4 +-- src/lua/Input.zig | 6 ++--- 6 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 src/KeyboardGroup.zig diff --git a/src/Cursor.zig b/src/Cursor.zig index 8e04a0f..3223245 100644 --- a/src/Cursor.zig +++ b/src/Cursor.zig @@ -81,7 +81,7 @@ pub fn processCursorMotion(self: *Cursor, time_msec: u32) void { var passthrough = true; if (self.mode == .drag) { - const modifiers = server.seat.keyboard_group.keyboard.getModifiers(); + const modifiers = server.seat.keyboard_group.wlr_group.keyboard.getModifiers(); std.debug.assert(self.drag != null); @@ -200,7 +200,7 @@ fn handleButton( } var passthrough = true; - const modifiers = server.seat.keyboard_group.keyboard.getModifiers(); + const modifiers = server.seat.keyboard_group.wlr_group.keyboard.getModifiers(); // Proceed if mousemap for current mouse and modifier state's exist if (server.mousemaps.get(Mousemap.hash(modifiers, @bitCast(event.button)))) |map| { diff --git a/src/Keyboard.zig b/src/Keyboard.zig index 546095e..fa1f3e8 100644 --- a/src/Keyboard.zig +++ b/src/Keyboard.zig @@ -53,7 +53,7 @@ pub fn init(device: *wlr.InputDevice) *Keyboard { self.wlr_keyboard.data = self; std.log.err("Adding new keyboard {s}", .{device.name orelse "(unnamed)"}); - if(!server.seat.keyboard_group.addKeyboard(self.wlr_keyboard)) { + if(!server.seat.keyboard_group.wlr_group.addKeyboard(self.wlr_keyboard)) { std.log.err("Adding new keyboard {s} failed", .{device.name orelse "(unnamed)"}); } @@ -71,32 +71,62 @@ fn handleModifiers(_: *wl.Listener(*wlr.Keyboard), wlr_keyboard: *wlr.Keyboard) server.seat.wlr_seat.keyboardNotifyModifiers(&wlr_keyboard.modifiers); } -fn handleKey(_: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void { +fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void { + const keyboard: *Keyboard = @fieldParentPtr("key", listener); // Translate libinput keycode -> xkbcommon const keycode = event.keycode + 8; - var handled = false; - const modifiers = server.seat.keyboard_group.keyboard.getModifiers(); - if (server.seat.keyboard_group.keyboard.xkb_state) |xkb_state| { - for (xkb_state.keyGetSyms(keycode)) |sym| { - if (server.keymaps.get(Keymap.hash(modifiers, sym))) |map| { - if (event.state == .pressed and map.options.lua_press_ref_idx > 0) { - map.callback(false); - handled = true; - } else if (event.state == .released and map.options.lua_release_ref_idx > 0) { - map.callback(true); - handled = true; - } - } + var handled: bool = false; + const modifiers = server.seat.keyboard_group.wlr_group.keyboard.getModifiers(); + if (server.seat.keyboard_group.wlr_group.keyboard.xkb_state) |xkb_state| { + const keysyms = xkb_state.keyGetSyms(keycode); + for (keysyms) |sym| { + handled = keypress(modifiers, sym, event.state); + } + + // give the keyboard group information about what to repeat and update it + if (handled and keyboard.wlr_keyboard.repeat_info.delay > 0) { + server.seat.keyboard_group.modifiers = modifiers; + server.seat.keyboard_group.keysyms = keysyms; + server.seat.keyboard_group.repeat_source.?.timerUpdate( + keyboard.wlr_keyboard.repeat_info.delay, + ) catch { + std.log.warn("failed to update keyboard repeat timer", .{}); + }; + } else { + server.seat.keyboard_group.modifiers = null; + server.seat.keyboard_group.keysyms = null; } } + if (handled and keyboard.wlr_keyboard.repeat_info.delay > 0) { + + } + if (!handled) { - server.seat.wlr_seat.setKeyboard(&server.seat.keyboard_group.keyboard); + server.seat.wlr_seat.setKeyboard(&server.seat.keyboard_group.wlr_group.keyboard); server.seat.wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state); } } +pub fn keypress( + modifiers: wlr.Keyboard.ModifierMask, + sym: xkb.Keysym, + state: wl.Keyboard.KeyState +) bool { + if (server.keymaps.get(Keymap.hash(modifiers, sym))) |map| { + if (state == .pressed and map.options.lua_press_ref_idx > 0) { + map.callback(false); + return true; + } else if (state == .released and map.options.lua_release_ref_idx > 0) { + map.callback(true); + return true; + } + } + + return false; +} + fn handleKeyMap(_: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void { std.log.err("Unimplemented handle keyboard keymap", .{}); } diff --git a/src/KeyboardGroup.zig b/src/KeyboardGroup.zig new file mode 100644 index 0000000..46327a4 --- /dev/null +++ b/src/KeyboardGroup.zig @@ -0,0 +1,63 @@ +const KeyboardGroup = @This(); + +const std = @import("std"); +const wl = @import("wayland").server.wl; +const wlr = @import("wlroots"); +const xkb = @import("xkbcommon"); + +const Keyboard = @import("Keyboard.zig"); +const Utils = @import("Utils.zig"); + +const server = &@import("main.zig").server; +const gpa = std.heap.c_allocator; + +wlr_group: *wlr.KeyboardGroup, +repeat_source: ?*wl.EventSource, +modifiers: ?wlr.Keyboard.ModifierMask, +keysyms: ?[]const xkb.Keysym, + +pub fn init() *KeyboardGroup { + errdefer Utils.oomPanic(); + + const self = try gpa.create(KeyboardGroup); + self.* = .{ + .wlr_group = wlr.KeyboardGroup.create() catch Utils.oomPanic(), + .repeat_source = blk: { + break :blk server.event_loop.addTimer(?*KeyboardGroup, handleRepeat, self) catch { + std.log.err("Failed to create event loop timer, keyboard repeating will not work!", .{}); + break :blk null; + }; + }, + .modifiers = null, + .keysyms = null, + }; + + return self; +} + +pub fn deinit(self: *KeyboardGroup) void { + self.wlr_group.destroy(); + gpa.destroy(self); +} + +fn handleRepeat(data: ?*KeyboardGroup) c_int { + // we failed to create the event loop timer, which means we can't repeat keys + if (data.?.repeat_source == null) return 0; + + if (data == null or data.?.keysyms == null or data.?.modifiers == null or + data.?.wlr_group.keyboard.repeat_info.rate <= 0) { + return 0; + } + + data.?.repeat_source.?.timerUpdate( + @divTrunc(1000, data.?.wlr_group.keyboard.repeat_info.rate), + ) catch { + // not sure how big of a deal it is if we miss a timer update + std.log.warn("failed to update keyboard repeat timer", .{}); + }; + for (data.?.keysyms.?) |sym| { + _ = Keyboard.keypress(data.?.modifiers.?, sym, .pressed); + } + + return 0; +} diff --git a/src/Seat.zig b/src/Seat.zig index 6e3879a..50db858 100644 --- a/src/Seat.zig +++ b/src/Seat.zig @@ -5,6 +5,7 @@ const wlr = @import("wlroots"); const wl = @import("wayland").server.wl; const xkb = @import("xkbcommon"); +const KeyboardGroup = @import("KeyboardGroup.zig"); const Utils = @import("Utils.zig"); const Popup = @import("Popup.zig"); const View = @import("View.zig"); @@ -30,7 +31,7 @@ wlr_seat: *wlr.Seat, focused_surface: ?FocusData, focused_output: ?*Output, -keyboard_group: *wlr.KeyboardGroup, +keyboard_group: *KeyboardGroup, keymap: *xkb.Keymap, request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = .init(handleRequestSetCursor), @@ -57,7 +58,7 @@ pub fn init(self: *Seat) void { .wlr_seat = try wlr.Seat.create(server.wl_server, "default"), .focused_surface = null, .focused_output = null, - .keyboard_group = try wlr.KeyboardGroup.create(), + .keyboard_group = .init(), .keymap = keymap.ref(), }; errdefer { @@ -65,8 +66,8 @@ pub fn init(self: *Seat) void { self.wlr_seat.destroy(); } - _ = self.keyboard_group.keyboard.setKeymap(self.keymap); - self.wlr_seat.setKeyboard(&self.keyboard_group.keyboard); + _ = self.keyboard_group.wlr_group.keyboard.setKeymap(self.keymap); + self.wlr_seat.setKeyboard(&self.keyboard_group.wlr_group.keyboard); self.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor); self.wlr_seat.events.request_set_selection.add(&self.request_set_selection); @@ -78,7 +79,7 @@ pub fn deinit(self: *Seat) void { self.request_set_selection.link.remove(); self.request_set_primary_selection.link.remove(); - self.keyboard_group.destroy(); + self.keyboard_group.deinit(); self.wlr_seat.destroy(); } diff --git a/src/View.zig b/src/View.zig index 2320848..26ec5a4 100644 --- a/src/View.zig +++ b/src/View.zig @@ -150,8 +150,8 @@ fn handleMap(listener: *wl.Listener(void)) void { const xdg_surface = view.xdg_toplevel.base; server.seat.wlr_seat.keyboardNotifyEnter( xdg_surface.surface, - &server.seat.keyboard_group.keyboard.keycodes, - &server.seat.keyboard_group.keyboard.modifiers + &server.seat.keyboard_group.wlr_group.keyboard.keycodes, + &server.seat.keyboard_group.wlr_group.keyboard.modifiers ); view.mapped = true; diff --git a/src/lua/Input.zig b/src/lua/Input.zig index 88ffb77..ef3ce74 100644 --- a/src/lua/Input.zig +++ b/src/lua/Input.zig @@ -148,9 +148,9 @@ pub fn del_mousemap(L: *zlua.Lua) i32 { pub fn get_repeat_info(L: *zlua.Lua) i32 { L.newTable(); - L.pushInteger(server.seat.keyboard_group.keyboard.repeat_info.rate); + L.pushInteger(server.seat.keyboard_group.wlr_group.keyboard.repeat_info.rate); L.setField(-2, "rate"); - L.pushInteger(server.seat.keyboard_group.keyboard.repeat_info.delay); + L.pushInteger(server.seat.keyboard_group.wlr_group.keyboard.repeat_info.delay); L.setField(-2, "delay"); return 1; @@ -167,7 +167,7 @@ pub fn set_repeat_info(L: *zlua.Lua) i32 { L.raiseErrorStr("The delay must be a valid number", .{}); }; - server.seat.keyboard_group.keyboard.setRepeatInfo(rate, delay); + server.seat.keyboard_group.wlr_group.keyboard.setRepeatInfo(rate, delay); return 0; }