// keys — flibc's console key decoder, ported to Flash from its hand-written // Zig. It turns the raw byte stream of a raw-mode console (kernel echo off, // byte-at-a-time — the mode /bin/login's password loop relies on) into // semantic Key events, including the multi-byte ESC-[ A/B/C/D arrow // sequences. This is the input half of FlashOS's full-screen tools. // // The pure Decoder is a three-state VT100 machine (ground → esc → csi) and is // host-testable in isolation. The SVC-driven readKey() loop is gated behind // has_driver exactly like readline's and execvp's, so the host build never // analyses the aarch64 syscall path. No allocator, no module state beyond the // caller-held Decoder; zero footprint until referenced (no boot binary calls // readKey yet — its first consumer, the /bin/mon hardware monitor, is not yet // ported). // // The first leaf that is a pure port: it adds no new grammar. The byte state // machine reuses what earlier leaves already landed — value and multi-pattern // switch prongs, an inclusive `0x20...0x7e` range prong, a labeled-block prong // (`blk: { … break :blk … }`), the driver-select `if (has_driver) struct {…} // else struct {…}`, and `&&` for the comptime gate. Its core lowers to Zig // whose token stream matches the reference. use builtin // Driver compiles only on aarch64-freestanding (the real flibc target); the // host-test build flips this off so the SVC trampoline never enters semantic // analysis. Only the pure Decoder is exercised on host. const has_driver = builtin.cpu.arch == .aarch64 && builtin.target.os.tag == .freestanding /// A decoded key. `.char` carries its byte in Event.ch; `.none` means the byte /// was consumed mid-escape-sequence (feed more); `.eof` means the stream closed. pub const Key = enum { up, down, left, right, enter, backspace, tab, escape, ctrl_c, ctrl_d, char, none, eof, } /// A key event. `ch` is meaningful only for `.char`. pub const Event = struct { key Key, ch u8 = 0, } /// Incremental VT100 input decoder. Feed it one byte at a time; it returns /// `.none` while inside an ESC sequence and a real key when one completes. A /// fresh Decoder per readKey() call is correct — a whole sequence is consumed /// within one call. pub const Decoder = struct { state State = .ground, const State = enum { ground, esc, csi, } pub fn feed(self *mut Decoder, b u8) Event { return switch self.state { .ground => self.atGround(b), .esc => self.atEsc(b), .csi => self.atCsi(b), } } fn atGround(self *mut Decoder, b u8) Event { return switch b { 0x1b => blk: { self.state = .esc break :blk .{ .key = .none } }, '\r', '\n' => .{ .key = .enter }, '\t' => .{ .key = .tab }, 0x08, 0x7f => .{ .key = .backspace }, 0x03 => .{ .key = .ctrl_c }, 0x04 => .{ .key = .ctrl_d }, 0x20...0x7e => .{ .key = .char, .ch = b }, else => .{ .key = .none }, } } fn atEsc(self *mut Decoder, b u8) Event { if b == '[' { self.state = .csi return .{ .key = .none } } if b == 0x1b { // A second ESC — stay pending on the newer one. return .{ .key = .none } } // ESC then anything else: a bare Escape; the trailing byte is dropped // (Alt- chords are out of scope for v1). self.state = .ground return .{ .key = .escape } } fn atCsi(self *mut Decoder, b u8) Event { // Parameter bytes (digits / ';') belong to the sequence — keep reading // so ESC[5~ (PgUp etc.) is absorbed cleanly rather than leaking bytes. if (b >= '0' && b <= '9') || b == ';' { return .{ .key = .none } } self.state = .ground return switch b { 'A' => .{ .key = .up }, 'B' => .{ .key = .down }, 'C' => .{ .key = .right }, 'D' => .{ .key = .left }, else => .{ .key = .none }, } } } /// Block until one whole key is read from fd 0. Returns `.eof` when the stream /// closes. Use inside a full-screen loop; pair with console_ui.screen.enter / /// leave and console mode 0. pub const readKey = driver.readKey const driver = if (has_driver) struct { use "syscalls" as sys pub fn readKey() Event { var dec = Decoder{} var b u8 = 0 while true { n := sys.read(0, #ptrCast(&b), 1) if n <= 0 { return .{ .key = .eof } } ev := dec.feed(b) if ev.key != .none { return ev } } } // Host-test stub: present only so the `pub const readKey` binding succeeds. } else struct { pub fn readKey() Event { return .{ .key = .eof } } }