Support more configurable keybindings (closes #679)

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion
2026-02-09 11:11:00 +01:00
parent cf5f62661c
commit 852a602032
4 changed files with 1286 additions and 298 deletions

View File

@@ -1,4 +1,5 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Random = std.Random;
const ly_core = @import("ly-core");
@@ -7,10 +8,14 @@ const LogFile = ly_core.LogFile;
pub const termbox = @import("termbox2");
const Cell = @import("Cell.zig");
const keyboard = @import("keyboard.zig");
const Position = @import("Position.zig");
const TerminalBuffer = @This();
const KeybindCallbackFn = *const fn (*anyopaque) anyerror!bool;
const KeybindMap = std.AutoHashMap(keyboard.Key, KeybindCallbackFn);
pub const InitOptions = struct {
fg: u32,
bg: u32,
@@ -59,6 +64,7 @@ pub const Color = struct {
pub const START_POSITION = Position.init(0, 0);
log_file: *LogFile,
random: Random,
width: usize,
height: usize,
@@ -78,8 +84,9 @@ box_chars: struct {
blank_cell: Cell,
full_color: bool,
termios: ?std.posix.termios,
keybinds: KeybindMap,
pub fn init(options: InitOptions, log_file: *LogFile, random: Random) !TerminalBuffer {
pub fn init(allocator: Allocator, options: InitOptions, log_file: *LogFile, random: Random) !TerminalBuffer {
// Initialize termbox
_ = termbox.tb_init();
@@ -101,6 +108,7 @@ pub fn init(options: InitOptions, log_file: *LogFile, random: Random) !TerminalB
try log_file.info("tui", "screen resolution is {d}x{d}", .{ width, height });
return .{
.log_file = log_file,
.random = random,
.width = width,
.height = height,
@@ -130,9 +138,15 @@ pub fn init(options: InitOptions, log_file: *LogFile, random: Random) !TerminalB
.full_color = options.full_color,
// Needed to reclaim the TTY after giving up its control
.termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO),
.keybinds = KeybindMap.init(allocator),
};
}
pub fn deinit(self: *TerminalBuffer) void {
self.keybinds.deinit();
TerminalBuffer.shutdownStatic();
}
pub fn getWidthStatic() usize {
return @intCast(termbox.tb_width());
}
@@ -205,6 +219,57 @@ pub fn cascade(self: TerminalBuffer) bool {
return changed;
}
pub fn registerKeybind(self: *TerminalBuffer, keybind: []const u8, callback: KeybindCallbackFn) !void {
var key = std.mem.zeroes(keyboard.Key);
var iterator = std.mem.splitScalar(u8, keybind, '+');
while (iterator.next()) |item| {
var found = false;
inline for (std.meta.fields(keyboard.Key)) |field| {
if (std.ascii.eqlIgnoreCase(field.name, item)) {
@field(key, field.name) = true;
found = true;
break;
}
}
if (!found) {
try self.log_file.err(
"tui",
"failed to parse key {s} of keybind {s}",
.{ item, keybind },
);
}
}
self.keybinds.put(key, callback) catch |err| {
try self.log_file.err(
"tui",
"failed to register keybind {s}: {s}",
.{ keybind, @errorName(err) },
);
};
}
pub fn handleKeybind(
self: *TerminalBuffer,
allocator: Allocator,
tb_event: termbox.tb_event,
context: *anyopaque,
) !bool {
var keys = try keyboard.getKeyList(allocator, tb_event);
defer keys.deinit(allocator);
for (keys.items) |key| {
if (self.keybinds.get(key)) |callback| {
return @call(.auto, callback, .{context});
}
}
return true;
}
pub fn drawText(
text: []const u8,
x: usize,

735
src/tui/keyboard.zig Normal file
View File

@@ -0,0 +1,735 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const KeyList = std.ArrayList(Key);
const TerminalBuffer = @import("TerminalBuffer.zig");
const termbox = TerminalBuffer.termbox;
pub const Key = packed struct {
ctrl: bool,
shift: bool,
alt: bool,
f1: bool,
f2: bool,
f3: bool,
f4: bool,
f5: bool,
f6: bool,
f7: bool,
f8: bool,
f9: bool,
f10: bool,
f11: bool,
f12: bool,
insert: bool,
delete: bool,
home: bool,
end: bool,
pageup: bool,
pagedown: bool,
up: bool,
down: bool,
left: bool,
right: bool,
tab: bool,
backspace: bool,
enter: bool,
space: bool,
@"!": bool,
@"`": bool,
esc: bool,
@"[": bool,
@"\\": bool,
@"]": bool,
@"/": bool,
_: bool,
@"'": bool,
@"\"": bool,
@",": bool,
@"-": bool,
@".": bool,
@"#": bool,
@"$": bool,
@"%": bool,
@"&": bool,
@"*": bool,
@"(": bool,
@")": bool,
@"+": bool,
@"=": bool,
@":": bool,
@";": bool,
@"<": bool,
@">": bool,
@"?": bool,
@"@": bool,
@"^": bool,
@"~": bool,
@"{": bool,
@"}": bool,
@"|": bool,
@"0": bool,
@"1": bool,
@"2": bool,
@"3": bool,
@"4": bool,
@"5": bool,
@"6": bool,
@"7": bool,
@"8": bool,
@"9": bool,
a: bool,
b: bool,
c: bool,
d: bool,
e: bool,
f: bool,
g: bool,
h: bool,
i: bool,
j: bool,
k: bool,
l: bool,
m: bool,
n: bool,
o: bool,
p: bool,
q: bool,
r: bool,
s: bool,
t: bool,
u: bool,
v: bool,
w: bool,
x: bool,
y: bool,
z: bool,
};
pub fn getKeyList(allocator: Allocator, tb_event: termbox.tb_event) !KeyList {
var keys: KeyList = .empty;
var key = std.mem.zeroes(Key);
if (tb_event.mod & termbox.TB_MOD_CTRL != 0) key.ctrl = true;
if (tb_event.mod & termbox.TB_MOD_SHIFT != 0) key.shift = true;
if (tb_event.mod & termbox.TB_MOD_ALT != 0) key.alt = true;
if (tb_event.key == termbox.TB_KEY_BACK_TAB) {
key.shift = true;
key.tab = true;
} else if (tb_event.key > termbox.TB_KEY_BACK_TAB) {
const code = 0xFFFF - tb_event.key;
switch (code) {
0 => key.f1 = true,
1 => key.f2 = true,
2 => key.f3 = true,
3 => key.f4 = true,
4 => key.f5 = true,
5 => key.f6 = true,
6 => key.f7 = true,
7 => key.f8 = true,
8 => key.f9 = true,
9 => key.f10 = true,
10 => key.f11 = true,
11 => key.f12 = true,
12 => key.insert = true,
13 => key.delete = true,
14 => key.home = true,
15 => key.end = true,
16 => key.pageup = true,
17 => key.pagedown = true,
18 => key.up = true,
19 => key.down = true,
20 => key.left = true,
21 => key.right = true,
else => {},
}
} else if (tb_event.ch < 128) {
const code = if (tb_event.ch == 0 and tb_event.key < 128) tb_event.key else tb_event.ch;
switch (code) {
0 => {
key.ctrl = true;
key.@"2" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"`" = true;
},
1 => {
key.ctrl = true;
key.a = true;
},
2 => {
key.ctrl = true;
key.b = true;
},
3 => {
key.ctrl = true;
key.c = true;
},
4 => {
key.ctrl = true;
key.d = true;
},
5 => {
key.ctrl = true;
key.e = true;
},
6 => {
key.ctrl = true;
key.f = true;
},
7 => {
key.ctrl = true;
key.g = true;
},
8 => {
key.ctrl = true;
key.h = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.backspace = true;
},
9 => {
key.ctrl = true;
key.i = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.tab = true;
},
10 => {
key.ctrl = true;
key.j = true;
},
11 => {
key.ctrl = true;
key.k = true;
},
12 => {
key.ctrl = true;
key.l = true;
},
13 => {
key.ctrl = true;
key.m = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.enter = true;
},
14 => {
key.ctrl = true;
key.n = true;
},
15 => {
key.ctrl = true;
key.o = true;
},
16 => {
key.ctrl = true;
key.p = true;
},
17 => {
key.ctrl = true;
key.q = true;
},
18 => {
key.ctrl = true;
key.r = true;
},
19 => {
key.ctrl = true;
key.s = true;
},
20 => {
key.ctrl = true;
key.t = true;
},
21 => {
key.ctrl = true;
key.u = true;
},
22 => {
key.ctrl = true;
key.v = true;
},
23 => {
key.ctrl = true;
key.w = true;
},
24 => {
key.ctrl = true;
key.x = true;
},
25 => {
key.ctrl = true;
key.y = true;
},
26 => {
key.ctrl = true;
key.z = true;
},
27 => {
key.ctrl = true;
key.@"3" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.esc = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"[" = true;
},
28 => {
key.ctrl = true;
key.@"4" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"\\" = true;
},
29 => {
key.ctrl = true;
key.@"5" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"]" = true;
},
30 => {
key.ctrl = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"6" = true;
},
31 => {
key.ctrl = true;
key.@"7" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"/" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key._ = true;
},
32 => {
key.space = true;
},
33 => {
key.shift = true;
key.@"1" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"!" = true;
},
34 => {
key.shift = true;
key.@"2" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"\"" = true;
},
35 => {
key.shift = true;
key.@"3" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"#" = true;
},
36 => {
key.shift = true;
key.@"4" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"$" = true;
},
37 => {
key.shift = true;
key.@"5" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"%" = true;
},
38 => {
key.shift = true;
key.@"6" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"&" = true;
},
39 => {
key.@"'" = true;
},
40 => {
key.shift = true;
key.@"9" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"(" = true;
},
41 => {
key.shift = true;
key.@"0" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@")" = true;
},
42 => {
key.shift = true;
key.@"8" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"*" = true;
},
43 => {
key.shift = true;
key.@"7" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"+" = true;
},
44 => {
key.@"," = true;
},
45 => {
key.@"-" = true;
},
46 => {
key.@"." = true;
},
47 => {
key.@"/" = true;
},
48 => {
key.@"0" = true;
},
49 => {
key.@"1" = true;
},
50 => {
key.@"2" = true;
},
51 => {
key.@"3" = true;
},
52 => {
key.@"4" = true;
},
53 => {
key.@"5" = true;
},
54 => {
key.@"6" = true;
},
55 => {
key.@"7" = true;
},
56 => {
key.@"8" = true;
},
57 => {
key.@"9" = true;
},
58 => {
key.shift = true;
key.@":" = true;
},
59 => {
key.@";" = true;
},
60 => {
key.shift = true;
key.@"<" = true;
},
61 => {
key.@"=" = true;
},
62 => {
key.shift = true;
key.@">" = true;
},
63 => {
key.shift = true;
key.@"?" = true;
},
64 => {
key.shift = true;
key.@"2" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"@" = true;
},
65 => {
key.shift = true;
key.a = true;
},
66 => {
key.shift = true;
key.b = true;
},
67 => {
key.shift = true;
key.c = true;
},
68 => {
key.shift = true;
key.d = true;
},
69 => {
key.shift = true;
key.e = true;
},
70 => {
key.shift = true;
key.f = true;
},
71 => {
key.shift = true;
key.g = true;
},
72 => {
key.shift = true;
key.h = true;
},
73 => {
key.shift = true;
key.i = true;
},
74 => {
key.shift = true;
key.j = true;
},
75 => {
key.shift = true;
key.k = true;
},
76 => {
key.shift = true;
key.l = true;
},
77 => {
key.shift = true;
key.m = true;
},
78 => {
key.shift = true;
key.n = true;
},
79 => {
key.shift = true;
key.o = true;
},
80 => {
key.shift = true;
key.p = true;
},
81 => {
key.shift = true;
key.q = true;
},
82 => {
key.shift = true;
key.r = true;
},
83 => {
key.shift = true;
key.s = true;
},
84 => {
key.shift = true;
key.t = true;
},
85 => {
key.shift = true;
key.u = true;
},
86 => {
key.shift = true;
key.v = true;
},
87 => {
key.shift = true;
key.w = true;
},
88 => {
key.shift = true;
key.x = true;
},
89 => {
key.shift = true;
key.y = true;
},
90 => {
key.shift = true;
key.z = true;
},
91 => {
key.@"[" = true;
},
92 => {
key.@"\\" = true;
},
93 => {
key.@"]" = true;
},
94 => {
key.shift = true;
key.@"6" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"^" = true;
},
95 => {
key.shift = true;
key.@"-" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key._ = true;
},
96 => {
key.@"`" = true;
},
97 => {
key.a = true;
},
98 => {
key.b = true;
},
99 => {
key.c = true;
},
100 => {
key.d = true;
},
101 => {
key.e = true;
},
102 => {
key.f = true;
},
103 => {
key.g = true;
},
104 => {
key.h = true;
},
105 => {
key.i = true;
},
106 => {
key.j = true;
},
107 => {
key.k = true;
},
108 => {
key.l = true;
},
109 => {
key.m = true;
},
110 => {
key.n = true;
},
111 => {
key.o = true;
},
112 => {
key.p = true;
},
113 => {
key.q = true;
},
114 => {
key.r = true;
},
115 => {
key.s = true;
},
116 => {
key.t = true;
},
117 => {
key.u = true;
},
118 => {
key.v = true;
},
119 => {
key.w = true;
},
120 => {
key.x = true;
},
121 => {
key.y = true;
},
122 => {
key.z = true;
},
123 => {
key.shift = true;
key.@"{" = true;
},
124 => {
key.shift = true;
key.@"\\" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"|" = true;
},
125 => {
key.shift = true;
key.@"}" = true;
},
126 => {
key.shift = true;
key.@"`" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"~" = true;
},
127 => {
key.ctrl = true;
key.@"8" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.backspace = true;
},
else => {},
}
}
try keys.append(allocator, key);
return keys;
}