From 23dd09bbf979612dcacb88d973b88422fbb2025e Mon Sep 17 00:00:00 2001 From: Jeeves Date: Thu, 25 Jul 2024 16:31:59 -0600 Subject: [PATCH] nix update flake and finish keyboard handling --- .gitignore | 1 + build.zig | 81 +++------- flake.lock | 18 +-- src/main.zig | 401 ++++++++++++++++++++++++++++++++++++++++++----- src/root.zig | 10 -- src/terminfo.zig | 2 +- 6 files changed, 397 insertions(+), 116 deletions(-) delete mode 100644 src/root.zig diff --git a/.gitignore b/.gitignore index feda423..f27e682 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # Cheers! # -andrewrk +.zig-cache/ zig-cache/ zig-out/ /release/ diff --git a/build.zig b/build.zig index 780b792..795106d 100644 --- a/build.zig +++ b/build.zig @@ -1,97 +1,68 @@ const std = @import("std"); -// Although this function looks imperative, note that its job is to -// declaratively construct a build graph that will be executed by an external -// runner. pub fn build(b: *std.Build) void { - // Standard target options allows the person running `zig build` to choose - // what target to build for. Here we do not override the defaults, which - // means any target is allowed, and the default is native. Other options - // for restricting supported target set are available. const target = b.standardTargetOptions(.{}); - - // Standard optimization options allow the person running `zig build` to select - // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not - // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); _ = b.addModule("silkdot", .{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - const lib = b.addStaticLibrary(.{ - .name = "master", - // In this case the main source file is merely a path, however, in more - // complicated build scripts, this could be a generated file. - .root_source_file = .{ .path = "src/root.zig" }, - .target = target, - .optimize = optimize, - }); + // const lib = b.addStaticLibrary(.{ + // .name = "master", + // .root_source_file = b.path("src/root.zig"), + // .target = target, + // .optimize = optimize, + // }); - // This declares intent for the library to be installed into the standard - // location when the user invokes the "install" step (the default step when - // running `zig build`). - b.installArtifact(lib); + // b.installArtifact(lib); const exe = b.addExecutable(.{ .name = "master", - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - // This declares intent for the executable to be installed into the - // standard location when the user invokes the "install" step (the default - // step when running `zig build`). b.installArtifact(exe); - // This *creates* a Run step in the build graph, to be executed when another - // step is evaluated that depends on it. The next line below will establish - // such a dependency. const run_cmd = b.addRunArtifact(exe); - - // By making the run step depend on the install step, it will be run from the - // installation directory rather than directly from within the cache directory. - // This is not necessary, however, if the application depends on other installed - // files, this ensures they will be present and in the expected location. run_cmd.step.dependOn(b.getInstallStep()); - - // This allows the user to pass arguments to the application in the build - // command itself, like this: `zig build run -- arg1 arg2 etc` if (b.args) |args| { run_cmd.addArgs(args); } - // This creates a build step. It will be visible in the `zig build --help` menu, - // and can be selected like this: `zig build run` - // This will evaluate the `run` step rather than the default, which is "install". const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - // Creates a step for unit testing. This only builds the test executable - // but does not run it. - const lib_unit_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/root.zig" }, - .target = target, - .optimize = optimize, - }); + // const lib_unit_tests = b.addTest(.{ + // .root_source_file = b.path("src/root.zig"), + // .target = target, + // .optimize = optimize, + // }); - const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + // const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); const exe_unit_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); - // Similar to creating the run step earlier, this exposes a `test` step to - // the `zig build --help` menu, providing a way for the user to request - // running the unit tests. const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_lib_unit_tests.step); + // test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_exe_unit_tests.step); + + const install_docs = b.addInstallDirectory(.{ + .source_dir = exe.getEmittedDocs(), + .install_dir = .prefix, + .install_subdir = "docs", + }); + + const docs_step = b.step("docs", "Copy documentation artifacts to prefix path"); + docs_step.dependOn(&install_docs.step); } diff --git a/flake.lock b/flake.lock index 125ebce..350402a 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1706063522, - "narHash": "sha256-o1m9en7ovSjyktXgX3n/6GJEwG06WYa/9Mfx5hTTf5g=", + "lastModified": 1718622336, + "narHash": "sha256-lywfxWRBn+lwdKaBy5x5uTkbCcEPUonCn6bK8OQPsw4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "95c1439b205d507f3cb88aae76e02cd6a01ac504", + "rev": "d0fc4188d246ab953653f00e9ce0cf51d10d5eda", "type": "github" }, "original": { @@ -59,11 +59,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1712106553, - "narHash": "sha256-p7xlViVQh/916u28pTtPw3L+s2PkkgA5MAMk/Vfibac=", + "lastModified": 1721870682, + "narHash": "sha256-NAIeaZpJR4ynuw1pUhhukPqbKGfOoXPcATAWVtmkiiU=", "owner": "Cloudef", "repo": "zig2nix", - "rev": "32bae779ad1712a83919e8a61da1ea8e60e328e1", + "rev": "4be136cc3615ba0047ded273d693116c8b05d0d4", "type": "github" }, "original": { diff --git a/src/main.zig b/src/main.zig index f941b04..523a3bb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -93,7 +93,7 @@ pub const Terminal = struct { try term.uncook(); try attachSignalHandlers(); try term.updateWinSize(); - try term.print("\x1b[>1u", .{}); + try term.print("\x1b[>{d}u", .{KittyFlags.asInt(.{})}); return term; } @@ -142,10 +142,10 @@ pub const Terminal = struct { .mask = posix.empty_sigset, .flags = 0, }; - // try posix.sigaction(posix.SIG.INT, &act, null); - try posix.sigaction(posix.SIG.USR1, &act, null); - try posix.sigaction(posix.SIG.USR2, &act, null); - try posix.sigaction(posix.SIG.WINCH, &act, null); + // 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 { @@ -174,21 +174,56 @@ pub const Terminal = struct { if (key[0] == '[') { var code_list = std.ArrayList(u8).init(self.allocator); defer code_list.deinit(); - try self.tty.reader().streamUntilDelimiter(code_list.writer(), 'u', 1024); + 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} }); - const idx1 = mem.indexOfScalar(u8, code_list.items, ';'); - // TODO: alternate key code - if (idx1) |idx| { - const idx2 = mem.indexOfScalarPos(u8, code_list.items, idx, ';'); - // if (idx2) |ix| { - const keycode = try fmt.parseInt(u21, code_list.items[0..idx], 10); - const modifiers = try fmt.parseInt(u9, if (idx2) |i| code_list.items[idx..i] else code_list.items[idx..], 10); - try self.events.append(Event{ .keyboard = .{ - .code = keycode, - .mods = Modifiers.init(modifiers), - } }); - // } else return error.InvalidKittyEscape; - } else return error.InvalidKittyEscape; // TODO better error name!!! + 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) }); } } } @@ -218,7 +253,11 @@ pub const Terminal = struct { }, keyboard: struct { code: u21, + altcode: ?u21 = null, mods: Modifiers, + evtype: EventType = .press, + text: ?u21 = null, + fnkey: ?FunctionalKey = null, }, }; @@ -245,28 +284,271 @@ pub const Terminal = struct { .num_lock = (b & 0b10000000) > 0, }; } - // pub const Bits = enum(u8) { - // shift = 0b1, - // alt = 0b10, - // ctrl = 0b100, - // super = 0b1000, - // hyper = 0b10000, - // meta = 0b100000, - // caps_lock = 0b1000000, - // num_lock = 0b10000000, - // }; + }; + + 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: os.linux.winsize = undefined; + 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.ws_row), - .w = @intCast(sz.ws_col), + .h = @intCast(sz.row), + .w = @intCast(sz.col), }; self.box.makeDirty(); } else unreachable; // TODO: handle else case @@ -482,19 +764,30 @@ pub const Terminal = struct { var x: u32 = 0; while (x < rect.w) : (x += 1) try term.print(" ", .{}); } - try term.cursorSet( - switch (self.content_halign) { + 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))), - }, - switch (self.content_valign) { + }; + y = switch (self.content_valign) { .top => rect.y, - .center => rect.y + (rect.h / 2), - .bottom => rect.y + rect.h - 1, - }, - ); - try term.print("{s}", .{self.content}); + .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; @@ -529,4 +822,30 @@ pub const Terminal = struct { } 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/root.zig b/src/root.zig deleted file mode 100644 index ecfeade..0000000 --- a/src/root.zig +++ /dev/null @@ -1,10 +0,0 @@ -const std = @import("std"); -const testing = std.testing; - -export fn add(a: i32, b: i32) i32 { - return a + b; -} - -test "basic add functionality" { - try testing.expect(add(3, 7) == 10); -} diff --git a/src/terminfo.zig b/src/terminfo.zig index 306f0c7..c51ae56 100644 --- a/src/terminfo.zig +++ b/src/terminfo.zig @@ -6,7 +6,7 @@ const io = std.io; const Self = @This(); pub fn init(allocator: mem.Allocator) !Self { - const result = try std.ChildProcess.run(.{ + const result = try std.process.Child.run(.{ .allocator = allocator, .argv = &[_][]const u8{ "infocmp", "-x" }, });