mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 04:39:01 +00:00
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.
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user