From c3b5cab94165ea253b8e33683d5fd76fdddb025a Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Tue, 28 Jul 2026 13:43:52 +0800 Subject: [PATCH] wayland/Hotkeys: polish & simplify I've come up with a way to avoid manually allocating each entry which honestly makes the code flow much more smoothly. Basically you collect all the applicable keybinds first, then try to bind them with their stable memory addresses. --- src/apprt/gtk/winproto/wayland/Hotkeys.zig | 115 ++++++++++----------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/src/apprt/gtk/winproto/wayland/Hotkeys.zig b/src/apprt/gtk/winproto/wayland/Hotkeys.zig index 57e0cd94b..4c5fda357 100644 --- a/src/apprt/gtk/winproto/wayland/Hotkeys.zig +++ b/src/apprt/gtk/winproto/wayland/Hotkeys.zig @@ -16,10 +16,7 @@ const log = std.log.scoped(.winproto_wayland_hotkeys); alloc: Allocator, app_id: [:0]const u8, - -/// Entries must have stable addresses: the hotkey listeners point to them. -arena: std.heap.ArenaAllocator, -entries: std.ArrayList(*Entry) = .empty, +entries: std.ArrayList(Entry) = .empty, const Entry = struct { /// Null once the binding was denied or revoked. @@ -28,13 +25,47 @@ const Entry = struct { action: Binding.Action, shortcuts: *GlobalShortcuts, - fn fail(entry: *Entry, message: [*:0]const u8, revoked: bool) void { - entry.hotkey.?.destroy(); - entry.hotkey = null; + fn deinit(self: *Entry) void { + if (self.hotkey) |hotkey| hotkey.destroy(); + } - entry.shortcuts.emitBindFailed(&.{ - .trigger = entry.trigger, - .action = entry.action, + fn bind( + self: *Entry, + manager: *vicinae.HotkeyManagerV1, + app_id: [:0]const u8, + ) !void { + // The only time this will return an error is when + // `trigger.key` is `catch_all`, which is guarded + // in the public `bind` function + const keysym = key.keysymFromTrigger(self.trigger) orelse unreachable; + + var desc_buf: [256]u8 = undefined; + const desc = std.fmt.bufPrintZ(&desc_buf, "{f}", .{self.action}) catch ""; + + const hotkey = try manager.bind( + keysym, + .{ + .shift = self.trigger.mods.shift, + .ctrl = self.trigger.mods.ctrl, + .alt = self.trigger.mods.alt, + .super = self.trigger.mods.super, + }, + null, + app_id.ptr, + desc.ptr, + ); + errdefer hotkey.destroy(); + hotkey.setListener(*Entry, hotkeyListener, self); + self.hotkey = hotkey; + } + + fn fail(self: *Entry, message: [*:0]const u8, revoked: bool) void { + self.hotkey.?.destroy(); + self.hotkey = null; + + self.shortcuts.emitBindFailed(&.{ + .trigger = self.trigger, + .action = self.action, .message = message, .revoked = revoked, }); @@ -45,7 +76,6 @@ pub fn init(alloc: Allocator, app_id: [:0]const u8) Allocator.Error!Hotkeys { return .{ .alloc = alloc, .app_id = try alloc.dupeZ(u8, app_id), - .arena = .init(alloc), }; } @@ -53,17 +83,12 @@ pub fn init(alloc: Allocator, app_id: [:0]const u8) Allocator.Error!Hotkeys { /// called after deinit during application teardown. pub fn deinit(self: *Hotkeys) void { self.clear(); - self.arena.deinit(); - self.arena = .init(self.alloc); self.alloc.free(self.app_id); } pub fn clear(self: *Hotkeys) void { - for (self.entries.items) |entry| { - if (entry.hotkey) |hotkey| hotkey.destroy(); - } - _ = self.arena.reset(.retain_capacity); - self.entries = .empty; + for (self.entries.items) |*entry| entry.deinit(); + self.entries.clearRetainingCapacity(); } pub fn bind( @@ -81,60 +106,32 @@ pub fn bind( inline .leaf, .leaf_chained => |leaf| leaf.generic(), }; if (!leaf.flags.global) continue; + // Catch all global keybinds don't really make sense + if (entry.key_ptr.key == .catch_all) continue; // Only single-action global keybinds are supported, as in the // portal implementation. const actions = leaf.actionsSlice(); if (actions.len != 1) continue; - self.bindOne(manager, shortcuts, entry.key_ptr.*, actions[0]) catch |err| { + self.entries.append(self.alloc, .{ + .hotkey = null, + .trigger = entry.key_ptr.*, + .action = actions[0], + .shortcuts = shortcuts, + }) catch {}; + } + + for (self.entries.items) |*entry| { + entry.bind(manager, self.app_id) catch |err| { log.warn("failed to request hotkey trigger={f} err={}", .{ - entry.key_ptr.*, + entry.trigger, err, }); }; } } -fn bindOne( - self: *Hotkeys, - manager: *vicinae.HotkeyManagerV1, - shortcuts: *GlobalShortcuts, - trigger: Binding.Trigger, - action: Binding.Action, -) !void { - const keysym = key.keysymFromTrigger(trigger) orelse return error.NoKeysym; - - var desc_buf: [256]u8 = undefined; - const description = std.fmt.bufPrintZ(&desc_buf, "{f}", .{action}) catch ""; - - const alloc = self.arena.allocator(); - const entry = try alloc.create(Entry); - - const hotkey = try manager.bind( - keysym, - .{ - .shift = trigger.mods.shift, - .ctrl = trigger.mods.ctrl, - .alt = trigger.mods.alt, - .super = trigger.mods.super, - }, - null, - self.app_id.ptr, - description.ptr, - ); - errdefer hotkey.destroy(); - - entry.* = .{ - .hotkey = hotkey, - .trigger = trigger, - .action = action, - .shortcuts = shortcuts, - }; - hotkey.setListener(*Entry, hotkeyListener, entry); - try self.entries.append(alloc, entry); -} - fn hotkeyListener( _: *vicinae.HotkeyV1, event: vicinae.HotkeyV1.Event,