this repo has no description
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}