Zig uncooked terminal library.
at master 346 lines 7.4 kB view raw
1//! VERSION: 0.0.2 2//! This module provides functionality to the uncooked terminal. 3//! The following termios setup was retrieved from zig.news[1] on 2024-08-04 4//! 10:57:50. 5//! - [1]: https://zig.news/lhp/want-to-create-a-tui-application-the-basics-of-uncooked-terminal-io-17gm 6 7const std = @import("std"); 8const os = std.os; 9const fs = std.fs; 10const mem = std.mem; 11 12/// Key enum. This is meant to capture any and all ascii input from the user. 13/// This can only capture ascii input and some simple escape sequences. 14pub const Key = enum { 15 Null, 16 StartOfHeading, 17 StartOfText, 18 EndOfText, 19 EndOfTransmission, 20 Enquiry, 21 Acknowledge, 22 Bell, 23 Backspace, 24 Tab, 25 NewLine, 26 VerticalTab, 27 NewPage, 28 CarriageReturn, 29 ShiftOut, 30 ShiftIn, 31 DataLinkEscape, 32 DeviceControl1, 33 DeviceControl2, 34 DeviceControl3, 35 DeviceControl4, 36 NegativeAcknowledgement, 37 SynchronousIdle, 38 EndOfTransmissionBlock, 39 Cancel, 40 EndOfMedium, 41 Substitute, 42 Escape, 43 FileSeparator, 44 GroupSeparator, 45 RecordSeparator, 46 UnitSeparator, 47 Space, 48 ExclamationMark, 49 QuotationMark, 50 NumberSign, 51 DollarSign, 52 PercentSign, 53 Ampersand, 54 Apostrophe, 55 LParen, 56 RParen, 57 Asterisk, 58 PlusSign, 59 Comma, 60 MinusSign, 61 Period, 62 ForwardSlash, 63 Zero, 64 One, 65 Two, 66 Three, 67 Four, 68 Five, 69 Six, 70 Seven, 71 Eight, 72 Nine, 73 Colon, 74 SemiColon, 75 LessThan, 76 Equal, 77 GreaterThan, 78 QuestionMark, 79 At, 80 A, 81 B, 82 C, 83 D, 84 E, 85 F, 86 G, 87 H, 88 I, 89 J, 90 K, 91 L, 92 M, 93 N, 94 O, 95 P, 96 Q, 97 R, 98 S, 99 T, 100 U, 101 V, 102 W, 103 X, 104 Y, 105 Z, 106 LBracket, 107 BackwardSlash, 108 RBracket, 109 Caret, 110 UnderScore, 111 Backtick, 112 a, 113 b, 114 c, 115 d, 116 e, 117 f, 118 g, 119 h, 120 i, 121 j, 122 k, 123 l, 124 m, 125 n, 126 o, 127 p, 128 q, 129 r, 130 s, 131 t, 132 u, 133 v, 134 w, 135 x, 136 y, 137 z, 138 LBrace, 139 Pipe, 140 RBrace, 141 Tilde, 142 // 143 ArrowLeft, 144 ArrowRight, 145 ArrowUp, 146 ArrowDown, 147}; 148 149/// Key errors. 150const KeyError = error{UnkownKey}; 151 152/// Waits until the user presses a key and returns the pressed key as the `Key` 153/// enum. 154pub fn get_key() !Key { 155 var tty: fs.File = try fs.cwd().openFile("/dev/tty", .{ .mode = fs.File.OpenMode.read_write }); 156 defer tty.close(); 157 158 const original_termios = try os.tcgetattr(tty.handle); 159 var raw = original_termios; 160 161 raw.lflag &= ~@as(os.system.tcflag_t, os.system.ECHO | os.system.ICANON | os.system.ISIG | os.system.IEXTEN); 162 raw.iflag &= ~@as(os.system.tcflag_t, os.system.IXON | os.system.ICRNL | os.system.BRKINT | os.system.INPCK | os.system.ISTRIP); 163 raw.oflag &= ~@as(os.system.tcflag_t, os.system.OPOST); 164 raw.cflag |= os.system.CS8; 165 166 raw.cc[os.system.V.TIME] = 0; 167 raw.cc[os.system.V.MIN] = 1; 168 169 os.tcsetattr(tty.handle, .FLUSH, raw) catch {}; 170 var flushed: bool = true; 171 defer { 172 if (flushed) { 173 os.tcsetattr(tty.handle, .FLUSH, original_termios) catch {}; 174 } else { 175 os.tcsetattr(tty.handle, .NOW, original_termios) catch {}; 176 } 177 } 178 179 tty.writeAll("\x1B[s") catch {}; // Save cursor position. 180 tty.writeAll("\x1B[?25l") catch {}; // Hide the cursor. 181 defer tty.writeAll("\x1B[u") catch {}; // Restore cursor position. 182 183 var buffer: [1]u8 = undefined; 184 _ = tty.read(&buffer) catch 0; 185 186 const ret = switch (buffer[0]) { 187 0 => .Null, 188 1 => .StartOfHeading, 189 2 => .StartOfText, 190 3 => .EndOfText, 191 4 => .EndOfTransmission, 192 5 => .Enquiry, 193 6 => .Acknowledge, 194 7 => .Bell, 195 8 => .Backspace, 196 9 => .Tab, 197 10 => .NewLine, 198 11 => .VerticalTab, 199 12 => .NewPage, 200 13 => .CarriageReturn, 201 14 => .ShiftOut, 202 15 => .ShiftIn, 203 16 => .DataLinkEscape, 204 17 => .DeviceControl1, 205 18 => .DeviceControl2, 206 19 => .DeviceControl3, 207 20 => .DeviceControl4, 208 21 => .NegativeAcknowledgement, 209 22 => .SynchronousIdle, 210 23 => .EndOfTransmissionBlock, 211 24 => .Cancel, 212 25 => .EndOfMedium, 213 26 => .Substitute, 214 27 => esc: { 215 raw.cc[os.system.V.TIME] = 1; 216 raw.cc[os.system.V.MIN] = 0; 217 218 flushed = false; 219 os.tcsetattr(tty.handle, .NOW, raw) catch {}; 220 221 var esc_buffer: [8]u8 = undefined; 222 const esc_read = try tty.read(&esc_buffer); 223 224 raw.cc[os.system.V.TIME] = 0; 225 raw.cc[os.system.V.MIN] = 1; 226 227 if (esc_read == 0) { 228 break :esc Key.Escape; 229 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[A")) { 230 break :esc Key.ArrowUp; 231 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[B")) { 232 break :esc Key.ArrowDown; 233 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[D")) { 234 break :esc Key.ArrowLeft; 235 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[C")) { 236 break :esc Key.ArrowRight; 237 } else { 238 return KeyError.UnkownKey; 239 } 240 }, 241 28 => .FileSeparator, 242 29 => .GroupSeparator, 243 30 => .RecordSeparator, 244 31 => .UnitSeparator, 245 32 => .Space, 246 33 => .ExclamationMark, 247 34 => .QuotationMark, 248 35 => .NumberSign, 249 36 => .DollarSign, 250 37 => .PercentSign, 251 38 => .Ampersand, 252 39 => .Apostrophe, 253 40 => .LParen, 254 41 => .RParen, 255 42 => .Asterisk, 256 43 => .PlusSign, 257 44 => .Comma, 258 45 => .MinusSign, 259 46 => .Period, 260 47 => .ForwardSlash, 261 48 => .Zero, 262 49 => .One, 263 50 => .Two, 264 51 => .Three, 265 52 => .Four, 266 53 => .Five, 267 54 => .Six, 268 55 => .Seven, 269 56 => .Eight, 270 57 => .Nine, 271 58 => .Colon, 272 59 => .SemiColon, 273 60 => .LessThan, 274 61 => .Equal, 275 62 => .GreaterThan, 276 63 => .QuestionMark, 277 64 => .At, 278 65 => .A, 279 66 => .B, 280 67 => .C, 281 68 => .D, 282 69 => .E, 283 70 => .F, 284 71 => .G, 285 72 => .H, 286 73 => .I, 287 74 => .J, 288 75 => .K, 289 76 => .L, 290 77 => .M, 291 78 => .N, 292 79 => .O, 293 80 => .P, 294 81 => .Q, 295 82 => .R, 296 83 => .S, 297 84 => .T, 298 85 => .U, 299 86 => .V, 300 87 => .W, 301 88 => .X, 302 89 => .Y, 303 90 => .Z, 304 91 => .LBracket, 305 92 => .BackwardSlash, 306 93 => .RBracket, 307 94 => .Caret, 308 95 => .UnderScore, 309 96 => .Backtick, 310 97 => .a, 311 98 => .b, 312 99 => .c, 313 100 => .d, 314 101 => .e, 315 102 => .f, 316 103 => .g, 317 104 => .h, 318 105 => .i, 319 106 => .j, 320 107 => .k, 321 108 => .l, 322 109 => .m, 323 110 => .n, 324 111 => .o, 325 112 => .p, 326 113 => .q, 327 114 => .r, 328 115 => .s, 329 116 => .t, 330 117 => .u, 331 118 => .v, 332 119 => .w, 333 120 => .x, 334 121 => .y, 335 122 => .z, 336 123 => .LBrace, 337 124 => .Pipe, 338 125 => .RBrace, 339 126 => .Tilde, 340 else => { 341 return KeyError.UnkownKey; 342 }, 343 }; 344 345 return ret; 346}