this repo has no description
13
fork

Configure Feed

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

at 3943a6f42f7f4c73ab9493dfb0a4e123be15d302 750 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 0xdf => '\\', 457 0xe2 => '\\', 458 0xdd => ']', 459 0xde => '\'', 460 else => { 461 const log = std.log.scoped(.vaxis); 462 log.warn("unknown wVirtualKeyCode: 0x{x}", .{event.wVirtualKeyCode}); 463 return null; 464 }, 465 }; 466 467 if (std.unicode.utf16IsHighSurrogate(base_layout)) { 468 state.utf16_buf[0] = base_layout; 469 state.utf16_half = true; 470 return null; 471 } 472 if (std.unicode.utf16IsLowSurrogate(base_layout)) { 473 return null; 474 } 475 476 var codepoint: u21 = base_layout; 477 var text: ?[]const u8 = null; 478 switch (event.uChar.UnicodeChar) { 479 0x00...0x1F => {}, 480 else => |cp| { 481 codepoint = cp; 482 const n = try std.unicode.utf8Encode(codepoint, &self.buf); 483 text = self.buf[0..n]; 484 }, 485 } 486 487 const key: Key = .{ 488 .codepoint = codepoint, 489 .base_layout_codepoint = base_layout, 490 .mods = translateMods(event.dwControlKeyState), 491 .text = text, 492 }; 493 494 switch (event.bKeyDown) { 495 0 => return .{ .key_release = key }, 496 else => return .{ .key_press = key }, 497 } 498 }, 499 0x0002 => { // Mouse event 500 // see https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str 501 502 const event = record.Event.MouseEvent; 503 504 // High word of dwButtonState represents mouse wheel. Positive is wheel_up, negative 505 // is wheel_down 506 // Low word represents button state 507 const mouse_wheel_direction: i16 = blk: { 508 const wheelu32: u32 = event.dwButtonState >> 16; 509 const wheelu16: u16 = @truncate(wheelu32); 510 break :blk @bitCast(wheelu16); 511 }; 512 513 const buttons: u16 = @truncate(event.dwButtonState); 514 // save the current state when we are done 515 defer self.last_mouse_button_press = buttons; 516 const button_xor = self.last_mouse_button_press ^ buttons; 517 518 var event_type: Mouse.Type = .press; 519 const btn: Mouse.Button = switch (button_xor) { 520 0x0000 => blk: { 521 // Check wheel event 522 if (event.dwEventFlags & 0x0004 > 0) { 523 if (mouse_wheel_direction > 0) 524 break :blk .wheel_up 525 else 526 break :blk .wheel_down; 527 } 528 529 // If we have no change but one of the buttons is still pressed we have a 530 // drag event. Find out which button is held down 531 if (buttons > 0 and event.dwEventFlags & 0x0001 > 0) { 532 event_type = .drag; 533 if (buttons & 0x0001 > 0) break :blk .left; 534 if (buttons & 0x0002 > 0) break :blk .right; 535 if (buttons & 0x0004 > 0) break :blk .middle; 536 if (buttons & 0x0008 > 0) break :blk .button_8; 537 if (buttons & 0x0010 > 0) break :blk .button_9; 538 } 539 540 if (event.dwEventFlags & 0x0001 > 0) event_type = .motion; 541 break :blk .none; 542 }, 543 0x0001 => blk: { 544 if (buttons & 0x0001 == 0) event_type = .release; 545 break :blk .left; 546 }, 547 0x0002 => blk: { 548 if (buttons & 0x0002 == 0) event_type = .release; 549 break :blk .right; 550 }, 551 0x0004 => blk: { 552 if (buttons & 0x0004 == 0) event_type = .release; 553 break :blk .middle; 554 }, 555 0x0008 => blk: { 556 if (buttons & 0x0008 == 0) event_type = .release; 557 break :blk .button_8; 558 }, 559 0x0010 => blk: { 560 if (buttons & 0x0010 == 0) event_type = .release; 561 break :blk .button_9; 562 }, 563 else => { 564 std.log.warn("unknown mouse event: {}", .{event}); 565 return null; 566 }, 567 }; 568 569 const shift: u32 = 0x0010; 570 const alt: u32 = 0x0001 | 0x0002; 571 const ctrl: u32 = 0x0004 | 0x0008; 572 const mods: Mouse.Modifiers = .{ 573 .shift = event.dwControlKeyState & shift > 0, 574 .alt = event.dwControlKeyState & alt > 0, 575 .ctrl = event.dwControlKeyState & ctrl > 0, 576 }; 577 578 const mouse: Mouse = .{ 579 .col = @as(i16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index 580 .row = @as(i16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index 581 .mods = mods, 582 .type = event_type, 583 .button = btn, 584 }; 585 return .{ .mouse = mouse }; 586 }, 587 0x0004 => { // Screen resize events 588 // NOTE: Even though the event comes with a size, it may not be accurate. We ask for 589 // the size directly when we get this event 590 var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; 591 if (windows.kernel32.GetConsoleScreenBufferInfo(self.stdout, &console_info) == 0) { 592 return windows.unexpectedError(windows.kernel32.GetLastError()); 593 } 594 const window_rect = console_info.srWindow; 595 const width = window_rect.Right - window_rect.Left + 1; 596 const height = window_rect.Bottom - window_rect.Top + 1; 597 return .{ 598 .winsize = .{ 599 .cols = @intCast(width), 600 .rows = @intCast(height), 601 .x_pixel = 0, 602 .y_pixel = 0, 603 }, 604 }; 605 }, 606 0x0010 => { // Focus events 607 switch (record.Event.FocusEvent.bSetFocus) { 608 0 => return .focus_out, 609 else => return .focus_in, 610 } 611 }, 612 else => {}, 613 } 614 return null; 615 } 616 617 fn translateMods(mods: u32) Key.Modifiers { 618 const left_alt: u32 = 0x0002; 619 const right_alt: u32 = 0x0001; 620 const left_ctrl: u32 = 0x0008; 621 const right_ctrl: u32 = 0x0004; 622 623 const caps: u32 = 0x0080; 624 const num_lock: u32 = 0x0020; 625 const shift: u32 = 0x0010; 626 const alt: u32 = left_alt | right_alt; 627 const ctrl: u32 = left_ctrl | right_ctrl; 628 629 return .{ 630 .shift = mods & shift > 0, 631 .alt = mods & alt > 0, 632 .ctrl = mods & ctrl > 0, 633 .caps_lock = mods & caps > 0, 634 .num_lock = mods & num_lock > 0, 635 }; 636 } 637 638 // From gitub.com/ziglibs/zig-windows-console. Thanks :) 639 // 640 // Events 641 const union_unnamed_248 = extern union { 642 UnicodeChar: windows.WCHAR, 643 AsciiChar: windows.CHAR, 644 }; 645 pub const KEY_EVENT_RECORD = extern struct { 646 bKeyDown: windows.BOOL, 647 wRepeatCount: windows.WORD, 648 wVirtualKeyCode: windows.WORD, 649 wVirtualScanCode: windows.WORD, 650 uChar: union_unnamed_248, 651 dwControlKeyState: windows.DWORD, 652 }; 653 pub const PKEY_EVENT_RECORD = *KEY_EVENT_RECORD; 654 655 pub const MOUSE_EVENT_RECORD = extern struct { 656 dwMousePosition: windows.COORD, 657 dwButtonState: windows.DWORD, 658 dwControlKeyState: windows.DWORD, 659 dwEventFlags: windows.DWORD, 660 }; 661 pub const PMOUSE_EVENT_RECORD = *MOUSE_EVENT_RECORD; 662 663 pub const WINDOW_BUFFER_SIZE_RECORD = extern struct { 664 dwSize: windows.COORD, 665 }; 666 pub const PWINDOW_BUFFER_SIZE_RECORD = *WINDOW_BUFFER_SIZE_RECORD; 667 668 pub const MENU_EVENT_RECORD = extern struct { 669 dwCommandId: windows.UINT, 670 }; 671 pub const PMENU_EVENT_RECORD = *MENU_EVENT_RECORD; 672 673 pub const FOCUS_EVENT_RECORD = extern struct { 674 bSetFocus: windows.BOOL, 675 }; 676 pub const PFOCUS_EVENT_RECORD = *FOCUS_EVENT_RECORD; 677 678 const union_unnamed_249 = extern union { 679 KeyEvent: KEY_EVENT_RECORD, 680 MouseEvent: MOUSE_EVENT_RECORD, 681 WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD, 682 MenuEvent: MENU_EVENT_RECORD, 683 FocusEvent: FOCUS_EVENT_RECORD, 684 }; 685 pub const INPUT_RECORD = extern struct { 686 EventType: windows.WORD, 687 Event: union_unnamed_249, 688 }; 689 pub const PINPUT_RECORD = *INPUT_RECORD; 690 691 pub extern "kernel32" fn ReadConsoleInputW(hConsoleInput: windows.HANDLE, lpBuffer: PINPUT_RECORD, nLength: windows.DWORD, lpNumberOfEventsRead: *windows.DWORD) callconv(.winapi) windows.BOOL; 692}; 693 694pub const TestTty = struct { 695 /// Used for API compat 696 fd: posix.fd_t, 697 pipe_read: posix.fd_t, 698 pipe_write: posix.fd_t, 699 tty_writer: *std.Io.Writer.Allocating, 700 701 /// Initializes a TestTty. 702 pub fn init(buffer: []u8) !TestTty { 703 _ = buffer; 704 705 if (builtin.os.tag == .windows) return error.SkipZigTest; 706 const list = try std.testing.allocator.create(std.Io.Writer.Allocating); 707 list.* = .init(std.testing.allocator); 708 const r, const w = try posix.pipe(); 709 return .{ 710 .fd = r, 711 .pipe_read = r, 712 .pipe_write = w, 713 .tty_writer = list, 714 }; 715 } 716 717 pub fn deinit(self: TestTty) void { 718 std.posix.close(self.pipe_read); 719 std.posix.close(self.pipe_write); 720 self.tty_writer.deinit(); 721 std.testing.allocator.destroy(self.tty_writer); 722 } 723 724 pub fn writer(self: *TestTty) *std.Io.Writer { 725 return &self.tty_writer.writer; 726 } 727 728 pub fn read(self: *const TestTty, buf: []u8) !usize { 729 return posix.read(self.fd, buf); 730 } 731 732 /// Get the window size from the kernel 733 pub fn getWinsize(_: posix.fd_t) !Winsize { 734 return .{ 735 .rows = 40, 736 .cols = 80, 737 .x_pixel = 40 * 8, 738 .y_pixel = 40 * 8 * 2, 739 }; 740 } 741 742 /// Implemented for the Windows API 743 pub fn nextEvent(_: *Tty, _: *Parser, _: ?std.mem.Allocator) !Event { 744 return error.SkipZigTest; 745 } 746 747 pub fn resetSignalHandler() void { 748 return; 749 } 750};