diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
commit | 133a45c109da5310add55824db21af5239951f93 (patch) | |
tree | ba6ac4c0a950a0dda56451944315d66409923918 /contrib/replxx/src/terminal.cxx | |
parent | Initial commit. (diff) | |
download | rspamd-133a45c109da5310add55824db21af5239951f93.tar.xz rspamd-133a45c109da5310add55824db21af5239951f93.zip |
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | contrib/replxx/src/terminal.cxx | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/contrib/replxx/src/terminal.cxx b/contrib/replxx/src/terminal.cxx new file mode 100644 index 0000000..e618219 --- /dev/null +++ b/contrib/replxx/src/terminal.cxx @@ -0,0 +1,742 @@ +#include <memory> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <array> +#include <stdexcept> + +#ifdef _WIN32 + +#include <conio.h> +#include <windows.h> +#include <io.h> +#define isatty _isatty +#define strcasecmp _stricmp +#define strdup _strdup +#define write _write +#define STDIN_FILENO 0 + +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; +#endif + +#include "windows.hxx" + +#else /* _WIN32 */ + +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <fcntl.h> +#include <signal.h> + +#endif /* _WIN32 */ + +#include "terminal.hxx" +#include "conversion.hxx" +#include "escape.hxx" +#include "replxx.hxx" +#include "util.hxx" + +using namespace std; + +namespace replxx { + +namespace tty { + +bool is_a_tty( int fd_ ) { + bool aTTY( isatty( fd_ ) != 0 ); +#ifdef _WIN32 + do { + if ( aTTY ) { + break; + } + HANDLE h( (HANDLE)_get_osfhandle( fd_ ) ); + if ( h == INVALID_HANDLE_VALUE ) { + break; + } + DWORD st( 0 ); + if ( ! GetConsoleMode( h, &st ) ) { + break; + } + aTTY = true; + } while ( false ); +#endif + return ( aTTY ); +} + +bool in( is_a_tty( 0 ) ); +bool out( is_a_tty( 1 ) ); + +} + +#ifndef _WIN32 +Terminal* _terminal_ = nullptr; +static void WindowSizeChanged( int ) { + if ( ! _terminal_ ) { + return; + } + _terminal_->notify_event( Terminal::EVENT_TYPE::RESIZE ); +} +#endif + + +Terminal::Terminal( void ) +#ifdef _WIN32 + : _consoleOut( INVALID_HANDLE_VALUE ) + , _consoleIn( INVALID_HANDLE_VALUE ) + , _origOutMode() + , _origInMode() + , _oldDisplayAttribute() + , _inputCodePage( GetConsoleCP() ) + , _outputCodePage( GetConsoleOutputCP() ) + , _interrupt( INVALID_HANDLE_VALUE ) + , _events() + , _empty() +#else + : _origTermios() + , _interrupt() +#endif + , _rawMode( false ) + , _utf8() { +#ifdef _WIN32 + _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) ); +#else + static_cast<void>( ::pipe( _interrupt ) == 0 ); +#endif +} + +Terminal::~Terminal( void ) { + if ( _rawMode ) { + disable_raw_mode(); + } +#ifdef _WIN32 + CloseHandle( _interrupt ); +#else + static_cast<void>( ::close( _interrupt[0] ) == 0 ); + static_cast<void>( ::close( _interrupt[1] ) == 0 ); +#endif +} + +void Terminal::write32( char32_t const* text32, int len32 ) { + _utf8.assign( text32, len32 ); + write8( _utf8.get(), _utf8.size() ); + return; +} + +void Terminal::write8( char const* data_, int size_ ) { +#ifdef _WIN32 + if ( ! _rawMode ) { + enable_out(); + } + int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) ); + if ( ! _rawMode ) { + disable_out(); + } +#else + int nWritten( write( 1, data_, size_ ) ); +#endif + if ( nWritten != size_ ) { + throw std::runtime_error( "write failed" ); + } + return; +} + +int Terminal::get_screen_columns( void ) { + int cols( 0 ); +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo( _consoleOut, &inf ); + cols = inf.dwSize.X; +#else + struct winsize ws; + cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col; +#endif + // cols is 0 in certain circumstances like inside debugger, which creates + // further issues + return ( cols > 0 ) ? cols : 80; +} + +int Terminal::get_screen_rows( void ) { + int rows; +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo( _consoleOut, &inf ); + rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top; +#else + struct winsize ws; + rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row; +#endif + return (rows > 0) ? rows : 24; +} + +namespace { +inline int notty( void ) { + errno = ENOTTY; + return ( -1 ); +} +} + +void Terminal::enable_out( void ) { +#ifdef _WIN32 + _consoleOut = GetStdHandle( STD_OUTPUT_HANDLE ); + SetConsoleOutputCP( 65001 ); + GetConsoleMode( _consoleOut, &_origOutMode ); + _autoEscape = SetConsoleMode( _consoleOut, _origOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0; +#endif +} + +void Terminal::disable_out( void ) { +#ifdef _WIN32 + SetConsoleMode( _consoleOut, _origOutMode ); + SetConsoleOutputCP( _outputCodePage ); + _consoleOut = INVALID_HANDLE_VALUE; + _autoEscape = false; +#endif +} + +void Terminal::enable_bracketed_paste( void ) { + static char const BRACK_PASTE_INIT[] = "\033[?2004h"; + write8( BRACK_PASTE_INIT, sizeof ( BRACK_PASTE_INIT ) - 1 ); +} + +void Terminal::disable_bracketed_paste( void ) { + static char const BRACK_PASTE_DISABLE[] = "\033[?2004l"; + write8( BRACK_PASTE_DISABLE, sizeof ( BRACK_PASTE_DISABLE ) - 1 ); +} + +int Terminal::enable_raw_mode( void ) { + if ( ! _rawMode ) { +#ifdef _WIN32 + _consoleIn = GetStdHandle( STD_INPUT_HANDLE ); + SetConsoleCP( 65001 ); + GetConsoleMode( _consoleIn, &_origInMode ); + SetConsoleMode( + _consoleIn, + _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) + ); + enable_out(); +#else + struct termios raw; + + if ( ! tty::in ) { + return ( notty() ); + } + if ( tcgetattr( 0, &_origTermios ) == -1 ) { + return ( notty() ); + } + + raw = _origTermios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + // this is wrong, we don't want raw output, it turns newlines into straight + // linefeeds + // raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - echoing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if ( tcsetattr(0, TCSADRAIN, &raw) < 0 ) { + return ( notty() ); + } + _terminal_ = this; +#endif + _rawMode = true; + } + return ( 0 ); +} + +void Terminal::disable_raw_mode(void) { + if ( _rawMode ) { +#ifdef _WIN32 + disable_out(); + SetConsoleMode( _consoleIn, _origInMode ); + SetConsoleCP( _inputCodePage ); + _consoleIn = INVALID_HANDLE_VALUE; +#else + _terminal_ = nullptr; + if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) { + return; + } +#endif + _rawMode = false; + } +} + +#ifndef _WIN32 + +/** + * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode + * (char32_t) character it encodes + * + * @return char32_t Unicode character + */ +char32_t read_unicode_character(void) { + static char8_t utf8String[5]; + static size_t utf8Count = 0; + while (true) { + char8_t c; + + /* Continue reading if interrupted by signal. */ + ssize_t nread; + do { + nread = read( STDIN_FILENO, &c, 1 ); + } while ((nread == -1) && (errno == EINTR)); + + if (nread <= 0) return 0; + if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII + utf8Count = 0; + return c; + } else if (utf8Count < sizeof(utf8String) - 1) { + utf8String[utf8Count++] = c; + utf8String[utf8Count] = 0; + char32_t unicodeChar[2]; + int ucharCount( 0 ); + ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String); + if (res == conversionOK && ucharCount) { + utf8Count = 0; + return unicodeChar[0]; + } + } else { + utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character + } + } +} + +#endif // #ifndef _WIN32 + +void beep() { + fprintf(stderr, "\x7"); // ctrl-G == bell/beep + fflush(stderr); +} + +// replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it +// into an encoded "keystroke". When convenient, extended keys are translated into their +// simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B. +// +// A return value of zero means "no input available", and a return value of -1 +// means "invalid key". +// +char32_t Terminal::read_char( void ) { + char32_t c( 0 ); +#ifdef _WIN32 + INPUT_RECORD rec; + DWORD count; + char32_t modifierKeys = 0; + bool escSeen = false; + int highSurrogate( 0 ); + while (true) { + ReadConsoleInputW( _consoleIn, &rec, 1, &count ); +#if __REPLXX_DEBUG__ // helper for debugging keystrokes, display info in the debug "Output" + // window in the debugger + { + if ( rec.EventType == KEY_EVENT ) { + //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) { + char buf[1024]; + sprintf( + buf, + "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, " + "virtual scancode 0x%04X, key %s%s%s%s%s\n", + rec.Event.KeyEvent.uChar.UnicodeChar, + rec.Event.KeyEvent.wRepeatCount, + rec.Event.KeyEvent.wVirtualKeyCode, + rec.Event.KeyEvent.wVirtualScanCode, + rec.Event.KeyEvent.bKeyDown ? "down" : "up", + (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "", + (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "", + (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "", + (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : "" + ); + OutputDebugStringA( buf ); + //} + } + } +#endif + if ( rec.EventType != KEY_EVENT ) { + continue; + } + // Windows provides for entry of characters that are not on your keyboard by sending the + // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ... + // accept these characters, otherwise only process characters on "key down" + if ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) { + continue; + } + modifierKeys = 0; + // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this + // combination as either CTRL or META we just turn off those two bits, so it is still + // possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or + // left-Alt + DWORD const AltGr( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); + if ( ( rec.Event.KeyEvent.dwControlKeyState & AltGr ) == AltGr ) { + rec.Event.KeyEvent.dwControlKeyState &= ~( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); + } + if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) { + modifierKeys |= Replxx::KEY::BASE_CONTROL; + } + if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) { + modifierKeys |= Replxx::KEY::BASE_META; + } + if ( escSeen ) { + modifierKeys |= Replxx::KEY::BASE_META; + } + int key( rec.Event.KeyEvent.uChar.UnicodeChar ); + if ( key == 0 ) { + switch (rec.Event.KeyEvent.wVirtualKeyCode) { + case VK_LEFT: + return modifierKeys | Replxx::KEY::LEFT; + case VK_RIGHT: + return modifierKeys | Replxx::KEY::RIGHT; + case VK_UP: + return modifierKeys | Replxx::KEY::UP; + case VK_DOWN: + return modifierKeys | Replxx::KEY::DOWN; + case VK_DELETE: + return modifierKeys | Replxx::KEY::DELETE; + case VK_HOME: + return modifierKeys | Replxx::KEY::HOME; + case VK_END: + return modifierKeys | Replxx::KEY::END; + case VK_PRIOR: + return modifierKeys | Replxx::KEY::PAGE_UP; + case VK_NEXT: + return modifierKeys | Replxx::KEY::PAGE_DOWN; + case VK_F1: + return modifierKeys | Replxx::KEY::F1; + case VK_F2: + return modifierKeys | Replxx::KEY::F2; + case VK_F3: + return modifierKeys | Replxx::KEY::F3; + case VK_F4: + return modifierKeys | Replxx::KEY::F4; + case VK_F5: + return modifierKeys | Replxx::KEY::F5; + case VK_F6: + return modifierKeys | Replxx::KEY::F6; + case VK_F7: + return modifierKeys | Replxx::KEY::F7; + case VK_F8: + return modifierKeys | Replxx::KEY::F8; + case VK_F9: + return modifierKeys | Replxx::KEY::F9; + case VK_F10: + return modifierKeys | Replxx::KEY::F10; + case VK_F11: + return modifierKeys | Replxx::KEY::F11; + case VK_F12: + return modifierKeys | Replxx::KEY::F12; + default: + continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them + } + } else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later + escSeen = true; + continue; + } else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) { + highSurrogate = key - 0xD800; + continue; + } else { + // we got a real character, return it + if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) { + key -= 0xDC00; + key |= ( highSurrogate << 10 ); + key += 0x10000; + } + if ( is_control_code( key ) ) { + key = control_to_human( key ); + modifierKeys |= Replxx::KEY::BASE_CONTROL; + } + key |= modifierKeys; + highSurrogate = 0; + c = key; + break; + } + } + +#else + c = read_unicode_character(); + if (c == 0) { + return 0; + } + +// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard +// debugging mode +// where we print out decimal and decoded values for whatever the "terminal" +// program +// gives us on different keystrokes. Hit ctrl-C to exit this mode. +// +#ifdef __REPLXX_DEBUG__ + if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit, + // ctrl-C to get out + printf( + "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit " + "this mode\n"); + while (true) { + unsigned char keys[10]; + int ret = read(0, keys, 10); + + if (ret <= 0) { + printf("\nret: %d\n", ret); + } + for (int i = 0; i < ret; ++i) { + char32_t key = static_cast<char32_t>(keys[i]); + char* friendlyTextPtr; + char friendlyTextBuf[10]; + const char* prefixText = (key < 0x80) ? "" : "0x80+"; + char32_t keyCopy = (key < 0x80) ? key : key - 0x80; + if (keyCopy >= '!' && keyCopy <= '~') { // printable + friendlyTextBuf[0] = '\''; + friendlyTextBuf[1] = keyCopy; + friendlyTextBuf[2] = '\''; + friendlyTextBuf[3] = 0; + friendlyTextPtr = friendlyTextBuf; + } else if (keyCopy == ' ') { + friendlyTextPtr = const_cast<char*>("space"); + } else if (keyCopy == 27) { + friendlyTextPtr = const_cast<char*>("ESC"); + } else if (keyCopy == 0) { + friendlyTextPtr = const_cast<char*>("NUL"); + } else if (keyCopy == 127) { + friendlyTextPtr = const_cast<char*>("DEL"); + } else { + friendlyTextBuf[0] = '^'; + friendlyTextBuf[1] = control_to_human( keyCopy ); + friendlyTextBuf[2] = 0; + friendlyTextPtr = friendlyTextBuf; + } + printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr); + } + printf("\x1b[1G\n"); // go to first column of new line + + // drop out of this loop on ctrl-C + if (keys[0] == ctrlChar('C')) { + printf("Leaving keyboard debugging mode (on ctrl-C)\n"); + fflush(stdout); + return -2; + } + } + } +#endif // __REPLXX_DEBUG__ + + c = EscapeSequenceProcessing::doDispatch(c); + if ( is_control_code( c ) ) { + c = Replxx::KEY::control( control_to_human( c ) ); + } +#endif // #_WIN32 + return ( c ); +} + +Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) { +#ifdef _WIN32 + std::array<HANDLE,2> handles = { _consoleIn, _interrupt }; + while ( true ) { + DWORD event( WaitForMultipleObjects( static_cast<DWORD>( handles.size() ), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) ); + switch ( event ) { + case ( WAIT_OBJECT_0 + 0 ): { + // peek events that will be skipped + INPUT_RECORD rec; + DWORD count; + PeekConsoleInputW( _consoleIn, &rec, 1, &count ); + + if ( + ( rec.EventType != KEY_EVENT ) + || ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) + ) { + // read the event to unsignal the handle + ReadConsoleInputW( _consoleIn, &rec, 1, &count ); + continue; + } else if (rec.EventType == KEY_EVENT) { + int key(rec.Event.KeyEvent.uChar.UnicodeChar); + if (key == 0) { + switch (rec.Event.KeyEvent.wVirtualKeyCode) { + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + break; + default: + ReadConsoleInputW(_consoleIn, &rec, 1, &count); + continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them + } + } + } + + return ( EVENT_TYPE::KEY_PRESS ); + } + case ( WAIT_OBJECT_0 + 1 ): { + ResetEvent( _interrupt ); + if ( _events.empty() ) { + continue; + } + EVENT_TYPE eventType( _events.front() ); + _events.pop_front(); + return ( eventType ); + } + case ( WAIT_TIMEOUT ): { + return ( EVENT_TYPE::TIMEOUT ); + } + } + } +#else + fd_set fdSet; + int nfds( max( _interrupt[0], _interrupt[1] ) + 1 ); + while ( true ) { + FD_ZERO( &fdSet ); + FD_SET( 0, &fdSet ); + FD_SET( _interrupt[0], &fdSet ); + timeval tv{ timeout_ / 1000, static_cast<suseconds_t>( ( timeout_ % 1000 ) * 1000 ) }; + int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) ); + if ( ( err == -1 ) && ( errno == EINTR ) ) { + continue; + } + if ( err == 0 ) { + return ( EVENT_TYPE::TIMEOUT ); + } + if ( FD_ISSET( _interrupt[0], &fdSet ) ) { + char data( 0 ); + static_cast<void>( read( _interrupt[0], &data, 1 ) == 1 ); + if ( data == 'k' ) { + return ( EVENT_TYPE::KEY_PRESS ); + } + if ( data == 'm' ) { + return ( EVENT_TYPE::MESSAGE ); + } + if ( data == 'r' ) { + return ( EVENT_TYPE::RESIZE ); + } + } + if ( FD_ISSET( 0, &fdSet ) ) { + return ( EVENT_TYPE::KEY_PRESS ); + } + } +#endif +} + +void Terminal::notify_event( EVENT_TYPE eventType_ ) { +#ifdef _WIN32 + _events.push_back( eventType_ ); + SetEvent( _interrupt ); +#else + char data( ( eventType_ == EVENT_TYPE::KEY_PRESS ) ? 'k' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) ); + static_cast<void>( write( _interrupt[1], &data, 1 ) == 1 ); +#endif +} + +/** + * Clear the screen ONLY (no redisplay of anything) + */ +void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) { +#ifdef _WIN32 + if ( _autoEscape ) { +#endif + if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) { + char const clearCode[] = "\033c\033[H\033[2J\033[0m"; + static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + } else { + char const clearCode[] = "\033[J"; + static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + } + return; +#ifdef _WIN32 + } + COORD coord = { 0, 0 }; + CONSOLE_SCREEN_BUFFER_INFO inf; + HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) ); + GetConsoleScreenBufferInfo( consoleOut, &inf ); + if ( clearScreen_ == CLEAR_SCREEN::TO_END ) { + coord = inf.dwCursorPosition; + DWORD nWritten( 0 ); + SHORT height( inf.srWindow.Bottom - inf.srWindow.Top ); + DWORD yPos( inf.dwCursorPosition.Y - inf.srWindow.Top ); + DWORD toWrite( ( height + 1 - yPos ) * inf.dwSize.X - inf.dwCursorPosition.X ); +// FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten ); + _empty.resize( toWrite - 1, ' ' ); + WriteConsoleA( consoleOut, _empty.data(), toWrite - 1, &nWritten, nullptr ); + } else { + COORD scrollTarget = { 0, -inf.dwSize.Y }; + CHAR_INFO fill{ TEXT( ' ' ), inf.wAttributes }; + SMALL_RECT scrollRect = { 0, 0, inf.dwSize.X, inf.dwSize.Y }; + ScrollConsoleScreenBuffer( consoleOut, &scrollRect, nullptr, scrollTarget, &fill ); + } + SetConsoleCursorPosition( consoleOut, coord ); +#endif +} + +void Terminal::jump_cursor( int xPos_, int yOffset_ ) { +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo( _consoleOut, &inf ); + inf.dwCursorPosition.X = xPos_; + inf.dwCursorPosition.Y += yOffset_; + SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition ); +#else + char seq[64]; + if ( yOffset_ != 0 ) { // move the cursor up as required + snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' ); + write8( seq, strlen( seq ) ); + } + // position at the end of the prompt, clear to end of screen + snprintf( + seq, sizeof seq, "\033[%dG", + xPos_ + 1 /* 1-based on VT100 */ + ); + write8( seq, strlen( seq ) ); +#endif +} + +#ifdef _WIN32 +void Terminal::set_cursor_visible( bool visible_ ) { + CONSOLE_CURSOR_INFO cursorInfo; + GetConsoleCursorInfo( _consoleOut, &cursorInfo ); + cursorInfo.bVisible = visible_; + SetConsoleCursorInfo( _consoleOut, &cursorInfo ); + return; +} +#else +void Terminal::set_cursor_visible( bool ) {} +#endif + +#ifndef _WIN32 +int Terminal::read_verbatim( char32_t* buffer_, int size_ ) { + int len( 0 ); + buffer_[len ++] = read_unicode_character(); + int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) ); + ::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK ); + while ( len < size_ ) { + char32_t c( read_unicode_character() ); + if ( c == 0 ) { + break; + } + buffer_[len ++] = c; + } + ::fcntl( STDIN_FILENO, F_SETFL, statusFlags ); + return ( len ); +} + +int Terminal::install_window_change_handler( void ) { + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = &WindowSizeChanged; + + if (sigaction(SIGWINCH, &sa, nullptr) == -1) { + return errno; + } + return 0; +} +#endif + +} + |