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