this repo has no description
13
fork

Configure Feed

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

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