this repo has no description
13
fork

Configure Feed

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

at main 847 lines 31 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 self: PosixTty = .{ 79 .io = io, 80 .fd = f, 81 .termios = termios, 82 .tty_writer = f.writerStreaming(io, buffer), 83 }; 84 85 global_tty = self; 86 87 return self; 88 } 89 90 /// release resources associated with the Tty return it to its original state 91 pub fn deinit(self: PosixTty) void { 92 posix.tcsetattr(self.fd.handle, .FLUSH, self.termios) catch |err| { 93 std.log.err("couldn't restore terminal: {}", .{err}); 94 }; 95 if (builtin.os.tag != .macos) // closing /dev/tty may block indefinitely on macos 96 self.fd.close(self.io); 97 } 98 99 /// Resets the signal handler to it's default 100 pub fn resetSignalHandler() void { 101 if (!handler_installed) return; 102 handler_installed = false; 103 var act = posix.Sigaction{ 104 .handler = .{ .handler = posix.SIG.DFL }, 105 .mask = switch (builtin.os.tag) { 106 .macos => 0, 107 else => posix.sigemptyset(), 108 }, 109 .flags = 0, 110 }; 111 posix.sigaction(posix.SIG.WINCH, &act, null); 112 } 113 114 pub fn writer(self: *PosixTty) *std.Io.Writer { 115 return &self.tty_writer.interface; 116 } 117 118 pub fn read(self: *const PosixTty, buf: []u8) !usize { 119 return try self.fd.readStreaming(self.io, &.{buf}); 120 } 121 122 /// Install a signal handler for winsize. A maximum of 8 handlers may be 123 /// installed 124 pub fn notifyWinsize(handler: SignalHandler) !void { 125 try handler_mutex.lock(handler_io); 126 defer handler_mutex.unlock(handler_io); 127 if (handler_idx == handlers.len) return error.OutOfMemory; 128 handlers[handler_idx] = handler; 129 handler_idx += 1; 130 } 131 132 fn handleWinch(_: std.posix.SIG) callconv(.c) void { 133 handler_mutex.lock(handler_io) catch @panic("unable to lock SIGWINCH handlers"); 134 defer handler_mutex.unlock(handler_io); 135 var i: usize = 0; 136 while (i < handler_idx) : (i += 1) { 137 const handler = handlers[i]; 138 handler.callback(handler.context); 139 } 140 } 141 142 /// makeRaw enters the raw state for the terminal. 143 pub fn makeRaw(fd: posix.fd_t) !posix.termios { 144 const state = try posix.tcgetattr(fd); 145 var raw = state; 146 // see termios(3) 147 raw.iflag.IGNBRK = false; 148 raw.iflag.BRKINT = false; 149 raw.iflag.PARMRK = false; 150 raw.iflag.ISTRIP = false; 151 raw.iflag.INLCR = false; 152 raw.iflag.IGNCR = false; 153 raw.iflag.ICRNL = false; 154 raw.iflag.IXON = false; 155 156 raw.oflag.OPOST = false; 157 158 raw.lflag.ECHO = false; 159 raw.lflag.ECHONL = false; 160 raw.lflag.ICANON = false; 161 raw.lflag.ISIG = false; 162 raw.lflag.IEXTEN = false; 163 164 raw.cflag.CSIZE = .CS8; 165 raw.cflag.PARENB = false; 166 167 raw.cc[@intFromEnum(posix.V.MIN)] = 1; 168 raw.cc[@intFromEnum(posix.V.TIME)] = 0; 169 try posix.tcsetattr(fd, .FLUSH, raw); 170 return state; 171 } 172 173 /// Get the window size from the kernel 174 pub fn getWinsize(self: *PosixTty) !Winsize { 175 var winsize = posix.winsize{ 176 .row = 0, 177 .col = 0, 178 .xpixel = 0, 179 .ypixel = 0, 180 }; 181 182 const err = posix.system.ioctl(self.fd.handle, posix.T.IOCGWINSZ, @intFromPtr(&winsize)); 183 if (posix.errno(err) == .SUCCESS) 184 return Winsize{ 185 .rows = winsize.row, 186 .cols = winsize.col, 187 .x_pixel = winsize.xpixel, 188 .y_pixel = winsize.ypixel, 189 }; 190 return error.IoctlError; 191 } 192}; 193 194pub const WindowsTty = struct { 195 stdin: windows.HANDLE, 196 stdout: windows.HANDLE, 197 198 initial_codepage: c_uint, 199 initial_input_mode: CONSOLE_MODE_INPUT, 200 initial_output_mode: CONSOLE_MODE_OUTPUT, 201 202 // a buffer to write key text into 203 buf: [4]u8 = undefined, 204 205 /// File.Writer for efficient buffered writing 206 tty_writer: std.Io.File.Writer, 207 208 /// The last mouse button that was pressed. We store the previous state of button presses on each 209 /// mouse event so we can detect which button was released 210 last_mouse_button_press: u16 = 0, 211 212 const utf8_codepage: c_uint = 65001; 213 214 /// The input mode set by init 215 pub const input_raw_mode: CONSOLE_MODE_INPUT = .{ 216 .WINDOW_INPUT = 1, // resize events 217 .MOUSE_INPUT = 1, 218 .EXTENDED_FLAGS = 1, // allow mouse events 219 }; 220 221 /// The output mode set by init 222 pub const output_raw_mode: CONSOLE_MODE_OUTPUT = .{ 223 .PROCESSED_OUTPUT = 1, // handle control sequences 224 .VIRTUAL_TERMINAL_PROCESSING = 1, // handle ANSI sequences 225 .DISABLE_NEWLINE_AUTO_RETURN = 1, // disable inserting a new line when we write at the last column 226 .ENABLE_LVB_GRID_WORLDWIDE = 1, // enables reverse video and underline 227 }; 228 229 pub fn init(io: std.Io, buffer: []u8) !WindowsTty { 230 const stdin: std.Io.File = .stdin(); 231 const stdout: std.Io.File = .stdout(); 232 233 // get initial modes 234 const initial_output_codepage = GetConsoleOutputCP(); 235 const initial_input_mode = try getConsoleMode(CONSOLE_MODE_INPUT, stdin.handle); 236 const initial_output_mode = try getConsoleMode(CONSOLE_MODE_OUTPUT, stdout.handle); 237 238 // set new modes 239 try setConsoleMode(stdin.handle, input_raw_mode); 240 try setConsoleMode(stdout.handle, output_raw_mode); 241 if (SetConsoleOutputCP(utf8_codepage) == .FALSE) 242 return windows.unexpectedError(windows.GetLastError()); 243 244 const self: WindowsTty = .{ 245 .stdin = stdin.handle, 246 .stdout = stdout.handle, 247 .initial_codepage = initial_output_codepage, 248 .initial_input_mode = initial_input_mode, 249 .initial_output_mode = initial_output_mode, 250 .tty_writer = .initStreaming(stdout, io, buffer), 251 }; 252 253 // save a copy of this tty as the global_tty for panic handling 254 global_tty = self; 255 256 return self; 257 } 258 259 pub fn deinit(self: WindowsTty) void { 260 _ = SetConsoleOutputCP(self.initial_codepage); 261 setConsoleMode(self.stdin, self.initial_input_mode) catch {}; 262 setConsoleMode(self.stdout, self.initial_output_mode) catch {}; 263 windows.CloseHandle(self.stdin); 264 windows.CloseHandle(self.stdout); 265 } 266 267 pub const CONSOLE_MODE_INPUT = packed struct(u32) { 268 PROCESSED_INPUT: u1 = 0, 269 LINE_INPUT: u1 = 0, 270 ECHO_INPUT: u1 = 0, 271 WINDOW_INPUT: u1 = 0, 272 MOUSE_INPUT: u1 = 0, 273 INSERT_MODE: u1 = 0, 274 QUICK_EDIT_MODE: u1 = 0, 275 EXTENDED_FLAGS: u1 = 0, 276 AUTO_POSITION: u1 = 0, 277 VIRTUAL_TERMINAL_INPUT: u1 = 0, 278 _: u22 = 0, 279 }; 280 281 pub const CONSOLE_MODE_OUTPUT = packed struct(u32) { 282 PROCESSED_OUTPUT: u1 = 0, 283 WRAP_AT_EOL_OUTPUT: u1 = 0, 284 VIRTUAL_TERMINAL_PROCESSING: u1 = 0, 285 DISABLE_NEWLINE_AUTO_RETURN: u1 = 0, 286 ENABLE_LVB_GRID_WORLDWIDE: u1 = 0, 287 _: u27 = 0, 288 }; 289 290 pub fn getConsoleMode(comptime T: type, handle: windows.HANDLE) !T { 291 var mode: u32 = undefined; 292 if (GetConsoleMode(handle, &mode) == .FALSE) return switch (windows.GetLastError()) { 293 .INVALID_HANDLE => error.InvalidHandle, 294 else => |e| windows.unexpectedError(e), 295 }; 296 return @bitCast(mode); 297 } 298 299 pub fn setConsoleMode(handle: windows.HANDLE, mode: anytype) !void { 300 if (SetConsoleMode(handle, @bitCast(mode)) == .FALSE) return switch (windows.GetLastError()) { 301 .INVALID_HANDLE => error.InvalidHandle, 302 else => |e| windows.unexpectedError(e), 303 }; 304 } 305 306 pub fn writer(self: *WindowsTty) *std.Io.Writer { 307 return &self.tty_writer.interface; 308 } 309 310 pub fn read(self: *const WindowsTty, buf: []u8) !usize { 311 return posix.read(self.fd, buf); 312 } 313 314 pub fn nextEvent(self: *WindowsTty, parser: *Parser, paste_allocator: ?std.mem.Allocator) !Event { 315 // We use a loop so we can ignore certain events 316 var state: EventState = .{}; 317 while (true) { 318 var event_count: u32 = 0; 319 var input_record: INPUT_RECORD = undefined; 320 if (ReadConsoleInputW(self.stdin, &input_record, 1, &event_count) == .FALSE) 321 return windows.unexpectedError(windows.GetLastError()); 322 323 if (try self.eventFromRecord(&input_record, &state, parser, paste_allocator)) |ev| { 324 return ev; 325 } 326 } 327 } 328 329 pub const EventState = struct { 330 ansi_buf: [128]u8 = undefined, 331 ansi_idx: usize = 0, 332 utf16_buf: [2]u16 = undefined, 333 utf16_half: bool = false, 334 }; 335 336 pub const SMALL_RECT = extern struct { 337 Left: windows.SHORT, 338 Top: windows.SHORT, 339 Right: windows.SHORT, 340 Bottom: windows.SHORT, 341 }; 342 343 pub const COORD = extern struct { 344 X: windows.SHORT, 345 Y: windows.SHORT, 346 }; 347 348 pub const CONSOLE_SCREEN_BUFFER_INFO = extern struct { 349 dwSize: COORD, 350 dwCursorPosition: COORD, 351 wAttributes: windows.WORD, 352 srWindow: SMALL_RECT, 353 dwMaximumWindowSize: COORD, 354 }; 355 356 pub fn eventFromRecord(self: *WindowsTty, record: *const INPUT_RECORD, state: *EventState, parser: *Parser, paste_allocator: ?std.mem.Allocator) !?Event { 357 switch (record.EventType) { 358 0x0001 => { // Key event 359 const event = record.Event.KeyEvent; 360 361 if (state.utf16_half) half: { 362 state.utf16_half = false; 363 state.utf16_buf[1] = event.uChar.UnicodeChar; 364 const codepoint: u21 = std.unicode.utf16DecodeSurrogatePair(&state.utf16_buf) catch break :half; 365 const n = std.unicode.utf8Encode(codepoint, &self.buf) catch return null; 366 367 const key: Key = .{ 368 .codepoint = codepoint, 369 .base_layout_codepoint = codepoint, 370 .mods = translateMods(event.dwControlKeyState), 371 .text = self.buf[0..n], 372 }; 373 374 switch (event.bKeyDown) { 375 .FALSE => return .{ .key_release = key }, 376 else => return .{ .key_press = key }, 377 } 378 } 379 380 const base_layout: u16 = switch (event.wVirtualKeyCode) { 381 0x00 => blk: { // delivered when we get an escape sequence or a unicode codepoint 382 if (state.ansi_idx == 0 and event.uChar.AsciiChar != 27) 383 break :blk event.uChar.UnicodeChar; 384 state.ansi_buf[state.ansi_idx] = event.uChar.AsciiChar; 385 state.ansi_idx += 1; 386 if (state.ansi_idx <= 2) return null; 387 const result = try parser.parse(state.ansi_buf[0..state.ansi_idx], paste_allocator); 388 return if (result.n == 0) null else evt: { 389 state.ansi_idx = 0; 390 break :evt result.event; 391 }; 392 }, 393 0x08 => Key.backspace, 394 0x09 => Key.tab, 395 0x0D => Key.enter, 396 0x13 => Key.pause, 397 0x14 => Key.caps_lock, 398 0x1B => Key.escape, 399 0x20 => Key.space, 400 0x21 => Key.page_up, 401 0x22 => Key.page_down, 402 0x23 => Key.end, 403 0x24 => Key.home, 404 0x25 => Key.left, 405 0x26 => Key.up, 406 0x27 => Key.right, 407 0x28 => Key.down, 408 0x2c => Key.print_screen, 409 0x2d => Key.insert, 410 0x2e => Key.delete, 411 0x30...0x39 => |k| k, 412 0x41...0x5a => |k| k + 0x20, // translate to lowercase 413 0x5b => Key.left_meta, 414 0x5c => Key.right_meta, 415 0x60 => Key.kp_0, 416 0x61 => Key.kp_1, 417 0x62 => Key.kp_2, 418 0x63 => Key.kp_3, 419 0x64 => Key.kp_4, 420 0x65 => Key.kp_5, 421 0x66 => Key.kp_6, 422 0x67 => Key.kp_7, 423 0x68 => Key.kp_8, 424 0x69 => Key.kp_9, 425 0x6a => Key.kp_multiply, 426 0x6b => Key.kp_add, 427 0x6c => Key.kp_separator, 428 0x6d => Key.kp_subtract, 429 0x6e => Key.kp_decimal, 430 0x6f => Key.kp_divide, 431 0x70 => Key.f1, 432 0x71 => Key.f2, 433 0x72 => Key.f3, 434 0x73 => Key.f4, 435 0x74 => Key.f5, 436 0x75 => Key.f6, 437 0x76 => Key.f8, 438 0x77 => Key.f8, 439 0x78 => Key.f9, 440 0x79 => Key.f10, 441 0x7a => Key.f11, 442 0x7b => Key.f12, 443 0x7c => Key.f13, 444 0x7d => Key.f14, 445 0x7e => Key.f15, 446 0x7f => Key.f16, 447 0x80 => Key.f17, 448 0x81 => Key.f18, 449 0x82 => Key.f19, 450 0x83 => Key.f20, 451 0x84 => Key.f21, 452 0x85 => Key.f22, 453 0x86 => Key.f23, 454 0x87 => Key.f24, 455 0x90 => Key.num_lock, 456 0x91 => Key.scroll_lock, 457 0xa0 => Key.left_shift, 458 0x10 => Key.left_shift, 459 0xa1 => Key.right_shift, 460 0xa2 => Key.left_control, 461 0x11 => Key.left_control, 462 0xa3 => Key.right_control, 463 0xa4 => Key.left_alt, 464 0x12 => Key.left_alt, 465 0xa5 => Key.right_alt, 466 0xad => Key.mute_volume, 467 0xae => Key.lower_volume, 468 0xaf => Key.raise_volume, 469 0xb0 => Key.media_track_next, 470 0xb1 => Key.media_track_previous, 471 0xb2 => Key.media_stop, 472 0xb3 => Key.media_play_pause, 473 0xba => ';', 474 0xbb => '+', 475 0xbc => ',', 476 0xbd => '-', 477 0xbe => '.', 478 0xbf => '/', 479 0xc0 => '`', 480 0xdb => '[', 481 0xdc => '\\', 482 0xdf => '\\', 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 .FALSE => 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(i16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index 606 .row = @as(i16, @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: CONSOLE_SCREEN_BUFFER_INFO = undefined; 617 if (GetConsoleScreenBufferInfo(self.stdout, &console_info) == .FALSE) { 618 return windows.unexpectedError(windows.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 .FALSE => 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 const altGr = (mods & right_alt > 0) and (mods & left_ctrl > 0); 656 657 return .{ 658 .shift = mods & shift > 0, 659 .alt = if (altGr) mods & left_alt > 0 else mods & alt > 0, 660 .ctrl = if (altGr) mods & right_ctrl > 0 else mods & ctrl > 0, 661 .caps_lock = mods & caps > 0, 662 .num_lock = mods & num_lock > 0, 663 }; 664 } 665 666 pub fn getWinsize(self: *WindowsTty) !Winsize { 667 var console_info: CONSOLE_SCREEN_BUFFER_INFO = undefined; 668 if (GetConsoleScreenBufferInfo(self.stdout, &console_info) == .FALSE) { 669 return windows.unexpectedError(windows.GetLastError()); 670 } 671 const window_rect = console_info.srWindow; 672 const width = window_rect.Right - window_rect.Left + 1; 673 const height = window_rect.Bottom - window_rect.Top + 1; 674 return .{ 675 .cols = @intCast(width), 676 .rows = @intCast(height), 677 .x_pixel = 0, 678 .y_pixel = 0, 679 }; 680 } 681 682 // From gitub.com/ziglibs/zig-windows-console. Thanks :) 683 // 684 // Events 685 const union_unnamed_248 = extern union { 686 UnicodeChar: windows.WCHAR, 687 AsciiChar: windows.CHAR, 688 }; 689 690 pub const KEY_EVENT_RECORD = extern struct { 691 bKeyDown: windows.BOOL, 692 wRepeatCount: windows.WORD, 693 wVirtualKeyCode: windows.WORD, 694 wVirtualScanCode: windows.WORD, 695 uChar: union_unnamed_248, 696 dwControlKeyState: windows.DWORD, 697 }; 698 699 pub const PKEY_EVENT_RECORD = *KEY_EVENT_RECORD; 700 701 pub const MOUSE_EVENT_RECORD = extern struct { 702 dwMousePosition: windows.COORD, 703 dwButtonState: windows.DWORD, 704 dwControlKeyState: windows.DWORD, 705 dwEventFlags: windows.DWORD, 706 }; 707 708 pub const PMOUSE_EVENT_RECORD = *MOUSE_EVENT_RECORD; 709 710 pub const WINDOW_BUFFER_SIZE_RECORD = extern struct { 711 dwSize: windows.COORD, 712 }; 713 714 pub const PWINDOW_BUFFER_SIZE_RECORD = *WINDOW_BUFFER_SIZE_RECORD; 715 716 pub const MENU_EVENT_RECORD = extern struct { 717 dwCommandId: windows.UINT, 718 }; 719 720 pub const PMENU_EVENT_RECORD = *MENU_EVENT_RECORD; 721 722 pub const FOCUS_EVENT_RECORD = extern struct { 723 bSetFocus: windows.BOOL, 724 }; 725 726 pub const PFOCUS_EVENT_RECORD = *FOCUS_EVENT_RECORD; 727 728 const union_unnamed_249 = extern union { 729 KeyEvent: KEY_EVENT_RECORD, 730 MouseEvent: MOUSE_EVENT_RECORD, 731 WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD, 732 MenuEvent: MENU_EVENT_RECORD, 733 FocusEvent: FOCUS_EVENT_RECORD, 734 }; 735 736 pub const INPUT_RECORD = extern struct { 737 EventType: windows.WORD, 738 Event: union_unnamed_249, 739 }; 740 741 pub const PINPUT_RECORD = *INPUT_RECORD; 742 743 pub extern "kernel32" fn ReadConsoleInputW(hConsoleInput: windows.HANDLE, lpBuffer: PINPUT_RECORD, nLength: windows.DWORD, lpNumberOfEventsRead: *windows.DWORD) callconv(.winapi) windows.BOOL; 744 pub extern "kernel32" fn GetConsoleOutputCP() callconv(.winapi) windows.UINT; 745 pub extern "kernel32" fn GetConsoleMode(kConsoleHandle: windows.HANDLE, lpMode: *windows.DWORD) callconv(.winapi) windows.BOOL; 746 pub extern "kernel32" fn SetConsoleMode(hConsoleHandle: windows.HANDLE, dwMode: windows.DWORD) callconv(.winapi) windows.BOOL; 747 pub extern "kernel32" fn SetConsoleOutputCP(wCodePageId: windows.UINT) callconv(.winapi) windows.BOOL; 748 pub extern "kernel32" fn GetConsoleScreenBufferInfo(hConsoleOutpur: windows.HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) callconv(.winapi) windows.BOOL; 749}; 750 751pub const TestTty = switch (builtin.os.tag) { 752 .linux => struct { 753 const linux = std.os.linux; 754 /// Used for API compat 755 fd: posix.fd_t, 756 pipe_read: posix.fd_t, 757 pipe_write: posix.fd_t, 758 tty_writer: *std.Io.Writer.Allocating, 759 760 /// Initializes a TestTty. 761 pub fn init(_: std.Io, buffer: []u8) !@This() { 762 _ = buffer; 763 764 const list = try std.testing.allocator.create(std.Io.Writer.Allocating); 765 list.* = .init(std.testing.allocator); 766 var fds: [2]i32 = undefined; 767 const rc = linux.pipe(&fds); 768 switch (linux.errno(rc)) { 769 .SUCCESS => {}, 770 else => return error.PipeCreateFailed, 771 } 772 return .{ 773 .fd = fds[0], 774 .pipe_read = fds[0], 775 .pipe_write = fds[1], 776 .tty_writer = list, 777 }; 778 } 779 780 pub fn deinit(self: *@This()) void { 781 _ = linux.close(self.pipe_read); 782 _ = linux.close(self.pipe_write); 783 self.tty_writer.deinit(); 784 std.testing.allocator.destroy(self.tty_writer); 785 } 786 787 pub fn writer(self: *@This()) *std.Io.Writer { 788 return &self.tty_writer.writer; 789 } 790 791 pub fn read(self: *const @This(), buf: []u8) !usize { 792 return posix.read(self.fd, buf); 793 } 794 795 /// Get the window size from the kernel 796 pub fn getWinsize(_: *@This()) !Winsize { 797 return .{ 798 .rows = 40, 799 .cols = 80, 800 .x_pixel = 40 * 8, 801 .y_pixel = 40 * 8 * 2, 802 }; 803 } 804 805 /// Implemented for the Windows API 806 pub fn nextEvent(_: *@This(), _: *Parser, _: ?std.mem.Allocator) !Event { 807 return error.SkipZigTest; 808 } 809 810 pub fn resetSignalHandler() void { 811 return; 812 } 813 }, 814 else => struct { 815 d: std.Io.Writer.Discarding, 816 817 pub fn init(_: std.Io, buf: []u8) !@This() { 818 return .{ 819 .d = .init(buf), 820 }; 821 } 822 823 pub fn deinit(_: *const @This()) void {} 824 825 pub fn writer(self: *@This()) *std.Io.Writer { 826 return &self.d.writer; 827 } 828 829 pub fn getWinsize(_: *@This()) !Winsize { 830 return .{ 831 .rows = 40, 832 .cols = 80, 833 .x_pixel = 40 * 8, 834 .y_pixel = 40 * 8 * 2, 835 }; 836 } 837 838 /// Implemented for the Windows API 839 pub fn nextEvent(_: *@This(), _: *Parser, _: ?std.mem.Allocator) !Event { 840 return error.SkipZigTest; 841 } 842 }, 843}; 844 845test { 846 std.testing.refAllDecls(@This()); 847}