terminal.cxx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. #include <memory>
  2. #include <cerrno>
  3. #include <cstdlib>
  4. #include <cstring>
  5. #include <array>
  6. #include <stdexcept>
  7. #ifdef _WIN32
  8. #include <conio.h>
  9. #include <windows.h>
  10. #include <io.h>
  11. #define isatty _isatty
  12. #define strcasecmp _stricmp
  13. #define strdup _strdup
  14. #define write _write
  15. #define STDIN_FILENO 0
  16. #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
  17. static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
  18. #endif
  19. #include "windows.hxx"
  20. #else /* _WIN32 */
  21. #include <unistd.h>
  22. #include <sys/ioctl.h>
  23. #include <sys/select.h>
  24. #include <fcntl.h>
  25. #include <signal.h>
  26. #endif /* _WIN32 */
  27. #include "terminal.hxx"
  28. #include "conversion.hxx"
  29. #include "escape.hxx"
  30. #include "replxx.hxx"
  31. #include "util.hxx"
  32. using namespace std;
  33. namespace replxx {
  34. namespace tty {
  35. bool is_a_tty( int fd_ ) {
  36. bool aTTY( isatty( fd_ ) != 0 );
  37. #ifdef _WIN32
  38. do {
  39. if ( aTTY ) {
  40. break;
  41. }
  42. HANDLE h( (HANDLE)_get_osfhandle( fd_ ) );
  43. if ( h == INVALID_HANDLE_VALUE ) {
  44. break;
  45. }
  46. DWORD st( 0 );
  47. if ( ! GetConsoleMode( h, &st ) ) {
  48. break;
  49. }
  50. aTTY = true;
  51. } while ( false );
  52. #endif
  53. return ( aTTY );
  54. }
  55. bool in( is_a_tty( 0 ) );
  56. bool out( is_a_tty( 1 ) );
  57. }
  58. #ifndef _WIN32
  59. Terminal* _terminal_ = nullptr;
  60. static void WindowSizeChanged( int ) {
  61. if ( ! _terminal_ ) {
  62. return;
  63. }
  64. _terminal_->notify_event( Terminal::EVENT_TYPE::RESIZE );
  65. }
  66. #endif
  67. Terminal::Terminal( void )
  68. #ifdef _WIN32
  69. : _consoleOut( INVALID_HANDLE_VALUE )
  70. , _consoleIn( INVALID_HANDLE_VALUE )
  71. , _origOutMode()
  72. , _origInMode()
  73. , _oldDisplayAttribute()
  74. , _inputCodePage( GetConsoleCP() )
  75. , _outputCodePage( GetConsoleOutputCP() )
  76. , _interrupt( INVALID_HANDLE_VALUE )
  77. , _events()
  78. , _empty()
  79. #else
  80. : _origTermios()
  81. , _rawModeTermios()
  82. , _interrupt()
  83. #endif
  84. , _rawMode( false )
  85. , _utf8() {
  86. #ifdef _WIN32
  87. _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) );
  88. #else
  89. static_cast<void>( ::pipe( _interrupt ) == 0 );
  90. #endif
  91. }
  92. Terminal::~Terminal( void ) {
  93. if ( _rawMode ) {
  94. disable_raw_mode();
  95. }
  96. #ifdef _WIN32
  97. CloseHandle( _interrupt );
  98. #else
  99. static_cast<void>( ::close( _interrupt[0] ) == 0 );
  100. static_cast<void>( ::close( _interrupt[1] ) == 0 );
  101. #endif
  102. }
  103. void Terminal::write32( char32_t const* text32, int len32 ) {
  104. _utf8.assign( text32, len32 );
  105. write8( _utf8.get(), _utf8.size() );
  106. return;
  107. }
  108. void Terminal::write8( char const* data_, int size_ ) {
  109. #ifdef _WIN32
  110. bool temporarilyEnabled( false );
  111. if ( _consoleOut == INVALID_HANDLE_VALUE ) {
  112. enable_out();
  113. temporarilyEnabled = true;
  114. }
  115. int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) );
  116. if ( temporarilyEnabled ) {
  117. disable_out();
  118. }
  119. #else
  120. int nWritten( write( 1, data_, size_ ) );
  121. #endif
  122. if ( nWritten != size_ ) {
  123. throw std::runtime_error( "write failed" );
  124. }
  125. return;
  126. }
  127. int Terminal::get_screen_columns( void ) {
  128. int cols( 0 );
  129. #ifdef _WIN32
  130. CONSOLE_SCREEN_BUFFER_INFO inf;
  131. GetConsoleScreenBufferInfo( _consoleOut, &inf );
  132. cols = inf.dwSize.X;
  133. #else
  134. struct winsize ws;
  135. cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col;
  136. #endif
  137. // cols is 0 in certain circumstances like inside debugger, which creates
  138. // further issues
  139. return ( cols > 0 ) ? cols : 80;
  140. }
  141. int Terminal::get_screen_rows( void ) {
  142. int rows;
  143. #ifdef _WIN32
  144. CONSOLE_SCREEN_BUFFER_INFO inf;
  145. GetConsoleScreenBufferInfo( _consoleOut, &inf );
  146. rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top;
  147. #else
  148. struct winsize ws;
  149. rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row;
  150. #endif
  151. return (rows > 0) ? rows : 24;
  152. }
  153. namespace {
  154. inline int notty( void ) {
  155. errno = ENOTTY;
  156. return ( -1 );
  157. }
  158. }
  159. void Terminal::enable_out( void ) {
  160. #ifdef _WIN32
  161. SetConsoleOutputCP( 65001 );
  162. _consoleOut = GetStdHandle( STD_OUTPUT_HANDLE );
  163. GetConsoleMode( _consoleOut, &_origOutMode );
  164. _autoEscape = SetConsoleMode( _consoleOut, _origOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0;
  165. #endif
  166. }
  167. void Terminal::disable_out( void ) {
  168. #ifdef _WIN32
  169. SetConsoleMode( _consoleOut, _origOutMode );
  170. SetConsoleOutputCP( _outputCodePage );
  171. _consoleOut = INVALID_HANDLE_VALUE;
  172. _autoEscape = false;
  173. #endif
  174. }
  175. void Terminal::enable_bracketed_paste( void ) {
  176. static char const BRACK_PASTE_INIT[] = "\033[?2004h";
  177. write8( BRACK_PASTE_INIT, sizeof ( BRACK_PASTE_INIT ) - 1 );
  178. }
  179. void Terminal::disable_bracketed_paste( void ) {
  180. static char const BRACK_PASTE_DISABLE[] = "\033[?2004l";
  181. write8( BRACK_PASTE_DISABLE, sizeof ( BRACK_PASTE_DISABLE ) - 1 );
  182. }
  183. int Terminal::enable_raw_mode( void ) {
  184. if ( _rawMode ) {
  185. return ( 0 );
  186. }
  187. #ifdef _WIN32
  188. _consoleIn = GetStdHandle( STD_INPUT_HANDLE );
  189. GetConsoleMode( _consoleIn, &_origInMode );
  190. #else
  191. if ( ! tty::in ) {
  192. return ( notty() );
  193. }
  194. if ( tcgetattr( 0, &_origTermios ) == -1 ) {
  195. return ( notty() );
  196. }
  197. _rawModeTermios = _origTermios; /* modify the original mode */
  198. /* input modes: no break, no CR to NL, no parity check, no strip char,
  199. * no start/stop output control. */
  200. _rawModeTermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  201. /* output modes - disable post processing */
  202. // this is wrong, we don't want _rawModeTermios output, it turns newlines into straight
  203. // linefeeds
  204. // _rawModeTermios.c_oflag &= ~(OPOST);
  205. /* control modes - set 8 bit chars */
  206. _rawModeTermios.c_cflag |= (CS8);
  207. /* local modes - echoing off, canonical off, no extended functions,
  208. * no signal chars (^Z,^C) */
  209. _rawModeTermios.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  210. /* control chars - set return condition: min number of bytes and timer.
  211. * We want read to return every single byte, without timeout. */
  212. _rawModeTermios.c_cc[VMIN] = 1;
  213. _rawModeTermios.c_cc[VTIME] = 0; /* 1 byte, no timer */
  214. #endif
  215. _rawMode = true;
  216. if ( reset_raw_mode() < 0 ) {
  217. _rawMode = false;
  218. return ( notty() );
  219. }
  220. #ifndef _WIN32
  221. _terminal_ = this;
  222. #endif
  223. return ( 0 );
  224. }
  225. int Terminal::reset_raw_mode( void ) {
  226. if ( ! _rawMode ) {
  227. return ( -1 );
  228. }
  229. #ifdef _WIN32
  230. SetConsoleMode(
  231. _consoleIn,
  232. ( _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) ) | ENABLE_QUICK_EDIT_MODE
  233. );
  234. SetConsoleCP( 65001 );
  235. enable_out();
  236. return ( 0 );
  237. #else
  238. /* put terminal in raw mode after flushing */
  239. return ( tcsetattr( 0, TCSADRAIN, &_rawModeTermios ) );
  240. #endif
  241. }
  242. void Terminal::disable_raw_mode(void) {
  243. if ( ! _rawMode ) {
  244. return;
  245. }
  246. #ifdef _WIN32
  247. disable_out();
  248. SetConsoleMode( _consoleIn, _origInMode );
  249. SetConsoleCP( _inputCodePage );
  250. _consoleIn = INVALID_HANDLE_VALUE;
  251. #else
  252. _terminal_ = nullptr;
  253. if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) {
  254. return;
  255. }
  256. #endif
  257. _rawMode = false;
  258. return;
  259. }
  260. #ifndef _WIN32
  261. /**
  262. * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode
  263. * (char32_t) character it encodes
  264. *
  265. * @return char32_t Unicode character
  266. */
  267. char32_t read_unicode_character(void) {
  268. static char8_t utf8String[5];
  269. static size_t utf8Count = 0;
  270. while (true) {
  271. char8_t c;
  272. /* Continue reading if interrupted by signal. */
  273. ssize_t nread;
  274. do {
  275. nread = read( STDIN_FILENO, &c, 1 );
  276. } while ((nread == -1) && (errno == EINTR));
  277. if (nread <= 0) return 0;
  278. if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII
  279. utf8Count = 0;
  280. return c;
  281. } else if (utf8Count < sizeof(utf8String) - 1) {
  282. utf8String[utf8Count++] = c;
  283. utf8String[utf8Count] = 0;
  284. char32_t unicodeChar[2];
  285. int ucharCount( 0 );
  286. ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String);
  287. if (res == conversionOK && ucharCount) {
  288. utf8Count = 0;
  289. return unicodeChar[0];
  290. }
  291. } else {
  292. utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character
  293. }
  294. }
  295. }
  296. #endif // #ifndef _WIN32
  297. void beep() {
  298. fprintf(stderr, "\x7"); // ctrl-G == bell/beep
  299. fflush(stderr);
  300. }
  301. // replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it
  302. // into an encoded "keystroke". When convenient, extended keys are translated into their
  303. // simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B.
  304. //
  305. // A return value of zero means "no input available", and a return value of -1
  306. // means "invalid key".
  307. //
  308. char32_t Terminal::read_char( void ) {
  309. char32_t c( 0 );
  310. #ifdef _WIN32
  311. INPUT_RECORD rec;
  312. DWORD count;
  313. char32_t modifierKeys = 0;
  314. bool escSeen = false;
  315. int highSurrogate( 0 );
  316. while (true) {
  317. ReadConsoleInputW( _consoleIn, &rec, 1, &count );
  318. #if __REPLXX_DEBUG__ // helper for debugging keystrokes, display info in the debug "Output"
  319. // window in the debugger
  320. {
  321. if ( rec.EventType == KEY_EVENT ) {
  322. //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) {
  323. char buf[1024];
  324. sprintf(
  325. buf,
  326. "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, "
  327. "virtual scancode 0x%04X, key %s%s%s%s%s\n",
  328. rec.Event.KeyEvent.uChar.UnicodeChar,
  329. rec.Event.KeyEvent.wRepeatCount,
  330. rec.Event.KeyEvent.wVirtualKeyCode,
  331. rec.Event.KeyEvent.wVirtualScanCode,
  332. rec.Event.KeyEvent.bKeyDown ? "down" : "up",
  333. (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "",
  334. (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "",
  335. (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "",
  336. (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : ""
  337. );
  338. OutputDebugStringA( buf );
  339. //}
  340. }
  341. }
  342. #endif
  343. if ( rec.EventType != KEY_EVENT ) {
  344. continue;
  345. }
  346. // Windows provides for entry of characters that are not on your keyboard by sending the
  347. // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ...
  348. // accept these characters, otherwise only process characters on "key down"
  349. if ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) {
  350. continue;
  351. }
  352. modifierKeys = 0;
  353. // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this
  354. // combination as either CTRL or META we just turn off those two bits, so it is still
  355. // possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or
  356. // left-Alt
  357. DWORD const AltGr( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED );
  358. if ( ( rec.Event.KeyEvent.dwControlKeyState & AltGr ) == AltGr ) {
  359. rec.Event.KeyEvent.dwControlKeyState &= ~( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED );
  360. }
  361. if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) {
  362. modifierKeys |= Replxx::KEY::BASE_CONTROL;
  363. }
  364. if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) {
  365. modifierKeys |= Replxx::KEY::BASE_META;
  366. }
  367. if ( escSeen ) {
  368. modifierKeys |= Replxx::KEY::BASE_META;
  369. }
  370. int key( rec.Event.KeyEvent.uChar.UnicodeChar );
  371. if ( key == 0 ) {
  372. if ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) {
  373. modifierKeys |= Replxx::KEY::BASE_SHIFT;
  374. }
  375. switch ( rec.Event.KeyEvent.wVirtualKeyCode ) {
  376. case VK_LEFT:
  377. return modifierKeys | Replxx::KEY::LEFT;
  378. case VK_RIGHT:
  379. return modifierKeys | Replxx::KEY::RIGHT;
  380. case VK_UP:
  381. return modifierKeys | Replxx::KEY::UP;
  382. case VK_DOWN:
  383. return modifierKeys | Replxx::KEY::DOWN;
  384. case VK_DELETE:
  385. return modifierKeys | Replxx::KEY::DELETE;
  386. case VK_HOME:
  387. return modifierKeys | Replxx::KEY::HOME;
  388. case VK_END:
  389. return modifierKeys | Replxx::KEY::END;
  390. case VK_PRIOR:
  391. return modifierKeys | Replxx::KEY::PAGE_UP;
  392. case VK_NEXT:
  393. return modifierKeys | Replxx::KEY::PAGE_DOWN;
  394. case VK_F1:
  395. return modifierKeys | Replxx::KEY::F1;
  396. case VK_F2:
  397. return modifierKeys | Replxx::KEY::F2;
  398. case VK_F3:
  399. return modifierKeys | Replxx::KEY::F3;
  400. case VK_F4:
  401. return modifierKeys | Replxx::KEY::F4;
  402. case VK_F5:
  403. return modifierKeys | Replxx::KEY::F5;
  404. case VK_F6:
  405. return modifierKeys | Replxx::KEY::F6;
  406. case VK_F7:
  407. return modifierKeys | Replxx::KEY::F7;
  408. case VK_F8:
  409. return modifierKeys | Replxx::KEY::F8;
  410. case VK_F9:
  411. return modifierKeys | Replxx::KEY::F9;
  412. case VK_F10:
  413. return modifierKeys | Replxx::KEY::F10;
  414. case VK_F11:
  415. return modifierKeys | Replxx::KEY::F11;
  416. case VK_F12:
  417. return modifierKeys | Replxx::KEY::F12;
  418. default:
  419. continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
  420. }
  421. } else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later
  422. escSeen = true;
  423. continue;
  424. } else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) {
  425. highSurrogate = key - 0xD800;
  426. continue;
  427. } else {
  428. if ( ( key == 13 ) && ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) ) {
  429. key = 10;
  430. }
  431. // we got a real character, return it
  432. if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) {
  433. key -= 0xDC00;
  434. key |= ( highSurrogate << 10 );
  435. key += 0x10000;
  436. }
  437. if ( is_control_code( key ) ) {
  438. key = control_to_human( key );
  439. modifierKeys |= Replxx::KEY::BASE_CONTROL;
  440. }
  441. key |= modifierKeys;
  442. highSurrogate = 0;
  443. c = key;
  444. break;
  445. }
  446. }
  447. #else
  448. c = read_unicode_character();
  449. if (c == 0) {
  450. return 0;
  451. }
  452. // If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard
  453. // debugging mode
  454. // where we print out decimal and decoded values for whatever the "terminal"
  455. // program
  456. // gives us on different keystrokes. Hit ctrl-C to exit this mode.
  457. //
  458. #ifdef __REPLXX_DEBUG__
  459. if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit,
  460. // ctrl-C to get out
  461. printf(
  462. "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit "
  463. "this mode\n");
  464. while (true) {
  465. unsigned char keys[10];
  466. int ret = read(0, keys, 10);
  467. if (ret <= 0) {
  468. printf("\nret: %d\n", ret);
  469. }
  470. for (int i = 0; i < ret; ++i) {
  471. char32_t key = static_cast<char32_t>(keys[i]);
  472. char* friendlyTextPtr;
  473. char friendlyTextBuf[10];
  474. const char* prefixText = (key < 0x80) ? "" : "0x80+";
  475. char32_t keyCopy = (key < 0x80) ? key : key - 0x80;
  476. if (keyCopy >= '!' && keyCopy <= '~') { // printable
  477. friendlyTextBuf[0] = '\'';
  478. friendlyTextBuf[1] = keyCopy;
  479. friendlyTextBuf[2] = '\'';
  480. friendlyTextBuf[3] = 0;
  481. friendlyTextPtr = friendlyTextBuf;
  482. } else if (keyCopy == ' ') {
  483. friendlyTextPtr = const_cast<char*>("space");
  484. } else if (keyCopy == 27) {
  485. friendlyTextPtr = const_cast<char*>("ESC");
  486. } else if (keyCopy == 0) {
  487. friendlyTextPtr = const_cast<char*>("NUL");
  488. } else if (keyCopy == 127) {
  489. friendlyTextPtr = const_cast<char*>("DEL");
  490. } else {
  491. friendlyTextBuf[0] = '^';
  492. friendlyTextBuf[1] = control_to_human( keyCopy );
  493. friendlyTextBuf[2] = 0;
  494. friendlyTextPtr = friendlyTextBuf;
  495. }
  496. printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr);
  497. }
  498. printf("\x1b[1G\n"); // go to first column of new line
  499. // drop out of this loop on ctrl-C
  500. if (keys[0] == ctrlChar('C')) {
  501. printf("Leaving keyboard debugging mode (on ctrl-C)\n");
  502. fflush(stdout);
  503. return -2;
  504. }
  505. }
  506. }
  507. #endif // __REPLXX_DEBUG__
  508. c = EscapeSequenceProcessing::doDispatch(c);
  509. if ( is_control_code( c ) ) {
  510. c = Replxx::KEY::control( control_to_human( c ) );
  511. }
  512. #endif // #_WIN32
  513. return ( c );
  514. }
  515. Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) {
  516. #ifdef _WIN32
  517. std::array<HANDLE, 2> handles = { _consoleIn, _interrupt };
  518. while ( true ) {
  519. DWORD event( WaitForMultipleObjects( static_cast<DWORD>( handles.size() ), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) );
  520. switch ( event ) {
  521. case ( WAIT_OBJECT_0 + 0 ): {
  522. // peek events that will be skipped
  523. INPUT_RECORD rec;
  524. DWORD count;
  525. PeekConsoleInputW( _consoleIn, &rec, 1, &count );
  526. if (
  527. ( rec.EventType != KEY_EVENT )
  528. || ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) )
  529. ) {
  530. // read the event to unsignal the handle
  531. ReadConsoleInputW( _consoleIn, &rec, 1, &count );
  532. continue;
  533. } else if ( rec.EventType == KEY_EVENT ) {
  534. int key( rec.Event.KeyEvent.uChar.UnicodeChar );
  535. if ( key == 0 ) {
  536. switch ( rec.Event.KeyEvent.wVirtualKeyCode ) {
  537. case VK_LEFT:
  538. case VK_RIGHT:
  539. case VK_UP:
  540. case VK_DOWN:
  541. case VK_DELETE:
  542. case VK_HOME:
  543. case VK_END:
  544. case VK_PRIOR:
  545. case VK_NEXT:
  546. case VK_F1:
  547. case VK_F2:
  548. case VK_F3:
  549. case VK_F4:
  550. case VK_F5:
  551. case VK_F6:
  552. case VK_F7:
  553. case VK_F8:
  554. case VK_F9:
  555. case VK_F10:
  556. case VK_F11:
  557. case VK_F12:
  558. break;
  559. default:
  560. ReadConsoleInputW( _consoleIn, &rec, 1, &count );
  561. continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
  562. }
  563. }
  564. }
  565. return ( EVENT_TYPE::KEY_PRESS );
  566. }
  567. case ( WAIT_OBJECT_0 + 1 ): {
  568. ResetEvent( _interrupt );
  569. if ( _events.empty() ) {
  570. continue;
  571. }
  572. EVENT_TYPE eventType( _events.front() );
  573. _events.pop_front();
  574. return ( eventType );
  575. }
  576. case ( WAIT_TIMEOUT ): {
  577. return ( EVENT_TYPE::TIMEOUT );
  578. }
  579. }
  580. }
  581. #else
  582. fd_set fdSet;
  583. int nfds( max( _interrupt[0], _interrupt[1] ) + 1 );
  584. while ( true ) {
  585. FD_ZERO( &fdSet );
  586. FD_SET( 0, &fdSet );
  587. FD_SET( _interrupt[0], &fdSet );
  588. timeval tv{ timeout_ / 1000, static_cast<suseconds_t>( ( timeout_ % 1000 ) * 1000 ) };
  589. int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) );
  590. if ( ( err == -1 ) && ( errno == EINTR ) ) {
  591. continue;
  592. }
  593. if ( err == 0 ) {
  594. return ( EVENT_TYPE::TIMEOUT );
  595. }
  596. if ( FD_ISSET( _interrupt[0], &fdSet ) ) {
  597. char data( 0 );
  598. static_cast<void>( read( _interrupt[0], &data, 1 ) == 1 );
  599. if ( data == 'k' ) {
  600. return ( EVENT_TYPE::KEY_PRESS );
  601. }
  602. if ( data == 'm' ) {
  603. return ( EVENT_TYPE::MESSAGE );
  604. }
  605. if ( data == 'r' ) {
  606. return ( EVENT_TYPE::RESIZE );
  607. }
  608. }
  609. if ( FD_ISSET( 0, &fdSet ) ) {
  610. return ( EVENT_TYPE::KEY_PRESS );
  611. }
  612. }
  613. #endif
  614. }
  615. void Terminal::notify_event( EVENT_TYPE eventType_ ) {
  616. #ifdef _WIN32
  617. _events.push_back( eventType_ );
  618. SetEvent( _interrupt );
  619. #else
  620. char data( ( eventType_ == EVENT_TYPE::KEY_PRESS ) ? 'k' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) );
  621. static_cast<void>( write( _interrupt[1], &data, 1 ) == 1 );
  622. #endif
  623. }
  624. /**
  625. * Clear the screen ONLY (no redisplay of anything)
  626. */
  627. void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) {
  628. #ifdef _WIN32
  629. if ( _autoEscape ) {
  630. #endif
  631. if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) {
  632. char const clearCode[] = "\033c\033[H\033[2J\033[0m";
  633. static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
  634. } else {
  635. char const clearCode[] = "\033[J";
  636. static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
  637. }
  638. return;
  639. #ifdef _WIN32
  640. }
  641. COORD coord = { 0, 0 };
  642. CONSOLE_SCREEN_BUFFER_INFO inf;
  643. HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) );
  644. GetConsoleScreenBufferInfo( consoleOut, &inf );
  645. if ( clearScreen_ == CLEAR_SCREEN::TO_END ) {
  646. coord = inf.dwCursorPosition;
  647. DWORD nWritten( 0 );
  648. SHORT height( inf.srWindow.Bottom - inf.srWindow.Top );
  649. DWORD yPos( inf.dwCursorPosition.Y - inf.srWindow.Top );
  650. DWORD toWrite( ( height + 1 - yPos ) * inf.dwSize.X - inf.dwCursorPosition.X );
  651. // FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten );
  652. _empty.resize( toWrite - 1, ' ' );
  653. WriteConsoleA( consoleOut, _empty.data(), toWrite - 1, &nWritten, nullptr );
  654. } else {
  655. COORD scrollTarget = { 0, static_cast<SHORT>( -inf.dwSize.Y ) };
  656. CHAR_INFO fill{ TEXT( ' ' ), inf.wAttributes };
  657. SMALL_RECT scrollRect = { 0, 0, inf.dwSize.X, inf.dwSize.Y };
  658. ScrollConsoleScreenBuffer( consoleOut, &scrollRect, nullptr, scrollTarget, &fill );
  659. }
  660. SetConsoleCursorPosition( consoleOut, coord );
  661. #endif
  662. }
  663. void Terminal::jump_cursor( int xPos_, int yOffset_ ) {
  664. #ifdef _WIN32
  665. CONSOLE_SCREEN_BUFFER_INFO inf;
  666. GetConsoleScreenBufferInfo( _consoleOut, &inf );
  667. inf.dwCursorPosition.X = xPos_;
  668. inf.dwCursorPosition.Y += yOffset_;
  669. SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition );
  670. #else
  671. char seq[64];
  672. if ( yOffset_ != 0 ) { // move the cursor up as required
  673. snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' );
  674. write8( seq, strlen( seq ) );
  675. }
  676. // position at the end of the prompt, clear to end of screen
  677. snprintf(
  678. seq, sizeof seq, "\033[%dG",
  679. xPos_ + 1 /* 1-based on VT100 */
  680. );
  681. write8( seq, strlen( seq ) );
  682. #endif
  683. }
  684. #ifdef _WIN32
  685. void Terminal::set_cursor_visible( bool visible_ ) {
  686. CONSOLE_CURSOR_INFO cursorInfo;
  687. GetConsoleCursorInfo( _consoleOut, &cursorInfo );
  688. cursorInfo.bVisible = visible_;
  689. SetConsoleCursorInfo( _consoleOut, &cursorInfo );
  690. return;
  691. }
  692. #else
  693. void Terminal::set_cursor_visible( bool ) {}
  694. #endif
  695. #ifndef _WIN32
  696. int Terminal::read_verbatim( char32_t* buffer_, int size_ ) {
  697. int len( 0 );
  698. buffer_[len ++] = read_unicode_character();
  699. int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) );
  700. ::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK );
  701. while ( len < size_ ) {
  702. char32_t c( read_unicode_character() );
  703. if ( c == 0 ) {
  704. break;
  705. }
  706. buffer_[len ++] = c;
  707. }
  708. ::fcntl( STDIN_FILENO, F_SETFL, statusFlags );
  709. return ( len );
  710. }
  711. int Terminal::install_window_change_handler( void ) {
  712. struct sigaction sa;
  713. sigemptyset(&sa.sa_mask);
  714. sa.sa_flags = 0;
  715. sa.sa_handler = &WindowSizeChanged;
  716. if (sigaction(SIGWINCH, &sa, nullptr) == -1) {
  717. return errno;
  718. }
  719. return 0;
  720. }
  721. #endif
  722. }