//! VERSION: 0.0.2 //! This module provides functionality to the uncooked terminal. //! The following termios setup was retrieved from zig.news[1] on 2024-08-04 //! 10:57:50. //! - [1]: https://zig.news/lhp/want-to-create-a-tui-application-the-basics-of-uncooked-terminal-io-17gm const std = @import("std"); const os = std.os; const fs = std.fs; const mem = std.mem; /// Key enum. This is meant to capture any and all ascii input from the user. /// This can only capture ascii input and some simple escape sequences. pub const Key = enum { Null, StartOfHeading, StartOfText, EndOfText, EndOfTransmission, Enquiry, Acknowledge, Bell, Backspace, Tab, NewLine, VerticalTab, NewPage, CarriageReturn, ShiftOut, ShiftIn, DataLinkEscape, DeviceControl1, DeviceControl2, DeviceControl3, DeviceControl4, NegativeAcknowledgement, SynchronousIdle, EndOfTransmissionBlock, Cancel, EndOfMedium, Substitute, Escape, FileSeparator, GroupSeparator, RecordSeparator, UnitSeparator, Space, ExclamationMark, QuotationMark, NumberSign, DollarSign, PercentSign, Ampersand, Apostrophe, LParen, RParen, Asterisk, PlusSign, Comma, MinusSign, Period, ForwardSlash, Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Colon, SemiColon, LessThan, Equal, GreaterThan, QuestionMark, At, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, LBracket, BackwardSlash, RBracket, Caret, UnderScore, Backtick, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, LBrace, Pipe, RBrace, Tilde, // ArrowLeft, ArrowRight, ArrowUp, ArrowDown, }; /// Key errors. const KeyError = error{UnkownKey}; /// Waits until the user presses a key and returns the pressed key as the `Key` /// enum. pub fn get_key() !Key { var tty: fs.File = try fs.cwd().openFile("/dev/tty", .{ .mode = fs.File.OpenMode.read_write }); defer tty.close(); const original_termios = try os.tcgetattr(tty.handle); var raw = original_termios; raw.lflag &= ~@as(os.system.tcflag_t, os.system.ECHO | os.system.ICANON | os.system.ISIG | os.system.IEXTEN); raw.iflag &= ~@as(os.system.tcflag_t, os.system.IXON | os.system.ICRNL | os.system.BRKINT | os.system.INPCK | os.system.ISTRIP); raw.oflag &= ~@as(os.system.tcflag_t, os.system.OPOST); raw.cflag |= os.system.CS8; raw.cc[os.system.V.TIME] = 0; raw.cc[os.system.V.MIN] = 1; os.tcsetattr(tty.handle, .FLUSH, raw) catch {}; var flushed: bool = true; defer { if (flushed) { os.tcsetattr(tty.handle, .FLUSH, original_termios) catch {}; } else { os.tcsetattr(tty.handle, .NOW, original_termios) catch {}; } } tty.writeAll("\x1B[s") catch {}; // Save cursor position. tty.writeAll("\x1B[?25l") catch {}; // Hide the cursor. defer tty.writeAll("\x1B[u") catch {}; // Restore cursor position. var buffer: [1]u8 = undefined; _ = tty.read(&buffer) catch 0; const ret = switch (buffer[0]) { 0 => .Null, 1 => .StartOfHeading, 2 => .StartOfText, 3 => .EndOfText, 4 => .EndOfTransmission, 5 => .Enquiry, 6 => .Acknowledge, 7 => .Bell, 8 => .Backspace, 9 => .Tab, 10 => .NewLine, 11 => .VerticalTab, 12 => .NewPage, 13 => .CarriageReturn, 14 => .ShiftOut, 15 => .ShiftIn, 16 => .DataLinkEscape, 17 => .DeviceControl1, 18 => .DeviceControl2, 19 => .DeviceControl3, 20 => .DeviceControl4, 21 => .NegativeAcknowledgement, 22 => .SynchronousIdle, 23 => .EndOfTransmissionBlock, 24 => .Cancel, 25 => .EndOfMedium, 26 => .Substitute, 27 => esc: { raw.cc[os.system.V.TIME] = 1; raw.cc[os.system.V.MIN] = 0; flushed = false; os.tcsetattr(tty.handle, .NOW, raw) catch {}; var esc_buffer: [8]u8 = undefined; const esc_read = try tty.read(&esc_buffer); raw.cc[os.system.V.TIME] = 0; raw.cc[os.system.V.MIN] = 1; if (esc_read == 0) { break :esc Key.Escape; } else if (mem.eql(u8, esc_buffer[0..esc_read], "[A")) { break :esc Key.ArrowUp; } else if (mem.eql(u8, esc_buffer[0..esc_read], "[B")) { break :esc Key.ArrowDown; } else if (mem.eql(u8, esc_buffer[0..esc_read], "[D")) { break :esc Key.ArrowLeft; } else if (mem.eql(u8, esc_buffer[0..esc_read], "[C")) { break :esc Key.ArrowRight; } else { return KeyError.UnkownKey; } }, 28 => .FileSeparator, 29 => .GroupSeparator, 30 => .RecordSeparator, 31 => .UnitSeparator, 32 => .Space, 33 => .ExclamationMark, 34 => .QuotationMark, 35 => .NumberSign, 36 => .DollarSign, 37 => .PercentSign, 38 => .Ampersand, 39 => .Apostrophe, 40 => .LParen, 41 => .RParen, 42 => .Asterisk, 43 => .PlusSign, 44 => .Comma, 45 => .MinusSign, 46 => .Period, 47 => .ForwardSlash, 48 => .Zero, 49 => .One, 50 => .Two, 51 => .Three, 52 => .Four, 53 => .Five, 54 => .Six, 55 => .Seven, 56 => .Eight, 57 => .Nine, 58 => .Colon, 59 => .SemiColon, 60 => .LessThan, 61 => .Equal, 62 => .GreaterThan, 63 => .QuestionMark, 64 => .At, 65 => .A, 66 => .B, 67 => .C, 68 => .D, 69 => .E, 70 => .F, 71 => .G, 72 => .H, 73 => .I, 74 => .J, 75 => .K, 76 => .L, 77 => .M, 78 => .N, 79 => .O, 80 => .P, 81 => .Q, 82 => .R, 83 => .S, 84 => .T, 85 => .U, 86 => .V, 87 => .W, 88 => .X, 89 => .Y, 90 => .Z, 91 => .LBracket, 92 => .BackwardSlash, 93 => .RBracket, 94 => .Caret, 95 => .UnderScore, 96 => .Backtick, 97 => .a, 98 => .b, 99 => .c, 100 => .d, 101 => .e, 102 => .f, 103 => .g, 104 => .h, 105 => .i, 106 => .j, 107 => .k, 108 => .l, 109 => .m, 110 => .n, 111 => .o, 112 => .p, 113 => .q, 114 => .r, 115 => .s, 116 => .t, 117 => .u, 118 => .v, 119 => .w, 120 => .x, 121 => .y, 122 => .z, 123 => .LBrace, 124 => .Pipe, 125 => .RBrace, 126 => .Tilde, else => { return KeyError.UnkownKey; }, }; return ret; }