this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 990fe29b38e57de69733025ab580b8917c2d092f 806 lines 29 kB view raw
1const std = @import("std"); 2const builtin = @import("builtin"); 3 4const vaxis = @import("main.zig"); 5 6const ctlseqs = vaxis.ctlseqs; 7const posix = std.posix; 8const windows = std.os.windows; 9 10const Event = vaxis.Event; 11const Key = vaxis.Key; 12const Mouse = vaxis.Mouse; 13const Parser = vaxis.Parser; 14const Winsize = vaxis.Winsize; 15 16/// The target TTY implementation 17pub const Tty = if (builtin.is_test) 18 TestTty 19else switch (builtin.os.tag) { 20 .windows => WindowsTty, 21 else => PosixTty, 22}; 23 24/// global tty instance, used in case of a panic. Not guaranteed to work if 25/// for some reason there are multiple TTYs open under a single vaxis 26/// compilation unit - but this is better than nothing 27pub var global_tty: ?Tty = null; 28 29pub const PosixTty = struct { 30 /// the original state of the terminal, prior to calling makeRaw 31 termios: posix.termios, 32 33 /// The file descriptor of the tty 34 fd: posix.fd_t, 35 36 pub const SignalHandler = struct { 37 context: *anyopaque, 38 callback: *const fn (context: *anyopaque) void, 39 }; 40 41 /// global signal handlers 42 var handlers: [8]SignalHandler = undefined; 43 var handler_mutex: std.Thread.Mutex = .{}; 44 var handler_idx: usize = 0; 45 46 var handler_installed: bool = false; 47 48 /// initializes a Tty instance by opening /dev/tty and "making it raw". A 49 /// signal handler is installed for SIGWINCH. No callbacks are installed, be 50 /// sure to register a callback when initializing the event loop 51 pub fn init() !PosixTty { 52 // Open our tty 53 const fd = try posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0); 54 55 // Set the termios of the tty 56 const termios = try makeRaw(fd); 57 58 var act = posix.Sigaction{ 59 .handler = .{ .handler = PosixTty.handleWinch }, 60 .mask = switch (builtin.os.tag) { 61 .macos => 0, 62 else => posix.sigemptyset(), 63 }, 64 .flags = 0, 65 }; 66 posix.sigaction(posix.SIG.WINCH, &act, null); 67 handler_installed = true; 68 69 const self: PosixTty = .{ 70 .fd = fd, 71 .termios = termios, 72 }; 73 74 global_tty = self; 75 76 return self; 77 } 78 79 /// release resources associated with the Tty return it to its original state 80 pub fn deinit(self: PosixTty) void { 81 posix.tcsetattr(self.fd, .FLUSH, self.termios) catch |err| { 82 std.log.err("couldn't restore terminal: {}", .{err}); 83 }; 84 if (builtin.os.tag != .macos) // closing /dev/tty may block indefinitely on macos 85 posix.close(self.fd); 86 } 87 88 /// Resets the signal handler to it's default 89 pub fn resetSignalHandler() void { 90 if (!handler_installed) return; 91 handler_installed = false; 92 var act = posix.Sigaction{ 93 .handler = .{ .handler = posix.SIG.DFL }, 94 .mask = switch (builtin.os.tag) { 95 .macos => 0, 96 else => posix.sigemptyset(), 97 }, 98 .flags = 0, 99 }; 100 posix.sigaction(posix.SIG.WINCH, &act, null); 101 } 102 103 /// Write bytes to the tty 104 pub fn write(self: *const PosixTty, bytes: []const u8) !usize { 105 return posix.write(self.fd, bytes); 106 } 107 108 pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize { 109 const self: *const PosixTty = @ptrCast(@alignCast(ptr)); 110 return posix.write(self.fd, bytes); 111 } 112 113 pub fn anyWriter(self: *const PosixTty) std.io.AnyWriter { 114 return .{ 115 .context = self, 116 .writeFn = PosixTty.opaqueWrite, 117 }; 118 } 119 120 pub fn read(self: *const PosixTty, buf: []u8) !usize { 121 return posix.read(self.fd, buf); 122 } 123 124 pub fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize { 125 const self: *const PosixTty = @ptrCast(@alignCast(ptr)); 126 return posix.read(self.fd, buf); 127 } 128 129 pub fn anyReader(self: *const PosixTty) std.io.AnyReader { 130 return .{ 131 .context = self, 132 .readFn = PosixTty.opaqueRead, 133 }; 134 } 135 136 /// Install a signal handler for winsize. A maximum of 8 handlers may be 137 /// installed 138 pub fn notifyWinsize(handler: SignalHandler) !void { 139 handler_mutex.lock(); 140 defer handler_mutex.unlock(); 141 if (handler_idx == handlers.len) return error.OutOfMemory; 142 handlers[handler_idx] = handler; 143 handler_idx += 1; 144 } 145 146 fn handleWinch(_: c_int) callconv(.c) void { 147 handler_mutex.lock(); 148 defer handler_mutex.unlock(); 149 var i: usize = 0; 150 while (i < handler_idx) : (i += 1) { 151 const handler = handlers[i]; 152 handler.callback(handler.context); 153 } 154 } 155 156 /// makeRaw enters the raw state for the terminal. 157 pub fn makeRaw(fd: posix.fd_t) !posix.termios { 158 const state = try posix.tcgetattr(fd); 159 var raw = state; 160 // see termios(3) 161 raw.iflag.IGNBRK = false; 162 raw.iflag.BRKINT = false; 163 raw.iflag.PARMRK = false; 164 raw.iflag.ISTRIP = false; 165 raw.iflag.INLCR = false; 166 raw.iflag.IGNCR = false; 167 raw.iflag.ICRNL = false; 168 raw.iflag.IXON = false; 169 170 raw.oflag.OPOST = false; 171 172 raw.lflag.ECHO = false; 173 raw.lflag.ECHONL = false; 174 raw.lflag.ICANON = false; 175 raw.lflag.ISIG = false; 176 raw.lflag.IEXTEN = false; 177 178 raw.cflag.CSIZE = .CS8; 179 raw.cflag.PARENB = false; 180 181 raw.cc[@intFromEnum(posix.V.MIN)] = 1; 182 raw.cc[@intFromEnum(posix.V.TIME)] = 0; 183 try posix.tcsetattr(fd, .FLUSH, raw); 184 return state; 185 } 186 187 /// Get the window size from the kernel 188 pub fn getWinsize(fd: posix.fd_t) !Winsize { 189 var winsize = posix.winsize{ 190 .row = 0, 191 .col = 0, 192 .xpixel = 0, 193 .ypixel = 0, 194 }; 195 196 const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize)); 197 if (posix.errno(err) == .SUCCESS) 198 return Winsize{ 199 .rows = winsize.row, 200 .cols = winsize.col, 201 .x_pixel = winsize.xpixel, 202 .y_pixel = winsize.ypixel, 203 }; 204 return error.IoctlError; 205 } 206 207 pub fn bufferedWriter(self: *const PosixTty) std.io.BufferedWriter(4096, std.io.AnyWriter) { 208 return std.io.bufferedWriter(self.anyWriter()); 209 } 210}; 211 212pub const WindowsTty = struct { 213 stdin: windows.HANDLE, 214 stdout: windows.HANDLE, 215 216 initial_codepage: c_uint, 217 initial_input_mode: CONSOLE_MODE_INPUT, 218 initial_output_mode: CONSOLE_MODE_OUTPUT, 219 220 // a buffer to write key text into 221 buf: [4]u8 = undefined, 222 223 /// The last mouse button that was pressed. We store the previous state of button presses on each 224 /// mouse event so we can detect which button was released 225 last_mouse_button_press: u16 = 0, 226 227 const utf8_codepage: c_uint = 65001; 228 229 /// The input mode set by init 230 pub const input_raw_mode: CONSOLE_MODE_INPUT = .{ 231 .WINDOW_INPUT = 1, // resize events 232 .MOUSE_INPUT = 1, 233 .EXTENDED_FLAGS = 1, // allow mouse events 234 }; 235 236 /// The output mode set by init 237 pub const output_raw_mode: CONSOLE_MODE_OUTPUT = .{ 238 .PROCESSED_OUTPUT = 1, // handle control sequences 239 .VIRTUAL_TERMINAL_PROCESSING = 1, // handle ANSI sequences 240 .DISABLE_NEWLINE_AUTO_RETURN = 1, // disable inserting a new line when we write at the last column 241 .ENABLE_LVB_GRID_WORLDWIDE = 1, // enables reverse video and underline 242 }; 243 244 pub fn init() !Tty { 245 const stdin = std.io.getStdIn().handle; 246 const stdout = std.io.getStdOut().handle; 247 248 // get initial modes 249 const initial_output_codepage = windows.kernel32.GetConsoleOutputCP(); 250 const initial_input_mode = try getConsoleMode(CONSOLE_MODE_INPUT, stdin); 251 const initial_output_mode = try getConsoleMode(CONSOLE_MODE_OUTPUT, stdout); 252 253 // set new modes 254 try setConsoleMode(stdin, input_raw_mode); 255 try setConsoleMode(stdout, output_raw_mode); 256 if (windows.kernel32.SetConsoleOutputCP(utf8_codepage) == 0) 257 return windows.unexpectedError(windows.kernel32.GetLastError()); 258 259 const self: Tty = .{ 260 .stdin = stdin, 261 .stdout = stdout, 262 .initial_codepage = initial_output_codepage, 263 .initial_input_mode = initial_input_mode, 264 .initial_output_mode = initial_output_mode, 265 }; 266 267 // save a copy of this tty as the global_tty for panic handling 268 global_tty = self; 269 270 return self; 271 } 272 273 pub fn deinit(self: Tty) void { 274 _ = windows.kernel32.SetConsoleOutputCP(self.initial_codepage); 275 setConsoleMode(self.stdin, self.initial_input_mode) catch {}; 276 setConsoleMode(self.stdout, self.initial_output_mode) catch {}; 277 windows.CloseHandle(self.stdin); 278 windows.CloseHandle(self.stdout); 279 } 280 281 pub const CONSOLE_MODE_INPUT = packed struct(u32) { 282 PROCESSED_INPUT: u1 = 0, 283 LINE_INPUT: u1 = 0, 284 ECHO_INPUT: u1 = 0, 285 WINDOW_INPUT: u1 = 0, 286 MOUSE_INPUT: u1 = 0, 287 INSERT_MODE: u1 = 0, 288 QUICK_EDIT_MODE: u1 = 0, 289 EXTENDED_FLAGS: u1 = 0, 290 AUTO_POSITION: u1 = 0, 291 VIRTUAL_TERMINAL_INPUT: u1 = 0, 292 _: u22 = 0, 293 }; 294 pub const CONSOLE_MODE_OUTPUT = packed struct(u32) { 295 PROCESSED_OUTPUT: u1 = 0, 296 WRAP_AT_EOL_OUTPUT: u1 = 0, 297 VIRTUAL_TERMINAL_PROCESSING: u1 = 0, 298 DISABLE_NEWLINE_AUTO_RETURN: u1 = 0, 299 ENABLE_LVB_GRID_WORLDWIDE: u1 = 0, 300 _: u27 = 0, 301 }; 302 303 pub fn getConsoleMode(comptime T: type, handle: windows.HANDLE) !T { 304 var mode: u32 = undefined; 305 if (windows.kernel32.GetConsoleMode(handle, &mode) == 0) return switch (windows.kernel32.GetLastError()) { 306 .INVALID_HANDLE => error.InvalidHandle, 307 else => |e| windows.unexpectedError(e), 308 }; 309 return @bitCast(mode); 310 } 311 312 pub fn setConsoleMode(handle: windows.HANDLE, mode: anytype) !void { 313 if (windows.kernel32.SetConsoleMode(handle, @bitCast(mode)) == 0) return switch (windows.kernel32.GetLastError()) { 314 .INVALID_HANDLE => error.InvalidHandle, 315 else => |e| windows.unexpectedError(e), 316 }; 317 } 318 319 pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize { 320 const self: *const Tty = @ptrCast(@alignCast(ptr)); 321 return windows.WriteFile(self.stdout, bytes, null); 322 } 323 324 pub fn anyWriter(self: *const Tty) std.io.AnyWriter { 325 return .{ 326 .context = self, 327 .writeFn = Tty.opaqueWrite, 328 }; 329 } 330 331 pub fn bufferedWriter(self: *const Tty) std.io.BufferedWriter(4096, std.io.AnyWriter) { 332 return std.io.bufferedWriter(self.anyWriter()); 333 } 334 335 pub fn nextEvent(self: *Tty, parser: *Parser, paste_allocator: ?std.mem.Allocator) !Event { 336 // We use a loop so we can ignore certain events 337 var state: EventState = .{}; 338 while (true) { 339 var event_count: u32 = 0; 340 var input_record: INPUT_RECORD = undefined; 341 if (ReadConsoleInputW(self.stdin, &input_record, 1, &event_count) == 0) 342 return windows.unexpectedError(windows.kernel32.GetLastError()); 343 344 if (try self.eventFromRecord(&input_record, &state, parser, paste_allocator)) |ev| { 345 return ev; 346 } 347 } 348 } 349 350 pub const EventState = struct { 351 ansi_buf: [128]u8 = undefined, 352 ansi_idx: usize = 0, 353 utf16_buf: [2]u16 = undefined, 354 utf16_half: bool = false, 355 }; 356 357 pub fn eventFromRecord(self: *Tty, record: *const INPUT_RECORD, state: *EventState, parser: *Parser, paste_allocator: ?std.mem.Allocator) !?Event { 358 switch (record.EventType) { 359 0x0001 => { // Key event 360 const event = record.Event.KeyEvent; 361 362 if (state.utf16_half) half: { 363 state.utf16_half = false; 364 state.utf16_buf[1] = event.uChar.UnicodeChar; 365 const codepoint: u21 = std.unicode.utf16DecodeSurrogatePair(&state.utf16_buf) catch break :half; 366 const n = std.unicode.utf8Encode(codepoint, &self.buf) catch return null; 367 368 const key: Key = .{ 369 .codepoint = codepoint, 370 .base_layout_codepoint = codepoint, 371 .mods = translateMods(event.dwControlKeyState), 372 .text = self.buf[0..n], 373 }; 374 375 switch (event.bKeyDown) { 376 0 => return .{ .key_release = key }, 377 else => return .{ .key_press = key }, 378 } 379 } 380 381 const base_layout: u16 = switch (event.wVirtualKeyCode) { 382 0x00 => blk: { // delivered when we get an escape sequence or a unicode codepoint 383 if (state.ansi_idx == 0 and event.uChar.AsciiChar != 27) 384 break :blk event.uChar.UnicodeChar; 385 state.ansi_buf[state.ansi_idx] = event.uChar.AsciiChar; 386 state.ansi_idx += 1; 387 if (state.ansi_idx <= 2) return null; 388 const result = try parser.parse(state.ansi_buf[0..state.ansi_idx], paste_allocator); 389 return if (result.n == 0) null else evt: { 390 state.ansi_idx = 0; 391 break :evt result.event; 392 }; 393 }, 394 0x08 => Key.backspace, 395 0x09 => Key.tab, 396 0x0D => Key.enter, 397 0x13 => Key.pause, 398 0x14 => Key.caps_lock, 399 0x1B => Key.escape, 400 0x20 => Key.space, 401 0x21 => Key.page_up, 402 0x22 => Key.page_down, 403 0x23 => Key.end, 404 0x24 => Key.home, 405 0x25 => Key.left, 406 0x26 => Key.up, 407 0x27 => Key.right, 408 0x28 => Key.down, 409 0x2c => Key.print_screen, 410 0x2d => Key.insert, 411 0x2e => Key.delete, 412 0x30...0x39 => |k| k, 413 0x41...0x5a => |k| k + 0x20, // translate to lowercase 414 0x5b => Key.left_meta, 415 0x5c => Key.right_meta, 416 0x60 => Key.kp_0, 417 0x61 => Key.kp_1, 418 0x62 => Key.kp_2, 419 0x63 => Key.kp_3, 420 0x64 => Key.kp_4, 421 0x65 => Key.kp_5, 422 0x66 => Key.kp_6, 423 0x67 => Key.kp_7, 424 0x68 => Key.kp_8, 425 0x69 => Key.kp_9, 426 0x6a => Key.kp_multiply, 427 0x6b => Key.kp_add, 428 0x6c => Key.kp_separator, 429 0x6d => Key.kp_subtract, 430 0x6e => Key.kp_decimal, 431 0x6f => Key.kp_divide, 432 0x70 => Key.f1, 433 0x71 => Key.f2, 434 0x72 => Key.f3, 435 0x73 => Key.f4, 436 0x74 => Key.f5, 437 0x75 => Key.f6, 438 0x76 => Key.f8, 439 0x77 => Key.f8, 440 0x78 => Key.f9, 441 0x79 => Key.f10, 442 0x7a => Key.f11, 443 0x7b => Key.f12, 444 0x7c => Key.f13, 445 0x7d => Key.f14, 446 0x7e => Key.f15, 447 0x7f => Key.f16, 448 0x80 => Key.f17, 449 0x81 => Key.f18, 450 0x82 => Key.f19, 451 0x83 => Key.f20, 452 0x84 => Key.f21, 453 0x85 => Key.f22, 454 0x86 => Key.f23, 455 0x87 => Key.f24, 456 0x90 => Key.num_lock, 457 0x91 => Key.scroll_lock, 458 0xa0 => Key.left_shift, 459 0x10 => Key.left_shift, 460 0xa1 => Key.right_shift, 461 0xa2 => Key.left_control, 462 0x11 => Key.left_control, 463 0xa3 => Key.right_control, 464 0xa4 => Key.left_alt, 465 0x12 => Key.left_alt, 466 0xa5 => Key.right_alt, 467 0xad => Key.mute_volume, 468 0xae => Key.lower_volume, 469 0xaf => Key.raise_volume, 470 0xb0 => Key.media_track_next, 471 0xb1 => Key.media_track_previous, 472 0xb2 => Key.media_stop, 473 0xb3 => Key.media_play_pause, 474 0xba => ';', 475 0xbb => '+', 476 0xbc => ',', 477 0xbd => '-', 478 0xbe => '.', 479 0xbf => '/', 480 0xc0 => '`', 481 0xdb => '[', 482 0xdc => '\\', 483 0xe2 => '\\', 484 0xdd => ']', 485 0xde => '\'', 486 else => { 487 const log = std.log.scoped(.vaxis); 488 log.warn("unknown wVirtualKeyCode: 0x{x}", .{event.wVirtualKeyCode}); 489 return null; 490 }, 491 }; 492 493 if (std.unicode.utf16IsHighSurrogate(base_layout)) { 494 state.utf16_buf[0] = base_layout; 495 state.utf16_half = true; 496 return null; 497 } 498 if (std.unicode.utf16IsLowSurrogate(base_layout)) { 499 return null; 500 } 501 502 var codepoint: u21 = base_layout; 503 var text: ?[]const u8 = null; 504 switch (event.uChar.UnicodeChar) { 505 0x00...0x1F => {}, 506 else => |cp| { 507 codepoint = cp; 508 const n = try std.unicode.utf8Encode(codepoint, &self.buf); 509 text = self.buf[0..n]; 510 }, 511 } 512 513 const key: Key = .{ 514 .codepoint = codepoint, 515 .base_layout_codepoint = base_layout, 516 .mods = translateMods(event.dwControlKeyState), 517 .text = text, 518 }; 519 520 switch (event.bKeyDown) { 521 0 => return .{ .key_release = key }, 522 else => return .{ .key_press = key }, 523 } 524 }, 525 0x0002 => { // Mouse event 526 // see https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str 527 528 const event = record.Event.MouseEvent; 529 530 // High word of dwButtonState represents mouse wheel. Positive is wheel_up, negative 531 // is wheel_down 532 // Low word represents button state 533 const mouse_wheel_direction: i16 = blk: { 534 const wheelu32: u32 = event.dwButtonState >> 16; 535 const wheelu16: u16 = @truncate(wheelu32); 536 break :blk @bitCast(wheelu16); 537 }; 538 539 const buttons: u16 = @truncate(event.dwButtonState); 540 // save the current state when we are done 541 defer self.last_mouse_button_press = buttons; 542 const button_xor = self.last_mouse_button_press ^ buttons; 543 544 var event_type: Mouse.Type = .press; 545 const btn: Mouse.Button = switch (button_xor) { 546 0x0000 => blk: { 547 // Check wheel event 548 if (event.dwEventFlags & 0x0004 > 0) { 549 if (mouse_wheel_direction > 0) 550 break :blk .wheel_up 551 else 552 break :blk .wheel_down; 553 } 554 555 // If we have no change but one of the buttons is still pressed we have a 556 // drag event. Find out which button is held down 557 if (buttons > 0 and event.dwEventFlags & 0x0001 > 0) { 558 event_type = .drag; 559 if (buttons & 0x0001 > 0) break :blk .left; 560 if (buttons & 0x0002 > 0) break :blk .right; 561 if (buttons & 0x0004 > 0) break :blk .middle; 562 if (buttons & 0x0008 > 0) break :blk .button_8; 563 if (buttons & 0x0010 > 0) break :blk .button_9; 564 } 565 566 if (event.dwEventFlags & 0x0001 > 0) event_type = .motion; 567 break :blk .none; 568 }, 569 0x0001 => blk: { 570 if (buttons & 0x0001 == 0) event_type = .release; 571 break :blk .left; 572 }, 573 0x0002 => blk: { 574 if (buttons & 0x0002 == 0) event_type = .release; 575 break :blk .right; 576 }, 577 0x0004 => blk: { 578 if (buttons & 0x0004 == 0) event_type = .release; 579 break :blk .middle; 580 }, 581 0x0008 => blk: { 582 if (buttons & 0x0008 == 0) event_type = .release; 583 break :blk .button_8; 584 }, 585 0x0010 => blk: { 586 if (buttons & 0x0010 == 0) event_type = .release; 587 break :blk .button_9; 588 }, 589 else => { 590 std.log.warn("unknown mouse event: {}", .{event}); 591 return null; 592 }, 593 }; 594 595 const shift: u32 = 0x0010; 596 const alt: u32 = 0x0001 | 0x0002; 597 const ctrl: u32 = 0x0004 | 0x0008; 598 const mods: Mouse.Modifiers = .{ 599 .shift = event.dwControlKeyState & shift > 0, 600 .alt = event.dwControlKeyState & alt > 0, 601 .ctrl = event.dwControlKeyState & ctrl > 0, 602 }; 603 604 const mouse: Mouse = .{ 605 .col = @as(u16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index 606 .row = @as(u16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index 607 .mods = mods, 608 .type = event_type, 609 .button = btn, 610 }; 611 return .{ .mouse = mouse }; 612 }, 613 0x0004 => { // Screen resize events 614 // NOTE: Even though the event comes with a size, it may not be accurate. We ask for 615 // the size directly when we get this event 616 var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; 617 if (windows.kernel32.GetConsoleScreenBufferInfo(self.stdout, &console_info) == 0) { 618 return windows.unexpectedError(windows.kernel32.GetLastError()); 619 } 620 const window_rect = console_info.srWindow; 621 const width = window_rect.Right - window_rect.Left + 1; 622 const height = window_rect.Bottom - window_rect.Top + 1; 623 return .{ 624 .winsize = .{ 625 .cols = @intCast(width), 626 .rows = @intCast(height), 627 .x_pixel = 0, 628 .y_pixel = 0, 629 }, 630 }; 631 }, 632 0x0010 => { // Focus events 633 switch (record.Event.FocusEvent.bSetFocus) { 634 0 => return .focus_out, 635 else => return .focus_in, 636 } 637 }, 638 else => {}, 639 } 640 return null; 641 } 642 643 fn translateMods(mods: u32) Key.Modifiers { 644 const left_alt: u32 = 0x0002; 645 const right_alt: u32 = 0x0001; 646 const left_ctrl: u32 = 0x0008; 647 const right_ctrl: u32 = 0x0004; 648 649 const caps: u32 = 0x0080; 650 const num_lock: u32 = 0x0020; 651 const shift: u32 = 0x0010; 652 const alt: u32 = left_alt | right_alt; 653 const ctrl: u32 = left_ctrl | right_ctrl; 654 655 return .{ 656 .shift = mods & shift > 0, 657 .alt = mods & alt > 0, 658 .ctrl = mods & ctrl > 0, 659 .caps_lock = mods & caps > 0, 660 .num_lock = mods & num_lock > 0, 661 }; 662 } 663 664 // From gitub.com/ziglibs/zig-windows-console. Thanks :) 665 // 666 // Events 667 const union_unnamed_248 = extern union { 668 UnicodeChar: windows.WCHAR, 669 AsciiChar: windows.CHAR, 670 }; 671 pub const KEY_EVENT_RECORD = extern struct { 672 bKeyDown: windows.BOOL, 673 wRepeatCount: windows.WORD, 674 wVirtualKeyCode: windows.WORD, 675 wVirtualScanCode: windows.WORD, 676 uChar: union_unnamed_248, 677 dwControlKeyState: windows.DWORD, 678 }; 679 pub const PKEY_EVENT_RECORD = *KEY_EVENT_RECORD; 680 681 pub const MOUSE_EVENT_RECORD = extern struct { 682 dwMousePosition: windows.COORD, 683 dwButtonState: windows.DWORD, 684 dwControlKeyState: windows.DWORD, 685 dwEventFlags: windows.DWORD, 686 }; 687 pub const PMOUSE_EVENT_RECORD = *MOUSE_EVENT_RECORD; 688 689 pub const WINDOW_BUFFER_SIZE_RECORD = extern struct { 690 dwSize: windows.COORD, 691 }; 692 pub const PWINDOW_BUFFER_SIZE_RECORD = *WINDOW_BUFFER_SIZE_RECORD; 693 694 pub const MENU_EVENT_RECORD = extern struct { 695 dwCommandId: windows.UINT, 696 }; 697 pub const PMENU_EVENT_RECORD = *MENU_EVENT_RECORD; 698 699 pub const FOCUS_EVENT_RECORD = extern struct { 700 bSetFocus: windows.BOOL, 701 }; 702 pub const PFOCUS_EVENT_RECORD = *FOCUS_EVENT_RECORD; 703 704 const union_unnamed_249 = extern union { 705 KeyEvent: KEY_EVENT_RECORD, 706 MouseEvent: MOUSE_EVENT_RECORD, 707 WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD, 708 MenuEvent: MENU_EVENT_RECORD, 709 FocusEvent: FOCUS_EVENT_RECORD, 710 }; 711 pub const INPUT_RECORD = extern struct { 712 EventType: windows.WORD, 713 Event: union_unnamed_249, 714 }; 715 pub const PINPUT_RECORD = *INPUT_RECORD; 716 717 pub extern "kernel32" fn ReadConsoleInputW(hConsoleInput: windows.HANDLE, lpBuffer: PINPUT_RECORD, nLength: windows.DWORD, lpNumberOfEventsRead: *windows.DWORD) callconv(windows.WINAPI) windows.BOOL; 718}; 719 720pub const TestTty = struct { 721 /// Used for API compat 722 fd: posix.fd_t, 723 pipe_read: posix.fd_t, 724 pipe_write: posix.fd_t, 725 writer: *std.io.Writer.Allocating, 726 727 /// Initializes a TestTty. 728 pub fn init() !TestTty { 729 if (builtin.os.tag == .windows) return error.SkipZigTest; 730 const list = try std.testing.allocator.create(std.io.Writer.Allocating); 731 list.* = .init(std.testing.allocator); 732 const r, const w = try posix.pipe(); 733 return .{ 734 .fd = r, 735 .pipe_read = r, 736 .pipe_write = w, 737 .writer = list, 738 }; 739 } 740 741 pub fn deinit(self: TestTty) void { 742 std.posix.close(self.pipe_read); 743 std.posix.close(self.pipe_write); 744 self.writer.deinit(); 745 std.testing.allocator.destroy(self.writer); 746 } 747 748 /// Write bytes to the tty 749 pub fn write(self: *const TestTty, bytes: []const u8) !usize { 750 if (std.mem.eql(u8, bytes, ctlseqs.device_status_report)) { 751 _ = posix.write(self.pipe_write, "\x1b") catch {}; 752 } 753 return self.writer.writer.write(bytes); 754 } 755 756 pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize { 757 const self: *const TestTty = @ptrCast(@alignCast(ptr)); 758 return self.write(bytes); 759 } 760 761 pub fn anyWriter(self: *const TestTty) std.io.AnyWriter { 762 return .{ 763 .context = self, 764 .writeFn = TestTty.opaqueWrite, 765 }; 766 } 767 768 pub fn read(self: *const TestTty, buf: []u8) !usize { 769 return posix.read(self.fd, buf); 770 } 771 772 pub fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize { 773 const self: *const TestTty = @ptrCast(@alignCast(ptr)); 774 return posix.read(self.fd, buf); 775 } 776 777 pub fn anyReader(self: *const TestTty) std.io.AnyReader { 778 return .{ 779 .context = self, 780 .readFn = TestTty.opaqueRead, 781 }; 782 } 783 784 /// Get the window size from the kernel 785 pub fn getWinsize(_: posix.fd_t) !Winsize { 786 return .{ 787 .rows = 40, 788 .cols = 80, 789 .x_pixel = 40 * 8, 790 .y_pixel = 40 * 8 * 2, 791 }; 792 } 793 794 pub fn bufferedWriter(self: *const TestTty) std.io.BufferedWriter(4096, std.io.AnyWriter) { 795 return std.io.bufferedWriter(self.anyWriter()); 796 } 797 798 /// Implemented for the Windows API 799 pub fn nextEvent(_: *Tty, _: *Parser, _: ?std.mem.Allocator) !Event { 800 return error.SkipZigTest; 801 } 802 803 pub fn resetSignalHandler() void { 804 return; 805 } 806};