This commit is contained in:
Jeeves 2025-03-03 18:41:40 -07:00
parent 23dd09bbf9
commit 84016c924b
4 changed files with 951 additions and 791 deletions

View file

@ -42,6 +42,13 @@ pub const RGBA = struct {
};
}
pub fn eql(a: *const RGBA, b: *const RGBA) bool {
return a.r == b.r and
a.g == b.g and
a.b == b.b and
a.a == b.a;
}
pub fn blend(base: *const RGBA, add: *const RGBA) RGBA {
var r: RGBA = undefined;
r.a = 1 - (1 - add.a) * (1 - base.a);

193
src/control.zig Normal file
View file

@ -0,0 +1,193 @@
const std = @import("std");
const mem = std.mem;
const Terminal = @import("terminal.zig");
const Cell = Terminal.Cell;
const Buffer = Terminal.Buffer;
const Color = @import("color.zig").RGBA;
const Control = @This();
allocator: mem.Allocator,
parent: ?*Control,
children: std.ArrayList(*Control),
rect: ?Rect = null, // TODO: maybe unionize this with .parent
left: u32,
right: u32,
top: u32,
bottom: u32,
border_type: enum { line, bg, none } = .none,
border_char: u8 = ' ',
border_bg: Color = Color{ .r = 0.0, .g = 0.0, .b = 0.0 },
border_fg: Color = Color{ .r = 1.0, .g = 1.0, .b = 1.0 },
content: std.ArrayList([]const u8),
content_halign: enum { left, center, right } = .left,
content_valign: enum { top, center, bottom } = .top,
_draw_buf: ?*Buffer = null,
pub fn init(allocator: mem.Allocator) Control {
return .{
.allocator = allocator,
.parent = null,
.children = std.ArrayList(*Control).init(allocator),
.left = 2,
.right = 2,
.top = 1,
.bottom = 1,
.content = std.ArrayList([]const u8).init(allocator),
};
}
pub fn deinit(self: *Control) void {
if (self._draw_buf) |b| b.deinit();
for (self.children.items) |child| child.deinit();
self.children.deinit();
self.content.deinit();
}
pub fn addChild(self: *Control, child: *Control) !void {
std.debug.assert(child.parent == null);
child.parent = self;
try self.children.append(child);
}
pub fn removeChild(self: *Control, child: *Control) !void {
_ = self;
_ = child;
}
pub fn removeChildByIndex(self: *Control, child: usize) !void {
_ = self;
_ = child;
}
pub fn moveChild(self: *Control, child: *Control, idx: usize) !void {
_ = self;
_ = child;
_ = idx;
}
pub fn moveChildByIndex(self: *Control, child: usize, idx: usize) !void {
_ = self;
_ = child;
_ = idx;
}
pub fn makeDirty(self: *Control) void {
if (self._draw_buf) |b| b.deinit();
self._draw_buf = null;
for (self.children.items) |child| child.makeDirty();
}
pub fn draw(self: *Control, term: *Terminal) !void {
if (self._draw_buf) |draw_buf|
try draw_buf.draw(term)
else {
const rect = self.getRect();
var buffer = try Buffer.init(self.allocator, rect.h, rect.w);
errdefer buffer.deinit();
switch (self.border_type) {
.none => {
var y: u32 = 0;
// try term.cursorSet(rect.x, rect.y);
// while (y < rect.h) : (y += 1) {
// try term.cursorSet(rect.x, rect.y + y);
// var x: u32 = 0;
// while (x < rect.w) : (x += 1) try term.print(" ", .{});
// }
if (self.content.items.len > 0) {
var lines = self.content.items.len;
for (self.content.items) |line| lines += mem.count(u8, line, "\n");
const tx = switch (self.content_halign) {
.left => rect.x,
.center => rect.x + (rect.w / 2) - (@as(u32, @intCast(self.content.items.len)) / 2),
.right => rect.x + rect.w - (@as(u32, @intCast(self.content.items.len))),
};
var x = tx;
y = switch (self.content_valign) {
.top => rect.y,
.center => rect.y + (rect.h / 2) - (@as(u32, @intCast(lines)) / 2),
.bottom => rect.y + rect.h - 1 - @as(u32, @intCast(lines)),
};
for (self.content.items) |line| {
var split = mem.splitScalar(u8, line, '\n');
while (split.next()) |l| {
var spliit = mem.splitScalar(u8, l, '\r');
while (spliit.next()) |s| for (s) |c| {
// if (y * rect.w + x >= buffer.buf.len) break :blk;
buffer.cell(x, y).char = c;
// std.debug.print("x{d} y{d} {c}\r\n", .{ x, y, c });
x += 1;
if (x >= rect.w) {
x = tx;
y += 1;
}
};
while (x < rect.w) : (x += 1) try term.print(" ", .{});
y += 1;
x = tx;
}
}
}
},
.line => {
try term.setFg(self.getBorderFgColor());
try term.setBg(self.getBorderBgColor());
var x: u32 = 0;
var y: u32 = 0;
try term.cursorSet(rect.x, rect.y);
try term.print("", .{});
while (x < rect.w - 2) : (x += 1) try term.print("", .{});
try term.print("", .{});
y += 1;
while (y < rect.h - 1) : (y += 1) {
try term.cursorSet(rect.x, rect.y + y);
try term.print("", .{});
try term.cursorSet(rect.x + rect.w - 1, rect.y + y);
try term.print("", .{});
}
x = 0;
try term.cursorSet(rect.x, rect.y + rect.h - 1);
try term.print("", .{});
while (x < rect.w - 2) : (x += 1) try term.print("", .{});
try term.print("", .{});
},
.bg => {},
}
self._draw_buf = buffer;
}
for (self.children.items) |child| try child.draw(term);
}
fn getRect(self: *Control) Rect {
var rect: Rect = undefined;
if (self.parent) |parent| {
const parent_rect = parent.getRect();
rect.x = parent_rect.x + self.left;
rect.y = parent_rect.y + self.top;
rect.w = parent_rect.w + parent_rect.x - rect.x - self.right;
rect.h = parent_rect.h + parent_rect.y - rect.y - self.bottom;
} else return self.rect.?;
return rect;
}
const Rect = struct { x: u32, y: u32, w: u32, h: u32 };
fn getBorderFgColor(self: *Control) Color {
if (self.parent) |parent| {
const parent_color = parent.getBorderFgColor();
return parent_color.blend(&self.border_fg);
} else return self.border_fg;
}
fn getBorderBgColor(self: *Control) Color {
if (self.parent) |parent| {
const parent_color = parent.getBorderBgColor();
return parent_color.blend(&self.border_bg);
} else return self.border_bg;
}

View file

@ -1,11 +1,13 @@
const std = @import("std");
const os = std.os;
const io = std.io;
const fs = std.fs;
const mem = std.mem;
const heap = std.heap;
const fmt = std.fmt;
const posix = std.posix;
pub const Terminal = @import("terminal.zig");
pub const Control = @import("control.zig");
pub const Color = @import("color.zig").RGBA;
pub fn main() !void {
var gpa = heap.GeneralPurposeAllocator(.{}){};
@ -17,26 +19,26 @@ pub fn main() !void {
try term.clearScreen();
try term.cursorHide();
var box1 = Terminal.Box.init(allocator);
var box1 = Control.init(allocator);
// defer box1.deinit();
box1.border_bg = try Terminal.Color.init("#3ace37ff");
box1.border_bg = try Color.init("#3ace37ff");
box1.top = 0;
box1.bottom = 0;
box1.left = 0;
box1.right = 0;
var box2 = Terminal.Box.init(allocator);
var box2 = Control.init(allocator);
// defer box2.deinit();
box2.border_bg = try Terminal.Color.init("#000000c0");
box2.border_bg = try Color.init("#000000c0");
box2.content = "hi";
box2.top = 1;
box2.bottom = 3;
box2.left = 20;
box2.right = 2;
var box3 = Terminal.Box.init(allocator);
var box3 = Control.init(allocator);
// defer box3.deinit();
box3.border_bg = try Terminal.Color.init("#48d5eaa0");
box3.border_bg = try Color.init("#48d5eaa0");
box3.content = "hello";
box3.content_halign = .center;
box3.content_valign = .center;
@ -65,787 +67,3 @@ pub fn main() !void {
std.time.sleep(1000);
}
}
pub const Terminal = struct {
tty: fs.File,
buffered_writer: io.BufferedWriter(32768, fs.File.Writer),
original_termios: os.linux.termios,
info: Info,
allocator: mem.Allocator,
box: Box,
events: std.ArrayList(Event),
pub fn init(allocator: mem.Allocator) !Terminal {
var term = Terminal{
.tty = try fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }),
.buffered_writer = undefined,
.original_termios = undefined,
.info = try Info.init(allocator),
.allocator = allocator,
.box = Box.init(allocator),
.events = std.ArrayList(Event).init(allocator),
};
errdefer term.tty.close();
errdefer term.info.deinit();
term.buffered_writer = io.BufferedWriter(32768, fs.File.Writer){ .unbuffered_writer = term.tty.writer() };
try term.uncook();
try attachSignalHandlers();
try term.updateWinSize();
try term.print("\x1b[>{d}u", .{KittyFlags.asInt(.{})});
return term;
}
pub fn deinit(self: *Terminal) void {
self.print("\x1b[<u", .{}) catch @panic("failed to restore keyboard mode");
self.events.deinit();
self.box.deinit();
self.cook() catch @panic("failed to restore termios");
self.cursorShow() catch @panic("failed to unhide cursor");
self.buffered_writer.flush() catch @panic("couldn't flush output buffer");
self.tty.close();
self.info.deinit();
}
fn uncook(self: *Terminal) !void {
self.original_termios = try posix.tcgetattr(self.tty.handle);
var raw = self.original_termios;
raw.lflag.ECHO = false;
raw.lflag.ICANON = false;
// raw.lflag.ISIG = false;
raw.lflag.ISIG = true;
raw.lflag.IEXTEN = false;
raw.iflag.IXON = false;
raw.iflag.ICRNL = false;
raw.iflag.BRKINT = false;
raw.iflag.INPCK = false;
raw.iflag.ISTRIP = false;
raw.oflag.OPOST = false;
raw.cc[@intFromEnum(os.linux.V.TIME)] = 0;
raw.cc[@intFromEnum(os.linux.V.MIN)] = 0;
try posix.tcsetattr(self.tty.handle, .FLUSH, raw);
}
fn cook(self: *Terminal) !void {
try posix.tcsetattr(self.tty.handle, .FLUSH, self.original_termios);
}
fn attachSignalHandlers() !void {
var act = posix.Sigaction{
.handler = .{ .handler = &S.handlerFn },
.mask = posix.empty_sigset,
.flags = 0,
};
// posix.sigaction(posix.SIG.INT, &act, null);
posix.sigaction(posix.SIG.USR1, &act, null);
posix.sigaction(posix.SIG.USR2, &act, null);
posix.sigaction(posix.SIG.WINCH, &act, null);
}
pub fn getEvents(self: *Terminal) ![]Event {
if (S.ev) |ev| {
switch (ev) {
Event.system => {
switch (ev.system) {
.winch => try self.updateWinSize(),
else => try self.events.append(ev),
}
},
else => try self.events.append(ev),
}
S.ev = null;
}
var pfd = [_]os.linux.pollfd{.{
.fd = self.tty.handle,
.events = os.linux.POLL.IN,
.revents = 0,
}};
if (os.linux.poll(&pfd, 1, 0) > 0) {
var key: [1]u8 = undefined;
_ = try self.tty.reader().read(&key);
if (key[0] != '\x1b') try self.events.append(Event{ .keyboard = .{ .code = key[0], .mods = Modifiers.init(1) } }) else {
_ = try self.tty.reader().read(&key);
if (key[0] == '[') {
var code_list = std.ArrayList(u8).init(self.allocator);
defer code_list.deinit();
var delim: u8 = 0;
while (code_list.items.len < 1024) {
const c = self.tty.reader().readByte() catch |e| {
if (e == error.EndOfStream) {
std.debug.print("\n\r\n{s}\r\n", .{fmt.fmtSliceHexLower(code_list.items)});
return error.UnknownControlCode;
} else return e;
};
switch (c) {
'u', '~', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'S' => {
delim = c;
break;
},
else => try code_list.append(c),
}
}
// std.debug.print("{s}{s}\r\n", .{ code_list.items, &[1]u8{delim} });
var keycode: u21 = 1;
var alt_keycode: ?u21 = null;
var modifiers: u9 = 1;
var event_type: EventType = .press;
var text: ?u21 = null;
var section: u8 = 0;
var semicolon_it = mem.splitScalar(u8, code_list.items, ';');
while (semicolon_it.next()) |semi| : (section += 1) try switch (section) {
0 => if (semi.len > 0) {
const colon = mem.indexOfScalar(u8, semi, ':');
keycode = try fmt.parseInt(u21, if (colon) |c| semi[0..c] else semi, 10);
if (colon) |c| alt_keycode = try fmt.parseInt(u21, semi[c + 1 ..], 10);
},
1 => if (semi.len > 0) {
const colon = mem.indexOfScalar(u8, semi, ':');
modifiers = try fmt.parseInt(u9, if (colon) |c| semi[0..c] else semi, 10);
if (colon) |c| event_type = EventType.init(try fmt.parseInt(u2, semi[c + 1 ..], 10));
},
2 => text = try fmt.parseInt(u21, semi, 10),
else => error.InvalidKittyEscape,
};
try self.events.append(Event{ .keyboard = .{
.code = keycode,
.altcode = alt_keycode,
.mods = Modifiers.init(modifiers),
.evtype = event_type,
.text = text,
.fnkey = FunctionalKey.init(keycode, delim),
} });
// std.debug.print("{any}\r\n", .{self.events.items[self.events.items.len - 1].keyboard});
// std.debug.print("keycode: {}\r\nalt_keycode: {?}\r\nmodifiers: {}\r\n{}\r\ntext: {?}\r\n\nspecial: {?}\r\n\n", .{ keycode, alt_keycode, modifiers, event_type, text, FunctionalKey.init(keycode, delim) });
}
}
}
return try self.events.toOwnedSlice();
}
const S = struct {
var ev: ?Event = null;
fn handlerFn(sig: i32) callconv(.C) void {
switch (sig) {
// posix.SIG.INT => ev = Event{ .system = .int },
posix.SIG.USR1 => ev = Event{ .system = .usr1 },
posix.SIG.USR2 => ev = Event{ .system = .usr2 },
posix.SIG.WINCH => ev = Event{ .system = .winch },
else => {},
}
}
};
pub const Event = union(enum) {
system: enum {
// int,
usr1,
usr2,
winch,
},
keyboard: struct {
code: u21,
altcode: ?u21 = null,
mods: Modifiers,
evtype: EventType = .press,
text: ?u21 = null,
fnkey: ?FunctionalKey = null,
},
};
pub const Modifiers = struct {
shift: bool,
alt: bool,
ctrl: bool,
super: bool,
hyper: bool,
meta: bool,
caps_lock: bool,
num_lock: bool,
pub fn init(bits: u9) Modifiers {
const b = bits - 1;
return .{
.shift = (b & 0b1) > 0,
.alt = (b & 0b10) > 0,
.ctrl = (b & 0b100) > 0,
.super = (b & 0b1000) > 0,
.hyper = (b & 0b10000) > 0,
.meta = (b & 0b100000) > 0,
.caps_lock = (b & 0b1000000) > 0,
.num_lock = (b & 0b10000000) > 0,
};
}
};
pub const EventType = enum(u2) {
press = 1,
repeat = 2,
release = 3,
pub fn init(int: u2) EventType {
return if (int == 0) .press else @enumFromInt(int);
}
};
pub const FunctionalKey = enum {
escape,
enter,
tab,
backspace,
insert,
delete,
left,
right,
up,
down,
page_up,
page_down,
home,
end,
caps_lock,
scroll_lock,
num_lock,
print_screen,
pause,
menu,
f1,
f2,
f3,
f4,
f5,
f6,
f7,
f8,
f9,
f10,
f11,
f12,
f13,
f14,
f15,
f16,
f17,
f18,
f19,
f20,
f21,
f22,
f23,
f24,
f25,
f26,
f27,
f28,
f29,
f30,
f31,
f32,
f33,
f34,
f35,
kp_0,
kp_1,
kp_2,
kp_3,
kp_4,
kp_5,
kp_6,
kp_7,
kp_8,
kp_9,
kp_decimal,
kp_divide,
kp_multiply,
kp_subtract,
kp_add,
kp_enter,
kp_equal,
kp_separator,
kp_left,
kp_right,
kp_up,
kp_down,
kp_page_up,
kp_page_down,
kp_home,
kp_end,
kp_insert,
kp_delete,
kp_begin,
media_play,
media_pause,
media_play_pause,
media_reverse,
media_stop,
media_fast_forward,
media_rewind,
media_track_next,
media_track_previous,
media_record,
lower_volume,
raise_volume,
mute_volume,
left_shift,
left_control,
left_alt,
left_super,
left_hyper,
left_meta,
right_shift,
right_control,
right_alt,
right_super,
right_hyper,
right_meta,
iso_level3_shift,
iso_level5_shift,
fn init(keycode: u21, delim: u8) ?FunctionalKey {
return switch (delim) {
'~' => switch (keycode) {
2 => .insert,
3 => .delete,
5 => .page_up,
6 => .page_down,
7 => .home,
8 => .end,
11 => .f1,
12 => .f2,
13 => .f3,
14 => .f4,
15 => .f5,
17 => .f6,
18 => .f7,
19 => .f8,
20 => .f9,
21 => .f10,
23 => .f11,
24 => .f12,
57427 => .kp_begin,
else => null,
},
'A' => if (keycode == 1) .up else null,
'B' => if (keycode == 1) .down else null,
'C' => if (keycode == 1) .right else null,
'D' => if (keycode == 1) .left else null,
'E' => if (keycode == 1) .kp_begin else null,
'F' => if (keycode == 1) .end else null,
'H' => if (keycode == 1) .home else null,
'P' => if (keycode == 1) .f1 else null,
'Q' => if (keycode == 1) .f2 else null,
'S' => if (keycode == 1) .f4 else null,
'u' => switch (keycode) {
27 => .escape,
13 => .enter,
9 => .tab,
127 => .backspace,
57358 => .caps_lock,
57359 => .scroll_lock,
57360 => .num_lock,
57361 => .print_screen,
57362 => .pause,
57363 => .menu,
57376 => .f13,
57377 => .f14,
57378 => .f15,
57379 => .f16,
57380 => .f17,
57381 => .f18,
57382 => .f19,
57383 => .f20,
57384 => .f21,
57385 => .f22,
57386 => .f23,
57387 => .f24,
57388 => .f25,
57389 => .f26,
57390 => .f27,
57391 => .f28,
57392 => .f29,
57393 => .f30,
57394 => .f31,
57395 => .f32,
57396 => .f33,
57397 => .f34,
57398 => .f35,
57399 => .kp_0,
57400 => .kp_1,
57401 => .kp_2,
57402 => .kp_3,
57403 => .kp_4,
57404 => .kp_5,
57405 => .kp_6,
57406 => .kp_7,
57407 => .kp_8,
57408 => .kp_9,
57409 => .kp_decimal,
57410 => .kp_divide,
57411 => .kp_multiply,
57412 => .kp_subtract,
57413 => .kp_add,
57414 => .kp_enter,
57415 => .kp_equal,
57416 => .kp_separator,
57417 => .kp_left,
57418 => .kp_right,
57419 => .kp_up,
57420 => .kp_down,
57421 => .kp_page_up,
57422 => .kp_page_down,
57423 => .kp_home,
57424 => .kp_end,
57425 => .kp_insert,
57426 => .kp_delete,
57428 => .media_play,
57429 => .media_pause,
57430 => .media_play_pause,
57431 => .media_reverse,
57432 => .media_stop,
57433 => .media_fast_forward,
57434 => .media_rewind,
57435 => .media_track_next,
57436 => .media_track_previous,
57437 => .media_record,
57438 => .lower_volume,
57439 => .raise_volume,
57440 => .mute_volume,
57441 => .left_shift,
57442 => .left_control,
57443 => .left_alt,
57444 => .left_super,
57445 => .left_hyper,
57446 => .left_meta,
57447 => .right_shift,
57448 => .right_control,
57449 => .right_alt,
57450 => .right_super,
57451 => .right_hyper,
57452 => .right_meta,
57453 => .iso_level3_shift,
57454 => .iso_level5_shift,
else => null,
},
else => null,
};
}
};
fn updateWinSize(self: *Terminal) !void {
var sz: posix.winsize = undefined;
const ret = os.linux.ioctl(0, os.linux.T.IOCGWINSZ, @intFromPtr(&sz));
// std.debug.print("ret: {d}, {any}\r\n", .{ ret, sz });
if (ret == 0) {
self.box.rect = .{
.x = 0,
.y = 0,
.h = @intCast(sz.row),
.w = @intCast(sz.col),
};
self.box.makeDirty();
} else unreachable; // TODO: handle else case
}
pub fn draw(self: *Terminal) !void {
if (self.box.dirty) try self.clearScreen();
try self.box.draw(self);
try self.buffered_writer.flush();
}
pub fn print(self: *Terminal, comptime format: []const u8, args: anytype) !void {
const formatted = try fmt.allocPrint(self.allocator, format, args);
defer self.allocator.free(formatted);
try self.buffered_writer.writer().writeAll(formatted);
}
pub fn cursorShow(self: *Terminal) !void {
try self.info.writeString(.cursor_visible, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorHide(self: *Terminal) !void {
try self.info.writeString(.cursor_invisible, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorUp(self: *Terminal) !void {
try self.info.writeString(.cursor_up, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorDown(self: *Terminal) !void {
try self.info.writeString(.cursor_down, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorLeft(self: *Terminal) !void {
try self.info.writeString(.cursor_left, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorRight(self: *Terminal) !void {
try self.info.writeString(.cursor_right, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorSet(self: *Terminal, x: u32, y: u32) !void {
try self.info.writeString(.cursor_address, self.buffered_writer.writer(), &[_]u32{ y, x });
}
pub fn blinkOn(self: *Terminal) !void {
try self.info.writeString(.enter_blink_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn blinkOff(self: *Terminal) !void {
try self.info.writeString(.exit_blink_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn boldOn(self: *Terminal) !void {
try self.info.writeString(.enter_bold_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn boldOff(self: *Terminal) !void {
try self.info.writeString(.exit_bold_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn italicsOn(self: *Terminal) !void {
try self.info.writeString(.enter_italics_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn italicsOff(self: *Terminal) !void {
try self.info.writeString(.exit_italics_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn underlineOn(self: *Terminal) !void {
try self.info.writeString(.enter_underline_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn underlineOff(self: *Terminal) !void {
try self.info.writeString(.exit_underline_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn clearScreen(self: *Terminal) !void {
try self.info.writeString(.clear_screen, self.buffered_writer.writer(), &[_]u32{});
}
// pub fn clearRegion(self: *Terminal, x: u32, y: u32, width: u32, height: u32) !void {
// var row = y;
// var i: u32 = 0;
// while (i < height) {
// i += 1;
// row += 1;
// }
// }
pub fn setFg(self: *Terminal, color: Color) !void {
try self.info.writeString(.set_rgb_foreground, self.buffered_writer.writer(), &[_]u32{
@intFromFloat(color.r * 0xff),
@intFromFloat(color.g * 0xff),
@intFromFloat(color.b * 0xff),
});
}
pub fn setBg(self: *Terminal, color: Color) !void {
try self.info.writeString(.set_rgb_background, self.buffered_writer.writer(), &[_]u32{
@intFromFloat(color.r * 0xff),
@intFromFloat(color.g * 0xff),
@intFromFloat(color.b * 0xff),
});
}
pub const Info = @import("terminfo.zig");
pub const Color = @import("color.zig").RGBA;
pub const Box = struct {
parent: ?*Box,
children: std.ArrayList(*Box),
rect: ?Rect = null, // TODO: maybe unionize this with .parent
dirty: bool = true,
left: u32,
right: u32,
top: u32,
bottom: u32,
border_type: enum { line, bg } = .bg,
border_char: u8 = ' ',
border_bg: Color = Color{ .r = 0.0, .g = 0.0, .b = 0.0 },
border_fg: Color = Color{ .r = 1.0, .g = 1.0, .b = 1.0 },
content: []const u8 = "",
content_halign: enum { left, center, right } = .left,
content_valign: enum { top, center, bottom } = .top,
pub fn init(allocator: mem.Allocator) Box {
return .{
.parent = null,
.children = std.ArrayList(*Box).init(allocator),
.left = 2,
.right = 2,
.top = 1,
.bottom = 1,
};
}
pub fn deinit(self: *Box) void {
for (self.children.items) |child| child.deinit();
self.children.deinit();
}
pub fn addChild(self: *Box, child: *Box) !void {
std.debug.assert(child.parent == null);
child.parent = self;
try self.children.append(child);
}
pub fn removeChild(self: *Box, child: *Box) !void {
_ = self;
_ = child;
}
pub fn removeChildByIndex(self: *Box, child: usize) !void {
_ = self;
_ = child;
}
pub fn moveChild(self: *Box, child: *Box, idx: usize) !void {
_ = self;
_ = child;
_ = idx;
}
pub fn moveChildByIndex(self: *Box, child: usize, idx: usize) !void {
_ = self;
_ = child;
_ = idx;
}
fn makeDirty(self: *Box) void {
self.dirty = true;
for (self.children.items) |child| child.makeDirty();
}
pub fn draw(self: *Box, term: *Terminal) !void {
if (self.dirty) {
const rect = self.getRect();
switch (self.border_type) {
.line => {
try term.setFg(self.getBorderFgColor());
try term.setBg(self.getBorderBgColor());
var x: u32 = 0;
var y: u32 = 0;
try term.cursorSet(rect.x, rect.y);
try term.print("", .{});
while (x < rect.w - 2) : (x += 1) try term.print("", .{});
try term.print("", .{});
y += 1;
while (y < rect.h - 1) : (y += 1) {
try term.cursorSet(rect.x, rect.y + y);
try term.print("", .{});
try term.cursorSet(rect.x + rect.w - 1, rect.y + y);
try term.print("", .{});
}
x = 0;
try term.cursorSet(rect.x, rect.y + rect.h - 1);
try term.print("", .{});
while (x < rect.w - 2) : (x += 1) try term.print("", .{});
try term.print("", .{});
},
.bg => {
try term.setBg(self.getBorderBgColor());
var y: u32 = 0;
try term.cursorSet(rect.x, rect.y);
while (y < rect.h) : (y += 1) {
try term.cursorSet(rect.x, rect.y + y);
var x: u32 = 0;
while (x < rect.w) : (x += 1) try term.print(" ", .{});
}
if (self.content.len > 0) {
const lines = mem.count(u8, self.content, "\n");
const x = switch (self.content_halign) {
.left => rect.x,
.center => rect.x + (rect.w / 2) - (@as(u32, @intCast(self.content.len)) / 2),
.right => rect.x + rect.w - (@as(u32, @intCast(self.content.len))),
};
y = switch (self.content_valign) {
.top => rect.y,
.center => rect.y + (rect.h / 2) - (@as(u32, @intCast(lines)) / 2),
.bottom => rect.y + rect.h - 1 - @as(u32, @intCast(lines)),
};
try term.cursorSet(x, y);
var split = mem.splitScalar(u8, self.content, '\n');
while (split.next()) |s| {
var spliit = mem.splitScalar(u8, s, '\r');
while (spliit.next()) |c| {
try term.cursorSet(x, y);
try term.print("{s}", .{c});
}
y += 1;
}
}
},
}
self.dirty = false;
}
for (self.children.items) |child| try child.draw(term);
}
fn getRect(self: *Box) Rect {
var rect: Rect = undefined;
if (self.parent) |parent| {
const parent_rect = parent.getRect();
rect.x = parent_rect.x + self.left;
rect.y = parent_rect.y + self.top;
rect.w = parent_rect.w + parent_rect.x - rect.x - self.right;
rect.h = parent_rect.h + parent_rect.y - rect.y - self.bottom;
} else return self.rect.?;
return rect;
}
const Rect = struct { x: u32, y: u32, w: u32, h: u32 };
fn getBorderFgColor(self: *Box) Color {
if (self.parent) |parent| {
const parent_color = parent.getBorderFgColor();
return parent_color.blend(&self.border_fg);
} else return self.border_fg;
}
fn getBorderBgColor(self: *Box) Color {
if (self.parent) |parent| {
const parent_color = parent.getBorderBgColor();
return parent_color.blend(&self.border_bg);
} else return self.border_bg;
}
};
const KittyFlags = struct {
/// Disambiguate escape codes
disambiguate: bool = true,
/// Report event types
event_types: bool = true,
/// Report alternate keys
alt_keys: bool = true,
/// Report all keys as escape codes
all_escapes: bool = true,
/// Report associated text
associated_text: bool = true,
fn asInt(flags: KittyFlags) u5 {
const d: u5 = if (flags.disambiguate) 1 else 0;
const e: u5 = if (flags.event_types) 1 else 0;
const k: u5 = if (flags.alt_keys) 1 else 0;
const a: u5 = if (flags.all_escapes) 1 else 0;
const t: u5 = if (flags.associated_text) 1 else 0;
return d << 0 |
e << 1 |
k << 2 |
a << 3 |
t << 4;
}
};
};

742
src/terminal.zig Normal file
View file

@ -0,0 +1,742 @@
const std = @import("std");
const fs = std.fs;
const io = std.io;
const os = std.os;
const mem = std.mem;
const fmt = std.fmt;
const posix = std.posix;
const Control = @import("control.zig");
const Terminal = @This();
tty: fs.File,
buffered_writer: io.BufferedWriter(32768, fs.File.Writer),
original_termios: os.linux.termios,
info: Info,
allocator: mem.Allocator,
root_control: Control,
buffer: *Buffer,
events: std.ArrayList(Event),
pub fn init(allocator: mem.Allocator) !Terminal {
var term = Terminal{
.tty = try fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }),
.buffered_writer = undefined,
.original_termios = undefined,
.info = try Info.init(allocator),
.allocator = allocator,
.root_control = Control.init(allocator),
.buffer = undefined,
.events = std.ArrayList(Event).init(allocator),
};
errdefer term.tty.close();
errdefer term.info.deinit();
term.buffered_writer = io.BufferedWriter(32768, fs.File.Writer){ .unbuffered_writer = term.tty.writer() };
try term.uncook();
try attachSignalHandlers();
try term.updateWinSize();
term.buffer = try Buffer.init(allocator, term.root_control.rect.?.h, term.root_control.rect.?.w);
errdefer term.buffer.deinit();
try term.print("\x1b[>{d}u", .{KittyFlags.asInt(.{})});
return term;
}
pub fn deinit(self: *Terminal) void {
self.print("\x1b[<u", .{}) catch @panic("failed to restore keyboard mode");
self.buffer.deinit();
self.events.deinit();
self.root_control.deinit();
self.cook() catch @panic("failed to restore termios");
self.cursorShow() catch @panic("failed to unhide cursor");
self.buffered_writer.flush() catch @panic("couldn't flush output buffer");
self.tty.close();
self.info.deinit();
}
fn uncook(self: *Terminal) !void {
self.original_termios = try posix.tcgetattr(self.tty.handle);
var raw = self.original_termios;
raw.lflag.ECHO = false;
raw.lflag.ICANON = false;
// raw.lflag.ISIG = false;
raw.lflag.ISIG = true;
raw.lflag.IEXTEN = false;
raw.iflag.IXON = false;
raw.iflag.ICRNL = false;
raw.iflag.BRKINT = false;
raw.iflag.INPCK = false;
raw.iflag.ISTRIP = false;
raw.oflag.OPOST = false;
raw.cc[@intFromEnum(os.linux.V.TIME)] = 0;
raw.cc[@intFromEnum(os.linux.V.MIN)] = 0;
try posix.tcsetattr(self.tty.handle, .FLUSH, raw);
}
fn cook(self: *Terminal) !void {
try posix.tcsetattr(self.tty.handle, .FLUSH, self.original_termios);
}
fn attachSignalHandlers() !void {
var act = posix.Sigaction{
.handler = .{ .handler = &S.handlerFn },
.mask = posix.empty_sigset,
.flags = 0,
};
// posix.sigaction(posix.SIG.INT, &act, null);
posix.sigaction(posix.SIG.USR1, &act, null);
posix.sigaction(posix.SIG.USR2, &act, null);
posix.sigaction(posix.SIG.WINCH, &act, null);
}
pub fn getEvents(self: *Terminal) ![]Event {
if (S.ev) |ev| {
switch (ev) {
Event.system => {
switch (ev.system) {
.winch => try self.updateWinSize(),
else => try self.events.append(ev),
}
},
else => try self.events.append(ev),
}
S.ev = null;
}
var pfd = [_]os.linux.pollfd{.{
.fd = self.tty.handle,
.events = os.linux.POLL.IN,
.revents = 0,
}};
if (os.linux.poll(&pfd, 1, 0) > 0) {
var key: [1]u8 = undefined;
_ = try self.tty.reader().read(&key);
if (key[0] != '\x1b') try self.events.append(Event{ .keyboard = .{ .code = key[0], .mods = Modifiers.init(1) } }) else {
_ = try self.tty.reader().read(&key);
if (key[0] == '[') {
var code_list = std.ArrayList(u8).init(self.allocator);
defer code_list.deinit();
var delim: u8 = 0;
while (code_list.items.len < 1024) {
const c = self.tty.reader().readByte() catch |e| {
if (e == error.EndOfStream) {
std.debug.print("\n\r\n{s}\r\n", .{fmt.fmtSliceHexLower(code_list.items)});
return error.UnknownControlCode;
} else return e;
};
switch (c) {
'u', '~', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'S' => {
delim = c;
break;
},
else => try code_list.append(c),
}
}
// std.debug.print("{s}{s}\r\n", .{ code_list.items, &[1]u8{delim} });
var keycode: u21 = 1;
var alt_keycode: ?u21 = null;
var modifiers: u9 = 1;
var event_type: EventType = .press;
var text: ?u21 = null;
var section: u8 = 0;
var semicolon_it = mem.splitScalar(u8, code_list.items, ';');
while (semicolon_it.next()) |semi| : (section += 1) try switch (section) {
0 => if (semi.len > 0) {
const colon = mem.indexOfScalar(u8, semi, ':');
keycode = try fmt.parseInt(u21, if (colon) |c| semi[0..c] else semi, 10);
if (colon) |c| alt_keycode = try fmt.parseInt(u21, semi[c + 1 ..], 10);
},
1 => if (semi.len > 0) {
const colon = mem.indexOfScalar(u8, semi, ':');
modifiers = try fmt.parseInt(u9, if (colon) |c| semi[0..c] else semi, 10);
if (colon) |c| event_type = EventType.init(try fmt.parseInt(u2, semi[c + 1 ..], 10));
},
2 => text = try fmt.parseInt(u21, semi, 10),
else => error.InvalidKittyEscape,
};
try self.events.append(Event{ .keyboard = .{
.code = keycode,
.altcode = alt_keycode,
.mods = Modifiers.init(modifiers),
.evtype = event_type,
.text = text,
.fnkey = FunctionalKey.init(keycode, delim),
} });
// std.debug.print("{any}\r\n", .{self.events.items[self.events.items.len - 1].keyboard});
// std.debug.print("keycode: {}\r\nalt_keycode: {?}\r\nmodifiers: {}\r\n{}\r\ntext: {?}\r\n\nspecial: {?}\r\n\n", .{ keycode, alt_keycode, modifiers, event_type, text, FunctionalKey.init(keycode, delim) });
}
}
}
return try self.events.toOwnedSlice();
}
const S = struct {
var ev: ?Event = null;
fn handlerFn(sig: i32) callconv(.C) void {
switch (sig) {
// posix.SIG.INT => ev = Event{ .system = .int },
posix.SIG.USR1 => ev = Event{ .system = .usr1 },
posix.SIG.USR2 => ev = Event{ .system = .usr2 },
posix.SIG.WINCH => ev = Event{ .system = .winch },
else => {},
}
}
};
pub const Event = union(enum) {
system: enum {
// int,
usr1,
usr2,
winch,
},
keyboard: struct {
code: u21,
altcode: ?u21 = null,
mods: Modifiers,
evtype: EventType = .press,
text: ?u21 = null,
fnkey: ?FunctionalKey = null,
},
};
pub const Modifiers = struct {
shift: bool,
alt: bool,
ctrl: bool,
super: bool,
hyper: bool,
meta: bool,
caps_lock: bool,
num_lock: bool,
pub fn init(bits: u9) Modifiers {
const b = bits - 1;
return .{
.shift = (b & 0b1) > 0,
.alt = (b & 0b10) > 0,
.ctrl = (b & 0b100) > 0,
.super = (b & 0b1000) > 0,
.hyper = (b & 0b10000) > 0,
.meta = (b & 0b100000) > 0,
.caps_lock = (b & 0b1000000) > 0,
.num_lock = (b & 0b10000000) > 0,
};
}
};
pub const EventType = enum(u2) {
press = 1,
repeat = 2,
release = 3,
pub fn init(int: u2) EventType {
return if (int == 0) .press else @enumFromInt(int);
}
};
fn updateWinSize(self: *Terminal) !void {
var sz: posix.winsize = undefined;
const ret = os.linux.ioctl(0, os.linux.T.IOCGWINSZ, @intFromPtr(&sz));
// std.debug.print("ret: {d}, {any}\r\n", .{ ret, sz });
if (ret == 0) {
self.root_control.rect = .{
.x = 0,
.y = 0,
.h = @intCast(sz.row),
.w = @intCast(sz.col),
};
self.root_control.makeDirty();
} else unreachable; // TODO: handle else case
}
pub fn draw(self: *Terminal) !void {
// if (self.root_control.buffer) try self.clearScreen();
try self.root_control.draw(self);
try self.buffered_writer.flush();
}
pub fn print(self: *Terminal, comptime format: []const u8, args: anytype) !void {
const formatted = try fmt.allocPrint(self.allocator, format, args);
defer self.allocator.free(formatted);
try self.buffered_writer.writer().writeAll(formatted);
}
pub fn cursorShow(self: *Terminal) !void {
try self.info.writeString(.cursor_visible, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorHide(self: *Terminal) !void {
try self.info.writeString(.cursor_invisible, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorUp(self: *Terminal) !void {
try self.info.writeString(.cursor_up, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorDown(self: *Terminal) !void {
try self.info.writeString(.cursor_down, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorLeft(self: *Terminal) !void {
try self.info.writeString(.cursor_left, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorRight(self: *Terminal) !void {
try self.info.writeString(.cursor_right, self.buffered_writer.writer(), &[_]u32{});
}
pub fn cursorSet(self: *Terminal, x: u32, y: u32) !void {
try self.info.writeString(.cursor_address, self.buffered_writer.writer(), &[_]u32{ y, x });
}
pub fn blinkOn(self: *Terminal) !void {
try self.info.writeString(.enter_blink_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn blinkOff(self: *Terminal) !void {
try self.info.writeString(.exit_blink_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn boldOn(self: *Terminal) !void {
try self.info.writeString(.enter_bold_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn boldOff(self: *Terminal) !void {
try self.info.writeString(.exit_bold_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn italicsOn(self: *Terminal) !void {
try self.info.writeString(.enter_italics_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn italicsOff(self: *Terminal) !void {
try self.info.writeString(.exit_italics_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn underlineOn(self: *Terminal) !void {
try self.info.writeString(.enter_underline_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn underlineOff(self: *Terminal) !void {
try self.info.writeString(.exit_underline_mode, self.buffered_writer.writer(), &[_]u32{});
}
pub fn clearScreen(self: *Terminal) !void {
try self.info.writeString(.clear_screen, self.buffered_writer.writer(), &[_]u32{});
}
// pub fn clearRegion(self: *Terminal, x: u32, y: u32, width: u32, height: u32) !void {
// var row = y;
// var i: u32 = 0;
// while (i < height) {
// i += 1;
// row += 1;
// }
// }
pub fn setFg(self: *Terminal, color: Color) !void {
try self.info.writeString(.set_rgb_foreground, self.buffered_writer.writer(), &[_]u32{
@intFromFloat(color.r * 0xff),
@intFromFloat(color.g * 0xff),
@intFromFloat(color.b * 0xff),
});
}
pub fn setBg(self: *Terminal, color: Color) !void {
try self.info.writeString(.set_rgb_background, self.buffered_writer.writer(), &[_]u32{
@intFromFloat(color.r * 0xff),
@intFromFloat(color.g * 0xff),
@intFromFloat(color.b * 0xff),
});
}
const Info = @import("terminfo.zig");
const Color = @import("color.zig").RGBA;
const KittyFlags = struct {
/// Disambiguate escape codes
disambiguate: bool = true,
/// Report event types
event_types: bool = true,
/// Report alternate keys
alt_keys: bool = true,
/// Report all keys as escape codes
all_escapes: bool = true,
/// Report associated text
associated_text: bool = true,
fn asInt(flags: KittyFlags) u5 {
const d: u5 = if (flags.disambiguate) 1 else 0;
const e: u5 = if (flags.event_types) 1 else 0;
const k: u5 = if (flags.alt_keys) 1 else 0;
const a: u5 = if (flags.all_escapes) 1 else 0;
const t: u5 = if (flags.associated_text) 1 else 0;
return d << 0 |
e << 1 |
k << 2 |
a << 3 |
t << 4;
}
};
pub const Buffer = struct {
// allocator: mem.Allocator,
buf: std.ArrayList(Cell),
height: u32,
width: u32,
pub fn init(allocator: mem.Allocator, height: u32, width: u32) !*Buffer {
var buffer = try allocator.create(Buffer);
errdefer allocator.destroy(buffer);
// buffer.buf = try allocator.alloc(Cell, height * width);
// errdefer allocator.free(buffer.buf);
// var buffer: Buffer = undefined;
// buffer.allocator = allocator;
buffer.buf = std.ArrayList(Cell).init(allocator);
try buffer.resize(height, width);
return buffer;
}
pub fn deinit(self: *Buffer) void {
// self.allocator.free(self.buf);
self.buf.deinit();
// self.allocator.destroy(self);
}
pub fn resize(self: *Buffer, h: u32, w: u32) !void {
self.buf.clearAndFree();
self.height = h;
self.width = w;
_ = try self.buf.addManyAsSlice(h * w);
}
pub fn cell(self: *Buffer, x: u32, y: u32) *Cell {
return &self.buf.items[self.width * y + x];
}
pub fn draw(self: *Buffer, term: *Terminal) !void {
var x: u32 = 0; // target x
var y: u32 = 0; // target y
var current_cell = term.buffer.cell(x, y);
var line = try self.allocator.alloc(u21, self.width);
defer self.allocator.free(line);
for (self.buf.items, 0..) |target_cell, i| {
if (x >= self.width) {}
var dirty = false;
if (current_cell.char != target_cell.char) {
if (!dirty) try term.cursorSet(x, y);
if (!dirty) {
try self.reprintLine(x, y);
}
dirty = true;
try term.print("{c}", .{target_cell.char});
}
// if (current_cell.italic != target_cell.italic) {
// if (!dirty) try term.cursorSet(tx, ty);
// dirty = true;
// try term.setFg(target_cell.fg);
// }
// if (current_cell.bold != target_cell.bold) {
// if (!dirty) try term.cursorSet(tx, ty);
// dirty = true;
// try term.setFg(target_cell.fg);
// }
// if (current_cell.underline != target_cell.underline) {
// if (!dirty) try term.cursorSet(tx, ty);
// dirty = true;
// try term.setFg(target_cell.fg);
// }
if (!current_cell.fg.eql(&target_cell.fg)) {
if (!dirty) try term.cursorSet(x, y);
dirty = true;
try term.setFg(target_cell.fg);
}
if (!current_cell.bg.eql(&target_cell.bg)) {
if (!dirty) try term.cursorSet(x, y);
dirty = true;
try term.setFg(target_cell.bg);
}
if (i >= self.buf.items.len - 1) break;
x += 1;
if (x >= self.width) {
y += 1;
x = 0;
}
current_cell = term.buffer.cell(x, y);
}
}
fn reprintLine(self: *Buffer, term: *Terminal, x: u32, y: u32) !void {
const half = self.width / 2;
try term.cursorSet(x, y);
if (x <= half) {
try term.print("\x1b[1K", .{});
try term.print("{u}", .{self.buf.items[self.width * y + x]});
} else {
try term.print("\x1b[0K", .{});
try term.print("{u}", .{self.buf.items[self.width * y + x]});
}
}
};
pub const Cell = struct {
fg: Color,
bg: Color,
char: u8,
italic: bool,
bold: bool,
underline: bool,
};
pub const FunctionalKey = enum {
escape,
enter,
tab,
backspace,
insert,
delete,
left,
right,
up,
down,
page_up,
page_down,
home,
end,
caps_lock,
scroll_lock,
num_lock,
print_screen,
pause,
menu,
f1,
f2,
f3,
f4,
f5,
f6,
f7,
f8,
f9,
f10,
f11,
f12,
f13,
f14,
f15,
f16,
f17,
f18,
f19,
f20,
f21,
f22,
f23,
f24,
f25,
f26,
f27,
f28,
f29,
f30,
f31,
f32,
f33,
f34,
f35,
kp_0,
kp_1,
kp_2,
kp_3,
kp_4,
kp_5,
kp_6,
kp_7,
kp_8,
kp_9,
kp_decimal,
kp_divide,
kp_multiply,
kp_subtract,
kp_add,
kp_enter,
kp_equal,
kp_separator,
kp_left,
kp_right,
kp_up,
kp_down,
kp_page_up,
kp_page_down,
kp_home,
kp_end,
kp_insert,
kp_delete,
kp_begin,
media_play,
media_pause,
media_play_pause,
media_reverse,
media_stop,
media_fast_forward,
media_rewind,
media_track_next,
media_track_previous,
media_record,
lower_volume,
raise_volume,
mute_volume,
left_shift,
left_control,
left_alt,
left_super,
left_hyper,
left_meta,
right_shift,
right_control,
right_alt,
right_super,
right_hyper,
right_meta,
iso_level3_shift,
iso_level5_shift,
fn init(keycode: u21, delim: u8) ?FunctionalKey {
return switch (delim) {
'~' => switch (keycode) {
2 => .insert,
3 => .delete,
5 => .page_up,
6 => .page_down,
7 => .home,
8 => .end,
11 => .f1,
12 => .f2,
13 => .f3,
14 => .f4,
15 => .f5,
17 => .f6,
18 => .f7,
19 => .f8,
20 => .f9,
21 => .f10,
23 => .f11,
24 => .f12,
57427 => .kp_begin,
else => null,
},
'A' => if (keycode == 1) .up else null,
'B' => if (keycode == 1) .down else null,
'C' => if (keycode == 1) .right else null,
'D' => if (keycode == 1) .left else null,
'E' => if (keycode == 1) .kp_begin else null,
'F' => if (keycode == 1) .end else null,
'H' => if (keycode == 1) .home else null,
'P' => if (keycode == 1) .f1 else null,
'Q' => if (keycode == 1) .f2 else null,
'S' => if (keycode == 1) .f4 else null,
'u' => switch (keycode) {
27 => .escape,
13 => .enter,
9 => .tab,
127 => .backspace,
57358 => .caps_lock,
57359 => .scroll_lock,
57360 => .num_lock,
57361 => .print_screen,
57362 => .pause,
57363 => .menu,
57376 => .f13,
57377 => .f14,
57378 => .f15,
57379 => .f16,
57380 => .f17,
57381 => .f18,
57382 => .f19,
57383 => .f20,
57384 => .f21,
57385 => .f22,
57386 => .f23,
57387 => .f24,
57388 => .f25,
57389 => .f26,
57390 => .f27,
57391 => .f28,
57392 => .f29,
57393 => .f30,
57394 => .f31,
57395 => .f32,
57396 => .f33,
57397 => .f34,
57398 => .f35,
57399 => .kp_0,
57400 => .kp_1,
57401 => .kp_2,
57402 => .kp_3,
57403 => .kp_4,
57404 => .kp_5,
57405 => .kp_6,
57406 => .kp_7,
57407 => .kp_8,
57408 => .kp_9,
57409 => .kp_decimal,
57410 => .kp_divide,
57411 => .kp_multiply,
57412 => .kp_subtract,
57413 => .kp_add,
57414 => .kp_enter,
57415 => .kp_equal,
57416 => .kp_separator,
57417 => .kp_left,
57418 => .kp_right,
57419 => .kp_up,
57420 => .kp_down,
57421 => .kp_page_up,
57422 => .kp_page_down,
57423 => .kp_home,
57424 => .kp_end,
57425 => .kp_insert,
57426 => .kp_delete,
57428 => .media_play,
57429 => .media_pause,
57430 => .media_play_pause,
57431 => .media_reverse,
57432 => .media_stop,
57433 => .media_fast_forward,
57434 => .media_rewind,
57435 => .media_track_next,
57436 => .media_track_previous,
57437 => .media_record,
57438 => .lower_volume,
57439 => .raise_volume,
57440 => .mute_volume,
57441 => .left_shift,
57442 => .left_control,
57443 => .left_alt,
57444 => .left_super,
57445 => .left_hyper,
57446 => .left_meta,
57447 => .right_shift,
57448 => .right_control,
57449 => .right_alt,
57450 => .right_super,
57451 => .right_hyper,
57452 => .right_meta,
57453 => .iso_level3_shift,
57454 => .iso_level5_shift,
else => null,
},
else => null,
};
}
};