this repo has no description
13
fork

Configure Feed

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

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