From b0b262ae0be91ad2aa318fee4daa7799a2dec70a Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Tue, 5 Nov 2024 19:59:55 +0100 Subject: [PATCH] mod(read_input): read user input from tty --- build.zig | 2 + build.zig.zon | 4 ++ src/app.zig | 47 +++++++++++----- src/event.zig | 2 +- src/main.zig | 6 +++ src/terminal.zig | 136 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 184 insertions(+), 13 deletions(-) diff --git a/build.zig b/build.zig index 2534bdd..6fac23a 100644 --- a/build.zig +++ b/build.zig @@ -29,6 +29,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .target = target, }); + const zg_dep = b.dependency("zg", .{}); const exe = b.addExecutable(.{ .name = "tui-website", @@ -39,6 +40,7 @@ pub fn build(b: *std.Build) void { exe.root_module.addImport("vaxis", vaxis_dep.module("vaxis")); exe.root_module.addImport("zlog", zlog_dep.module("zlog")); exe.root_module.addImport("zmd", zmd_dep.module("zmd")); + exe.root_module.addImport("code_point", zg_dep.module("code_point")); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default diff --git a/build.zig.zon b/build.zig.zon index 673f7a6..92c3069 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -35,6 +35,10 @@ .url = "git+https://github.com/jetzig-framework/zmd#90e52429cacd4fdc90fd615596fe584ae40ec8e9", .hash = "12202a4edefedd52478223a44cdc9a3b41d4bc5cf3670497a48377f45ff16a5e3363", }, + .zg = .{ + .url = "https://codeberg.org/dude_the_builder/zg/archive/v0.13.2.tar.gz", + .hash = "122055beff332830a391e9895c044d33b15ea21063779557024b46169fb1984c6e40", + }, }, .paths = .{ "build.zig", diff --git a/src/app.zig b/src/app.zig index 8c986e0..f93e2df 100644 --- a/src/app.zig +++ b/src/app.zig @@ -1,6 +1,7 @@ //! Application type for TUI-applications const std = @import("std"); const terminal = @import("terminal.zig"); +const code_point = terminal.code_point; const mergeTaggedUnions = @import("event.zig").mergeTaggedUnions; const isTaggedUnion = @import("event.zig").isTaggedUnion; @@ -70,20 +71,42 @@ pub fn App(comptime E: type) type { const size = terminal.getTerminalSize(); this.postEvent(.{ .resize = size }); // read input in loop - const buf: [256]u8 = undefined; - _ = buf; + var buf: [256]u8 = undefined; while (!this.quit) { - std.time.sleep(5 * std.time.ns_per_s); - break; - // try terminal.read(buf[0..]); - // TODO: send corresponding events with key_presses - // -> create corresponding event - // -> handle key inputs (modifier, op codes, etc.) - // -> I could take inspiration from `libvaxis` for this + // FIXME: here is a race-condition -> i.e. there could be events + // in the queue, but they will not be executed because the main + // loop will close! + const read_bytes = try terminal.read(buf[0..]); + // escape key presses + if (buf[0] == 0x1b and read_bytes > 1) { + switch (buf[1]) { + // TODO: parse corresponding codes + else => {}, + } + } else { + const b = buf[0]; + const key: terminal.Key = switch (b) { + 0x00 => .{ .cp = '@', .mod = .{ .ctrl = true } }, + 0x08 => .{ .cp = terminal.Key.backspace }, + 0x09 => .{ .cp = terminal.Key.tab }, + 0x0a, 0x0d => .{ .cp = terminal.Key.enter }, + 0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } }, + 0x1b => escape: { + std.debug.assert(read_bytes == 1); + break :escape .{ .cp = terminal.Key.escape }; + }, + 0x7f => .{ .cp = terminal.Key.backspace }, + else => { + var iter = code_point.Iterator{ .bytes = buf[0..read_bytes] }; + while (iter.next()) |cp| { + this.postEvent(.{ .key = .{ .cp = cp.code } }); + } + continue; + }, + }; + this.postEvent(.{ .key = key }); + } } - // FIXME: here is a race-condition -> i.e. there could be events in - // the queue, but they will not be executed because the main loop - // will close! this.postEvent(.quit); } }; diff --git a/src/event.zig b/src/event.zig index c6a5068..29b2b03 100644 --- a/src/event.zig +++ b/src/event.zig @@ -19,7 +19,7 @@ const ApplicationEvent = union(enum) { // size has changed, etc. const SystemEvent = union(enum) { resize: terminal.Size, - // key_press: terminal.Key, + key: terminal.Key, }; pub const BuiltinEvent = mergeTaggedUnions(SystemEvent, ApplicationEvent); diff --git a/src/main.zig b/src/main.zig index b0baf99..74c90dd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -50,6 +50,12 @@ pub fn main() !void { try terminal.setCursorPositionHome(); log.debug("Size := [x: {d}, y: {d}]", .{ size.cols, size.rows }); }, + .key => |key| { + log.debug("received key: {any}", .{key}); + if (terminal.Key.matches(key, .{ .cp = 'q' })) { + app.quit = true; // TODO: who should emit the .quit event? + } + }, else => {}, } const events = try layout.handle(event); diff --git a/src/terminal.zig b/src/terminal.zig index 1a71f0d..5ef1d3f 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -1,4 +1,5 @@ const std = @import("std"); +pub const code_point = @import("code_point"); const log = std.log.scoped(.terminal); @@ -12,6 +13,137 @@ pub const Position = struct { row: u16, }; +pub const Key = struct { + cp: u21, + mod: Modifier = .{}, + + pub fn matches(self: Key, other: Key) bool { + return std.meta.eql(self, other); + } + + // codepoints for keys + pub const tab: u21 = 0x09; + pub const enter: u21 = 0x0D; + pub const escape: u21 = 0x1B; + pub const space: u21 = 0x20; + pub const backspace: u21 = 0x7F; + + // kitty key encodings (re-used here) + pub const insert: u21 = 57348; + pub const delete: u21 = 57349; + pub const left: u21 = 57350; + pub const right: u21 = 57351; + pub const up: u21 = 57352; + pub const down: u21 = 57353; + pub const page_up: u21 = 57354; + pub const page_down: u21 = 57355; + pub const home: u21 = 57356; + pub const end: u21 = 57357; + pub const caps_lock: u21 = 57358; + pub const scroll_lock: u21 = 57359; + pub const num_lock: u21 = 57360; + pub const print_screen: u21 = 57361; + pub const pause: u21 = 57362; + pub const menu: u21 = 57363; + pub const f1: u21 = 57364; + pub const f2: u21 = 57365; + pub const f3: u21 = 57366; + pub const f4: u21 = 57367; + pub const f5: u21 = 57368; + pub const f6: u21 = 57369; + pub const f7: u21 = 57370; + pub const f8: u21 = 57371; + pub const f9: u21 = 57372; + pub const f10: u21 = 57373; + pub const f11: u21 = 57374; + pub const f12: u21 = 57375; + pub const f13: u21 = 57376; + pub const f14: u21 = 57377; + pub const f15: u21 = 57378; + pub const @"f16": u21 = 57379; + pub const f17: u21 = 57380; + pub const f18: u21 = 57381; + pub const f19: u21 = 57382; + pub const f20: u21 = 57383; + pub const f21: u21 = 57384; + pub const f22: u21 = 57385; + pub const f23: u21 = 57386; + pub const f24: u21 = 57387; + pub const f25: u21 = 57388; + pub const f26: u21 = 57389; + pub const f27: u21 = 57390; + pub const f28: u21 = 57391; + pub const f29: u21 = 57392; + pub const f30: u21 = 57393; + pub const f31: u21 = 57394; + pub const @"f32": u21 = 57395; + pub const f33: u21 = 57396; + pub const f34: u21 = 57397; + pub const f35: u21 = 57398; + pub const kp_0: u21 = 57399; + pub const kp_1: u21 = 57400; + pub const kp_2: u21 = 57401; + pub const kp_3: u21 = 57402; + pub const kp_4: u21 = 57403; + pub const kp_5: u21 = 57404; + pub const kp_6: u21 = 57405; + pub const kp_7: u21 = 57406; + pub const kp_8: u21 = 57407; + pub const kp_9: u21 = 57408; + pub const kp_decimal: u21 = 57409; + pub const kp_divide: u21 = 57410; + pub const kp_multiply: u21 = 57411; + pub const kp_subtract: u21 = 57412; + pub const kp_add: u21 = 57413; + pub const kp_enter: u21 = 57414; + pub const kp_equal: u21 = 57415; + pub const kp_separator: u21 = 57416; + pub const kp_left: u21 = 57417; + pub const kp_right: u21 = 57418; + pub const kp_up: u21 = 57419; + pub const kp_down: u21 = 57420; + pub const kp_page_up: u21 = 57421; + pub const kp_page_down: u21 = 57422; + pub const kp_home: u21 = 57423; + pub const kp_end: u21 = 57424; + pub const kp_insert: u21 = 57425; + pub const kp_delete: u21 = 57426; + pub const kp_begin: u21 = 57427; + pub const media_play: u21 = 57428; + pub const media_pause: u21 = 57429; + pub const media_play_pause: u21 = 57430; + pub const media_reverse: u21 = 57431; + pub const media_stop: u21 = 57432; + pub const media_fast_forward: u21 = 57433; + pub const media_rewind: u21 = 57434; + pub const media_track_next: u21 = 57435; + pub const media_track_previous: u21 = 57436; + pub const media_record: u21 = 57437; + pub const lower_volume: u21 = 57438; + pub const raise_volume: u21 = 57439; + pub const mute_volume: u21 = 57440; + pub const left_shift: u21 = 57441; + pub const left_control: u21 = 57442; + pub const left_alt: u21 = 57443; + pub const left_super: u21 = 57444; + pub const left_hyper: u21 = 57445; + pub const left_meta: u21 = 57446; + pub const right_shift: u21 = 57447; + pub const right_control: u21 = 57448; + pub const right_alt: u21 = 57449; + pub const right_super: u21 = 57450; + pub const right_hyper: u21 = 57451; + pub const right_meta: u21 = 57452; + pub const iso_level_3_shift: u21 = 57453; + pub const iso_level_5_shift: u21 = 57454; +}; + +pub const Modifier = struct { + shift: bool = false, + alt: bool = false, + ctrl: bool = false, +}; + // Ref: https://vt100.net/docs/vt510-rm/DECRPM.html pub const ReportMode = enum { not_recognized, @@ -52,6 +184,10 @@ pub fn setCursorPositionHome() !void { _ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[H"); } +pub fn read(buf: []u8) !usize { + return try std.posix.read(std.posix.STDIN_FILENO, buf); +} + pub fn getCursorPosition() !Position { // Needs Raw mode (no wait for \n) to work properly cause // control sequence will not be written without it.