Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

terminal.cxx 20KB

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