/* ---------------------------------------------------------------------------- Copyright (c) 2021, Daan Leijen This is free software; you can redistribute it and/or modify it under the terms of the MIT License. A copy of the license can be found in the "LICENSE" file at the root of this distribution. -----------------------------------------------------------------------------*/ #include #include #include #include #include #include "tty.h" #if defined(_WIN32) #include #include #define isatty(fd) _isatty(fd) #define read(fd,s,n) _read(fd,s,n) #define STDIN_FILENO 0 #if (_WIN32_WINNT < 0x0600) WINBASEAPI ULONGLONG WINAPI GetTickCount64(VOID); #endif #else #include #include #include #include #include #include #if !defined(FIONREAD) #include #endif #endif #define TTY_PUSH_MAX (32) struct tty_s { int fd_in; // input handle bool raw_enabled; // is raw mode enabled? bool is_utf8; // is the input stream in utf-8 mode? bool has_term_resize_event; // are resize events generated? bool term_resize_event; // did a term resize happen? alloc_t* mem; // memory allocator code_t pushbuf[TTY_PUSH_MAX]; // push back buffer for full key codes ssize_t push_count; uint8_t cpushbuf[TTY_PUSH_MAX]; // low level push back buffer for bytes ssize_t cpush_count; long esc_initial_timeout; // initial ms wait to see if ESC starts an escape sequence long esc_timeout; // follow up delay for characters in an escape sequence #if defined(_WIN32) HANDLE hcon; // console input handle DWORD hcon_orig_mode; // original console mode #else struct termios orig_ios; // original terminal settings struct termios raw_ios; // raw terminal settings #endif }; //------------------------------------------------------------- // Forward declarations of platform dependent primitives below //------------------------------------------------------------- ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms); // does not modify `c` when no input (false is returned) //------------------------------------------------------------- // Key code helpers //------------------------------------------------------------- ic_private bool code_is_ascii_char(code_t c, char* chr ) { if (c >= ' ' && c <= 0x7F) { if (chr != NULL) *chr = (char)c; return true; } else { if (chr != NULL) *chr = 0; return false; } } ic_private bool code_is_unicode(code_t c, unicode_t* uchr) { if (c <= KEY_UNICODE_MAX) { if (uchr != NULL) *uchr = c; return true; } else { if (uchr != NULL) *uchr = 0; return false; } } ic_private bool code_is_virt_key(code_t c ) { return (KEY_NO_MODS(c) <= 0x20 || KEY_NO_MODS(c) >= KEY_VIRT); } //------------------------------------------------------------- // Read a key code //------------------------------------------------------------- static code_t modify_code( code_t code ); static code_t tty_read_utf8( tty_t* tty, uint8_t c0 ) { uint8_t buf[5]; memset(buf, 0, 5); // try to read as many bytes as potentially needed buf[0] = c0; ssize_t count = 1; if (c0 > 0x7F) { if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) { count++; if (c0 > 0xDF) { if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) { count++; if (c0 > 0xEF) { if (tty_readc_noblock(tty, buf+count, tty->esc_timeout)) { count++; } } } } } } buf[count] = 0; debug_msg("tty: read utf8: count: %zd: %02x,%02x,%02x,%02x", count, buf[0], buf[1], buf[2], buf[3]); // decode the utf8 to unicode ssize_t read = 0; code_t code = key_unicode(unicode_from_qutf8(buf, count, &read)); // push back unused bytes (in the case of invalid utf8) while (count > read) { count--; if (count >= 0 && count <= 4) { // to help the static analyzer tty_cpush_char(tty, buf[count]); } } return code; } // pop a code from the pushback buffer. static bool tty_code_pop(tty_t* tty, code_t* code); // read a single char/key ic_private bool tty_read_timeout(tty_t* tty, long timeout_ms, code_t* code) { // is there a push_count back code? if (tty_code_pop(tty,code)) { return code; } // read a single char/byte from a character stream uint8_t c; if (!tty_readc_noblock(tty, &c, timeout_ms)) return false; if (c == KEY_ESC) { // escape sequence? *code = tty_read_esc(tty, tty->esc_initial_timeout, tty->esc_timeout); } else if (c <= 0x7F) { // ascii *code = key_unicode(c); } else if (tty->is_utf8) { // utf8 sequence *code = tty_read_utf8(tty,c); } else { // c >= 0x80 but tty is not utf8; use raw plane so we can translate it back in the end *code = key_unicode( unicode_from_raw(c) ); } *code = modify_code(*code); return true; } // Transform virtual keys to be more portable across platforms static code_t modify_code( code_t code ) { code_t key = KEY_NO_MODS(code); code_t mods = KEY_MODS(code); debug_msg( "tty: readc %s%s%s 0x%03x ('%c')\n", mods&KEY_MOD_SHIFT ? "shift+" : "", mods&KEY_MOD_CTRL ? "ctrl+" : "", mods&KEY_MOD_ALT ? "alt+" : "", key, (key >= ' ' && key <= '~' ? key : ' ')); // treat KEY_RUBOUT (0x7F) as KEY_BACKSP if (key == KEY_RUBOUT) { code = KEY_BACKSP | mods; } // ctrl+'_' is translated to '\x1F' on Linux, translate it back else if (key == key_char('\x1F') && (mods & KEY_MOD_ALT) == 0) { key = '_'; code = WITH_CTRL(key_char('_')); } // treat ctrl/shift + enter always as KEY_LINEFEED for portability else if (key == KEY_ENTER && (mods == KEY_MOD_SHIFT || mods == KEY_MOD_ALT || mods == KEY_MOD_CTRL)) { code = KEY_LINEFEED; } // treat ctrl+tab always as shift+tab for portability else if (code == WITH_CTRL(KEY_TAB)) { code = KEY_SHIFT_TAB; } // treat ctrl+end/alt+>/alt-down and ctrl+home/alt+') || code == WITH_CTRL(KEY_END)) { code = KEY_PAGEDOWN; } else if (code == WITH_ALT(KEY_UP) || code == WITH_ALT('<') || code == WITH_CTRL(KEY_HOME)) { code = KEY_PAGEUP; } // treat C0 codes without KEY_MOD_CTRL if (key < ' ' && (mods&KEY_MOD_CTRL) != 0) { code &= ~KEY_MOD_CTRL; } return code; } // read a single char/key ic_private code_t tty_read(tty_t* tty) { code_t code; if (!tty_read_timeout(tty, -1, &code)) return KEY_NONE; return code; } //------------------------------------------------------------- // Read back an ANSI query response //------------------------------------------------------------- ic_private bool tty_read_esc_response(tty_t* tty, char esc_start, bool final_st, char* buf, ssize_t buflen ) { buf[0] = 0; ssize_t len = 0; uint8_t c = 0; if (!tty_readc_noblock(tty, &c, 2*tty->esc_initial_timeout) || c != '\x1B') { debug_msg("initial esc response failed: 0x%02x\n", c); return false; } if (!tty_readc_noblock(tty, &c, tty->esc_timeout) || (c != esc_start)) return false; while( len < buflen ) { if (!tty_readc_noblock(tty, &c, tty->esc_timeout)) return false; if (final_st) { // OSC is terminated by BELL, or ESC \ (ST) (and STX) if (c=='\x07' || c=='\x02') { break; } else if (c=='\x1B') { uint8_t c1; if (!tty_readc_noblock(tty, &c1, tty->esc_timeout)) return false; if (c1=='\\') break; tty_cpush_char(tty,c1); } } else { if (c == '\x02') { // STX break; } else if (!((c >= '0' && c <= '9') || strchr("<=>?;:",c) != NULL)) { buf[len++] = (char)c; // for non-OSC save the terminating character break; } } buf[len++] = (char)c; } buf[len] = 0; debug_msg("tty: escape query response: %s\n", buf); return true; } //------------------------------------------------------------- // High level code pushback //------------------------------------------------------------- static bool tty_code_pop( tty_t* tty, code_t* code ) { if (tty->push_count <= 0) return false; tty->push_count--; *code = tty->pushbuf[tty->push_count]; return true; } ic_private void tty_code_pushback( tty_t* tty, code_t c ) { // note: must be signal safe if (tty->push_count >= TTY_PUSH_MAX) return; tty->pushbuf[tty->push_count] = c; tty->push_count++; } //------------------------------------------------------------- // low-level character pushback (for escape sequences and windows) //------------------------------------------------------------- ic_private bool tty_cpop(tty_t* tty, uint8_t* c) { if (tty->cpush_count <= 0) { // do not modify c on failure (see `tty_decode_unicode`) return false; } else { tty->cpush_count--; *c = tty->cpushbuf[tty->cpush_count]; return true; } } static void tty_cpush(tty_t* tty, const char* s) { ssize_t len = ic_strlen(s); if (tty->push_count + len > TTY_PUSH_MAX) { debug_msg("tty: cpush buffer full! (pushing %s)\n", s); assert(false); return; } for (ssize_t i = 0; i < len; i++) { tty->cpushbuf[tty->cpush_count + i] = (uint8_t)( s[len - i - 1] ); } tty->cpush_count += len; return; } // convenience function for small sequences static void tty_cpushf(tty_t* tty, const char* fmt, ...) { va_list args; va_start(args,fmt); char buf[TTY_PUSH_MAX+1]; vsnprintf(buf,TTY_PUSH_MAX,fmt,args); buf[TTY_PUSH_MAX] = 0; tty_cpush(tty,buf); va_end(args); return; } ic_private void tty_cpush_char(tty_t* tty, uint8_t c) { uint8_t buf[2]; buf[0] = c; buf[1] = 0; tty_cpush(tty, (const char*)buf); } //------------------------------------------------------------- // Push escape codes (used on Windows to insert keys) //------------------------------------------------------------- static unsigned csi_mods(code_t mods) { unsigned m = 1; if (mods&KEY_MOD_SHIFT) m += 1; if (mods&KEY_MOD_ALT) m += 2; if (mods&KEY_MOD_CTRL) m += 4; return m; } // Push ESC [ ; ~ static void tty_cpush_csi_vt( tty_t* tty, code_t mods, uint32_t vtcode ) { tty_cpushf(tty,"\x1B[%u;%u~", vtcode, csi_mods(mods) ); } // push ESC [ 1 ; static void tty_cpush_csi_xterm( tty_t* tty, code_t mods, char xcode ) { tty_cpushf(tty,"\x1B[1;%u%c", csi_mods(mods), xcode ); } // push ESC [ ; u static void tty_cpush_csi_unicode( tty_t* tty, code_t mods, uint32_t unicode ) { if ((unicode < 0x80 && mods == 0) || (mods == KEY_MOD_CTRL && unicode < ' ' && unicode != KEY_TAB && unicode != KEY_ENTER && unicode != KEY_LINEFEED && unicode != KEY_BACKSP) || (mods == KEY_MOD_SHIFT && unicode >= ' ' && unicode <= KEY_RUBOUT)) { tty_cpush_char(tty,(uint8_t)unicode); } else { tty_cpushf(tty,"\x1B[%u;%uu", unicode, csi_mods(mods) ); } } //------------------------------------------------------------- // Init //------------------------------------------------------------- static bool tty_init_raw(tty_t* tty); static void tty_done_raw(tty_t* tty); static bool tty_init_utf8(tty_t* tty) { #ifdef _WIN32 tty->is_utf8 = true; #else const char* loc = setlocale(LC_ALL,""); tty->is_utf8 = (ic_icontains(loc,"UTF-8") || ic_icontains(loc,"utf8") || ic_stricmp(loc,"C") == 0); debug_msg("tty: utf8: %s (loc=%s)\n", tty->is_utf8 ? "true" : "false", loc); #endif return true; } ic_private tty_t* tty_new(alloc_t* mem, int fd_in) { tty_t* tty = mem_zalloc_tp(mem, tty_t); tty->mem = mem; tty->fd_in = (fd_in < 0 ? STDIN_FILENO : fd_in); #if defined(__APPLE__) tty->esc_initial_timeout = 200; // apple use ESC+ for alt- #else tty->esc_initial_timeout = 100; #endif tty->esc_timeout = 10; if (!(isatty(tty->fd_in) && tty_init_raw(tty) && tty_init_utf8(tty))) { tty_free(tty); return NULL; } return tty; } ic_private void tty_free(tty_t* tty) { if (tty==NULL) return; tty_end_raw(tty); tty_done_raw(tty); mem_free(tty->mem,tty); } ic_private bool tty_is_utf8(const tty_t* tty) { if (tty == NULL) return true; return (tty->is_utf8); } ic_private bool tty_term_resize_event(tty_t* tty) { if (tty == NULL) return true; if (tty->has_term_resize_event) { if (!tty->term_resize_event) return false; tty->term_resize_event = false; // reset. } return true; // always return true on systems without a resize event (more expensive but still ok) } ic_private void tty_set_esc_delay(tty_t* tty, long initial_delay_ms, long followup_delay_ms) { tty->esc_initial_timeout = (initial_delay_ms < 0 ? 0 : (initial_delay_ms > 1000 ? 1000 : initial_delay_ms)); tty->esc_timeout = (followup_delay_ms < 0 ? 0 : (followup_delay_ms > 1000 ? 1000 : followup_delay_ms)); } //------------------------------------------------------------- // Unix //------------------------------------------------------------- #if !defined(_WIN32) static bool tty_readc_blocking(tty_t* tty, uint8_t* c) { if (tty_cpop(tty,c)) return true; *c = 0; ssize_t nread = read(tty->fd_in, (char*)c, 1); if (nread < 0 && errno == EINTR) { // can happen on SIGWINCH signal for terminal resize } return (nread == 1); } // non blocking read -- with a small timeout used for reading escape sequences. ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms) { // in our pushback buffer? if (tty_cpop(tty, c)) return true; // blocking read? if (timeout_ms < 0) { return tty_readc_blocking(tty,c); } // if supported, peek first if any char is available. #if defined(FIONREAD) { int navail = 0; if (ioctl(0, FIONREAD, &navail) == 0) { if (navail >= 1) { return tty_readc_blocking(tty, c); } else if (timeout_ms == 0) { return false; // return early if there is no input available (with a zero timeout) } } } #endif // otherwise block for at most timeout milliseconds #if defined(FD_SET) // we can use select to detect when input becomes available fd_set readset; struct timeval time; FD_ZERO(&readset); FD_SET(tty->fd_in, &readset); time.tv_sec = (timeout_ms > 0 ? timeout_ms / 1000 : 0); time.tv_usec = (timeout_ms > 0 ? 1000*(timeout_ms % 1000) : 0); if (select(tty->fd_in + 1, &readset, NULL, NULL, &time) == 1) { // input available return tty_readc_blocking(tty, c); } #else // no select, we cannot timeout; use usleeps :-( // todo: this seems very rare nowadays; should be even support this? do { // peek ahead if possible #if defined(FIONREAD) int navail = 0; if (ioctl(0, FIONREAD, &navail) == 0 && navail >= 1) { return tty_readc_blocking(tty, c); } #elif defined(O_NONBLOCK) // use a temporary non-blocking read mode int fstatus = fcntl(tty->fd_in, F_GETFL, 0); if (fstatus != -1) { if (fcntl(tty->fd_in, F_SETFL, (fstatus | O_NONBLOCK)) != -1) { char buf[2] = { 0, 0 }; ssize_t nread = read(tty->fd_in, buf, 1); fcntl(tty->fd_in, F_SETFL, fstatus); if (nread >= 1) { *c = (uint8_t)buf[0]; return true; } } } #else #error "define an nonblocking read for this platform" #endif // and sleep a bit if (timeout_ms > 0) { usleep(50*1000L); // sleep at most 0.05s at a time timeout_ms -= 100; if (timeout_ms < 0) { timeout_ms = 0; } } } while (timeout_ms > 0); #endif return false; } #if defined(TIOCSTI) ic_private bool tty_async_stop(const tty_t* tty) { // insert ^C in the input stream char c = KEY_CTRL_C; return (ioctl(tty->fd_in, TIOCSTI, &c) >= 0); } #else ic_private bool tty_async_stop(const tty_t* tty) { return false; } #endif // We install various signal handlers to restore the terminal settings // in case of a terminating signal. This is also used to catch terminal window resizes. // This is not strictly needed so this can be disabled on // (older) platforms that do not support signal handling well. #if defined(SIGWINCH) && defined(SA_RESTART) // ensure basic signal functionality is defined // store the tty in a global so we access it on unexpected termination static tty_t* sig_tty; // = NULL // Catch all termination signals (and SIGWINCH) typedef struct signal_handler_s { int signum; union { int _avoid_warning; struct sigaction previous; } action; } signal_handler_t; static signal_handler_t sighandlers[] = { { SIGWINCH, {0} }, { SIGTERM , {0} }, { SIGINT , {0} }, { SIGQUIT , {0} }, { SIGHUP , {0} }, { SIGSEGV , {0} }, { SIGTRAP , {0} }, { SIGBUS , {0} }, { SIGTSTP , {0} }, { SIGTTIN , {0} }, { SIGTTOU , {0} }, { 0 , {0} } }; static bool sigaction_is_valid( struct sigaction* sa ) { return (sa->sa_sigaction != NULL && sa->sa_handler != SIG_DFL && sa->sa_handler != SIG_IGN); } // Generic signal handler static void sig_handler(int signum, siginfo_t* siginfo, void* uap ) { if (signum == SIGWINCH) { if (sig_tty != NULL) { sig_tty->term_resize_event = true; } } else { // the rest are termination signals; restore the terminal mode. (`tcsetattr` is signal-safe) if (sig_tty != NULL && sig_tty->raw_enabled) { tcsetattr(sig_tty->fd_in, TCSAFLUSH, &sig_tty->orig_ios); sig_tty->raw_enabled = false; } } // call previous handler signal_handler_t* sh = sighandlers; while( sh->signum != 0 && sh->signum != signum) { sh++; } if (sh->signum == signum) { if (sigaction_is_valid(&sh->action.previous)) { (sh->action.previous.sa_sigaction)(signum, siginfo, uap); } } } static void signals_install(tty_t* tty) { sig_tty = tty; // generic signal handler struct sigaction handler; memset(&handler,0,sizeof(handler)); sigemptyset(&handler.sa_mask); handler.sa_sigaction = &sig_handler; handler.sa_flags = SA_RESTART; // install for all signals for( signal_handler_t* sh = sighandlers; sh->signum != 0; sh++ ) { if (sigaction( sh->signum, NULL, &sh->action.previous) == 0) { // get previous if (sh->action.previous.sa_handler != SIG_IGN) { // if not to be ignored if (sigaction( sh->signum, &handler, &sh->action.previous ) < 0) { // install our handler sh->action.previous.sa_sigaction = NULL; // do not restore on error } else if (sh->signum == SIGWINCH) { sig_tty->has_term_resize_event = true; }; } } } } static void signals_restore(void) { // restore all signal handlers for( signal_handler_t* sh = sighandlers; sh->signum != 0; sh++ ) { if (sigaction_is_valid(&sh->action.previous)) { sigaction( sh->signum, &sh->action.previous, NULL ); }; } sig_tty = NULL; } #else static void signals_install(tty_t* tty) { ic_unused(tty); // nothing } static void signals_restore(void) { // nothing } #endif ic_private bool tty_start_raw(tty_t* tty) { if (tty->raw_enabled) return true; if (tcsetattr(tty->fd_in,TCSAFLUSH,&tty->raw_ios) < 0) return false; tty->raw_enabled = true; return true; } ic_private void tty_end_raw(tty_t* tty) { if (!tty->raw_enabled) return; tty->cpush_count = 0; if (tcsetattr(tty->fd_in,TCSAFLUSH,&tty->orig_ios) < 0) return; tty->raw_enabled = false; } static bool tty_init_raw(tty_t* tty) { // Set input to raw mode. See . if (tcgetattr(tty->fd_in,&tty->orig_ios) == -1) return false; tty->raw_ios = tty->orig_ios; // input: no break signal, no \r to \n, no parity check, no 8-bit to 7-bit, no flow control tty->raw_ios.c_iflag &= ~(unsigned long)(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // control: allow 8-bit tty->raw_ios.c_cflag |= CS8; // local: no echo, no line-by-line (canonical), no extended input processing, no signals for ^z,^c tty->raw_ios.c_lflag &= ~(unsigned long)(ECHO | ICANON | IEXTEN | ISIG); // 1 byte at a time, no delay tty->raw_ios.c_cc[VTIME] = 0; tty->raw_ios.c_cc[VMIN] = 1; // store in global so our signal handlers can restore the terminal mode signals_install(tty); return true; } static void tty_done_raw(tty_t* tty) { ic_unused(tty); signals_restore(); } #else //------------------------------------------------------------- // Windows // For best portability we push CSI escape sequences directly // to the character stream (instead of returning key codes). //------------------------------------------------------------- static void tty_waitc_console(tty_t* tty, long timeout_ms); ic_private bool tty_readc_noblock(tty_t* tty, uint8_t* c, long timeout_ms) { // don't modify `c` if there is no input // in our pushback buffer? if (tty_cpop(tty, c)) return true; // any events in the input queue? tty_waitc_console(tty, timeout_ms); return tty_cpop(tty, c); } // Read from the console input events and push escape codes into the tty cbuffer. static void tty_waitc_console(tty_t* tty, long timeout_ms) { // wait for a key down event INPUT_RECORD inp; DWORD count; uint32_t surrogate_hi = 0; while (true) { // check if there are events if in non-blocking timeout mode if (timeout_ms >= 0) { // first peek ahead if (!GetNumberOfConsoleInputEvents(tty->hcon, &count)) return; if (count == 0) { if (timeout_ms == 0) { // out of time return; } else { // wait for input events for at most timeout milli seconds ULONGLONG start_ms = GetTickCount64(); DWORD res = WaitForSingleObject(tty->hcon, (DWORD)timeout_ms); switch (res) { case WAIT_OBJECT_0: { // input is available, decrease our timeout ULONGLONG waited_ms = (GetTickCount64() - start_ms); timeout_ms -= (long)waited_ms; if (timeout_ms < 0) { timeout_ms = 0; } break; } case WAIT_TIMEOUT: case WAIT_ABANDONED: case WAIT_FAILED: default: return; } } } } // (blocking) Read from the input if (!ReadConsoleInputW(tty->hcon, &inp, 1, &count)) return; if (count != 1) return; // resize event? if (inp.EventType == WINDOW_BUFFER_SIZE_EVENT) { tty->term_resize_event = true; continue; } // wait for key down events if (inp.EventType != KEY_EVENT) continue; // the modifier state DWORD modstate = inp.Event.KeyEvent.dwControlKeyState; // we need to handle shift up events separately if (!inp.Event.KeyEvent.bKeyDown && inp.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT) { modstate &= (DWORD)~SHIFT_PRESSED; } // ignore AltGr DWORD altgr = LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED; if ((modstate & altgr) == altgr) { modstate &= ~altgr; } // get modifiers code_t mods = 0; if ((modstate & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED )) != 0) mods |= KEY_MOD_CTRL; if ((modstate & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED )) != 0) mods |= KEY_MOD_ALT; if ((modstate & SHIFT_PRESSED) != 0) mods |= KEY_MOD_SHIFT; // virtual keys uint32_t chr = (uint32_t)inp.Event.KeyEvent.uChar.UnicodeChar; WORD virt = inp.Event.KeyEvent.wVirtualKeyCode; debug_msg("tty: console %s: %s%s%s virt 0x%04x, chr 0x%04x ('%c')\n", inp.Event.KeyEvent.bKeyDown ? "down" : "up", mods&KEY_MOD_CTRL ? "ctrl-" : "", mods&KEY_MOD_ALT ? "alt-" : "", mods&KEY_MOD_SHIFT ? "shift-" : "", virt, chr, chr); // only process keydown events (except for Alt-up which is used for unicode pasting...) if (!inp.Event.KeyEvent.bKeyDown && virt != VK_MENU) { continue; } if (chr == 0) { switch (virt) { case VK_UP: tty_cpush_csi_xterm(tty, mods, 'A'); return; case VK_DOWN: tty_cpush_csi_xterm(tty, mods, 'B'); return; case VK_RIGHT: tty_cpush_csi_xterm(tty, mods, 'C'); return; case VK_LEFT: tty_cpush_csi_xterm(tty, mods, 'D'); return; case VK_END: tty_cpush_csi_xterm(tty, mods, 'F'); return; case VK_HOME: tty_cpush_csi_xterm(tty, mods, 'H'); return; case VK_DELETE: tty_cpush_csi_vt(tty,mods,3); return; case VK_PRIOR: tty_cpush_csi_vt(tty,mods,5); return; //page up case VK_NEXT: tty_cpush_csi_vt(tty,mods,6); return; //page down case VK_TAB: tty_cpush_csi_unicode(tty,mods,9); return; case VK_RETURN: tty_cpush_csi_unicode(tty,mods,13); return; default: { uint32_t vtcode = 0; if (virt >= VK_F1 && virt <= VK_F5) { vtcode = 10 + (virt - VK_F1); } else if (virt >= VK_F6 && virt <= VK_F10) { vtcode = 17 + (virt - VK_F6); } else if (virt >= VK_F11 && virt <= VK_F12) { vtcode = 13 + (virt - VK_F11); } if (vtcode > 0) { tty_cpush_csi_vt(tty,mods,vtcode); return; } } } // ignore other control keys (shift etc). } // high surrogate pair else if (chr >= 0xD800 && chr <= 0xDBFF) { surrogate_hi = (chr - 0xD800); } // low surrogate pair else if (chr >= 0xDC00 && chr <= 0xDFFF) { chr = ((surrogate_hi << 10) + (chr - 0xDC00) + 0x10000); tty_cpush_csi_unicode(tty,mods,chr); surrogate_hi = 0; return; } // regular character else { tty_cpush_csi_unicode(tty,mods,chr); return; } } } ic_private bool tty_async_stop(const tty_t* tty) { // send ^c INPUT_RECORD events[2]; memset(events, 0, 2*sizeof(INPUT_RECORD)); events[0].EventType = KEY_EVENT; events[0].Event.KeyEvent.bKeyDown = TRUE; events[0].Event.KeyEvent.uChar.AsciiChar = KEY_CTRL_C; events[1] = events[0]; events[1].Event.KeyEvent.bKeyDown = FALSE; DWORD nwritten = 0; WriteConsoleInput(tty->hcon, events, 2, &nwritten); return (nwritten == 2); } ic_private bool tty_start_raw(tty_t* tty) { if (tty->raw_enabled) return true; GetConsoleMode(tty->hcon,&tty->hcon_orig_mode); DWORD mode = ENABLE_QUICK_EDIT_MODE // cut&paste allowed | ENABLE_WINDOW_INPUT // to catch resize events // | ENABLE_VIRTUAL_TERMINAL_INPUT // | ENABLE_PROCESSED_INPUT ; SetConsoleMode(tty->hcon, mode ); tty->raw_enabled = true; return true; } ic_private void tty_end_raw(tty_t* tty) { if (!tty->raw_enabled) return; SetConsoleMode(tty->hcon, tty->hcon_orig_mode ); tty->raw_enabled = false; } static bool tty_init_raw(tty_t* tty) { tty->hcon = GetStdHandle( STD_INPUT_HANDLE ); tty->has_term_resize_event = true; return true; } static void tty_done_raw(tty_t* tty) { ic_unused(tty); } #endif