From 84016c924b4113a329ba1bef7d8a2d42f49b9927 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Mon, 3 Mar 2025 18:41:40 -0700 Subject: [PATCH] idk --- src/color.zig | 7 + src/control.zig | 193 ++++++++++++ src/main.zig | 800 +---------------------------------------------- src/terminal.zig | 742 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 951 insertions(+), 791 deletions(-) create mode 100644 src/control.zig create mode 100644 src/terminal.zig diff --git a/src/color.zig b/src/color.zig index ce666c1..8e9c185 100644 --- a/src/color.zig +++ b/src/color.zig @@ -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); diff --git a/src/control.zig b/src/control.zig new file mode 100644 index 0000000..2715170 --- /dev/null +++ b/src/control.zig @@ -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; +} diff --git a/src/main.zig b/src/main.zig index 523a3bb..c9b24e3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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[ { - 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; - } - }; -}; diff --git a/src/terminal.zig b/src/terminal.zig new file mode 100644 index 0000000..813687d --- /dev/null +++ b/src/terminal.zig @@ -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[ { + 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, + }; + } +};