this repo has no description
13
fork

Configure Feed

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

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