luau/extern/isocline/src/tty.c
2022-02-04 08:45:57 -08:00

889 lines
27 KiB
C

/* ----------------------------------------------------------------------------
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 <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>
#include <locale.h>
#include "tty.h"
#if defined(_WIN32)
#include <windows.h>
#include <io.h>
#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 <signal.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#if !defined(FIONREAD)
#include <fcntl.h>
#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+</alt-up always as pagedown/pageup for portability
else if (code == WITH_ALT(KEY_DOWN) || code == WITH_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 [ <vtcode> ; <mods> ~
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 ; <mods> <xcmd>
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 [ <unicode> ; <mods> 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+<key> for alt-<key>
#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 == NULL) return false;
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 == NULL) return;
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 <https://man7.org/linux/man-pages/man3/termios.3.html>.
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