1 /* ----------------------------------------------------------------------------
2 Copyright (c) 2021, Daan Leijen
3 This is free software; you can redistribute it and/or modify it
4 under the terms of the MIT License. A copy of the license can be
5 found in the "LICENSE" file at the root of this distribution.
6 -----------------------------------------------------------------------------*/
19 #define isatty(fd) _isatty(fd)
20 #define read(fd,s,n) _read(fd,s,n)
21 #define STDIN_FILENO 0
22 #if (_WIN32_WINNT < 0x0600)
23 WINBASEAPI ULONGLONG WINAPI
GetTickCount64(VOID
);
30 #include <sys/ioctl.h>
31 #include <sys/select.h>
32 #if !defined(FIONREAD)
37 #define TTY_PUSH_MAX (32)
40 int fd_in
; // input handle
41 bool raw_enabled
; // is raw mode enabled?
42 bool is_utf8
; // is the input stream in utf-8 mode?
43 bool has_term_resize_event
; // are resize events generated?
44 bool term_resize_event
; // did a term resize happen?
45 alloc_t
* mem
; // memory allocator
46 code_t pushbuf
[TTY_PUSH_MAX
]; // push back buffer for full key codes
48 uint8_t cpushbuf
[TTY_PUSH_MAX
]; // low level push back buffer for bytes
50 long esc_initial_timeout
; // initial ms wait to see if ESC starts an escape sequence
51 long esc_timeout
; // follow up delay for characters in an escape sequence
53 HANDLE hcon
; // console input handle
54 DWORD hcon_orig_mode
; // original console mode
56 struct termios orig_ios
; // original terminal settings
57 struct termios raw_ios
; // raw terminal settings
62 //-------------------------------------------------------------
63 // Forward declarations of platform dependent primitives below
64 //-------------------------------------------------------------
66 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)
68 //-------------------------------------------------------------
70 //-------------------------------------------------------------
72 ic_private
bool code_is_ascii_char(code_t c
, char* chr
) {
73 if (c
>= ' ' && c
<= 0x7F) {
74 if (chr
!= NULL
) { *chr
= (char)c
; }
78 if (chr
!= NULL
) { *chr
= 0; }
83 ic_private
bool code_is_unicode(code_t c
, unicode_t
* uchr
) {
84 if (c
<= KEY_UNICODE_MAX
) {
85 if (uchr
!= NULL
) { *uchr
= c
; }
89 if (uchr
!= NULL
) { *uchr
= 0; }
94 ic_private
bool code_is_virt_key(code_t c
) {
95 return (KEY_NO_MODS(c
) <= 0x20 || KEY_NO_MODS(c
) >= KEY_VIRT
);
99 //-------------------------------------------------------------
101 //-------------------------------------------------------------
102 static code_t
modify_code( code_t code
);
104 static code_t
tty_read_utf8( tty_t
* tty
, uint8_t c0
) {
108 // try to read as many bytes as potentially needed
112 if (tty_readc_noblock(tty
, buf
+count
, tty
->esc_timeout
)) {
115 if (tty_readc_noblock(tty
, buf
+count
, tty
->esc_timeout
)) {
118 if (tty_readc_noblock(tty
, buf
+count
, tty
->esc_timeout
)) {
128 debug_msg("tty: read utf8: count: %" PRIz
"d: %02x,%02x,%02x,%02x", count
, buf
[0], buf
[1], buf
[2], buf
[3]);
130 // decode the utf8 to unicode
132 code_t code
= key_unicode(unicode_from_qutf8(buf
, count
, &read
));
134 // push back unused bytes (in the case of invalid utf8)
135 while (count
> read
) {
137 if (count
>= 0 && count
<= 4) { // to help the static analyzer
138 tty_cpush_char(tty
, buf
[count
]);
144 // pop a code from the pushback buffer.
145 static bool tty_code_pop(tty_t
* tty
, code_t
* code
);
148 // read a single char/key
149 ic_private
bool tty_read_timeout(tty_t
* tty
, long timeout_ms
, code_t
* code
)
151 // is there a push_count back code?
152 if (tty_code_pop(tty
,code
)) {
156 // read a single char/byte from a character stream
158 if (!tty_readc_noblock(tty
, &c
, timeout_ms
)) { return false; }
162 *code
= tty_read_esc(tty
, tty
->esc_initial_timeout
, tty
->esc_timeout
);
164 else if (c
<= 0x7F) {
166 *code
= key_unicode(c
);
168 else if (tty
->is_utf8
) {
170 *code
= tty_read_utf8(tty
,c
);
173 // c >= 0x80 but tty is not utf8; use raw plane so we can translate it back in the end
174 *code
= key_unicode( unicode_from_raw(c
) );
177 *code
= modify_code(*code
);
181 // Transform virtual keys to be more portable across platforms
182 static code_t
modify_code( code_t code
) {
183 code_t key
= KEY_NO_MODS(code
);
184 code_t mods
= KEY_MODS(code
);
185 debug_msg( "tty: readc %s%s%s 0x%03x ('%c')\n",
186 mods
&KEY_MOD_SHIFT
? "shift+" : "", mods
&KEY_MOD_CTRL
? "ctrl+" : "", mods
&KEY_MOD_ALT
? "alt+" : "",
187 key
, (key
>= ' ' && key
<= '~' ? key
: ' '));
189 // treat KEY_RUBOUT (0x7F) as KEY_BACKSP
190 if (key
== KEY_RUBOUT
) {
191 code
= KEY_BACKSP
| mods
;
193 // ctrl+'_' is translated to '\x1F' on Linux, translate it back
194 else if (key
== key_char('\x1F') && (mods
& KEY_MOD_ALT
) == 0) {
196 code
= WITH_CTRL(key_char('_'));
198 // treat ctrl/shift + enter always as KEY_LINEFEED for portability
199 else if (key
== KEY_ENTER
&& (mods
== KEY_MOD_SHIFT
|| mods
== KEY_MOD_ALT
|| mods
== KEY_MOD_CTRL
)) {
202 // treat ctrl+tab always as shift+tab for portability
203 else if (code
== WITH_CTRL(KEY_TAB
)) {
204 code
= KEY_SHIFT_TAB
;
206 // treat ctrl+end/alt+>/alt-down and ctrl+home/alt+</alt-up always as pagedown/pageup for portability
207 else if (code
== WITH_ALT(KEY_DOWN
) || code
== WITH_ALT('>') || code
== WITH_CTRL(KEY_END
)) {
210 else if (code
== WITH_ALT(KEY_UP
) || code
== WITH_ALT('<') || code
== WITH_CTRL(KEY_HOME
)) {
214 // treat C0 codes without KEY_MOD_CTRL
215 if (key
< ' ' && (mods
&KEY_MOD_CTRL
) != 0) {
216 code
&= ~KEY_MOD_CTRL
;
223 // read a single char/key
224 ic_private code_t
tty_read(tty_t
* tty
)
227 if (!tty_read_timeout(tty
, -1, &code
)) { return KEY_NONE
; }
231 //-------------------------------------------------------------
232 // Read back an ANSI query response
233 //-------------------------------------------------------------
235 ic_private
bool tty_read_esc_response(tty_t
* tty
, char esc_start
, bool final_st
, char* buf
, ssize_t buflen
)
240 if (!tty_readc_noblock(tty
, &c
, 2*tty
->esc_initial_timeout
) || c
!= '\x1B') {
241 debug_msg("initial esc response failed: 0x%02x\n", c
);
244 if (!tty_readc_noblock(tty
, &c
, tty
->esc_timeout
) || (c
!= esc_start
)) { return false; }
245 while( len
< buflen
) {
246 if (!tty_readc_noblock(tty
, &c
, tty
->esc_timeout
)) { return false; }
248 // OSC is terminated by BELL, or ESC \ (ST) (and STX)
249 if (c
== '\x07' || c
== '\x02') {
252 else if (c
== '\x1B') {
254 if (!tty_readc_noblock(tty
, &c1
, tty
->esc_timeout
)) { return false; }
255 if (c1
== '\\') { break; }
256 tty_cpush_char(tty
,c1
);
260 if (c
== '\x02') { // STX
263 else if (!((c
>= '0' && c
<= '9') || strchr("<=>?;:",c
) != NULL
)) {
264 buf
[len
++] = (char)c
; // for non-OSC save the terminating character
268 buf
[len
++] = (char)c
;
271 debug_msg("tty: escape query response: %s\n", buf
);
275 //-------------------------------------------------------------
276 // High level code pushback
277 //-------------------------------------------------------------
279 static bool tty_code_pop( tty_t
* tty
, code_t
* code
) {
280 if (tty
->push_count
<= 0) { return false; }
282 *code
= tty
->pushbuf
[tty
->push_count
];
286 ic_private
void tty_code_pushback( tty_t
* tty
, code_t c
) {
287 // note: must be signal safe
288 if (tty
->push_count
>= TTY_PUSH_MAX
) { return; }
289 tty
->pushbuf
[tty
->push_count
] = c
;
294 //-------------------------------------------------------------
295 // low-level character pushback (for escape sequences and windows)
296 //-------------------------------------------------------------
298 ic_private
bool tty_cpop(tty_t
* tty
, uint8_t* c
) {
299 if (tty
->cpush_count
<= 0) { // do not modify c on failure (see `tty_decode_unicode`)
304 *c
= tty
->cpushbuf
[tty
->cpush_count
];
309 static void tty_cpush(tty_t
* tty
, const char* s
) {
310 ssize_t len
= ic_strlen(s
);
311 if (tty
->push_count
+ len
> TTY_PUSH_MAX
) {
312 debug_msg("tty: cpush buffer full! (pushing %s)\n", s
);
316 for (ssize_t i
= 0; i
< len
; i
++) {
317 tty
->cpushbuf
[tty
->cpush_count
+ i
] = (uint8_t)( s
[len
- i
- 1] );
319 tty
->cpush_count
+= len
;
323 // convenience function for small sequences
324 static void tty_cpushf(tty_t
* tty
, const char* fmt
, ...) {
327 char buf
[TTY_PUSH_MAX
+1];
328 vsnprintf(buf
,TTY_PUSH_MAX
,fmt
,args
);
329 buf
[TTY_PUSH_MAX
] = 0;
335 ic_private
void tty_cpush_char(tty_t
* tty
, uint8_t c
) {
339 tty_cpush(tty
, (const char*)buf
);
343 //-------------------------------------------------------------
344 // Push escape codes (used on Windows to insert keys)
345 //-------------------------------------------------------------
347 static unsigned csi_mods(code_t mods
) {
349 if (mods
&KEY_MOD_SHIFT
) { m
+= 1; }
350 if (mods
&KEY_MOD_ALT
) { m
+= 2; }
351 if (mods
&KEY_MOD_CTRL
) { m
+= 4; }
355 // Push ESC [ <vtcode> ; <mods> ~
356 static void tty_cpush_csi_vt( tty_t
* tty
, code_t mods
, uint32_t vtcode
) {
357 tty_cpushf(tty
,"\x1B[%u;%u~", vtcode
, csi_mods(mods
) );
360 // push ESC [ 1 ; <mods> <xcmd>
361 static void tty_cpush_csi_xterm( tty_t
* tty
, code_t mods
, char xcode
) {
362 tty_cpushf(tty
,"\x1B[1;%u%c", csi_mods(mods
), xcode
);
365 // push ESC [ <unicode> ; <mods> u
366 static void tty_cpush_csi_unicode( tty_t
* tty
, code_t mods
, uint32_t unicode
) {
367 if ((unicode
< 0x80 && mods
== 0) ||
368 (mods
== KEY_MOD_CTRL
&& unicode
< ' ' && unicode
!= KEY_TAB
&& unicode
!= KEY_ENTER
369 && unicode
!= KEY_LINEFEED
&& unicode
!= KEY_BACKSP
) ||
370 (mods
== KEY_MOD_SHIFT
&& unicode
>= ' ' && unicode
<= KEY_RUBOUT
)) {
371 tty_cpush_char(tty
,(uint8_t)unicode
);
374 tty_cpushf(tty
,"\x1B[%u;%uu", unicode
, csi_mods(mods
) );
378 //-------------------------------------------------------------
380 //-------------------------------------------------------------
382 static bool tty_init_raw(tty_t
* tty
);
383 static void tty_done_raw(tty_t
* tty
);
385 static bool tty_init_utf8(tty_t
* tty
) {
389 const char* loc
= setlocale(LC_ALL
,"");
390 tty
->is_utf8
= (ic_icontains(loc
,"UTF-8") || ic_icontains(loc
,"utf8") || ic_stricmp(loc
,"C") == 0);
391 debug_msg("tty: utf8: %s (loc=%s)\n", tty
->is_utf8
? "true" : "false", loc
);
396 ic_private tty_t
* tty_new(alloc_t
* mem
, int fd_in
)
398 tty_t
* tty
= mem_zalloc_tp(mem
, tty_t
);
399 if (tty
== NULL
) { return NULL
; }
401 tty
->fd_in
= (fd_in
< 0 ? STDIN_FILENO
: fd_in
);
402 #if defined(__APPLE__)
403 tty
->esc_initial_timeout
= 200; // apple use ESC+<key> for alt-<key>
405 tty
->esc_initial_timeout
= 100;
407 tty
->esc_timeout
= 10;
408 if (!(isatty(tty
->fd_in
) && tty_init_raw(tty
) && tty_init_utf8(tty
))) {
415 ic_private
void tty_free(tty_t
* tty
) {
416 if (tty
== NULL
) { return; }
419 mem_free(tty
->mem
,tty
);
422 ic_private
bool tty_is_utf8(const tty_t
* tty
) {
423 if (tty
== NULL
) { return true; }
424 return (tty
->is_utf8
);
427 ic_private
bool tty_term_resize_event(tty_t
* tty
) {
428 if (tty
== NULL
) { return true; }
429 if (tty
->has_term_resize_event
) {
430 if (!tty
->term_resize_event
) { return false; }
431 tty
->term_resize_event
= false; // reset.
433 return true; // always return true on systems without a resize event (more expensive but still ok)
436 ic_private
void tty_set_esc_delay(tty_t
* tty
, long initial_delay_ms
, long followup_delay_ms
) {
437 tty
->esc_initial_timeout
= (initial_delay_ms
< 0 ? 0 : (initial_delay_ms
> 1000 ? 1000 : initial_delay_ms
));
438 tty
->esc_timeout
= (followup_delay_ms
< 0 ? 0 : (followup_delay_ms
> 1000 ? 1000 : followup_delay_ms
));
441 //-------------------------------------------------------------
443 //-------------------------------------------------------------
446 static bool tty_readc_blocking(tty_t
* tty
, uint8_t* c
) {
447 if (tty_cpop(tty
,c
)) { return true; }
449 ssize_t nread
= read(tty
->fd_in
, (char*)c
, 1);
450 if (nread
< 0 && errno
== EINTR
) {
451 // can happen on SIGWINCH signal for terminal resize
457 // non blocking read -- with a small timeout used for reading escape sequences.
458 ic_private
bool tty_readc_noblock(tty_t
* tty
, uint8_t* c
, long timeout_ms
)
460 // in our pushback buffer?
461 if (tty_cpop(tty
, c
)) { return true; }
464 if (timeout_ms
< 0) {
465 return tty_readc_blocking(tty
,c
);
468 // if supported, peek first if any char is available.
469 #if defined(FIONREAD)
471 if (ioctl(0, FIONREAD
, &navail
) == 0) {
473 return tty_readc_blocking(tty
, c
);
475 else if (timeout_ms
== 0) {
476 return false; // return early if there is no input available (with a zero timeout)
482 // otherwise block for at most timeout milliseconds
484 // we can use select to detect when input becomes available
488 FD_SET(tty
->fd_in
, &readset
);
489 time
.tv_sec
= (timeout_ms
> 0 ? timeout_ms
/ 1000 : 0);
490 time
.tv_usec
= (timeout_ms
> 0 ? 1000*(timeout_ms
% 1000) : 0);
491 if (select(tty
->fd_in
+ 1, &readset
, NULL
, NULL
, &time
) == 1) {
493 return tty_readc_blocking(tty
, c
);
496 // no select, we cannot timeout; use usleeps :-(
497 // todo: this seems very rare nowadays; should be even support this?
499 // peek ahead if possible
500 #if defined(FIONREAD)
502 if (ioctl(0, FIONREAD
, &navail
) == 0 && navail
>= 1) {
503 return tty_readc_blocking(tty
, c
);
505 #elif defined(O_NONBLOCK)
506 // use a temporary non-blocking read mode
507 int fstatus
= fcntl(tty
->fd_in
, F_GETFL
, 0);
509 if (fcntl(tty
->fd_in
, F_SETFL
, (fstatus
| O_NONBLOCK
)) != -1) {
510 char buf
[2] = { 0, 0 };
511 ssize_t nread
= read(tty
->fd_in
, buf
, 1);
512 fcntl(tty
->fd_in
, F_SETFL
, fstatus
);
514 *c
= (uint8_t)buf
[0];
520 #error "define an nonblocking read for this platform"
523 if (timeout_ms
> 0) {
524 usleep(50*1000L); // sleep at most 0.05s at a time
526 if (timeout_ms
< 0) { timeout_ms
= 0; }
529 while (timeout_ms
> 0);
535 ic_private
bool tty_async_stop(const tty_t
* tty
) {
536 // insert ^C in the input stream
538 return (ioctl(tty
->fd_in
, TIOCSTI
, &c
) >= 0);
541 ic_private
bool tty_async_stop(const tty_t
* tty
) {
546 // We install various signal handlers to restore the terminal settings
547 // in case of a terminating signal. This is also used to catch terminal window resizes.
548 // This is not strictly needed so this can be disabled on
549 // (older) platforms that do not support signal handling well.
550 #if defined(SIGWINCH) && defined(SA_RESTART) // ensure basic signal functionality is defined
552 // store the tty in a global so we access it on unexpected termination
553 static tty_t
* sig_tty
; // = NULL
555 // Catch all termination signals (and SIGWINCH)
556 typedef struct signal_handler_s
{
560 struct sigaction previous
;
564 static signal_handler_t sighandlers
[] = {
579 static bool sigaction_is_valid( struct sigaction
* sa
) {
580 return (sa
->sa_sigaction
!= NULL
&& sa
->sa_handler
!= SIG_DFL
&& sa
->sa_handler
!= SIG_IGN
);
583 // Generic signal handler
584 static void sig_handler(int signum
, siginfo_t
* siginfo
, void* uap
) {
585 if (signum
== SIGWINCH
) {
586 if (sig_tty
!= NULL
) {
587 sig_tty
->term_resize_event
= true;
591 // the rest are termination signals; restore the terminal mode. (`tcsetattr` is signal-safe)
592 if (sig_tty
!= NULL
&& sig_tty
->raw_enabled
) {
593 tcsetattr(sig_tty
->fd_in
, TCSAFLUSH
, &sig_tty
->orig_ios
);
594 sig_tty
->raw_enabled
= false;
597 // call previous handler
598 signal_handler_t
* sh
= sighandlers
;
599 while( sh
->signum
!= 0 && sh
->signum
!= signum
) { sh
++; }
600 if (sh
->signum
== signum
) {
601 if (sigaction_is_valid(&sh
->action
.previous
)) {
602 (sh
->action
.previous
.sa_sigaction
)(signum
, siginfo
, uap
);
607 static void signals_install(tty_t
* tty
) {
609 // generic signal handler
610 struct sigaction handler
;
611 memset(&handler
,0,sizeof(handler
));
612 sigemptyset(&handler
.sa_mask
);
613 handler
.sa_sigaction
= &sig_handler
;
614 handler
.sa_flags
= SA_RESTART
;
615 // install for all signals
616 for( signal_handler_t
* sh
= sighandlers
; sh
->signum
!= 0; sh
++ ) {
617 if (sigaction( sh
->signum
, NULL
, &sh
->action
.previous
) == 0) { // get previous
618 if (sh
->action
.previous
.sa_handler
!= SIG_IGN
) { // if not to be ignored
619 if (sigaction( sh
->signum
, &handler
, &sh
->action
.previous
) < 0) { // install our handler
620 sh
->action
.previous
.sa_sigaction
= NULL
; // do not restore on error
622 else if (sh
->signum
== SIGWINCH
) {
623 sig_tty
->has_term_resize_event
= true;
630 static void signals_restore(void) {
631 // restore all signal handlers
632 for( signal_handler_t
* sh
= sighandlers
; sh
->signum
!= 0; sh
++ ) {
633 if (sigaction_is_valid(&sh
->action
.previous
)) {
634 sigaction( sh
->signum
, &sh
->action
.previous
, NULL
);
641 static void signals_install(tty_t
* tty
) {
645 static void signals_restore(void) {
651 ic_private
bool tty_start_raw(tty_t
* tty
) {
652 if (tty
== NULL
) { return false; }
653 if (tty
->raw_enabled
) { return true; }
654 if (tcsetattr(tty
->fd_in
,TCSAFLUSH
,&tty
->raw_ios
) < 0) { return false; }
655 tty
->raw_enabled
= true;
659 ic_private
void tty_end_raw(tty_t
* tty
) {
660 if (tty
== NULL
) { return; }
661 if (!tty
->raw_enabled
) { return; }
662 tty
->cpush_count
= 0;
663 if (tcsetattr(tty
->fd_in
,TCSAFLUSH
,&tty
->orig_ios
) < 0) { return; }
664 tty
->raw_enabled
= false;
667 static bool tty_init_raw(tty_t
* tty
)
669 // Set input to raw mode. See <https://man7.org/linux/man-pages/man3/termios.3.html>.
670 if (tcgetattr(tty
->fd_in
,&tty
->orig_ios
) == -1) { return false; }
671 tty
->raw_ios
= tty
->orig_ios
;
672 // input: no break signal, no \r to \n, no parity check, no 8-bit to 7-bit, no flow control
673 tty
->raw_ios
.c_iflag
&= ~(unsigned long)(BRKINT
| ICRNL
| INPCK
| ISTRIP
| IXON
);
674 // control: allow 8-bit
675 tty
->raw_ios
.c_cflag
|= CS8
;
676 // local: no echo, no line-by-line (canonical), no extended input processing, no signals for ^z,^c
677 tty
->raw_ios
.c_lflag
&= ~(unsigned long)(ECHO
| ICANON
| IEXTEN
| ISIG
);
678 // 1 byte at a time, no delay
679 tty
->raw_ios
.c_cc
[VTIME
] = 0;
680 tty
->raw_ios
.c_cc
[VMIN
] = 1;
682 // store in global so our signal handlers can restore the terminal mode
683 signals_install(tty
);
688 static void tty_done_raw(tty_t
* tty
) {
696 //-------------------------------------------------------------
698 // For best portability we push CSI escape sequences directly
699 // to the character stream (instead of returning key codes).
700 //-------------------------------------------------------------
702 static void tty_waitc_console(tty_t
* tty
, long timeout_ms
);
704 ic_private
bool tty_readc_noblock(tty_t
* tty
, uint8_t* c
, long timeout_ms
) { // don't modify `c` if there is no input
705 // in our pushback buffer?
706 if (tty_cpop(tty
, c
)) { return true; }
707 // any events in the input queue?
708 tty_waitc_console(tty
, timeout_ms
);
709 return tty_cpop(tty
, c
);
712 // Read from the console input events and push escape codes into the tty cbuffer.
713 static void tty_waitc_console(tty_t
* tty
, long timeout_ms
)
715 // wait for a key down event
718 uint32_t surrogate_hi
= 0;
720 // check if there are events if in non-blocking timeout mode
721 if (timeout_ms
>= 0) {
723 if (!GetNumberOfConsoleInputEvents(tty
->hcon
, &count
)) { return; }
725 if (timeout_ms
== 0) {
730 // wait for input events for at most timeout milli seconds
731 ULONGLONG start_ms
= GetTickCount64();
732 DWORD res
= WaitForSingleObject(tty
->hcon
, (DWORD
)timeout_ms
);
734 case WAIT_OBJECT_0
: {
735 // input is available, decrease our timeout
736 ULONGLONG waited_ms
= (GetTickCount64() - start_ms
);
737 timeout_ms
-= (long)waited_ms
;
738 if (timeout_ms
< 0) {
753 // (blocking) Read from the input
754 if (!ReadConsoleInputW(tty
->hcon
, &inp
, 1, &count
)) { return; }
755 if (count
!= 1) { return; }
758 if (inp
.EventType
== WINDOW_BUFFER_SIZE_EVENT
) {
759 tty
->term_resize_event
= true;
763 // wait for key down events
764 if (inp
.EventType
!= KEY_EVENT
) { continue; }
766 // the modifier state
767 DWORD modstate
= inp
.Event
.KeyEvent
.dwControlKeyState
;
769 // we need to handle shift up events separately
770 if (!inp
.Event
.KeyEvent
.bKeyDown
&& inp
.Event
.KeyEvent
.wVirtualKeyCode
== VK_SHIFT
) {
771 modstate
&= (DWORD
)~SHIFT_PRESSED
;
775 DWORD altgr
= LEFT_CTRL_PRESSED
| RIGHT_ALT_PRESSED
;
776 if ((modstate
& altgr
) == altgr
) { modstate
&= ~altgr
; }
781 if ((modstate
& ( RIGHT_CTRL_PRESSED
| LEFT_CTRL_PRESSED
)) != 0) { mods
|= KEY_MOD_CTRL
; }
782 if ((modstate
& ( RIGHT_ALT_PRESSED
| LEFT_ALT_PRESSED
)) != 0) { mods
|= KEY_MOD_ALT
; }
783 if ((modstate
& SHIFT_PRESSED
) != 0) { mods
|= KEY_MOD_SHIFT
; }
786 uint32_t chr
= (uint32_t)inp
.Event
.KeyEvent
.uChar
.UnicodeChar
;
787 WORD virt
= inp
.Event
.KeyEvent
.wVirtualKeyCode
;
788 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
);
790 // only process keydown events (except for Alt-up which is used for unicode pasting...)
791 if (!inp
.Event
.KeyEvent
.bKeyDown
&& virt
!= VK_MENU
) {
797 case VK_UP
: tty_cpush_csi_xterm(tty
, mods
, 'A'); return;
798 case VK_DOWN
: tty_cpush_csi_xterm(tty
, mods
, 'B'); return;
799 case VK_RIGHT
: tty_cpush_csi_xterm(tty
, mods
, 'C'); return;
800 case VK_LEFT
: tty_cpush_csi_xterm(tty
, mods
, 'D'); return;
801 case VK_END
: tty_cpush_csi_xterm(tty
, mods
, 'F'); return;
802 case VK_HOME
: tty_cpush_csi_xterm(tty
, mods
, 'H'); return;
803 case VK_DELETE
: tty_cpush_csi_vt(tty
,mods
,3); return;
804 case VK_PRIOR
: tty_cpush_csi_vt(tty
,mods
,5); return; //page up
805 case VK_NEXT
: tty_cpush_csi_vt(tty
,mods
,6); return; //page down
806 case VK_TAB
: tty_cpush_csi_unicode(tty
,mods
,9); return;
807 case VK_RETURN
: tty_cpush_csi_unicode(tty
,mods
,13); return;
810 if (virt
>= VK_F1
&& virt
<= VK_F5
) {
811 vtcode
= 10 + (virt
- VK_F1
);
813 else if (virt
>= VK_F6
&& virt
<= VK_F10
) {
814 vtcode
= 17 + (virt
- VK_F6
);
816 else if (virt
>= VK_F11
&& virt
<= VK_F12
) {
817 vtcode
= 13 + (virt
- VK_F11
);
820 tty_cpush_csi_vt(tty
,mods
,vtcode
);
825 // ignore other control keys (shift etc).
827 // high surrogate pair
828 else if (chr
>= 0xD800 && chr
<= 0xDBFF) {
829 surrogate_hi
= (chr
- 0xD800);
831 // low surrogate pair
832 else if (chr
>= 0xDC00 && chr
<= 0xDFFF) {
833 chr
= ((surrogate_hi
<< 10) + (chr
- 0xDC00) + 0x10000);
834 tty_cpush_csi_unicode(tty
,mods
,chr
);
840 tty_cpush_csi_unicode(tty
,mods
,chr
);
846 ic_private
bool tty_async_stop(const tty_t
* tty
) {
848 INPUT_RECORD events
[2];
849 memset(events
, 0, 2*sizeof(INPUT_RECORD
));
850 events
[0].EventType
= KEY_EVENT
;
851 events
[0].Event
.KeyEvent
.bKeyDown
= TRUE
;
852 events
[0].Event
.KeyEvent
.uChar
.AsciiChar
= KEY_CTRL_C
;
853 events
[1] = events
[0];
854 events
[1].Event
.KeyEvent
.bKeyDown
= FALSE
;
856 WriteConsoleInput(tty
->hcon
, events
, 2, &nwritten
);
857 return (nwritten
== 2);
860 ic_private
bool tty_start_raw(tty_t
* tty
) {
861 if (tty
->raw_enabled
) { return true; }
862 GetConsoleMode(tty
->hcon
,&tty
->hcon_orig_mode
);
863 DWORD mode
= ENABLE_QUICK_EDIT_MODE
// cut&paste allowed
864 | ENABLE_WINDOW_INPUT
// to catch resize events
865 // | ENABLE_VIRTUAL_TERMINAL_INPUT
866 // | ENABLE_PROCESSED_INPUT
868 SetConsoleMode(tty
->hcon
, mode
);
869 tty
->raw_enabled
= true;
873 ic_private
void tty_end_raw(tty_t
* tty
) {
874 if (!tty
->raw_enabled
) { return; }
875 SetConsoleMode(tty
->hcon
, tty
->hcon_orig_mode
);
876 tty
->raw_enabled
= false;
879 static bool tty_init_raw(tty_t
* tty
) {
880 tty
->hcon
= GetStdHandle( STD_INPUT_HANDLE
);
881 tty
->has_term_resize_event
= true;
885 static void tty_done_raw(tty_t
* tty
) {