/// Terminal/console handling. /// Copyright: dd86k <dd@dax.moe> /// License: MIT /// Authors: $(LINK2 https://github.com/dd86k, dd86k) module os.terminal; //TODO: readline // automatically pause input, stdio.readln, resume input //TODO: Switch key input depending on $TERM // xterm, xterm-color, xterm-256color, linux, vt100 //TODO: Invert color support // NOTE: Useful links for escape codes // https://man7.org/linux/man-pages/man0/termios.h.0p.html // https://man7.org/linux/man-pages/man3/tcsetattr.3.html // https://man7.org/linux/man-pages/man4/console_codes.4.html /// private extern (C) int putchar(int); private import std.stdio : _IONBF, _IOLBF, _IOFBF, stdin, stdout; version (TestInput) private import std.stdio : printf; private import core.stdc.stdlib : system, atexit; version (Windows) { //TODO: To reduce output binary, import only modules necessary private import core.sys.windows.windows; private import std.windows.syserror : WindowsException; private enum ALT_PRESSED = RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED; private enum CTRL_PRESSED = RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED; private enum DEFAULT_COLOR = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; private enum CP_UTF8 = 65_001; private __gshared HANDLE hIn, hOut; private __gshared USHORT defaultColor = DEFAULT_COLOR; private __gshared DWORD oldCP; private __gshared ushort oldAttr; } else version (Posix) { private import core.stdc.stdio : snprintf; private import core.sys.posix.sys.stat; private import core.sys.posix.sys.ioctl; private import core.sys.posix.unistd; private import core.sys.posix.termios; private import core.sys.posix.signal; private enum NULL_SIGACTION = cast(sigaction_t*)0; private enum SIGWINCH = 28; // Bionic depends on the Linux system it's compiled on. // But Glibc and Musl have the same settings, so does Bionic. // ...And uClibc, at least on Linux. // D are missing the following bindings. version (CRuntime_Musl) version = IncludeTermiosLinux; version (CRuntime_Bionic) version = IncludeTermiosLinux; version (CRuntime_UClibc) version = IncludeTermiosLinux; version (IncludeTermiosLinux) { //siginfo_t // termios.h, bits/termios.h private alias uint tcflag_t; private alias uint speed_t; private alias char cc_t; private enum TCSANOW = 0; private enum NCCS = 32; private enum ICANON = 2; private enum ECHO = 10; private enum BRKINT = 2; private enum INPCK = 20; private enum ISTRIP = 40; private enum ICRNL = 400; private enum IXON = 2000; private enum IEXTEN = 100000; private enum CS8 = 60; private enum TCSAFLUSH = 2; private struct termios { tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_line; cc_t[NCCS] c_cc; speed_t __c_ispeed; speed_t __c_ospeed; } private extern (C) int tcgetattr(int fd, termios *termios_p); private extern (C) int tcsetattr(int fd, int a, termios *termios_p); // ioctl.h private enum TIOCGWINSZ = 0x5413; private struct winsize { ushort ws_row; ushort ws_col; ushort ws_xpixel; ushort ws_ypixel; } private extern (C) int ioctl(int fd, ulong request, ...); } struct KeyInfo { string text; int value; } immutable KeyInfo[] keyInputsVTE = [ // text Key value { "\033[A", Key.UpArrow }, { "\033[1;2A", Key.UpArrow | Mod.shift }, { "\033[1;3A", Key.UpArrow | Mod.alt }, { "\033[1;5A", Key.UpArrow | Mod.ctrl }, { "\033[A:4A", Key.UpArrow | Mod.shift | Mod.alt }, { "\033[B", Key.DownArrow }, { "\033[1;2B", Key.DownArrow | Mod.shift }, { "\033[1;3B", Key.DownArrow | Mod.alt }, { "\033[1;5B", Key.DownArrow | Mod.ctrl }, { "\033[A:4B", Key.DownArrow | Mod.shift | Mod.alt }, { "\033[C", Key.RightArrow }, { "\033[1;2C", Key.RightArrow | Mod.shift }, { "\033[1;3C", Key.RightArrow | Mod.alt }, { "\033[1;5C", Key.RightArrow | Mod.ctrl }, { "\033[A:4C", Key.RightArrow | Mod.shift | Mod.alt }, { "\033[D", Key.LeftArrow }, { "\033[1;2D", Key.LeftArrow | Mod.shift }, { "\033[1;3D", Key.LeftArrow | Mod.alt }, { "\033[1;5D", Key.LeftArrow | Mod.ctrl }, { "\033[A:4D", Key.LeftArrow | Mod.shift | Mod.alt }, { "\033[2~", Key.Insert }, { "\033[2;3~", Key.Insert | Mod.alt }, { "\033[3~", Key.Delete }, { "\033[3;5~", Key.Delete | Mod.ctrl }, { "\033[H", Key.Home }, { "\033[1;3H", Key.Home | Mod.alt }, { "\033[1;5H", Key.Home | Mod.ctrl }, { "\033[F", Key.End }, { "\033[1;3F", Key.End | Mod.alt }, { "\033[1;5F", Key.End | Mod.ctrl }, { "\033[5~", Key.PageUp }, { "\033[5;5~", Key.PageUp | Mod.ctrl }, { "\033[6~", Key.PageDown }, { "\033[6;5~", Key.PageDown | Mod.ctrl }, { "\033OP", Key.F1 }, { "\033[1;2P", Key.F1 | Mod.shift, }, { "\033[1;3R", Key.F1 | Mod.alt, }, { "\033[1;5P", Key.F1 | Mod.ctrl, }, { "\033OQ", Key.F2 }, { "\033[1;2Q", Key.F2 | Mod.shift }, { "\033OR", Key.F3 }, { "\033[1;2R", Key.F3 | Mod.shift }, { "\033OS", Key.F4 }, { "\033[1;2S", Key.F4 | Mod.shift }, { "\033[15~", Key.F5 }, { "\033[15;2~", Key.F5 | Mod.shift }, { "\033[17~", Key.F6 }, { "\033[17;2~", Key.F6 | Mod.shift }, { "\033[18~", Key.F7 }, { "\033[18;2~", Key.F7 | Mod.shift }, { "\033[19~", Key.F8 }, { "\033[19;2~", Key.F8 | Mod.shift }, { "\033[20~", Key.F9 }, { "\033[20;2~", Key.F9 | Mod.shift }, { "\033[21~", Key.F10 }, { "\033[21;2~", Key.F10 | Mod.shift }, { "\033[23~", Key.F11 }, { "\033[23;2~", Key.F11 | Mod.shift}, { "\033[24~", Key.F12 }, { "\033[24;2~", Key.F12 | Mod.shift }, ]; private __gshared termios old_ios, new_ios; } /// Flags for terminalInit. //TODO: captureCtrlC: Block CTRL+C enum TermFeat : ushort { /// Initiate only the basic. none = 0, /// Initiate the input system. inputSys = 1, /// Initiate the alternative screen buffer. altScreen = 1 << 1, /// Initiate everything. all = 0xffff, } private __gshared TermFeat current_features; /// Initiate terminal. /// Params: features = Feature bits to initiate. /// Throws: (Windows) WindowsException on OS exception void terminalInit(TermFeat features) { current_features = features; version (Windows) { CONSOLE_SCREEN_BUFFER_INFO csbi = void; if (features & TermFeat.inputSys) { //NOTE: Re-opening stdin before new screen fixes quite a few things // - usage with CreateConsoleScreenBuffer // - readln (for menu) // - receiving key input when stdin was used for reading a buffer hIn = CreateFileA("CONIN$", GENERIC_READ, 0, null, OPEN_EXISTING, 0, null); if (hIn == INVALID_HANDLE_VALUE) throw new WindowsException(GetLastError); SetConsoleMode(hIn, ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); stdin.windowsHandleOpen(hIn, "r"); SetStdHandle(STD_INPUT_HANDLE, hIn); } else { hIn = GetStdHandle(STD_INPUT_HANDLE); } if (features & TermFeat.altScreen) { // // Setting up stdout // hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hIn == INVALID_HANDLE_VALUE) throw new WindowsException(GetLastError); if (GetConsoleScreenBufferInfo(hOut, &csbi) == FALSE) throw new WindowsException(GetLastError); DWORD attr = void; if (GetConsoleMode(hOut, &attr) == FALSE) throw new WindowsException(GetLastError); hOut = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess FILE_SHARE_READ | FILE_SHARE_WRITE, // dwShareMode null, // lpSecurityAttributes CONSOLE_TEXTMODE_BUFFER, // dwFlags null, // lpScreenBufferData ); if (hOut == INVALID_HANDLE_VALUE) throw new WindowsException(GetLastError); stdout.flush; stdout.windowsHandleOpen(hOut, "wb"); // fixes using write functions SetStdHandle(STD_OUTPUT_HANDLE, hOut); SetConsoleScreenBufferSize(hOut, csbi.dwSize); SetConsoleMode(hOut, attr | ENABLE_PROCESSED_OUTPUT); if (SetConsoleActiveScreenBuffer(hOut) == FALSE) throw new WindowsException(GetLastError); } else { hOut = GetStdHandle(STD_OUTPUT_HANDLE); } stdout.setvbuf(0, _IONBF); // fixes weird cursor positions with alt buffer // NOTE: While Windows supports UTF-16LE (1200) and UTF-32LE, // it's only for "managed applications" (.NET). // LINK: https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers oldCP = GetConsoleOutputCP(); if (SetConsoleOutputCP(CP_UTF8) == FALSE) throw new WindowsException(GetLastError); //TODO: Get active (or default) colors GetConsoleScreenBufferInfo(hOut, &csbi); oldAttr = csbi.wAttributes; } else version (Posix) { stdout.setvbuf(0, _IONBF); if (features & TermFeat.inputSys) { // Should it re-open tty by default? stat_t s = void; fstat(STDIN_FILENO, &s); if (S_ISFIFO(s.st_mode)) stdin.reopen("/dev/tty", "r"); tcgetattr(STDIN_FILENO, &old_ios); new_ios = old_ios; // NOTE: input modes // - IXON enables ^S and ^Q // - ICRNL enables ^M // - BRKINT causes SIGINT (^C) on break conditions // - INPCK enables parity checking // - ISTRIP strips the 8th bit new_ios.c_iflag &= ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP); // NOTE: output modes // - OPOST turns on output post-processing //new_ios.c_oflag &= ~(OPOST); // NOTE: local modes // - ICANON turns on canonical mode (per-line instead of per-byte) // - ECHO turns on character echo // - ISIG enables ^C and ^Z signals // - IEXTEN enables ^V new_ios.c_lflag &= ~(ICANON | ECHO | IEXTEN); // NOTE: control modes // - CS8 sets Character Size to 8-bit new_ios.c_cflag |= CS8; // minimum amount of bytes to read, // 0 being return as soon as there is data //new_ios.c_cc[VMIN] = 0; // maximum amount of time to wait for input, // 1 being 1/10 of a second (100 milliseconds) //new_ios.c_cc[VTIME] = 0; terminalResumeInput; } if (features & TermFeat.altScreen) { // change to alternative screen buffer stdout.write("\033[?1049h"); } } atexit(&terminalQuit); } private extern (C) void terminalQuit() { terminalRestore; } /// Restore CP and other settings void terminalRestore() { version (Windows) { SetConsoleOutputCP(oldCP); // unconditionally } else version (Posix) { // restore main screen buffer if (current_features & TermFeat.altScreen) terminalOutput2("\033[?1049l"); terminalShowCursor; } if (current_features & TermFeat.inputSys) terminalPauseInput; } private __gshared void function() terminalOnResizeEvent; void terminalOnResize(void function() func) { version (Posix) { sigaction_t sa = void; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = &terminalResized; assert(sigaction(SIGWINCH, &sa, NULL_SIGACTION) != -1); } terminalOnResizeEvent = func; } version (Posix) private extern (C) void terminalResized(int signo, siginfo_t *info, void *content) { if (terminalOnResizeEvent) terminalOnResizeEvent(); } void terminalPauseInput() { version (Posix) tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_ios); } void terminalResumeInput() { version (Posix) tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_ios); } /// Clear screen void terminalClear() { version (Windows) { CONSOLE_SCREEN_BUFFER_INFO csbi = void; COORD c; GetConsoleScreenBufferInfo(hOut, &csbi); const int size = csbi.dwSize.X * csbi.dwSize.Y; DWORD num; if (FillConsoleOutputCharacterA(hOut, ' ', size, c, &num) == 0 /*|| FillConsoleOutputAttribute(hOut, csbi.wAttributes, size, c, &num) == 0*/) { terminalPos(0, 0); } else // If that fails, run cls. system("cls"); } else version (Posix) { // \033c is a Reset // \033[2J is "Erase whole display" terminalOutput2("\033[2J"); } else static assert(0, "Clear: Not implemented"); } /// Get terminal window size in characters. /// Returns: Size TerminalSize terminalSize() { TerminalSize size = void; version (Windows) { CONSOLE_SCREEN_BUFFER_INFO c = void; GetConsoleScreenBufferInfo(hOut, &c); size.height = c.srWindow.Bottom - c.srWindow.Top + 1; size.width = c.srWindow.Right - c.srWindow.Left + 1; } else version (Posix) { //TODO: Consider using LINES and COLUMNS environment variables // as fallback if ioctl returns -1. //TODO: Consider ESC [ 18 t for fallback of environment. // Reply: ESC [ 8 ; ROWS ; COLUMNS t winsize ws = void; ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); size.height = ws.ws_row; size.width = ws.ws_col; } else static assert(0, "terminalSize: Not implemented"); return size; } /// Set cursor position x and y position respectively from the top left corner, /// 0-based. /// Params: /// x = X position (horizontal) /// y = Y position (vertical) void terminalPos(int x, int y) { version (Windows) // 0-based { COORD c = void; c.X = cast(short)x; c.Y = cast(short)y; SetConsoleCursorPosition(hOut, c); } else version (Posix) // 1-based, so 0,0 needs to be output as 1,1 { char[16] b = void; int r = snprintf(b.ptr, 16, "\033[%d;%dH", ++y, ++x); assert(r > 0); terminalOutput(b.ptr, r); } } /// Hide the terminal cursor. void terminalHideCursor() { version (Windows) { CONSOLE_CURSOR_INFO cci = void; GetConsoleCursorInfo(hOut, &cci); cci.bVisible = FALSE; SetConsoleCursorInfo(hOut, &cci); } else version (Posix) { terminalOutput2("\033[?25l"); } } /// Show the terminal cursor. void terminalShowCursor() { version (Windows) { CONSOLE_CURSOR_INFO cci = void; GetConsoleCursorInfo(hOut, &cci); cci.bVisible = TRUE; SetConsoleCursorInfo(hOut, &cci); } else version (Posix) { terminalOutput2("\033[?25h"); } } void terminalHighlight() { version (Windows) { SetConsoleTextAttribute(hOut, oldAttr | BACKGROUND_RED); } else version (Posix) { terminalOutput2("\033[41m"); } } /// Invert color. void terminalInvertColor() { version (Windows) { SetConsoleTextAttribute(hOut, oldAttr | COMMON_LVB_REVERSE_VIDEO); } else version (Posix) { terminalOutput2("\033[7m"); } } /// Underline. /// Bugs: Does not work on Windows Terminal. See https://github.com/microsoft/terminal/issues/8037 void terminalUnderline() { version (Windows) { SetConsoleTextAttribute(hOut, oldAttr | COMMON_LVB_UNDERSCORE); } else version (Posix) { terminalOutput2("\033[4m"); } } /// Reset color. void terminalResetColor() { version (Windows) { SetConsoleTextAttribute(hOut, oldAttr); } else version (Posix) { terminalOutput2("\033[0m"); } } size_t terminalOutput2(const(void)[] data) { return terminalOutput(data.ptr, data.length); } /// Directly write to output. /// Params: /// data = Character data. /// size = Amount in bytes. /// Returns: Number of bytes written. size_t terminalOutput(const(void) *data, size_t size) { version (Windows) { import core.sys.windows.winbase : STD_OUTPUT_HANDLE, WriteFile, GetStdHandle; uint r = void; assert(WriteFile(hOut, data, cast(uint)size, &r, null)); return r; } else version (Posix) { import core.sys.posix.unistd : write, STDOUT_FILENO; import core.sys.posix.sys.types : ssize_t; ssize_t r = write(STDOUT_FILENO, data, size); assert(r >= 0); return r; } } /// Read an input event. This function is blocking. /// Params: /// event = TerminalInfo struct /// Throws: (Windows) WindowsException on OS error void terminalInput(ref TerminalInput event) { version (Windows) { INPUT_RECORD ir = void; DWORD num = void; L_READ: if (ReadConsoleInputA(hIn, &ir, 1, &num) == 0) throw new WindowsException(GetLastError); if (num == 0) goto L_READ; switch (ir.EventType) { case KEY_EVENT: if (ir.KeyEvent.bKeyDown == FALSE) goto L_READ; version (TestInput) { printf( "KeyEvent: AsciiChar=%d wVirtualKeyCode=%d dwControlKeyState=%x\n", ir.KeyEvent.AsciiChar, ir.KeyEvent.wVirtualKeyCode, ir.KeyEvent.dwControlKeyState, ); } const ushort keycode = ir.KeyEvent.wVirtualKeyCode; // Filter out single modifier key events switch (keycode) { case 16, 17, 18: goto L_READ; // shift,ctrl,alt default: } event.type = InputType.keyDown; const char ascii = ir.KeyEvent.AsciiChar; if (ascii >= 'a' && ascii <= 'z') { event.key = ascii - 32; return; } else if (ascii >= 0x20 && ascii < 0x7f) { event.key = ascii; // '?' on a fr-ca kb is technically shift+6, // breaking app input since expecting no modifiers if (ascii >= 'A' && ascii <= 'Z') event.key = Mod.shift; return; } event.key = keycode; const DWORD state = ir.KeyEvent.dwControlKeyState; if (state & ALT_PRESSED) event.key |= Mod.alt; if (state & CTRL_PRESSED) event.key |= Mod.ctrl; if (state & SHIFT_PRESSED) event.key |= Mod.shift; return; /*case MOUSE_EVENT: if (ir.MouseEvent.dwEventFlags & MOUSE_WHEELED) { // Up=0x00780000 Down=0xFF880000 event.type = ir.MouseEvent.dwButtonState > 0xFF_0000 ? Mouse.ScrollDown : Mouse.ScrollUp; }*/ case WINDOW_BUFFER_SIZE_EVENT: if (terminalOnResizeEvent) terminalOnResizeEvent(); goto L_READ; default: goto L_READ; } } else version (Posix) { //TODO: Mouse reporting in Posix terminals // * X10 compatbility mode (mouse-down only) // Enable: ESC [ ? 9 h // Disable: ESC [ ? 9 l // "sends ESC [ M bxy (6 characters)" // - ESC [ M button column row (1-based) // - 0,0 click: ESC [ M ! ! // ! is 0x21, so '!' - 0x21 = 0 // - end,end click: ESC [ M q ; // q is 0x71, so 'q' - 0x21 = 0x50 (column 80) // ; is 0x3b, so ';' - 0x21 = 0x1a (row 26) // - button left: ' ' // - button right: '"' // - button middle: '!' // * Normal tracking mode // Enable: ESC [ ? 1000 h // Disable: ESC [ ? 1000 l // b bits[1:0] 0=MB1 pressed, 1=MB2 pressed, 2=MB3 pressed, 3=release // b bits[7:2] 4=Shift (bit 3), 8=Meta (bit 4), 16=Control (bit 5) //TODO: Faster scanning // So we have a few choices: // - string table (current, works alright) // - string[string] // - needs active init though // - string decoding (slower?) // [ -> escape // 1;2 -> shift (optional) // B -> right arrow // - template char[8] to long // - very cursed // - screwed if there are keys more than 8 bytes // - template should do endianness // - Manually hash it // - Allows static arrays // - std.digest.murmurhash already available enum BLEN = 8; char[BLEN] b = void; L_READ: ssize_t r = read(STDIN_FILENO, b.ptr, BLEN); event.type = InputType.keyDown; // Assuming for now event.key = 0; // clear as safety measure switch (r) { case -1: assert(0, "read(2) failed"); case 0: // How even version (TestInput) printf("stdin: empty\n"); goto L_READ; case 1: char c = b[0]; version (TestInput) printf("stdin: \\0%o (%d)\n", c, c); // Filtering here adjusts the value only if necessary. switch (c) { case 0: // Ctrl+Space event.key = Key.Spacebar | Mod.ctrl; return; case 13: event.key = Key.Enter; return; case 8, 127: // ^H event.key = Key.Backspace; return; default: } if (c >= 'a' && c <= 'z') event.key = cast(ushort)(c - 32); else if (c >= 'A' && c <= 'Z') event.key = c | Mod.shift; else if (c < 32) event.key = (c + 64) | Mod.ctrl; else event.key = c; return; default: } version (TestInput) { printf("stdin:"); for (size_t i; i < r; ++i) { char c = b[i]; if (c < 32 || c > 126) printf(" \\0%o", c); else putchar(b[i]); } putchar('\n'); stdout.flush(); } // Make a slice of misc. input. const(char)[] inputString = b[0..r]; //TODO: Checking for mouse inputs // Starts with \033[M // Checking for other key inputs foreach (ki; keyInputsVTE) { if (r != ki.text.length) continue; if (inputString != ki.text) continue; event.key = ki.value; return; } // Matched to nothing goto L_READ; } // version posix } /// Terminal input type. enum InputType { keyDown, keyUp, mouseDown, mouseUp, } /// Key modifier enum Mod { ctrl = 1 << 16, shift = 1 << 17, alt = 1 << 18, } /// Key codes map. //TODO: Consider mapping these to os/ascii-specific enum Key { Undefined = 0, Backspace = 8, Tab = 9, Clear = 12, Enter = 13, Pause = 19, Escape = 27, Spacebar = 32, PageUp = 33, PageDown = 34, End = 35, Home = 36, LeftArrow = 37, UpArrow = 38, RightArrow = 39, DownArrow = 40, Select = 41, Print = 42, Execute = 43, PrintScreen = 44, Insert = 45, Delete = 46, Help = 47, D0 = 48, D1 = 49, D2 = 50, D3 = 51, D4 = 52, D5 = 53, D6 = 54, D7 = 55, D8 = 56, D9 = 57, Colon = 58, SemiColon = 59, A = 65, B = 66, C = 67, D = 68, E = 69, F = 70, G = 71, H = 72, I = 73, J = 74, K = 75, L = 76, M = 77, N = 78, O = 79, P = 80, Q = 81, R = 82, S = 83, T = 84, U = 85, V = 86, W = 87, X = 88, Y = 89, Z = 90, LeftMeta = 91, RightMeta = 92, Applications = 93, Sleep = 95, NumPad0 = 96, NumPad1 = 97, NumPad2 = 98, NumPad3 = 99, NumPad4 = 100, NumPad5 = 101, NumPad6 = 102, NumPad7 = 103, NumPad8 = 104, NumPad9 = 105, Multiply = 106, Add = 107, Separator = 108, Subtract = 109, Decimal = 110, Divide = 111, F1 = 112, F2 = 113, F3 = 114, F4 = 115, F5 = 116, F6 = 117, F7 = 118, F8 = 119, F9 = 120, F10 = 121, F11 = 122, F12 = 123, F13 = 124, F14 = 125, F15 = 126, F16 = 127, F17 = 128, F18 = 129, F19 = 130, F20 = 131, F21 = 132, F22 = 133, F23 = 134, F24 = 135, BrowserBack = 166, BrowserForward = 167, BrowserRefresh = 168, BrowserStop = 169, BrowserSearch = 170, BrowserFavorites = 171, BrowserHome = 172, VolumeMute = 173, VolumeDown = 174, VolumeUp = 175, MediaNext = 176, MediaPrevious = 177, MediaStop = 178, MediaPlay = 179, LaunchMail = 180, LaunchMediaSelect = 181, LaunchApp1 = 182, LaunchApp2 = 183, Oem1 = 186, OemPlus = 187, OemComma = 188, OemMinus = 189, OemPeriod = 190, Oem2 = 191, Oem3 = 192, Oem4 = 219, Oem5 = 220, Oem6 = 221, Oem7 = 222, Oem8 = 223, Oem102 = 226, Process = 229, Packet = 231, Attention = 246, CrSel = 247, ExSel = 248, EraseEndOfFile = 249, Play = 250, Zoom = 251, NoName = 252, Pa1 = 253, OemClear = 254 } /// Terminal input structure struct TerminalInput { union { int key; /// Keyboard input with possible Mod flags. struct { ushort mouseX; /// Mouse column coord ushort mouseY; /// Mouse row coord } } int type; /// Terminal input event type } /// Terminal size structure struct TerminalSize { union { /// Terminal width in character columns int width; int columns; /// Ditto } union { /// Terminal height in character rows int height; int rows; /// Ditto } }