diff --git a/.gitignore b/.gitignore index d6b66cbc3..901c3261a 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,5 @@ ecode.dmg *.AppImage /projects/mingw32/ecode/ecode/ /projects/mingw32/ecode/ecode-* -*.dmg \ No newline at end of file +*.dmg +/bin/eterm* diff --git a/premake4.lua b/premake4.lua index 12d7c2df5..ffb8f2431 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1180,6 +1180,12 @@ solution "eepp" build_link_configuration( "ecode", true ) + project "eterm" + set_kind() + language "C++" + files { "src/tools/eterm/**.cpp" } + build_link_configuration( "eterm", true ) + project "eepp-texturepacker" kind "ConsoleApp" language "C++" diff --git a/premake5.lua b/premake5.lua index 6724b4716..a92432129 100644 --- a/premake5.lua +++ b/premake5.lua @@ -923,6 +923,12 @@ workspace "eepp" filter { "system:not windows", "system:not haiku" } links { "pthread" } + project "eterm" + set_kind() + language "C++" + files { "src/tools/eterm/**.cpp" } + build_link_configuration( "eterm", true ) + project "eepp-texturepacker" kind "ConsoleApp" language "C++" diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index f182b3a25..11ab5c845 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -109,7 +109,7 @@ {6d057187-158a-4883-8d5b-d470a6b6b025} 10 0 - 19 + 20 ../../make/linux @@ -1234,6 +1234,30 @@ false %{buildDir}../../../bin/ + + dwarf + + cpu-cycles + + -F + true + 0 + true + true + + 2 + + %{buildDir}../../../bin/eterm-debug + eterm-debug + ProjectExplorer.CustomExecutableRunConfiguration + + true + false + false + false + false + %{buildDir}../../../bin/ + dwarf @@ -1402,7 +1426,7 @@ false %{buildDir}../../../bin/ - 20 + 21 diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 0b08f5176..6bc33b6c4 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1132,6 +1132,36 @@ ../../src/tools/ecode/version.cpp ../../src/tools/ecode/version.hpp ../../src/tools/ecode/widgetcommandexecuter.hpp +../../src/tools/eterm/eterm.cpp +../../src/tools/eterm/eterminaldisplay.cpp +../../src/tools/eterm/eterminaldisplay.hpp +../../src/tools/eterm/system/autohandle.cpp +../../src/tools/eterm/system/autohandle.hpp +../../src/tools/eterm/system/ipipe.hpp +../../src/tools/eterm/system/iprocess.hpp +../../src/tools/eterm/system/iprocessfactory.hpp +../../src/tools/eterm/system/pipe.hpp +../../src/tools/eterm/system/pipe.win32.cpp +../../src/tools/eterm/system/process.cpp +../../src/tools/eterm/system/process.hpp +../../src/tools/eterm/system/process.win32.cpp +../../src/tools/eterm/system/processfactory.cpp +../../src/tools/eterm/system/processfactory.hpp +../../src/tools/eterm/terminal/boxdraw.hpp +../../src/tools/eterm/terminal/boxdrawdata.hpp +../../src/tools/eterm/terminal/config.def.hpp +../../src/tools/eterm/terminal/ipseudoterminal.hpp +../../src/tools/eterm/terminal/nonspacing.hpp +../../src/tools/eterm/terminal/pseudoterminal.cpp +../../src/tools/eterm/terminal/pseudoterminal.hpp +../../src/tools/eterm/terminal/pseudoterminal.win32.cpp +../../src/tools/eterm/terminal/terminaldisplay.cpp +../../src/tools/eterm/terminal/terminaldisplay.hpp +../../src/tools/eterm/terminal/terminalemulator.cpp +../../src/tools/eterm/terminal/terminalemulator.hpp +../../src/tools/eterm/terminal/types.hpp +../../src/tools/eterm/terminal/wide.hpp +../../src/tools/eterm/terminal/windowserrors.hpp ../../src/tools/mapeditor/mapeditor.cpp ../../src/tools/textureatlaseditor/textureatlaseditor.cpp ../../src/tools/texturepacker/texturepacker.cpp diff --git a/projects/linux/ee.includes b/projects/linux/ee.includes index ef2b6688f..c8228f235 100644 --- a/projects/linux/ee.includes +++ b/projects/linux/ee.includes @@ -8,3 +8,4 @@ ../../src/thirdparty/mbedtls/include /usr/include/freetype2/ /usr/lib64/gcc/x86_64-suse-linux/10/include +../../src/tools/eterm diff --git a/src/tools/eterm/eterm.cpp b/src/tools/eterm/eterm.cpp new file mode 100644 index 000000000..6eec5c94a --- /dev/null +++ b/src/tools/eterm/eterm.cpp @@ -0,0 +1,62 @@ +#include "eterminaldisplay.hpp" +#include + +EE::Window::Window* win = NULL; +std::shared_ptr terminal = nullptr; + +void tryInitTerminal( Font* fontDefault ) { + if ( !terminal || terminal->HasTerminated() ) { + auto spacingChar = fontDefault->getGlyph( 'A', 12, false ); + auto charWidth = spacingChar.advance; + auto charHeight = 12; + Sizef contentRegion = win->getSize().asFloat(); + + auto columns = (int)std::floor( std::max( 1.0f, contentRegion.x / charWidth ) ); + auto rows = (int)std::floor( std::max( 1.0f, contentRegion.y / charHeight ) ); + + terminal = + ETerminalDisplay::Create( win, fontDefault, columns, rows, "/bin/sh", {}, "", 0 ); + } +} + +void mainLoop() { + win->clear(); + + win->getInput()->update(); + + if ( win->getInput()->isKeyDown( KEY_ESCAPE ) ) { + win->close(); + } + + if ( terminal ) { + terminal->Update(); + + terminal->Draw( Rectf( { 0, 0 }, win->getSize().asFloat() ) ); + } + + win->display(); +} + +EE_MAIN_FUNC int main( int, char*[] ) { + win = Engine::instance()->createWindow( WindowSettings( 960, 640, "eepp - Empty Window" ), + ContextSettings( true ) ); + + if ( win->isOpen() ) { + win->setClearColor( RGB( 50, 50, 50 ) ); + + FontTrueType* fontMono = FontTrueType::New( "monospace" ); + fontMono->loadFromFile( "assets/fonts/DejaVuSansMono.ttf" ); + + tryInitTerminal( fontMono ); + + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + win->runMainLoop( &mainLoop ); + } + + Engine::destroySingleton(); + + MemoryManager::showResults(); + + return EXIT_SUCCESS; +} diff --git a/src/tools/eterm/eterminaldisplay.cpp b/src/tools/eterm/eterminaldisplay.cpp new file mode 100644 index 000000000..2b19bb7e2 --- /dev/null +++ b/src/tools/eterm/eterminaldisplay.cpp @@ -0,0 +1,546 @@ +#include "eterminaldisplay.hpp" +#include "system/processfactory.hpp" +#include +#include +#include +#include +#include + +// This is the customizable colorscheme +const char* colornames[256] = { "#1e2127", "#e06c75", "#98c379", "#d19a66", "#61afef", "#c678dd", + "#56b6c2", "#abb2bf", "#5c6370", "#e06c75", "#98c379", "#d19a66", + "#61afef", "#c678dd", "#56b6c2", "#ffffff", "#1e2127", "#abb2bf" }; + +// This is the default Xterm palette +static const Color colormapped[256] = { + Color( 0, 0, 0 ), Color( 128, 0, 0 ), Color( 0, 128, 0 ), + Color( 128, 128, 0 ), Color( 0, 0, 128 ), Color( 128, 0, 128 ), + Color( 0, 128, 128 ), Color( 192, 192, 192 ), Color( 128, 128, 128 ), + Color( 255, 0, 0 ), Color( 0, 255, 0 ), Color( 255, 255, 0 ), + Color( 0, 0, 255 ), Color( 255, 0, 255 ), Color( 0, 255, 255 ), + Color( 255, 255, 255 ), Color( 0, 0, 0 ), Color( 0, 0, 95 ), + Color( 0, 0, 135 ), Color( 0, 0, 175 ), Color( 0, 0, 215 ), + Color( 0, 0, 255 ), Color( 0, 95, 0 ), Color( 0, 95, 95 ), + Color( 0, 95, 135 ), Color( 0, 95, 175 ), Color( 0, 95, 215 ), + Color( 0, 95, 255 ), Color( 0, 135, 0 ), Color( 0, 135, 95 ), + Color( 0, 135, 135 ), Color( 0, 135, 175 ), Color( 0, 135, 215 ), + Color( 0, 135, 255 ), Color( 0, 175, 0 ), Color( 0, 175, 95 ), + Color( 0, 175, 135 ), Color( 0, 175, 175 ), Color( 0, 175, 215 ), + Color( 0, 175, 255 ), Color( 0, 215, 0 ), Color( 0, 215, 95 ), + Color( 0, 215, 135 ), Color( 0, 215, 175 ), Color( 0, 215, 215 ), + Color( 0, 215, 255 ), Color( 0, 255, 0 ), Color( 0, 255, 95 ), + Color( 0, 255, 135 ), Color( 0, 255, 175 ), Color( 0, 255, 215 ), + Color( 0, 255, 255 ), Color( 95, 0, 0 ), Color( 95, 0, 95 ), + Color( 95, 0, 135 ), Color( 95, 0, 175 ), Color( 95, 0, 215 ), + Color( 95, 0, 255 ), Color( 95, 95, 0 ), Color( 95, 95, 95 ), + Color( 95, 95, 135 ), Color( 95, 95, 175 ), Color( 95, 95, 215 ), + Color( 95, 95, 255 ), Color( 95, 135, 0 ), Color( 95, 135, 95 ), + Color( 95, 135, 135 ), Color( 95, 135, 175 ), Color( 95, 135, 215 ), + Color( 95, 135, 255 ), Color( 95, 175, 0 ), Color( 95, 175, 95 ), + Color( 95, 175, 135 ), Color( 95, 175, 175 ), Color( 95, 175, 215 ), + Color( 95, 175, 255 ), Color( 95, 215, 0 ), Color( 95, 215, 95 ), + Color( 95, 215, 135 ), Color( 95, 215, 175 ), Color( 95, 215, 215 ), + Color( 95, 215, 255 ), Color( 95, 255, 0 ), Color( 95, 255, 95 ), + Color( 95, 255, 135 ), Color( 95, 255, 175 ), Color( 95, 255, 215 ), + Color( 95, 255, 255 ), Color( 135, 0, 0 ), Color( 135, 0, 95 ), + Color( 135, 0, 135 ), Color( 135, 0, 175 ), Color( 135, 0, 215 ), + Color( 135, 0, 255 ), Color( 135, 95, 0 ), Color( 135, 95, 95 ), + Color( 135, 95, 135 ), Color( 135, 95, 175 ), Color( 135, 95, 215 ), + Color( 135, 95, 255 ), Color( 135, 135, 0 ), Color( 135, 135, 95 ), + Color( 135, 135, 135 ), Color( 135, 135, 175 ), Color( 135, 135, 215 ), + Color( 135, 135, 255 ), Color( 135, 175, 0 ), Color( 135, 175, 95 ), + Color( 135, 175, 135 ), Color( 135, 175, 175 ), Color( 135, 175, 215 ), + Color( 135, 175, 255 ), Color( 135, 215, 0 ), Color( 135, 215, 95 ), + Color( 135, 215, 135 ), Color( 135, 215, 175 ), Color( 135, 215, 215 ), + Color( 135, 215, 255 ), Color( 135, 255, 0 ), Color( 135, 255, 95 ), + Color( 135, 255, 135 ), Color( 135, 255, 175 ), Color( 135, 255, 215 ), + Color( 135, 255, 255 ), Color( 175, 0, 0 ), Color( 175, 0, 95 ), + Color( 175, 0, 135 ), Color( 175, 0, 175 ), Color( 175, 0, 215 ), + Color( 175, 0, 255 ), Color( 175, 95, 0 ), Color( 175, 95, 95 ), + Color( 175, 95, 135 ), Color( 175, 95, 175 ), Color( 175, 95, 215 ), + Color( 175, 95, 255 ), Color( 175, 135, 0 ), Color( 175, 135, 95 ), + Color( 175, 135, 135 ), Color( 175, 135, 175 ), Color( 175, 135, 215 ), + Color( 175, 135, 255 ), Color( 175, 175, 0 ), Color( 175, 175, 95 ), + Color( 175, 175, 135 ), Color( 175, 175, 175 ), Color( 175, 175, 215 ), + Color( 175, 175, 255 ), Color( 175, 215, 0 ), Color( 175, 215, 95 ), + Color( 175, 215, 135 ), Color( 175, 215, 175 ), Color( 175, 215, 215 ), + Color( 175, 215, 255 ), Color( 175, 255, 0 ), Color( 175, 255, 95 ), + Color( 175, 255, 135 ), Color( 175, 255, 175 ), Color( 175, 255, 215 ), + Color( 175, 255, 255 ), Color( 215, 0, 0 ), Color( 215, 0, 95 ), + Color( 215, 0, 135 ), Color( 215, 0, 175 ), Color( 215, 0, 215 ), + Color( 215, 0, 255 ), Color( 215, 95, 0 ), Color( 215, 95, 95 ), + Color( 215, 95, 135 ), Color( 215, 95, 175 ), Color( 215, 95, 215 ), + Color( 215, 95, 255 ), Color( 215, 135, 0 ), Color( 215, 135, 95 ), + Color( 215, 135, 135 ), Color( 215, 135, 175 ), Color( 215, 135, 215 ), + Color( 215, 135, 255 ), Color( 215, 175, 0 ), Color( 215, 175, 95 ), + Color( 215, 175, 135 ), Color( 215, 175, 175 ), Color( 215, 175, 215 ), + Color( 215, 175, 255 ), Color( 215, 215, 0 ), Color( 215, 215, 95 ), + Color( 215, 215, 135 ), Color( 215, 215, 175 ), Color( 215, 215, 215 ), + Color( 215, 215, 255 ), Color( 215, 255, 0 ), Color( 215, 255, 95 ), + Color( 215, 255, 135 ), Color( 215, 255, 175 ), Color( 215, 255, 215 ), + Color( 215, 255, 255 ), Color( 255, 0, 0 ), Color( 255, 0, 95 ), + Color( 255, 0, 135 ), Color( 255, 0, 175 ), Color( 255, 0, 215 ), + Color( 255, 0, 255 ), Color( 255, 95, 0 ), Color( 255, 95, 95 ), + Color( 255, 95, 135 ), Color( 255, 95, 175 ), Color( 255, 95, 215 ), + Color( 255, 95, 255 ), Color( 255, 135, 0 ), Color( 255, 135, 95 ), + Color( 255, 135, 135 ), Color( 255, 135, 175 ), Color( 255, 135, 215 ), + Color( 255, 135, 255 ), Color( 255, 175, 0 ), Color( 255, 175, 95 ), + Color( 255, 175, 135 ), Color( 255, 175, 175 ), Color( 255, 175, 215 ), + Color( 255, 175, 255 ), Color( 255, 215, 0 ), Color( 255, 215, 95 ), + Color( 255, 215, 135 ), Color( 255, 215, 175 ), Color( 255, 215, 215 ), + Color( 255, 215, 255 ), Color( 255, 255, 0 ), Color( 255, 255, 95 ), + Color( 255, 255, 135 ), Color( 255, 255, 175 ), Color( 255, 255, 215 ), + Color( 255, 255, 255 ), Color( 8, 8, 8 ), Color( 18, 18, 18 ), + Color( 28, 28, 28 ), Color( 38, 38, 38 ), Color( 48, 48, 48 ), + Color( 58, 58, 58 ), Color( 68, 68, 68 ), Color( 78, 78, 78 ), + Color( 88, 88, 88 ), Color( 98, 98, 98 ), Color( 108, 108, 108 ), + Color( 118, 118, 118 ), Color( 128, 128, 128 ), Color( 138, 138, 138 ), + Color( 148, 148, 148 ), Color( 158, 158, 158 ), Color( 168, 168, 168 ), + Color( 178, 178, 178 ), Color( 188, 188, 188 ), Color( 198, 198, 198 ), + Color( 208, 208, 208 ), Color( 218, 218, 218 ), Color( 228, 228, 228 ), + Color( 238, 238, 238 ) }; + +std::shared_ptr +ETerminalDisplay::Create( EE::Window::Window* window, Font* font, + std::shared_ptr&& terminalEmulator, + TerminalConfig* config ) { + std::shared_ptr terminal = std::shared_ptr( + new ETerminalDisplay( window, font, terminalEmulator->GetNumColumns(), + terminalEmulator->GetNumRows(), config ) ); + terminal->m_terminal = std::move( terminalEmulator ); + return terminal; +} + +static Hexe::System::IProcessFactory* g_processFactory = new Hexe::System::ProcessFactory(); + +std::shared_ptr +ETerminalDisplay::Create( EE::Window::Window* window, Font* font, int columns, int rows, + const std::string& program, const std::vector& args, + const std::string& workingDir, uint32_t options, + Hexe::System::IProcessFactory* processFactory ) { + using namespace Hexe::System; + + TerminalConfig config{}; + config.options = options; + + if ( processFactory == nullptr ) { + processFactory = g_processFactory; + } + + std::unique_ptr pseudoTerminal = nullptr; + std::vector argsV( args.begin(), args.end() ); + auto process = processFactory->CreateWithPseudoTerminal( program, argsV, workingDir, columns, + rows, pseudoTerminal ); + + if ( !pseudoTerminal ) { + fprintf( stderr, "Failed to create pseudo terminal\n" ); + return nullptr; + } + + if ( !process ) { + fprintf( stderr, "Failed to spawn process\n" ); + return nullptr; + } + + std::shared_ptr terminal = std::shared_ptr( + new ETerminalDisplay( window, font, columns, rows, &config ) ); + terminal->m_terminal = + TerminalEmulator::Create( std::move( pseudoTerminal ), std::move( process ), terminal ); + return terminal; +} + +ETerminalDisplay::ETerminalDisplay( EE::Window::Window* window, Font* font, int columns, int rows, + TerminalConfig* config ) : + TerminalDisplay(), mWindow( window ), mFont( font ), m_columns( columns ), m_rows( rows ) { + if ( config != nullptr ) { + if ( config->options & OPTION_COLOR_EMOJI ) + m_useColorEmoji = true; + if ( config->options & OPTION_NO_BOXDRAWING ) + m_useBoxDrawing = false; + if ( config->options & OPTION_PASTE_CRLF ) + m_pasteNewlineFix = true; + } + + Hexe::Terminal::Glyph defaultGlyph; + defaultGlyph.mode = ATTR_INVISIBLE; + auto defaultColor = std::make_pair( 0U, "" ); + m_cursorg = defaultGlyph; + m_colors.resize( eeARRAY_SIZE( colornames ), defaultColor ); + m_buffer.resize( m_columns * m_rows, defaultGlyph ); + ( (int&)mMode ) |= MODE_FOCUSED; + + if ( config != nullptr ) { + if ( config->options & OPTION_COLOR_EMOJI ) + m_useColorEmoji = true; + if ( config->options & OPTION_NO_BOXDRAWING ) + m_useBoxDrawing = false; + if ( config->options & OPTION_PASTE_CRLF ) + m_pasteNewlineFix = true; + } +} + +void ETerminalDisplay::ResetColors() { + for ( Uint32 i = 0; i < eeARRAY_SIZE( colornames ); i++ ) { + ResetColor( i, colornames[i] ); + } +} + +int ETerminalDisplay::ResetColor( int index, const char* name ) { + if ( !name ) { + if ( index >= 0 && index < (int)m_colors.size() ) { + Color col = 0x000000FF; + + if ( index < 256 ) + col = colormapped[index]; + + m_colors[index].first = col; + m_colors[index].second = ""; + return 0; + } + } + + if ( index >= 0 && index < (int)m_colors.size() ) { + m_colors[index].first = Color::fromString( name ); + m_colors[index].second = name; + } + + return 1; +} + +void ETerminalDisplay::Update() { + if ( m_terminal ) + m_terminal->Update(); +} + +bool ETerminalDisplay::HasTerminated() const { + return m_terminal->HasExited(); +} + +void ETerminalDisplay::SetTitle( const char* title ) {} + +void ETerminalDisplay::SetIconTitle( const char* title ) {} + +void ETerminalDisplay::SetClipboard( const char* text ) { + mClipboard = text; + mWindow->getClipboard()->setText( mClipboard ); +} + +const char* ETerminalDisplay::GetClipboard() const { + mClipboard = mWindow->getClipboard()->getText(); + return mClipboard.c_str(); +} + +bool ETerminalDisplay::DrawBegin( int columns, int rows ) { + if ( columns != m_columns || rows != m_rows ) { + Hexe::Terminal::Glyph defaultGlyph{}; + m_buffer.resize( columns * rows, defaultGlyph ); + m_columns = columns; + m_rows = rows; + } + m_checkDirty = false; + + return ( ( mMode & MODE_VISIBLE ) != 0 ); +} + +void ETerminalDisplay::DrawLine( Line line, int x1, int y, int x2 ) { + m_checkDirty = true; + memcpy( &m_buffer[y * m_columns + x1], line, ( x2 - x1 ) * sizeof( Hexe::Terminal::Glyph ) ); + for ( int i = x1; i < x2; i++ ) { + if ( m_terminal->selected( i, y ) ) { + m_buffer[y * m_columns + i].mode |= ATTR_REVERSE; + } + } +} + +void ETerminalDisplay::DrawCursor( int cx, int cy, Hexe::Terminal::Glyph g, int ox, int oy, + Hexe::Terminal::Glyph og ) { + m_cursorx = cx; + m_cursory = cy; + m_cursorg = g; +} + +void ETerminalDisplay::DrawEnd() { + if ( m_checkDirty ) + m_dirty = true; +} + +void ETerminalDisplay::Draw( const Rectf& contentArea ) { + bool hasFocus = mWindow->hasFocus(); + + bool modeFocus = mMode & MODE_FOCUSED; + if ( hasFocus != modeFocus ) { + if ( hasFocus ) { + mMode |= MODE_FOCUSED | MODE_FOCUS; + } else { + mMode ^= MODE_FOCUS | MODE_FOCUSED; + } + } else { + mMode ^= MODE_FOCUS; + } + + Draw( Vector2i( contentArea.Left, contentArea.Top ), contentArea.getSize().asInt(), hasFocus ); +} + +static inline Color GetCol( unsigned int terminalColor, + const std::vector>& colors ) { + if ( ( terminalColor & ( 1 << 24 ) ) == 0 ) { + return colors[terminalColor & 0xFF].first; + } + return Color( ( terminalColor >> 16 ) & 0xFF, ( terminalColor >> 8 ) & 0xFF, + terminalColor & 0xFF, ( ~( ( terminalColor >> 25 ) & 0xFF ) ) & 0xFF ); +} + +#define MIN( a, b ) ( ( a ) < ( b ) ? ( a ) : ( b ) ) +#define MAX( a, b ) ( ( a ) < ( b ) ? ( b ) : ( a ) ) +#define LEN( a ) ( sizeof( a ) / sizeof( a )[0] ) +#define BETWEEN( x, a, b ) ( ( a ) <= ( x ) && ( x ) <= ( b ) ) +#define IS_SET( flag ) ( ( mMode & ( flag ) ) != 0 ) +#define DIV( n, d ) ( ( ( n ) + ( d ) / 2.0f ) / ( d ) ) +#define DIVI( n, d ) ( ( ( n ) + ( d ) / 2 ) / ( d ) ) +#define IM_COL32_R_SHIFT 24 +#define IM_COL32_G_SHIFT 16 +#define IM_COL32_B_SHIFT 8 +#define IM_COL32_A_SHIFT 0 + +void ETerminalDisplay::Draw( Vector2i pos, const Sizei& clip_rect, bool hasFocus ) { + if ( mClock.getElapsedTime().asSeconds() > 0.7 ) { + mMode ^= MODE_BLINK; + mClock.restart(); + } + + auto fontSize = (Float)mFont->getFontHeight( 12 ); + auto spaceCharAdvanceX = mFont->getGlyph( 'A', 12, false ).advance; + + auto width = m_columns * spaceCharAdvanceX; + auto height = m_rows * fontSize; + + auto clipWidth = clip_rect.getWidth(); + auto clipHeight = clip_rect.getHeight(); + + auto clipColumns = (int)std::floor( std::max( 1.0f, clipWidth / spaceCharAdvanceX ) ); + auto clipRows = (int)std::floor( std::max( 1.0f, clipHeight / fontSize ) ); + + if ( width < clipWidth ) { + pos.x = std::floor( pos.x + ( ( clipWidth - width ) / 2.0f ) ); + } + if ( height < clipHeight ) { + pos.y = std::floor( pos.y + ( ( clipHeight - height ) / 2.0f ) ); + } + + Primitives p; + p.setColor( mEmulator->GetDefaultBackground() ); + p.drawRectangle( Rectf( pos.asFloat(), clip_rect.asFloat() ) ); + + if ( clipColumns != m_terminal->GetNumColumns() || clipRows != m_terminal->GetNumRows() ) { + m_terminal->Resize( clipColumns, clipRows ); + } + + float x = 0.0f; + float y = std::floor( pos.y ); + const float line_height = fontSize; + auto defaultFg = GetCol( mEmulator->GetDefaultForeground(), m_colors ); + auto defaultBg = GetCol( mEmulator->GetDefaultBackground(), m_colors ); + + for ( int j = 0; j < m_rows; j++ ) { + x = std::floor( pos.x ); + + if ( pos.y + line_height * j > clip_rect.getWidth() ) + break; + + for ( int i = 0; i < m_columns; i++ ) { + auto& glyph = m_buffer[j * m_columns + i]; + auto fg = GetCol( glyph.fg, m_colors ); + auto bg = GetCol( glyph.bg, m_colors ); + Color temp{ Color::Transparent }; + + if ( ( glyph.mode & ATTR_BOLD_FAINT ) == ATTR_BOLD && BETWEEN( glyph.fg, 0, 7 ) ) + fg = GetCol( glyph.fg + 8, m_colors ); + + if ( IS_SET( MODE_REVERSE ) ) { + if ( fg == defaultFg ) { + fg = defaultBg; + } else { + Uint32 red = ( fg.getValue() >> IM_COL32_R_SHIFT ) & 0xFF; + Uint32 green = ( fg.getValue() >> IM_COL32_G_SHIFT ) & 0xFF; + Uint32 blue = ( fg.getValue() >> IM_COL32_B_SHIFT ) & 0xFF; + Uint32 alpha = ( fg.getValue() >> IM_COL32_A_SHIFT ) & 0xFF; + fg = Color( ( ~red ) & 0xFF, ( ~green ) & 0xFF, ( ~blue ) & 0xFF, alpha ); + } + + if ( bg == defaultBg ) { + bg = defaultFg; + } else { + Uint32 red = ( bg.getValue() >> IM_COL32_R_SHIFT ) & 0xFF; + Uint32 green = ( bg.getValue() >> IM_COL32_G_SHIFT ) & 0xFF; + Uint32 blue = ( bg.getValue() >> IM_COL32_B_SHIFT ) & 0xFF; + Uint32 alpha = ( bg.getValue() >> IM_COL32_A_SHIFT ) & 0xFF; + bg = Color( ( ~red ) & 0xFF, ( ~green ) & 0xFF, ( ~blue ) & 0xFF, alpha ); + } + } + + if ( ( glyph.mode & ATTR_BOLD_FAINT ) == ATTR_FAINT ) { + Uint32 red = ( fg.getValue() >> IM_COL32_R_SHIFT ) & 0xFF; + Uint32 green = ( fg.getValue() >> IM_COL32_G_SHIFT ) & 0xFF; + Uint32 blue = ( fg.getValue() >> IM_COL32_B_SHIFT ) & 0xFF; + Uint32 alpha = ( fg.getValue() >> IM_COL32_A_SHIFT ) & 0xFF; + red /= 2; + green /= 2; + blue /= 2; + fg = Color( red, green, blue, alpha ); + } + + if ( glyph.mode & ATTR_REVERSE ) { + temp = fg; + fg = bg; + bg = temp; + } + + if ( glyph.mode & ATTR_BLINK && mMode & MODE_BLINK ) + fg = bg; + + if ( glyph.mode & ATTR_INVISIBLE ) + fg = bg; + + bool isWide = glyph.mode & ATTR_WIDE; + + auto advanceX = spaceCharAdvanceX * ( isWide ? 2.0f : 1.0f ); + + if ( glyph.mode & ATTR_WDUMMY || glyph.u == 32 ) { + continue; + } + + Text t( mFont, 12 ); + t.setDisableCacheWidth( true ); + t.setBackgroundColor( bg ); + t.setColor( fg ); + t.setStyle( 0 | ( glyph.mode & ATTR_BOLD ? Text::Style::Bold : 0 ) | + ( glyph.mode & ATTR_ITALIC ? Text::Style::Italic : 0 ) | + ( glyph.mode & ATTR_UNDERLINE ? Text::Style::Underlined : 0 ) ); + String str; + str.push_back( glyph.u ); + t.setString( str ); + t.draw( x, y ); + + /*if ( glyph.mode & ATTR_BOXDRAW && m_useBoxDrawing ) { + auto bd = boxdrawindex( &glyph ); + drawbox( x, y, advanceX, line_height, fg, bg, bd, vtx_write, idx_write, + vtx_current_idx, drawList->_Data->TexUvWhitePixel ); + } + + if ( glyph.mode & ATTR_STRUCK ) { + ImVec2 a( x, y + 2 * ascent / 3 ); + ImVec2 c( x + advanceX, a.y + 1 ); + ImVec2 b( c.x, a.y ), d( a.x, c.y ), uv( drawList->_Data->TexUvWhitePixel ); + idx_write[0] = vtx_current_idx; + idx_write[1] = (ImDrawIdx)( vtx_current_idx + 1 ); + idx_write[2] = (ImDrawIdx)( vtx_current_idx + 2 ); + idx_write[3] = vtx_current_idx; + idx_write[4] = (ImDrawIdx)( vtx_current_idx + 2 ); + idx_write[5] = (ImDrawIdx)( vtx_current_idx + 3 ); + vtx_write[0].pos = a; + vtx_write[0].uv = uv; + vtx_write[0].col = fg; + vtx_write[1].pos = b; + vtx_write[1].uv = uv; + vtx_write[1].col = fg; + vtx_write[2].pos = c; + vtx_write[2].uv = uv; + vtx_write[2].col = fg; + vtx_write[3].pos = d; + vtx_write[3].uv = uv; + vtx_write[3].col = fg; + vtx_write += 4; + vtx_current_idx += 4; + idx_write += 6; + }*/ + + x += advanceX; + } + + y += line_height; + } + + if ( !IS_SET( MODE_HIDE ) ) { + Color drawcol; + + m_cursorg.mode &= + ATTR_BOLD | ATTR_ITALIC | ATTR_UNDERLINE | ATTR_STRUCK | ATTR_WIDE | ATTR_BOXDRAW; + if ( IS_SET( MODE_REVERSE ) ) { + m_cursorg.mode |= ATTR_REVERSE; + m_cursorg.bg = mEmulator->GetDefaultForeground(); + if ( mEmulator->IsSelected( m_cursorx, m_cursory ) ) { + drawcol = GetCol( mEmulator->GetDefaultCursorColor(), m_colors ); + m_cursorg.fg = mEmulator->GetDefaultReverseCursorColor(); + } else { + drawcol = GetCol( mEmulator->GetDefaultReverseCursorColor(), m_colors ); + m_cursorg.fg = mEmulator->GetDefaultCursorColor(); + } + } else { + if ( mEmulator->IsSelected( m_cursorx, m_cursory ) ) { + m_cursorg.fg = mEmulator->GetDefaultForeground(); + m_cursorg.bg = mEmulator->GetDefaultReverseCursorColor(); + } else { + m_cursorg.fg = mEmulator->GetDefaultBackground(); + m_cursorg.bg = mEmulator->GetDefaultCursorColor(); + } + drawcol = GetCol( m_cursorg.bg, m_colors ); + } + + Vector2f a{}, b{}, c{}, d{}; + + auto borderpx = 1.f; + auto cursorthickness = 2.f; + + p.setColor( drawcol ); + /* draw the new one */ + if ( IS_SET( MODE_FOCUSED ) ) { + switch ( mCursorMode ) { + case 7: /* st extension */ + m_cursorg.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + // TODO: Implement cursor glyph rendering + // xdrawglyph(g, cx, cy); + // break; + case 3: /* Blinking Underline */ + case Hexe::Terminal::MAX_CURSOR: + case 4: /* Steady Underline */ + p.drawRectangle( Rectf( + { pos.x + borderpx + m_cursorx * spaceCharAdvanceX, + pos.y + borderpx + ( m_cursory + 1 ) * line_height - cursorthickness }, + { spaceCharAdvanceX, cursorthickness } ) ); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + p.drawRectangle( Rectf( { pos.x + borderpx + m_cursorx * spaceCharAdvanceX, + pos.y + m_cursory * line_height }, + { spaceCharAdvanceX, line_height } ) ); + break; + } + } else { + p.setFillMode( PrimitiveFillMode::DRAW_LINE ); + p.drawRectangle( Rectf( { pos.x + borderpx + m_cursorx * spaceCharAdvanceX, + pos.y + borderpx + m_cursory * line_height }, + { spaceCharAdvanceX, line_height } ) ); + } + } + + /*if ( hasFocus ) { + auto mousePos = mWindow->getInput()->getMousePos(); + auto relPos = Vector2i{ mousePos.x - pos.x, mousePos.y - pos.y }; + int mouseX = 0; + int mouseY = 0; + + if ( mousePos.x <= 0.0f || mousePos.y <= 0.0f ) { + mouseX = 0; + mouseY = 0; + } else if ( relPos.x >= 0.0f && relPos.y >= 0.0f ) { + mouseX = eeclamp( (int)std::floor( relPos.x / spaceCharAdvanceX ), 0, clipColumns ); + mouseY = eeclamp( (int)std::floor( relPos.y / fontSize ), 0, clipRows ); + } + + ProcessInput( mouseX, mouseY ); + }*/ +} diff --git a/src/tools/eterm/eterminaldisplay.hpp b/src/tools/eterm/eterminaldisplay.hpp new file mode 100644 index 000000000..c90670bc3 --- /dev/null +++ b/src/tools/eterm/eterminaldisplay.hpp @@ -0,0 +1,90 @@ +#ifndef ETERMINALDISPLAY_HPP +#define ETERMINALDISPLAY_HPP + +#include "system/iprocessfactory.hpp" +#include "terminal/terminaldisplay.hpp" +#include "terminal/terminalemulator.hpp" +#include +#include +#include +#include +#include +#include + +using namespace Hexe::Terminal; +using namespace EE; +using namespace EE::Window; +using namespace EE::System; + +enum TerminalOptions { + OPTION_NONE = 0, + OPTION_COLOR_EMOJI = 1 << 0, + OPTION_NO_BOXDRAWING = 1 << 1, + OPTION_PASTE_CRLF = 1 << 2 +}; + +struct TerminalConfig { + int options; +}; + +class ETerminalDisplay : public TerminalDisplay { + public: + static std::shared_ptr + Create( EE::Window::Window* window, Font* font, + std::shared_ptr&& terminalEmulator, + TerminalConfig* config = 0 ); + + static std::shared_ptr + Create( EE::Window::Window* window, Font* font, int columns, int rows, + const std::string& program, const std::vector& args, + const std::string& workingDir, uint32_t options = 0, + Hexe::System::IProcessFactory* processFactory = nullptr ); + + virtual void ResetColors(); + virtual int ResetColor( int index, const char* name ); + + virtual void SetTitle( const char* title ); + virtual void SetIconTitle( const char* title ); + + virtual void SetClipboard( const char* text ); + virtual const char* GetClipboard() const; + + virtual bool DrawBegin( int columns, int rows ); + virtual void DrawLine( Line line, int x1, int y, int x2 ); + virtual void DrawCursor( int cx, int cy, Hexe::Terminal::Glyph g, int ox, int oy, + Hexe::Terminal::Glyph og ); + virtual void DrawEnd(); + + virtual void Update(); + + bool HasTerminated() const; + + void Draw( const Rectf& contentArea ); + + void Draw( Vector2i pos, const Sizei& clip_rect, bool hasFocus ); + + protected: + EE::Window::Window* mWindow; + std::vector m_buffer; + std::vector> m_colors; + std::shared_ptr m_terminal; + mutable std::string mClipboard; + + Font* mFont; + int m_columns{ 0 }; + int m_rows{ 0 }; + bool m_dirty; + bool m_checkDirty; + int m_cursorx; + int m_cursory; + Hexe::Terminal::Glyph m_cursorg; + bool m_useBoxDrawing; + bool m_useColorEmoji; + bool m_pasteNewlineFix; + Clock mClock; + + ETerminalDisplay( EE::Window::Window* window, Font* font, int columns, int rows, + TerminalConfig* config ); +}; + +#endif // ETERMINALDISPLAY_HPP diff --git a/src/tools/eterm/system/autohandle.cpp b/src/tools/eterm/system/autohandle.cpp new file mode 100644 index 000000000..825a75bcc --- /dev/null +++ b/src/tools/eterm/system/autohandle.cpp @@ -0,0 +1,97 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#include "autohandle.hpp" +#ifndef WIN32 +#include +#endif + +#ifdef WIN32 +#include +#endif + +Hexe::AutoHandle::AutoHandle() : m_hHandle( invalid_value() ) {} +Hexe::AutoHandle::AutoHandle( AutoHandle&& other ) : m_hHandle( other.m_hHandle ) { + other.m_hHandle = invalid_value(); +} +Hexe::AutoHandle::~AutoHandle() { + Release(); +} + +Hexe::AutoHandle& Hexe::AutoHandle::operator=( AutoHandle&& other ) { + if ( m_hHandle == other.m_hHandle ) + return *this; + Release(); + m_hHandle = other.m_hHandle; + other.m_hHandle = invalid_value(); + return *this; +} + +#ifdef WIN32 +Hexe::AutoHandle::AutoHandle( HANDLE handle ) : m_hHandle( handle ) {} + +void Hexe::AutoHandle::Release() const { + if ( m_hHandle != INVALID_HANDLE_VALUE ) { + CloseHandle( m_hHandle ); + m_hHandle = INVALID_HANDLE_VALUE; + } +} +#else +Hexe::AutoHandle::AutoHandle( int fd ) : m_hHandle( fd ) {} + +void Hexe::AutoHandle::Release() const { + if ( m_hHandle != -1 ) { + close( m_hHandle ); + } +} + +#endif + +Hexe::AutoHandle::operator bool() const noexcept { + return m_hHandle != invalid_value(); +} + +Hexe::AutoHandle::type* Hexe::AutoHandle::Get() { + return &m_hHandle; +} + +const Hexe::AutoHandle::type* Hexe::AutoHandle::Get() const { + return &m_hHandle; +} + +Hexe::AutoHandle::operator Hexe::AutoHandle::type() const noexcept { + return m_hHandle; +} + +#ifdef WIN32 +// On Windows we deal with the Windows API, which means HANDLE is used as the handle to files +// and pipes +constexpr Hexe::AutoHandle::type Hexe::AutoHandle::invalid_value() { + return INVALID_HANDLE_VALUE; +} +#else +// On non-Windows OS'es we deal with file descriptors +constexpr Hexe::AutoHandle::type Hexe::AutoHandle::invalid_value() { + return -1; +} +#endif + diff --git a/src/tools/eterm/system/autohandle.hpp b/src/tools/eterm/system/autohandle.hpp new file mode 100644 index 000000000..ebd2c7518 --- /dev/null +++ b/src/tools/eterm/system/autohandle.hpp @@ -0,0 +1,61 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +namespace Hexe { + +// This class is designed to automatically free a resource handle when destructed +class AutoHandle final { + public: +#ifdef WIN32 + // On Windows we deal with the Windows API, which means HANDLE is used as the handle to files + // and pipes + using type = void*; + static constexpr type invalid_value(); +#else + // On non-Windows OS'es we deal with file descriptors + using type = int; + static constexpr type invalid_value(); +#endif + private: + mutable type m_hHandle; + + public: + explicit AutoHandle( type handle ); + AutoHandle(); + AutoHandle( AutoHandle&& other ); + ~AutoHandle(); + + AutoHandle& operator=( AutoHandle&& other ); + AutoHandle( const AutoHandle& ) = delete; + AutoHandle& operator=( const AutoHandle& ) = delete; + + explicit operator type() const noexcept; + operator bool() const noexcept; + + type* Get(); + const type* Get() const; + + void Release() const; +}; + +} // namespace Hexe diff --git a/src/tools/eterm/system/ipipe.hpp b/src/tools/eterm/system/ipipe.hpp new file mode 100644 index 000000000..0ce345da7 --- /dev/null +++ b/src/tools/eterm/system/ipipe.hpp @@ -0,0 +1,42 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include +#include + +namespace Hexe { namespace System { +class IPipe { + public: + IPipe() = default; + IPipe( IPipe&& ) = delete; + IPipe( const IPipe& ) = delete; + IPipe& operator=( IPipe&& ) = delete; + IPipe& operator=( const IPipe& ) = delete; + virtual ~IPipe() = default; + + virtual bool IsTTY() const = 0; + + virtual int Write( const char* s, size_t n ) = 0; + virtual int Read( char* buf, size_t n, bool block = false ) = 0; +}; +}} // namespace Hexe::System diff --git a/src/tools/eterm/system/iprocess.hpp b/src/tools/eterm/system/iprocess.hpp new file mode 100644 index 000000000..7a62a4622 --- /dev/null +++ b/src/tools/eterm/system/iprocess.hpp @@ -0,0 +1,43 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +namespace Hexe { namespace System { +enum class ProcessStatus { RUNNING = 0, EXITED }; + +class IProcess { + public: + IProcess() = default; + virtual ~IProcess() = default; + IProcess( const IProcess& ) = delete; + IProcess( IProcess&& ) = delete; + IProcess& operator=( const IProcess& ) = delete; + IProcess& operator=( IProcess&& ) = delete; + + virtual void CheckExitStatus() = 0; + virtual bool HasExited() const = 0; + virtual int GetExitCode() const = 0; + + virtual void Terminate() = 0; + virtual void WaitForExit() = 0; +}; +}} // namespace Hexe::System \ No newline at end of file diff --git a/src/tools/eterm/system/iprocessfactory.hpp b/src/tools/eterm/system/iprocessfactory.hpp new file mode 100644 index 000000000..73b5517a5 --- /dev/null +++ b/src/tools/eterm/system/iprocessfactory.hpp @@ -0,0 +1,53 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include "../terminal/ipseudoterminal.hpp" +#include "iprocess.hpp" + +#include +#include +#include + +namespace Hexe { namespace System { +class IProcessFactory { + public: + IProcessFactory() = default; + virtual ~IProcessFactory() = default; + IProcessFactory( const IProcessFactory& ) = delete; + IProcessFactory( IProcessFactory&& ) = delete; + IProcessFactory& operator=( const IProcessFactory& ) = delete; + IProcessFactory& operator=( IProcessFactory&& ) = delete; + + virtual std::unique_ptr CreateWithStdioPipe( const std::string& program, + const std::vector& args, + const std::string& workingDirectory, + std::unique_ptr& outPipe, + bool withStderr = true ) = 0; + + virtual std::unique_ptr CreateWithPseudoTerminal( + const std::string& program, const std::vector& args, + const std::string& workingDirectory, int numColumns, int numRows, + std::unique_ptr& outPseudoTerminal ) = 0; +}; + +}} // namespace Hexe::System diff --git a/src/tools/eterm/system/pipe.hpp b/src/tools/eterm/system/pipe.hpp new file mode 100644 index 000000000..719497b32 --- /dev/null +++ b/src/tools/eterm/system/pipe.hpp @@ -0,0 +1,57 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include "autohandle.hpp" +#include "../system/ipipe.hpp" +#include + +namespace Hexe { namespace System { +class Process; +class Pipe : public IPipe { + private: + friend class Process; +#ifdef WIN32 + AutoHandle m_hInput; + AutoHandle m_hOutput; + + Pipe( AutoHandle&& readHandle, AutoHandle&& writeHandle ); +#else + AutoHandle m_handle; + + Pipe( AutoHandle&& handle ); +#endif + public: + Pipe( Pipe&& ) = delete; + Pipe( const Pipe& ) = delete; + Pipe& operator=( Pipe&& ) = delete; + Pipe& operator=( const Pipe& ) = delete; + virtual ~Pipe() = default; + + virtual bool IsTTY() const override; + + virtual int Write( const char* s, size_t n ) override; + virtual int Read( char* buf, size_t n, bool block = false ) override; + + static bool CreatePipePair( std::unique_ptr& outPipeA, std::unique_ptr& outPipeB ); +}; +}} // namespace Hexe::System diff --git a/src/tools/eterm/system/pipe.win32.cpp b/src/tools/eterm/system/pipe.win32.cpp new file mode 100644 index 000000000..3d62b711e --- /dev/null +++ b/src/tools/eterm/system/pipe.win32.cpp @@ -0,0 +1,51 @@ +#ifdef WIN32 +#include "../system/pipe.h" +#include "windowserrors.h" + +using namespace Hexe::System; + +Pipe::Pipe( AutoHandle&& readHandle, AutoHandle&& writeHandle ) : + m_hInput( std::move( readHandle ) ), m_hOutput( std::move( writeHandle ) ) {} + +bool Pipe::IsTTY() const { + return false; +} + +int Pipe::Write( const char* s, size_t n ) { + constexpr size_t lim = 256; + + DWORD c = (DWORD)n; + DWORD r = 0; + while ( n > 0 ) { + if ( !WriteFile( (HANDLE)m_hOutput, s, (DWORD)( n > lim ? lim : n ), &r, nullptr ) ) { + PrintLastWinApiError(); + return -1; + } + + s += r; + n -= r; + } + return (int)c; +} + +int Pipe::Read( char* buf, size_t n, bool block ) { + DWORD available; + DWORD read; + + if ( !block ) { + if ( !PeekNamedPipe( (HANDLE)m_hInput, nullptr, 0, nullptr, &available, nullptr ) ) { + PrintLastWinApiError(); + return -1; + } + if ( available == 0 ) + return 0; + } + + if ( !ReadFile( (HANDLE)m_hInput, buf, (DWORD)n, &read, nullptr ) ) { + PrintLastWinApiError(); + return -1; + } + return (int)read; +} + +#endif diff --git a/src/tools/eterm/system/process.cpp b/src/tools/eterm/system/process.cpp new file mode 100644 index 000000000..ab47aabb0 --- /dev/null +++ b/src/tools/eterm/system/process.cpp @@ -0,0 +1,190 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifndef WIN32 +#include "process.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT( a, b ) ( a ) = ( a ) ? ( a ) : ( b ) + +#if defined( __linux ) +#include +#elif defined( __OpenBSD__ ) || defined( __NetBSD__ ) || defined( __APPLE__ ) +#include +#elif defined( __FreeBSD__ ) || defined( __DragonFly__ ) +#include +#endif + +using namespace Hexe::System; + +Process::~Process() { + if ( m_pid != -1 ) { + kill( m_pid, SIGHUP ); + } +} + +void Process::CheckExitStatus() { + if ( m_status == ProcessStatus::EXITED ) { + return; + } + int status = 0; + int p = waitpid( m_pid, &status, WNOHANG ); + if ( p < 0 ) { + perror( "Process::CheckExitStatus(waitpid)" ); + } + if ( p == 0 ) { + return; + } + m_status = ProcessStatus::EXITED; + m_exitCode = WEXITSTATUS( status ); +} + +void Process::Terminate() { + if ( m_status == ProcessStatus::EXITED ) { + return; + } + if ( m_pid != -1 ) { + kill( m_pid, SIGHUP ); + m_exitCode = 1; + m_status = ProcessStatus::EXITED; + } +} + +void Process::WaitForExit() { + if ( m_status == ProcessStatus::EXITED ) { + return; + } + int status = 0; + int p = waitpid( m_pid, &status, 0 ); + if ( p < 0 ) { + perror( "Process::WaitForExit(waitpid)" ); + return; + } + m_status = ProcessStatus::EXITED; + m_exitCode = WEXITSTATUS( status ); +} + +bool Process::HasExited() const { + return m_status == Hexe::System::ProcessStatus::EXITED; +} + +int Process::GetExitCode() const { + return m_status == ProcessStatus::EXITED ? m_exitCode : 255; +} + +Process::Process( int pid ) : m_pid( pid ) {} + +static void execshell( const char* cmd, const char* const* args ) { + const struct passwd* pw; + + errno = 0; + if ( ( pw = getpwuid( getuid() ) ) == NULL ) { + if ( errno ) { + fprintf( stderr, "getpwuid: %s\n", strerror( errno ) ); + _exit( 1 ); + } else { + fprintf( stderr, "who are you?\n" ); + _exit( 1 ); + } + } + + unsetenv( "COLUMNS" ); + unsetenv( "LINES" ); + unsetenv( "TERMCAP" ); + setenv( "LOGNAME", pw->pw_name, 1 ); + setenv( "USER", pw->pw_name, 1 ); + // setenv( "SHELL", sh, 1 ); + setenv( "HOME", pw->pw_dir, 1 ); + setenv( "TERM", "st-256color", 1 ); + + signal( SIGCHLD, SIG_DFL ); + signal( SIGHUP, SIG_DFL ); + signal( SIGINT, SIG_DFL ); + signal( SIGQUIT, SIG_DFL ); + signal( SIGTERM, SIG_DFL ); + signal( SIGALRM, SIG_DFL ); + + execvp( cmd, (char* const*)args ); + _exit( 1 ); +} + +std::unique_ptr Process::CreateWithPipe( const std::string& /*program*/, + const std::vector& /*args*/, + const std::string& /*workingDirectory*/, + std::unique_ptr& outPipe, + bool /*withStderr*/ ) { + outPipe = nullptr; + return nullptr; +} + +std::unique_ptr +Process::CreateWithPseudoTerminal( const std::string& program, const std::vector& args, + const std::string& /*workingDirectory*/, + Terminal::PseudoTerminal& pseudoTerminal ) { + int pid = fork(); + if ( pid == -1 ) { + fprintf( stderr, "Failed to fork process\n" ); + return nullptr; + } else if ( pid == 0 ) { + setsid(); + dup2( (int)pseudoTerminal.m_slave, 0 ); + dup2( (int)pseudoTerminal.m_slave, 1 ); + dup2( (int)pseudoTerminal.m_slave, 2 ); + + if ( ioctl( (int)pseudoTerminal.m_slave, TIOCSCTTY, NULL ) < 0 ) { + fprintf( stderr, "ioctl TIOCSCTTY failed: %s", strerror( errno ) ); + exit( 1 ); + } + + // Make sure all non stdio file descriptors are closed before exec + int fdlimit = (int)sysconf( _SC_OPEN_MAX ); + for ( int i = 3; i < fdlimit; i++ ) + close( i ); + +#ifdef __OpenBSD__ + if ( pledge( "stdio getpw proc exec", NULL ) == -1 ) { + fprintf( stderr, "pledge\n" ); + exit( 1 ); + } +#endif + std::vector argsV; + argsV.push_back( program.c_str() ); + for ( auto& a : args ) { + argsV.push_back( a.c_str() ); + } + argsV.push_back( nullptr ); + execshell( program.c_str(), argsV.data() ); + } else { + pseudoTerminal.m_slave.Release(); + return std::unique_ptr( new Process( pid ) ); + } + return nullptr; +} + +#endif diff --git a/src/tools/eterm/system/process.hpp b/src/tools/eterm/system/process.hpp new file mode 100644 index 000000000..5442dd0ce --- /dev/null +++ b/src/tools/eterm/system/process.hpp @@ -0,0 +1,79 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include +#include +#include + +#include "../terminal/pseudoterminal.hpp" +#include "iprocess.hpp" + +#ifndef WIN32 +#include +#include +#endif + +namespace Hexe { namespace System { +class Process final : public IProcess { + private: + ProcessStatus m_status; + bool m_leaveRunning; + int m_exitCode; +#ifdef WIN32 + AutoHandle m_hProcess; + LPPROC_THREAD_ATTRIBUTE_LIST m_lpAttributeList; + + Process( AutoHandle&& processHandle, LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList ); +#else + int m_pid; + + Process( int pid ); +#endif + + public: + ~Process(); + Process( const Process& ) = delete; + Process( Process&& ) = delete; + Process& operator=( const Process& ) = delete; + Process& operator=( Process&& ) = delete; + + virtual void CheckExitStatus() override; + virtual bool HasExited() const override; + virtual int GetExitCode() const override; + + virtual void Terminate() override; + virtual void WaitForExit() override; + + static std::unique_ptr CreateWithPipe( const std::string& program, + const std::vector& args, + const std::string& workingDirectory, + std::unique_ptr& outPipe, + bool withStderr = true ); + + static std::unique_ptr + CreateWithPseudoTerminal( const std::string& program, const std::vector& args, + const std::string& workingDirectory, + Terminal::PseudoTerminal& pseudoTerminal ); +}; + +}} // namespace Hexe::System diff --git a/src/tools/eterm/system/process.win32.cpp b/src/tools/eterm/system/process.win32.cpp new file mode 100644 index 000000000..109cb082f --- /dev/null +++ b/src/tools/eterm/system/process.win32.cpp @@ -0,0 +1,266 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifdef WIN32 +#include "../system/process.h" +#include "../system/pipe.h" +#include "windowserrors.h" +#include +#include +#include + +using namespace Hexe::System; +using namespace Hexe; + +static std::wstring stringToWideString( const std::string& str ) { + if ( str.empty() ) + return std::wstring(); + int size_needed = MultiByteToWideChar( CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0 ); + std::wstring wstrTo( size_needed, 0 ); + MultiByteToWideChar( CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed ); + return wstrTo; +} + +#define HANDLE_WIN_ERR( err ) HRESULT_FROM_WIN32( err ), PrintWinApiError( err ) + +static HRESULT InitializeStartupInfoAttachedToPseudoConsole( STARTUPINFOEXW* pStartupInfo, + HPCON hPC ) { + HRESULT hr{ E_UNEXPECTED }; + + if ( pStartupInfo ) { + size_t attrListSize{}; + + pStartupInfo->StartupInfo.cb = sizeof( STARTUPINFOEXW ); + pStartupInfo->StartupInfo.dwFlags = STARTF_USESTDHANDLES; + + // Get the size of the thread attribute list. + InitializeProcThreadAttributeList( NULL, 1, 0, &attrListSize ); + + // Allocate a thread attribute list of the correct size + pStartupInfo->lpAttributeList = reinterpret_cast( + HeapAlloc( GetProcessHeap(), 0, attrListSize ) ); + + // Initialize thread attribute list + if ( pStartupInfo->lpAttributeList && + InitializeProcThreadAttributeList( pStartupInfo->lpAttributeList, 1, 0, + &attrListSize ) ) { + // Set Pseudo Console attribute + hr = UpdateProcThreadAttribute( pStartupInfo->lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC, sizeof( hPC ), + NULL, NULL ) + ? S_OK + : HANDLE_WIN_ERR( GetLastError() ); + } else { + hr = HANDLE_WIN_ERR( GetLastError() ); + } + } + return hr; +} + +Process::Process( AutoHandle&& hProcess, LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList ) : + m_status( ProcessStatus::RUNNING ), + m_leaveRunning( false ), + m_exitCode( 1 ), + m_hProcess( std::move( hProcess ) ), + m_lpAttributeList( lpAttributeList ) {} + +Process::~Process() { + if ( m_lpAttributeList ) { + DeleteProcThreadAttributeList( m_lpAttributeList ); + HeapFree( GetProcessHeap(), 0, m_lpAttributeList ); + } + if ( !m_leaveRunning ) { + Terminate(); + } +} + +void Process::CheckExitStatus() { + if ( m_status == ProcessStatus::RUNNING ) { + DWORD exitCode; + if ( GetExitCodeProcess( (HANDLE)m_hProcess, &exitCode ) && exitCode != STILL_ACTIVE ) { + m_status = ProcessStatus::EXITED; + m_exitCode = (int)exitCode; + } + } +} + +bool Process::HasExited() const { + return m_status == ProcessStatus::EXITED; +} + +int Process::GetExitCode() const { + return m_status == ProcessStatus::EXITED ? m_exitCode : STILL_ACTIVE; +} + +void Process::Terminate() { + if ( m_status == ProcessStatus::RUNNING ) { + TerminateProcess( (HANDLE)m_hProcess, EXIT_FAILURE ); + m_exitCode = EXIT_FAILURE; + m_status = ProcessStatus::EXITED; + m_hProcess.Release(); + } +} + +void Process::WaitForExit() { + CheckExitStatus(); + + if ( m_status == ProcessStatus::RUNNING ) { + WaitForSingleObject( (HANDLE)m_hProcess, INFINITE ); + } +} + +std::unique_ptr Process::CreateWithPipe( const std::string& program, + const std::vector& args, + const std::string& workingDirectory, + std::unique_ptr& outPipe, + bool withStderr ) { + outPipe = nullptr; + + SECURITY_ATTRIBUTES saAttr; + ZeroMemory( &saAttr, sizeof( saAttr ) ); + saAttr.nLength = sizeof( saAttr ); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + AutoHandle g_hChildStd_IN_Rd{}; + AutoHandle g_hChildStd_IN_Wr{}; + AutoHandle g_hChildStd_OUT_Rd{}; + AutoHandle g_hChildStd_OUT_Wr{}; + + if ( !CreatePipe( g_hChildStd_OUT_Rd.Get(), g_hChildStd_OUT_Wr.Get(), &saAttr, 0 ) ) { + fprintf( stderr, "Failed to create pipe (Stdout)\n" ); + return nullptr; + } + + if ( !SetHandleInformation( (HANDLE)g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0 ) ) { + fprintf( stderr, "Failed to set handle information (Stdout)\n" ); + return nullptr; + } + + if ( !CreatePipe( g_hChildStd_IN_Rd.Get(), g_hChildStd_IN_Wr.Get(), &saAttr, 0 ) ) { + fprintf( stderr, "Failed to create pipe (Stdin)\n" ); + return nullptr; + } + + if ( !SetHandleInformation( (HANDLE)g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0 ) ) { + fprintf( stderr, "Failed to set handle information (Stdin)\n" ); + return nullptr; + } + + PROCESS_INFORMATION piProcInfo; + STARTUPINFOW siStartInfo; + + ZeroMemory( &piProcInfo, sizeof( PROCESS_INFORMATION ) ); + ZeroMemory( &siStartInfo, sizeof( STARTUPINFOW ) ); + + siStartInfo.cb = sizeof( STARTUPINFOW ); + siStartInfo.hStdError = withStderr ? (HANDLE)g_hChildStd_OUT_Wr : INVALID_HANDLE_VALUE; + siStartInfo.hStdOutput = (HANDLE)g_hChildStd_OUT_Wr; + siStartInfo.hStdInput = (HANDLE)g_hChildStd_IN_Rd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + std::ostringstream oss; + oss << std::quoted( program ); + for ( auto& arg : args ) { + oss << ' ' << arg; + } + std::wstring commandLine = stringToWideString( oss.str() ); + // TODO: If workingDirectory is relative, make it absolute (relative to the + // current process working directory) + std::wstring workingDir = stringToWideString( workingDirectory ); + + if ( CreateProcessW( NULL, (LPWSTR)commandLine.c_str(), NULL, NULL, TRUE, 0, NULL, + workingDir.empty() ? NULL : workingDir.c_str(), &siStartInfo, + &piProcInfo ) ) { + std::unique_ptr pipe = std::unique_ptr( + new Pipe( std::move( g_hChildStd_OUT_Rd ), std::move( g_hChildStd_IN_Wr ) ) ); + outPipe = std::move( pipe ); + + AutoHandle hProcess{ piProcInfo.hProcess }; + AutoHandle hThread{ piProcInfo.hThread }; + + return std::unique_ptr( new Process( std::move( hProcess ), NULL ) ); + } + + PrintWinApiError( GetLastError() ); + + return nullptr; +} + +std::unique_ptr +Process::CreateWithPseudoTerminal( const std::string& program, const std::vector& args, + const std::string& workingDirectory, + Terminal::PseudoTerminal& pseudoTerminal ) { + HRESULT hr{ E_UNEXPECTED }; + + AutoHandle hProcess{}; + AutoHandle hThread{}; + + STARTUPINFOEXW startupInfo{}; + PROCESS_INFORMATION piClient{}; + + std::ostringstream oss; + oss << std::quoted( program ); + for ( auto& arg : args ) { + oss << ' ' << arg; + } + std::wstring commandLine = stringToWideString( oss.str() ); + // TODO: If workingDirectory is relative, make it absolute (relative to the + // current process working directory) + std::wstring workingDir = stringToWideString( workingDirectory ); + + if ( ( hr = InitializeStartupInfoAttachedToPseudoConsole( &startupInfo, + pseudoTerminal.m_phPC ) ) != S_OK ) { + PrintErrorResult( hr ); + goto fail; + } + + hr = CreateProcessW( NULL, // No module name - use Command Line + (wchar_t*)commandLine.c_str(), // Command Line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Inherit handles + EXTENDED_STARTUPINFO_PRESENT, // Creation flags + NULL, // Use parent's environment block + workingDir.empty() ? NULL + : workingDir.c_str(), // Use parent's starting directory + &startupInfo.StartupInfo, // Pointer to STARTUPINFO + &piClient ) // Pointer to PROCESS_INFORMATION + ? S_OK + : HANDLE_WIN_ERR( GetLastError() ); + + if ( hr != S_OK ) { + goto fail; + } + + hProcess = AutoHandle( piClient.hProcess ); + hThread = AutoHandle( piClient.hThread ); + + pseudoTerminal.m_attached = true; + + return std::unique_ptr( + new Process( std::move( hProcess ), startupInfo.lpAttributeList ) ); +fail: + return std::unique_ptr(); +} + +#endif diff --git a/src/tools/eterm/system/processfactory.cpp b/src/tools/eterm/system/processfactory.cpp new file mode 100644 index 000000000..2b9b36047 --- /dev/null +++ b/src/tools/eterm/system/processfactory.cpp @@ -0,0 +1,36 @@ +#include "../system/processfactory.hpp" +#include "../system/process.hpp" +#include "../terminal/pseudoterminal.hpp" + +using namespace Hexe::System; + +std::unique_ptr ProcessFactory::CreateWithStdioPipe( const std::string& program, + const std::vector& args, + const std::string& workingDirectory, + std::unique_ptr& outPipe, + bool withStderr ) { + return Process::CreateWithPipe( program, args, workingDirectory, outPipe, withStderr ); +} + +std::unique_ptr ProcessFactory::CreateWithPseudoTerminal( + const std::string& program, const std::vector& args, + const std::string& workingDirectory, int numColumns, int numRows, + std::unique_ptr& outPseudoTerminal ) { + outPseudoTerminal = nullptr; + + auto pseudoTerminal = Terminal::PseudoTerminal::Create( numColumns, numRows ); + if ( !pseudoTerminal ) { + fprintf( stderr, "Failed to create pseudo terminal\n" ); + return nullptr; + } + + auto process = System::Process::CreateWithPseudoTerminal( program, args, workingDirectory, + *pseudoTerminal ); + if ( !process ) { + fprintf( stderr, "Failed to spawn process\n" ); + return nullptr; + } + + outPseudoTerminal = std::move( pseudoTerminal ); + return process; +} diff --git a/src/tools/eterm/system/processfactory.hpp b/src/tools/eterm/system/processfactory.hpp new file mode 100644 index 000000000..fe720d59f --- /dev/null +++ b/src/tools/eterm/system/processfactory.hpp @@ -0,0 +1,47 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include "iprocessfactory.hpp" + +namespace Hexe { namespace System { +class ProcessFactory : public IProcessFactory { + public: + ProcessFactory() = default; + virtual ~ProcessFactory() = default; + ProcessFactory( const ProcessFactory& ) = delete; + ProcessFactory( ProcessFactory&& ) = delete; + ProcessFactory& operator=( const ProcessFactory& ) = delete; + ProcessFactory& operator=( ProcessFactory&& ) = delete; + + virtual std::unique_ptr CreateWithStdioPipe( const std::string& program, + const std::vector& args, + const std::string& workingDirectory, + std::unique_ptr& outPipe, + bool withStderr = true ) override; + + virtual std::unique_ptr CreateWithPseudoTerminal( + const std::string& program, const std::vector& args, + const std::string& workingDirectory, int numColumns, int numRows, + std::unique_ptr& outPseudoTerminal ) override; +}; +}} // namespace Hexe::System diff --git a/src/tools/eterm/terminal/boxdraw.hpp b/src/tools/eterm/terminal/boxdraw.hpp new file mode 100644 index 000000000..51be50c40 --- /dev/null +++ b/src/tools/eterm/terminal/boxdraw.hpp @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih + * MIT/X Consortium License + */ +/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */ +/* Categories (mutually exclusive except BDB): */ +/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */ +#define BDL ( 1 << 8 ) /* Box Draw Lines (light/double/heavy) */ +#define BDA ( 1 << 9 ) /* Box Draw Arc (light) */ + +#define BBD ( 1 << 10 ) /* Box Block Down (lower) X/8 */ +#define BBL ( 2 << 10 ) /* Box Block Left X/8 */ +#define BBU ( 3 << 10 ) /* Box Block Upper X/8 */ +#define BBR ( 4 << 10 ) /* Box Block Right X/8 */ +#define BBQ ( 5 << 10 ) /* Box Block Quadrants */ +#define BRL ( 6 << 10 ) /* Box Braille (data is lower byte of U28XX) */ + +#define BBS ( 1 << 14 ) /* Box Block Shades */ +#define BDB ( 1 << 15 ) /* Box Draw is Bold */ + +/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */ +/* Heavy is light+double (literally drawing light+double align to form heavy) */ +#define LL ( 1 << 0 ) +#define LU ( 1 << 1 ) +#define LR ( 1 << 2 ) +#define LD ( 1 << 3 ) +#define LH ( LL + LR ) +#define LV ( LU + LD ) + +#define DL ( 1 << 4 ) +#define DU ( 1 << 5 ) +#define DR ( 1 << 6 ) +#define DD ( 1 << 7 ) +#define DH ( DL + DR ) +#define DV ( DU + DD ) + +#define HL ( LL + DL ) +#define HU ( LU + DU ) +#define HR ( LR + DR ) +#define HD ( LD + DD ) +#define HH ( HL + HR ) +#define HV ( HU + HD ) + +/* (BBQ) Quadrants Top/Bottom x Left/Right */ +#define TL ( 1 << 0 ) +#define TR ( 1 << 1 ) +#define BL ( 1 << 2 ) +#define BR ( 1 << 3 ) \ No newline at end of file diff --git a/src/tools/eterm/terminal/boxdrawdata.hpp b/src/tools/eterm/terminal/boxdrawdata.hpp new file mode 100644 index 000000000..f8230378b --- /dev/null +++ b/src/tools/eterm/terminal/boxdrawdata.hpp @@ -0,0 +1,220 @@ +/* + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih + * MIT/X Consortium License + */ + +/* + * U+25XX codepoints data + * + * References: + * http://www.unicode.org/charts/PDF/U2500.pdf + * http://www.unicode.org/charts/PDF/U2580.pdf + * + * Test page: + * https://github.com/GNOME/vte/blob/master/doc/boxes.txt + */ + +#include "../terminal/boxdraw.hpp" + +/* Data for U+2500 - U+259F except dashes/diagonals */ +static const unsigned short boxdata[256] = { + BDL + LH, + BDL + HH, + BDL + LV, + BDL + HV, + + 0, + 0, + 0, + 0, + + 0, + 0, + 0, + 0, + + BDL + LD + LR, + BDL + HR + LD, + BDL + HD + LR, + BDL + HD + HR, + + BDL + LD + LL, + BDL + HL + LD, + BDL + HD + LL, + BDL + HD + HL, + + BDL + LU + LR, + BDL + HR + LU, + BDL + HU + LR, + BDL + HU + HR, + + BDL + LU + LL, + BDL + HL + LU, + BDL + HU + LL, + BDL + HU + HL, + + BDL + LV + LR, + BDL + HR + LV, + BDL + HU + LD + LR, + BDL + HD + LR + LU, + + BDL + HV + LR, + BDL + HU + HR + LD, + BDL + HD + HR + LU, + BDL + HV + HR, + + BDL + LV + LL, + BDL + HL + LV, + BDL + HU + LD + LL, + BDL + HD + LU + LL, + + BDL + HV + LL, + BDL + HU + HL + LD, + BDL + HD + HL + LU, + BDL + HV + HL, + + BDL + LH + LD, + BDL + HL + LD + LR, + BDL + HR + LL + LD, + BDL + HH + LD, + + BDL + HD + LH, + BDL + HD + HL + LR, + BDL + HR + HD + LL, + BDL + HH + HD, + + BDL + LH + LU, + BDL + HL + LU + LR, + BDL + HR + LU + LL, + BDL + HH + LU, + + BDL + HU + LH, + BDL + HU + HL + LR, + BDL + HU + HR + LL, + BDL + HH + HU, + + BDL + LV + LH, + BDL + HL + LV + LR, + BDL + HR + LV + LL, + BDL + HH + LV, + + BDL + HU + LH + LD, + BDL + HD + LH + LU, + BDL + HV + LH, + BDL + HU + HL + LD + LR, + + BDL + HU + HR + LD + LL, + BDL + HD + HL + LU + LR, + BDL + HD + HR + LU + LL, + BDL + HH + HU + LD, + + BDL + HH + HD + LU, + BDL + HV + HL + LR, + BDL + HV + HR + LL, + BDL + HV + HH, + + 0, + 0, + 0, + 0, + + BDL + DH, + BDL + DV, + BDL + DR + LD, + BDL + DD + LR, + + BDL + DR + DD, + BDL + DL + LD, + BDL + DD + LL, + BDL + DL + DD, + + BDL + DR + LU, + BDL + DU + LR, + BDL + DU + DR, + BDL + DL + LU, + + BDL + DU + LL, + BDL + DL + DU, + BDL + DR + LV, + BDL + DV + LR, + + BDL + DV + DR, + BDL + DL + LV, + BDL + DV + LL, + BDL + DV + DL, + + BDL + DH + LD, + BDL + DD + LH, + BDL + DD + DH, + BDL + DH + LU, + + BDL + DU + LH, + BDL + DH + DU, + BDL + DH + LV, + BDL + DV + LH, + + BDL + DH + DV, + BDA + LD + LR, + BDA + LD + LL, + BDA + LU + LL, + + BDA + LU + LR, + 0, + 0, + 0, + BDL + LL, + + BDL + LU, + BDL + LR, + BDL + LD, + BDL + HL, + + BDL + HU, + BDL + HR, + BDL + HD, + + BDL + HR + LL, + BDL + HD + LU, + BDL + HL + LR, + BDL + HU + LD, + + BBU + 4, + BBD + 7, + BBD + 6, + BBD + 5, + + BBD + 4, + BBD + 3, + BBD + 2, + BBD + 1, + + BBD + 0, + BBL + 7, + BBL + 6, + BBL + 5, + + BBL + 4, + BBL + 3, + BBL + 2, + BBL + 1, + + BBR + 4, + BBS + 1, + BBS + 2, + BBS + 3, + + BBU + 1, + BBR + 7, + BBQ + BL, + BBQ + BR, + + BBQ + TL, + BBQ + TL + BL + BR, + BBQ + TL + BR, + BBQ + TL + TR + BL, + + BBQ + TL + TR + BR, + BBQ + TR, + BBQ + BL + TR, + BBQ + BL + TR + BR, +}; diff --git a/src/tools/eterm/terminal/config.def.hpp b/src/tools/eterm/terminal/config.def.hpp new file mode 100644 index 000000000..8536d783a --- /dev/null +++ b/src/tools/eterm/terminal/config.def.hpp @@ -0,0 +1,97 @@ +/* See LICENSE file for copyright and license details. */ + +/* identification sequence returned in DA and DECID */ +const char* vtiden = "\033[?6c"; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +const unsigned int worddelimiters[] = { ' ', 0 }; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +const char* termname = "st-256color"; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +const unsigned int tabspaces = 8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char* colorname[258] = { + // /* 8 normal colors */ + // "black", + // "red3", + // "green3", + // "yellow3", + // "blue2", + // "magenta3", + // "cyan3", + // "gray90", + + // /* 8 bright colors */ + // "gray50", + // "red", + // "green", + // "yellow", + // "#5c5cff", + // "magenta", + // "cyan", + // "white" + + "#1e2127", "#e06c75", "#98c379", "#d19a66", "#61afef", "#c678dd", "#56b6c2", + "#abb2bf", "#5c6370", "#e06c75", "#98c379", "#d19a66", "#61afef", "#c678dd", + "#56b6c2", "#ffffff", "#1e2127", "#abb2bf" + +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static const char ascii_printable[] = " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + +// /* +// * Selection types' masks. +// * Use the same masks as usual. +// * Button1Mask is always unset, to make masks match between ButtonPress. +// * ButtonRelease and MotionNotify. +// * If no match is found, regular selection is used. +// */ +// static unsigned int selmasks[] = { +// [SEL_RECTANGULAR] = Mod1Mask, +// }; diff --git a/src/tools/eterm/terminal/ipseudoterminal.hpp b/src/tools/eterm/terminal/ipseudoterminal.hpp new file mode 100644 index 000000000..3460fef13 --- /dev/null +++ b/src/tools/eterm/terminal/ipseudoterminal.hpp @@ -0,0 +1,42 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include "../system/ipipe.hpp" +#include + +namespace Hexe { namespace Terminal { +class IPseudoTerminal : public Hexe::System::IPipe { + public: + IPseudoTerminal() = default; + IPseudoTerminal( IPseudoTerminal&& ) = delete; + IPseudoTerminal( const IPseudoTerminal& ) = delete; + IPseudoTerminal& operator=( IPseudoTerminal&& ) = delete; + IPseudoTerminal& operator=( const IPseudoTerminal& ) = delete; + virtual ~IPseudoTerminal() = default; + + virtual int GetNumColumns() const = 0; + virtual int GetNumRows() const = 0; + + virtual bool Resize( int columns, int rows ) = 0; +}; +}} // namespace Hexe::Terminal diff --git a/src/tools/eterm/terminal/nonspacing.hpp b/src/tools/eterm/terminal/nonspacing.hpp new file mode 100644 index 000000000..63899d15c --- /dev/null +++ b/src/tools/eterm/terminal/nonspacing.hpp @@ -0,0 +1,70 @@ +16, 16, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 16, 32, 16, 16, 16, 33, 34, + 35, 36, 37, 38, 39, 16, 16, 40, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 41, 42, 16, 16, 43, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 44, 16, 45, + 46, 47, 48, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 49, 16, 16, 50, 51, 16, 52, 16, 16, 16, 16, 16, 16, 16, + 16, 53, 16, 16, 16, 16, 16, 54, 55, 16, 16, 16, 16, 56, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 57, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 58, 59, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, + 255, 255, 191, 182, 0, 0, 0, 0, 0, 0, 0, 31, 0, 255, 7, 0, 0, 0, 0, 0, 248, 255, 255, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 191, 159, 61, 0, 0, 0, 128, 2, 0, 0, 0, 255, 255, 255, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 255, 1, 0, 0, 0, 0, 0, 0, 248, 15, 0, 0, 0, 192, 251, 239, + 62, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 255, 255, 127, 7, 0, + 0, 0, 0, 0, 0, 20, 254, 33, 254, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 16, 30, 32, 0, 0, 12, 0, + 0, 0, 6, 0, 0, 0, 0, 0, 0, 16, 134, 57, 2, 0, 0, 0, 35, 0, 6, 0, 0, 0, 0, 0, 0, 16, 190, 33, 0, + 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 144, 30, 32, 64, 0, 12, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 193, 61, 96, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 144, 64, 48, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 32, 0, 0, 12, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 4, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 7, 128, 127, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 242, 27, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 160, 2, 0, 0, 0, 0, 0, 0, + 254, 127, 223, 224, 255, 254, 255, 255, 255, 31, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, + 253, 102, 0, 0, 0, 195, 1, 0, 30, 0, 100, 32, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 28, 0, 0, 0, 28, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 176, 63, 64, 254, 15, 32, 0, + 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 135, 1, 4, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 128, 1, 0, 0, 0, 0, 0, 0, 64, 127, 229, 31, 248, 159, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 208, 23, 4, 0, 0, 0, 0, 248, 15, 0, 3, 0, 0, 0, 60, + 11, 0, 0, 0, 0, 0, 0, 64, 163, 3, 0, 0, 0, 0, 0, 0, 240, 207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 247, 255, 253, 33, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 127, 0, 0, 240, 0, 248, 0, 0, 0, 124, 0, 0, 0, 0, + 0, 0, 31, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, + 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 247, 63, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 68, 8, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 255, 255, + 3, 0, 0, 0, 0, 0, 192, 63, 0, 0, 128, 255, 3, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 200, 19, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 102, 0, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157, 193, + 2, 0, 0, 0, 0, 48, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32, 33, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 110, 240, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 255, 127, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 120, 38, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 128, 239, 31, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 128, 3, 248, 255, 231, 15, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/src/tools/eterm/terminal/pseudoterminal.cpp b/src/tools/eterm/terminal/pseudoterminal.cpp new file mode 100644 index 000000000..9f83f42b5 --- /dev/null +++ b/src/tools/eterm/terminal/pseudoterminal.cpp @@ -0,0 +1,124 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifndef WIN32 +// Windows has its own source file +#include "../terminal/pseudoterminal.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace Hexe::Terminal; + +PseudoTerminal::~PseudoTerminal() {} +PseudoTerminal::PseudoTerminal( int columns, int rows, AutoHandle&& master, AutoHandle&& slave ) : + m_columns( columns ), + m_rows( rows ), + m_master( std::move( master ) ), + m_slave( std::move( slave ) ) {} + +bool PseudoTerminal::IsTTY() const { + return true; +} + +bool PseudoTerminal::Resize( int columns, int rows ) { + struct winsize w; + + w.ws_row = rows; + w.ws_col = columns; + w.ws_xpixel = 0; + w.ws_ypixel = 0; + + if ( ioctl( (int)m_master, TIOCSWINSZ, &w ) < 0 ) { + perror( "PseudoTerminal::Resize" ); + return false; + } + + return true; +} + +int PseudoTerminal::GetNumColumns() const { + return m_columns; +} + +int PseudoTerminal::GetNumRows() const { + return m_rows; +} + +int PseudoTerminal::Write( const char* s, size_t n ) { + size_t c = n; + while ( n > 0 ) { + ssize_t r = write( (int)m_master, s, n ); + if ( r < 0 ) { + return -1; + } + n -= r; + s += r; + } + return c; +} + +int PseudoTerminal::Read( char* s, size_t n, bool block ) { + struct pollfd pfd; + pfd.fd = (int)m_master; + pfd.events = POLLIN; + pfd.revents = 0; + + if ( !block ) { + auto i = poll( &pfd, 1, 0 ); + if ( i == 0 || !( pfd.revents & POLLIN ) ) { + return 0; + } + if ( i < 0 ) { + perror( "PseudoTerminal::Reader(poll)" ); + return -1; + } + } + ssize_t r = read( (int)m_master, s, n ); + return (int)r; +} + +std::unique_ptr PseudoTerminal::Create( int columns, int rows ) { + AutoHandle master; + AutoHandle slave; + + struct winsize win; + + memset( &win, 0, sizeof( win ) ); + + win.ws_col = columns; + win.ws_row = rows; + + if ( openpty( master.Get(), slave.Get(), nullptr, NULL, &win ) != 0 ) { + perror( "PseudoTerminal::Create(openpty)" ); + return nullptr; + } + + return std::unique_ptr( + new PseudoTerminal( columns, rows, std::move( master ), std::move( slave ) ) ); +} + +#endif diff --git a/src/tools/eterm/terminal/pseudoterminal.hpp b/src/tools/eterm/terminal/pseudoterminal.hpp new file mode 100644 index 000000000..07e88b9e2 --- /dev/null +++ b/src/tools/eterm/terminal/pseudoterminal.hpp @@ -0,0 +1,75 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include "../system/autohandle.hpp" +#include "ipseudoterminal.hpp" +#include + +namespace Hexe { +namespace System { +class Process; +} +namespace Terminal { +class PseudoTerminal final : public IPseudoTerminal { + private: + friend class ::Hexe::System::Process; +#ifdef WIN32 + COORD m_size; + + AutoHandle m_hInput; + AutoHandle m_hOutput; + HPCON m_phPC; + + bool m_attached; + + PseudoTerminal( int columns, int rows, AutoHandle&& hInput, AutoHandle&& hOutput, HPCON hPC ); +#else + int m_columns; + int m_rows; + + AutoHandle m_master; + AutoHandle m_slave; + + PseudoTerminal( int columns, int rows, AutoHandle&& master, AutoHandle&& slave ); +#endif + public: + virtual ~PseudoTerminal(); + + PseudoTerminal( PseudoTerminal&& ) = delete; + PseudoTerminal( const PseudoTerminal& ) = delete; + PseudoTerminal& operator=( PseudoTerminal&& ) = delete; + PseudoTerminal& operator=( const PseudoTerminal& ) = delete; + + virtual bool IsTTY() const override; + + virtual int GetNumColumns() const override; + virtual int GetNumRows() const override; + + virtual bool Resize( int columns, int rows ) override; + virtual int Write( const char* s, size_t n ) override; + virtual int Read( char* buf, size_t n, bool block = false ) override; + + static std::unique_ptr Create( int columns, int rows ); +}; +} // namespace Terminal +} // namespace Hexe diff --git a/src/tools/eterm/terminal/pseudoterminal.win32.cpp b/src/tools/eterm/terminal/pseudoterminal.win32.cpp new file mode 100644 index 000000000..55d6cc57e --- /dev/null +++ b/src/tools/eterm/terminal/pseudoterminal.win32.cpp @@ -0,0 +1,135 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifdef WIN32 +#include "../terminal/pseudoterminal.h" +#include "windowserrors.h" +#include + +using namespace Hexe::Terminal; + +PseudoTerminal::~PseudoTerminal() { + ClosePseudoConsole( m_phPC ); +} + +PseudoTerminal::PseudoTerminal( int columns, int rows, AutoHandle&& hInput, AutoHandle&& hOutput, + HPCON hPC ) : + m_hInput( std::move( hInput ) ), + m_hOutput( std::move( hOutput ) ), + m_phPC( hPC ), + m_attached( false ) { + m_size.X = (SHORT)columns; + m_size.Y = (SHORT)rows; +} + +bool PseudoTerminal::IsTTY() const { + return true; +} + +bool PseudoTerminal::Resize( int columns, int rows ) { + HRESULT hr = ResizePseudoConsole( m_phPC, { (SHORT)columns, (SHORT)rows } ); + if ( hr != S_OK ) { + PrintErrorResult( hr ); + return false; + } + m_size.X = (SHORT)columns; + m_size.Y = (SHORT)rows; + return true; +} + +int PseudoTerminal::Write( const char* s, size_t n ) { + constexpr size_t lim = 256; + + DWORD c = (DWORD)n; + DWORD r = 0; + while ( n > 0 ) { + if ( !WriteFile( (HANDLE)m_hOutput, s, (DWORD)( n > lim ? lim : n ), &r, nullptr ) ) { + PrintLastWinApiError(); + return -1; + } + + s += r; + n -= r; + } + return (int)c; +} + +int PseudoTerminal::Read( char* buf, size_t n, bool block ) { + DWORD available; + DWORD read; + + if ( !block ) { + if ( !PeekNamedPipe( (HANDLE)m_hInput, nullptr, 0, nullptr, &available, nullptr ) ) { + PrintLastWinApiError(); + return -1; + } + if ( available == 0 ) + return 0; + } + + if ( !ReadFile( (HANDLE)m_hInput, buf, (DWORD)n, &read, nullptr ) ) { + PrintLastWinApiError(); + return -1; + } + return (int)read; +} + +int PseudoTerminal::GetNumColumns() const { + return m_size.X; +} + +int PseudoTerminal::GetNumRows() const { + return m_size.Y; +} + +std::unique_ptr PseudoTerminal::Create( int columns, int rows ) { + using Pointer = std::unique_ptr; + + HRESULT hr{ E_UNEXPECTED }; + HPCON hPC{ INVALID_HANDLE_VALUE }; + AutoHandle hPipeIn{}; + AutoHandle hPipeOut{}; + + AutoHandle hPipePTYIn{}; + AutoHandle hPipePTYOut{}; + + if ( CreatePipe( hPipePTYIn.Get(), hPipeOut.Get(), NULL, 0 ) && + CreatePipe( hPipeIn.Get(), hPipePTYOut.Get(), NULL, 0 ) ) { + COORD ptySize; + ptySize.X = (SHORT)columns; + ptySize.Y = (SHORT)rows; + + assert( ptySize.X > 0 ); + assert( ptySize.Y > 0 ); + + hr = CreatePseudoConsole( ptySize, (HANDLE)hPipePTYIn, (HANDLE)hPipePTYOut, 0, &hPC ); + } + + if ( hr != S_OK ) { + PrintErrorResult( hr ); + return Pointer(); + } + + return Pointer( + new PseudoTerminal( columns, rows, std::move( hPipeIn ), std::move( hPipeOut ), hPC ) ); +} +#endif diff --git a/src/tools/eterm/terminal/terminaldisplay.cpp b/src/tools/eterm/terminal/terminaldisplay.cpp new file mode 100644 index 000000000..30546258a --- /dev/null +++ b/src/tools/eterm/terminal/terminaldisplay.cpp @@ -0,0 +1,66 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#include "../terminal/terminaldisplay.hpp" +#include "../terminal/terminalemulator.hpp" +#include + +#define MODBIT( x, set, bit ) ( ( set ) ? ( ( x ) |= ( bit ) ) : ( ( x ) &= ~( bit ) ) ) + +using namespace Hexe::Terminal; + +TerminalDisplay::TerminalDisplay() : + mMode( MODE_VISIBLE ), mCursorMode( SteadyBar ), mEmulator( nullptr ) {} +void TerminalDisplay::Attach( TerminalEmulator* terminal ) { + assert( mEmulator == nullptr ); + mEmulator = terminal; +} +void TerminalDisplay::Detach( TerminalEmulator* terminal ) { + assert( mEmulator == terminal ); + mEmulator = nullptr; +} +void TerminalDisplay::Bell() {} +void TerminalDisplay::ResetColors() {} +int TerminalDisplay::ResetColor( int index, const char* name ) { + return 0; +} + +void TerminalDisplay::SetMode( win_mode mode, int set ) { + int m = mMode; + MODBIT( ( (int&)mMode ), set, mode ); + if ( ( mMode & MODE_REVERSE ) != ( m & MODE_REVERSE ) && mEmulator ) { + mEmulator->Redraw(); + } +} + +void TerminalDisplay::SetCursorMode( cursor_mode cursor ) { + mCursorMode = cursor; +} + +cursor_mode TerminalDisplay::GetCursorMode() const { + return mCursorMode; +} +void TerminalDisplay::SetTitle( const char* title ) {} +void TerminalDisplay::SetIconTitle( const char* title ) {} +void TerminalDisplay::SetClipboard( const char* text ) {} +const char* TerminalDisplay::GetClipboard() const { + return ""; +} diff --git a/src/tools/eterm/terminal/terminaldisplay.hpp b/src/tools/eterm/terminal/terminaldisplay.hpp new file mode 100644 index 000000000..5e2e1b48a --- /dev/null +++ b/src/tools/eterm/terminal/terminaldisplay.hpp @@ -0,0 +1,67 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include "types.hpp" + +namespace Hexe { namespace Terminal { +class TerminalEmulator; +class TerminalDisplay { + protected: + int mMode; + cursor_mode mCursorMode; + TerminalEmulator* mEmulator; + + public: + TerminalDisplay(); + virtual ~TerminalDisplay() = default; + TerminalDisplay( const TerminalDisplay& ) = delete; + TerminalDisplay( TerminalDisplay&& ) = delete; + TerminalDisplay& operator=( const TerminalDisplay& ) = delete; + TerminalDisplay& operator=( TerminalDisplay&& ) = delete; + + virtual void Attach( TerminalEmulator* terminal ); + virtual void Detach( TerminalEmulator* terminal ); + + virtual void Bell(); + + virtual void ResetColors(); + virtual int ResetColor( int index, const char* name ); + + virtual void SetMode( win_mode mode, int set ); + inline bool GetMode( win_mode mode ) const { return mMode & mode; } + + virtual void SetCursorMode( cursor_mode cursor ); + virtual cursor_mode GetCursorMode() const; + + virtual void SetTitle( const char* title ); + virtual void SetIconTitle( const char* title ); + + virtual void SetClipboard( const char* text ); + virtual const char* GetClipboard() const; + + virtual bool DrawBegin( int columns, int rows ) = 0; + virtual void DrawLine( Line line, int x1, int y, int x2 ) = 0; + virtual void DrawCursor( int cx, int cy, Glyph g, int ox, int oy, Glyph og ) = 0; + virtual void DrawEnd() = 0; +}; +}} // namespace Hexe::Terminal diff --git a/src/tools/eterm/terminal/terminalemulator.cpp b/src/tools/eterm/terminal/terminalemulator.cpp new file mode 100644 index 000000000..a82705242 --- /dev/null +++ b/src/tools/eterm/terminal/terminalemulator.cpp @@ -0,0 +1,2313 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen +// Copyright (c) 2014 - 2020 Hiltjo Posthuma +// Copyright (c) 2018 Devin J.Pohly +// Copyright (c) 2014 - 2017 Quentin Rameau +// Copyright (c) 2009 - 2012 Aurélien APTEL +// Copyright (c) 2008 - 2017 Anselm R Garbe +// Copyright (c) 2012 - 2017 Roberto E.Vargas Caballero +// Copyright (c) 2012 - 2016 Christoph Lohmann<20h at r - 36 dot net> +// Copyright (c) 2013 Eon S.Jeon +// Copyright (c) 2013 Alexander Sedov +// Copyright (c) 2013 Mark Edgar +// Copyright (c) 2013 - 2014 Eric Pruitt +// Copyright (c) 2013 Michael Forney +// Copyright (c) 2013 - 2014 Markus Teich +// Copyright (c) 2014 - 2015 Laslo Hunhold + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#include "../terminal/terminalemulator.hpp" +#include "boxdrawdata.hpp" + +#include "config.def.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIN( a, b ) ( ( a ) < ( b ) ? ( a ) : ( b ) ) +#define MAX( a, b ) ( ( a ) < ( b ) ? ( b ) : ( a ) ) +#define LEN( a ) ( sizeof( a ) / sizeof( a )[0] ) +#define BETWEEN( x, a, b ) ( ( a ) <= ( x ) && ( x ) <= ( b ) ) +#define DIVCEIL( n, d ) ( ( ( n ) + ( (d)-1 ) ) / ( d ) ) +#define DEFAULT( a, b ) ( a ) = ( a ) ? ( a ) : ( b ) +#define LIMIT( x, a, b ) ( x ) = ( x ) < ( a ) ? ( a ) : ( x ) > ( b ) ? ( b ) : ( x ) +#define ATTRCMP( a, b ) ( ( a ).mode != ( b ).mode || ( a ).fg != ( b ).fg || ( a ).bg != ( b ).bg ) +#define TIMEDIFF( t1, t2 ) ( ( t1.tv_sec - t2.tv_sec ) * 1000 + ( t1.tv_nsec - t2.tv_nsec ) / 1E6 ) +#define MODBIT( x, set, bit ) ( ( set ) ? ( ( x ) |= ( bit ) ) : ( ( x ) &= ~( bit ) ) ) + +#define TRUECOLOR( r, g, b ) ( 1 << 24 | ( r ) << 16 | ( g ) << 8 | ( b ) ) +#define IS_TRUECOL( x ) ( 1 << 24 & ( x ) ) + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +/* macros */ +#define IS_SET( flag ) ( ( term.mode & ( flag ) ) != 0 ) +#define ISCONTROLC0( c ) ( BETWEEN( c, 0, 0x1f ) || ( c ) == 0x7f ) +#define ISCONTROLC1( c ) ( BETWEEN( c, 0x80, 0x9f ) ) +#define ISCONTROL( c ) ( ISCONTROLC0( c ) || ISCONTROLC1( c ) ) +#define ISDELIM( u ) ( u && Hexe_wcschr( worddelimiters, u ) ) + +using namespace Hexe::Terminal; + +static inline bool is_emoji_presentation( int32_t code ) { + const int32_t rangeMin = 127744; + const int32_t rangeMax = 131069; + const int32_t rangeMin2 = 126980; + const int32_t rangeMax2 = 127569; + const int32_t rangeMin3 = 8986; + const int32_t rangeMax3 = 12953; + return code >= 8986 && ( ( rangeMin <= code && code <= rangeMax ) || + ( rangeMin2 <= code && code <= rangeMax2 ) || + ( rangeMin3 <= code && code <= rangeMax3 ) ); +} + +static inline bool is_emoji( int32_t code ) { + if ( is_emoji_presentation( code ) ) + return true; + return false; +} + +static size_t Hexe_wcslen( const Rune* s ) { + const Rune* a; + for ( a = s; *s; s++ ) + ; + return s - a; +} + +static Rune* Hexe_wcschr( const Rune* s, Rune c ) { + if ( !c ) + return (Rune*)s + Hexe_wcslen( s ); + for ( ; *s && *s != c; s++ ) + ; + return *s ? (Rune*)s : 0; +} + +static const uchar utfbyte[UTF_SIZ + 1] = { 0x80, 0, 0xC0, 0xE0, 0xF0 }; +static const uchar utfmask[UTF_SIZ + 1] = { 0xC0, 0x80, 0xE0, 0xF0, 0xF8 }; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000 }; +static const Rune utfmax[UTF_SIZ + 1] = { 0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF }; + +int Hexe::Terminal::isboxdraw( Rune u ) { + Rune block = u & ~0xff; + return ( block == 0x2500 && boxdata[u & 0xFF] ) || ( block == 0x2800 ); +} + +/* the "index" is actually the entire shape data encoded as ushort */ +ushort Hexe::Terminal::boxdrawindex( const Glyph* g ) { + if ( ( g->u & ~0xff ) == 0x2800 ) + return BRL | ( g->u & 0xFF ); + if ( ( g->mode & ATTR_BOLD ) ) + return BDB | boxdata[g->u & 0xFF]; + return boxdata[g->u & 0xFF]; +} + +static void* xmalloc( size_t len ) { + void* p; + + p = malloc( len ); + assert( p != NULL ); + + return p; +} + +static void* xrealloc( void* p, size_t len ) { + p = realloc( p, len ); + assert( p != NULL ); + + return p; +} + +static char* xstrdup( char* s ) { +#ifdef WIN32 + s = _strdup( s ); +#else + s = strdup( s ); +#endif + assert( s != NULL ); + return s; +} + +static size_t utf8decode( const char*, Rune*, size_t ); +static Rune utf8decodebyte( char, size_t* ); +static char utf8encodebyte( Rune, size_t ); +static size_t utf8validate( Rune*, size_t ); +static size_t utf8encode( Rune, char* ); + +static char* base64dec( const char* ); +static char base64dec_getc( const char** ); + +static intmax_t xwrite( int, const char*, size_t ); + +size_t utf8decode( const char* c, Rune* u, size_t clen ) { + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if ( !clen ) + return 0; + udecoded = utf8decodebyte( c[0], &len ); + if ( !BETWEEN( len, 1, UTF_SIZ ) ) + return 1; + for ( i = 1, j = 1; i < clen && j < len; ++i, ++j ) { + udecoded = ( udecoded << 6 ) | utf8decodebyte( c[i], &type ); + if ( type != 0 ) + return j; + } + if ( j < len ) + return 0; + *u = udecoded; + utf8validate( u, len ); + + return len; +} + +Rune utf8decodebyte( char c, size_t* i ) { + for ( *i = 0; *i < LEN( utfmask ); ++( *i ) ) + if ( ( (uchar)c & utfmask[*i] ) == utfbyte[*i] ) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t utf8encode( Rune u, char* c ) { + size_t len, i; + + len = utf8validate( &u, 0 ); + if ( len > UTF_SIZ ) + return 0; + + for ( i = len - 1; i != 0; --i ) { + c[i] = utf8encodebyte( u, 0 ); + u >>= 6; + } + c[0] = utf8encodebyte( u, len ); + + return len; +} + +char utf8encodebyte( Rune u, size_t i ) { + return utfbyte[i] | ( u & ~utfmask[i] ); +} + +size_t utf8validate( Rune* u, size_t i ) { + if ( !BETWEEN( *u, utfmin[i], utfmax[i] ) || BETWEEN( *u, 0xD800, 0xDFFF ) ) + *u = UTF_INVALID; + for ( i = 1; *u > utfmax[i]; ++i ) + ; + + return i; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +char base64dec_getc( const char** src ) { + while ( **src && !isprint( **src ) ) + ( *src )++; + return **src ? *( ( *src )++ ) : '='; /* emulate padding if string ends */ +} + +char* base64dec( const char* src ) { + size_t in_len = strlen( src ); + char *result, *dst; + + if ( in_len % 4 ) + in_len += 4 - ( in_len % 4 ); + result = dst = (char*)xmalloc( in_len / 4 * 3 + 1 ); + while ( *src ) { + int a = base64_digits[(unsigned char)base64dec_getc( &src )]; + int b = base64_digits[(unsigned char)base64dec_getc( &src )]; + int c = base64_digits[(unsigned char)base64dec_getc( &src )]; + int d = base64_digits[(unsigned char)base64dec_getc( &src )]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if ( a == -1 || b == -1 ) + break; + + *dst++ = ( a << 2 ) | ( ( b & 0x30 ) >> 4 ); + if ( c == -1 ) + break; + *dst++ = ( ( b & 0x0f ) << 4 ) | ( ( c & 0x3c ) >> 2 ); + if ( d == -1 ) + break; + *dst++ = ( ( c & 0x03 ) << 6 ) | d; + } + *dst = '\0'; + return result; +} + +void TerminalEmulator::selinit( void ) { + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int TerminalEmulator::tlinelen( int y ) { + int i = term.col; + + if ( term.line[y][i - 1].mode & ATTR_WRAP ) + return i; + + while ( i > 0 && term.line[y][i - 1].u == ' ' ) + --i; + + return i; +} + +void TerminalEmulator::selstart( int col, int row, int snap ) { + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET( MODE_ALTSCREEN ); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if ( sel.snap != 0 ) + sel.mode = SEL_READY; + tsetdirt( sel.nb.y, sel.ne.y ); +} + +void TerminalEmulator::selextend( int col, int row, int type, int done ) { + int oldey, oldex, oldsby, oldsey, oldtype; + + if ( sel.mode == SEL_IDLE ) + return; + if ( done && sel.mode == SEL_EMPTY ) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if ( oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY ) + tsetdirt( MIN( sel.nb.y, oldsby ), MAX( sel.ne.y, oldsey ) ); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void TerminalEmulator::selnormalize( void ) { + int i; + + if ( sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y ) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN( sel.ob.x, sel.oe.x ); + sel.ne.x = MAX( sel.ob.x, sel.oe.x ); + } + sel.nb.y = MIN( sel.ob.y, sel.oe.y ); + sel.ne.y = MAX( sel.ob.y, sel.oe.y ); + + selsnap( &sel.nb.x, &sel.nb.y, -1 ); + selsnap( &sel.ne.x, &sel.ne.y, +1 ); + + /* expand selection over line breaks */ + if ( sel.type == SEL_RECTANGULAR ) + return; + i = tlinelen( sel.nb.y ); + if ( i < sel.nb.x ) + sel.nb.x = i; + if ( tlinelen( sel.ne.y ) <= sel.ne.x ) + sel.ne.x = term.col - 1; +} + +int TerminalEmulator::selected( int x, int y ) { + if ( sel.mode == SEL_EMPTY || sel.ob.x == -1 || sel.alt != IS_SET( MODE_ALTSCREEN ) ) + return 0; + + if ( sel.type == SEL_RECTANGULAR ) + return BETWEEN( y, sel.nb.y, sel.ne.y ) && BETWEEN( x, sel.nb.x, sel.ne.x ); + + return BETWEEN( y, sel.nb.y, sel.ne.y ) && ( y != sel.nb.y || x >= sel.nb.x ) && + ( y != sel.ne.y || x <= sel.ne.x ); +} + +void TerminalEmulator::selsnap( int* x, int* y, int direction ) { + int newx, newy, xt, yt; + int delim, prevdelim; + Glyph *gp, *prevgp; + + switch ( sel.snap ) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM( prevgp->u ); + for ( ;; ) { + newx = *x + direction; + newy = *y; + if ( !BETWEEN( newx, 0, term.col - 1 ) ) { + newy += direction; + newx = ( newx + term.col ) % term.col; + if ( !BETWEEN( newy, 0, term.row - 1 ) ) + break; + + if ( direction > 0 ) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if ( !( term.line[yt][xt].mode & ATTR_WRAP ) ) + break; + } + + if ( newx >= tlinelen( newy ) ) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM( gp->u ); + if ( !( gp->mode & ATTR_WDUMMY ) && + ( delim != prevdelim || ( delim && gp->u != prevgp->u ) ) ) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = ( direction < 0 ) ? 0 : term.col - 1; + if ( direction < 0 ) { + for ( ; *y > 0; *y += direction ) { + if ( !( term.line[*y - 1][term.col - 1].mode & ATTR_WRAP ) ) { + break; + } + } + } else if ( direction > 0 ) { + for ( ; *y < term.row - 1; *y += direction ) { + if ( !( term.line[*y][term.col - 1].mode & ATTR_WRAP ) ) { + break; + } + } + } + break; + } +} + +char* TerminalEmulator::getsel( void ) { + char *str, *ptr; + int y, bufsize, lastx, linelen; + Glyph *gp, *last; + + if ( sel.ob.x == -1 ) + return NULL; + + bufsize = ( term.col + 1 ) * ( sel.ne.y - sel.nb.y + 1 ) * UTF_SIZ; + ptr = str = (char*)xmalloc( bufsize ); + + /* append every set & selected glyph to the selection */ + for ( y = sel.nb.y; y <= sel.ne.y; y++ ) { + if ( ( linelen = tlinelen( y ) ) == 0 ) { + *ptr++ = '\n'; + continue; + } + + if ( sel.type == SEL_RECTANGULAR ) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = ( sel.ne.y == y ) ? sel.ne.x : term.col - 1; + } + last = &term.line[y][MIN( lastx, linelen - 1 )]; + while ( last >= gp && last->u == ' ' ) + --last; + + for ( ; gp <= last; ++gp ) { + if ( gp->mode & ATTR_WDUMMY ) + continue; + + ptr += utf8encode( gp->u, ptr ); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ( ( y < sel.ne.y || lastx >= linelen ) && + ( !( last->mode & ATTR_WRAP ) || sel.type == SEL_RECTANGULAR ) ) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void TerminalEmulator::selclear( void ) { + if ( sel.ob.x == -1 ) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt( sel.nb.y, sel.ne.y ); +} + +void TerminalEmulator::_die( const char* errstr, ... ) { + char buf[256]; + + memset( buf, 0, 256 ); + va_list ap; + va_start( ap, errstr ); + vsnprintf( buf, 256, errstr, ap ); + va_end( ap ); + LogError( buf ); + Terminate(); +} + +size_t TerminalEmulator::ttyread( void ) { + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = m_pty->Read( m_buf + m_buflen, LEN( m_buf ) - m_buflen ); + + switch ( ret ) { + case 0: + return 0; + case -1: + _die( "couldn't read from shell: %s\n", strerror( errno ) ); + return 0; + default: + m_buflen += ret; + written = twrite( m_buf, m_buflen, 0 ); + m_buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if ( m_buflen > 0 ) + memmove( m_buf, m_buf + written, m_buflen ); + return ret; + } + + return 0; +} + +void TerminalEmulator::ttywrite( const char* s, size_t n, int may_echo ) { + const char* next; + + if ( may_echo && IS_SET( MODE_ECHO ) ) + twrite( s, (int)n, 1 ); + + if ( !IS_SET( MODE_CRLF ) ) { + ttywriteraw( s, n ); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while ( n > 0 ) { + if ( *s == '\r' ) { + next = s + 1; + ttywriteraw( "\r\n", 2 ); + } else { + next = (const char*)memchr( s, '\r', n ); + DEFAULT( next, s + n ); + ttywriteraw( s, next - s ); + } + n -= next - s; + s = next; + } +} + +void TerminalEmulator::ttywriteraw( const char* s, size_t n ) { + if ( m_pty->Write( s, n ) < n ) { + _die( "Failed to write to TTY" ); + } +} + +void TerminalEmulator::ttyhangup() { + if ( m_process ) + m_process->Terminate(); +} + +int TerminalEmulator::tattrset( int attr ) { + int i, j; + + for ( i = 0; i < term.row - 1; i++ ) { + for ( j = 0; j < term.col - 1; j++ ) { + if ( term.line[i][j].mode & attr ) + return 1; + } + } + + return 0; +} + +void TerminalEmulator::tsetdirt( int top, int bot ) { + int i; + + LIMIT( top, 0, term.row - 1 ); + LIMIT( bot, 0, term.row - 1 ); + + for ( i = top; i <= bot; i++ ) + term.dirty[i] = 1; +} + +void TerminalEmulator::tsetdirtattr( int attr ) { + int i, j; + + for ( i = 0; i < term.row - 1; i++ ) { + for ( j = 0; j < term.col - 1; j++ ) { + if ( term.line[i][j].mode & attr ) { + tsetdirt( i, i ); + break; + } + } + } +} + +void TerminalEmulator::tfulldirt( void ) { + tsetdirt( 0, term.row - 1 ); +} + +void TerminalEmulator::tcursor( int mode ) { + static TCursor c[2]; + int alt = IS_SET( MODE_ALTSCREEN ); + + if ( mode == CURSOR_SAVE ) { + c[alt] = term.c; + } else if ( mode == CURSOR_LOAD ) { + term.c = c[alt]; + tmoveto( c[alt].x, c[alt].y ); + } +} + +void TerminalEmulator::treset( void ) { + uint i; + + term.c = TCursor{}; + term.c.attr = Glyph{}; + term.c.attr.mode = ATTR_NULL; + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + term.c.x = 0; + term.c.y = 0; + term.c.state = CURSOR_DEFAULT; + + memset( term.tabs, 0, term.col * sizeof( *term.tabs ) ); + for ( i = tabspaces; i < (uint)term.col; i += tabspaces ) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP | MODE_UTF8; + memset( term.trantbl, CS_USA, sizeof( term.trantbl ) ); + term.charset = 0; + + for ( i = 0; i < 2; i++ ) { + tmoveto( 0, 0 ); + tcursor( CURSOR_SAVE ); + tclearregion( 0, 0, term.col - 1, term.row - 1 ); + tswapscreen(); + } +} + +void TerminalEmulator::tnew( int col, int row ) { + term = Term{}; + term.c = TCursor{}; + term.c.attr = Glyph{}; + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + + tresize( col, row ); + treset(); +} + +void TerminalEmulator::tswapscreen( void ) { + Line* tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void TerminalEmulator::tscrolldown( int orig, int n ) { + int i; + Line temp; + + LIMIT( n, 0, term.bot - orig + 1 ); + + tsetdirt( orig, term.bot - n ); + tclearregion( 0, term.bot - n + 1, term.col - 1, term.bot ); + + for ( i = term.bot; i >= orig + n; i-- ) { + temp = term.line[i]; + term.line[i] = term.line[i - n]; + term.line[i - n] = temp; + } + + selscroll( orig, n ); +} + +void TerminalEmulator::tscrollup( int orig, int n ) { + int i; + Line temp; + + LIMIT( n, 0, term.bot - orig + 1 ); + + tclearregion( 0, orig, term.col - 1, orig + n - 1 ); + tsetdirt( orig + n, term.bot ); + + for ( i = orig; i <= term.bot - n; i++ ) { + temp = term.line[i]; + term.line[i] = term.line[i + n]; + term.line[i + n] = temp; + } + + selscroll( orig, -n ); +} + +void TerminalEmulator::selscroll( int orig, int n ) { + if ( sel.ob.x == -1 ) + return; + + if ( BETWEEN( sel.nb.y, orig, term.bot ) != BETWEEN( sel.ne.y, orig, term.bot ) ) { + selclear(); + } else if ( BETWEEN( sel.nb.y, orig, term.bot ) ) { + sel.ob.y += n; + sel.oe.y += n; + if ( sel.ob.y < term.top || sel.ob.y > term.bot || sel.oe.y < term.top || + sel.oe.y > term.bot ) { + selclear(); + } else { + selnormalize(); + } + } +} + +void TerminalEmulator::tnewline( int first_col ) { + int y = term.c.y; + + if ( y == term.bot ) { + tscrollup( term.top, 1 ); + } else { + y++; + } + tmoveto( first_col ? 0 : term.c.x, y ); +} + +void TerminalEmulator::csiparse( void ) { + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if ( *p == '?' ) { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while ( p < csiescseq.buf + csiescseq.len ) { + np = NULL; + v = strtol( p, &np, 10 ); + if ( np == p ) + v = 0; + if ( v == LONG_MAX || v == LONG_MIN ) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if ( *p != ';' || csiescseq.narg == ESC_ARG_SIZ ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = ( p < csiescseq.buf + csiescseq.len ) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void TerminalEmulator::tmoveato( int x, int y ) { + tmoveto( x, y + ( ( term.c.state & CURSOR_ORIGIN ) ? term.top : 0 ) ); +} + +void TerminalEmulator::tmoveto( int x, int y ) { + int miny, maxy; + + if ( term.c.state & CURSOR_ORIGIN ) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT( x, 0, term.col - 1 ); + term.c.y = LIMIT( y, miny, maxy ); +} + +void TerminalEmulator::tsetchar( Rune u, Glyph* attr, int x, int y ) { + static const char* vt100_0[62] = { + /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if ( term.trantbl[term.charset] == CS_GRAPHIC0 && BETWEEN( u, 0x41, 0x7e ) && + vt100_0[u - 0x41] ) + utf8decode( vt100_0[u - 0x41], &u, UTF_SIZ ); + + if ( term.line[y][x].mode & ATTR_WIDE ) { + if ( x + 1 < term.col ) { + term.line[y][x + 1].u = ' '; + term.line[y][x + 1].mode &= ~ATTR_WDUMMY; + } + } else if ( term.line[y][x].mode & ATTR_WDUMMY ) { + term.line[y][x - 1].u = ' '; + term.line[y][x - 1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; + + if ( isboxdraw( u ) ) + term.line[y][x].mode |= ATTR_BOXDRAW; +} + +void TerminalEmulator::tclearregion( int x1, int y1, int x2, int y2 ) { + int x, y, temp; + Glyph* gp; + + if ( x1 > x2 ) + temp = x1, x1 = x2, x2 = temp; + if ( y1 > y2 ) + temp = y1, y1 = y2, y2 = temp; + + LIMIT( x1, 0, term.col - 1 ); + LIMIT( x2, 0, term.col - 1 ); + LIMIT( y1, 0, term.row - 1 ); + LIMIT( y2, 0, term.row - 1 ); + + for ( y = y1; y <= y2; y++ ) { + term.dirty[y] = 1; + for ( x = x1; x <= x2; x++ ) { + gp = &term.line[y][x]; + if ( selected( x, y ) ) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void TerminalEmulator::tdeletechar( int n ) { + int dst, src, size; + Glyph* line; + + LIMIT( n, 0, term.col - term.c.x ); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove( &line[dst], &line[src], size * sizeof( Glyph ) ); + tclearregion( term.col - n, term.c.y, term.col - 1, term.c.y ); +} + +void TerminalEmulator::tinsertblank( int n ) { + int dst, src, size; + Glyph* line; + + LIMIT( n, 0, term.col - term.c.x ); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove( &line[dst], &line[src], size * sizeof( Glyph ) ); + tclearregion( src, term.c.y, dst - 1, term.c.y ); +} + +void TerminalEmulator::tinsertblankline( int n ) { + if ( BETWEEN( term.c.y, term.top, term.bot ) ) + tscrolldown( term.c.y, n ); +} + +void TerminalEmulator::tdeleteline( int n ) { + if ( BETWEEN( term.c.y, term.top, term.bot ) ) + tscrollup( term.c.y, n ); +} + +int32_t TerminalEmulator::tdefcolor( int* attr, int* npar, int l ) { + int32_t idx = -1; + uint r, g, b; + + switch ( attr[*npar + 1] ) { + case 2: /* direct color in RGB space */ + if ( *npar + 4 >= l ) { + fprintf( stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar ); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if ( !BETWEEN( r, 0, 255 ) || !BETWEEN( g, 0, 255 ) || !BETWEEN( b, 0, 255 ) ) + fprintf( stderr, "erresc: bad rgb color (%u,%u,%u)\n", r, g, b ); + else + idx = TRUECOLOR( r, g, b ); + break; + case 5: /* indexed color */ + if ( *npar + 2 >= l ) { + fprintf( stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar ); + break; + } + *npar += 2; + if ( !BETWEEN( attr[*npar], 0, 255 ) ) + fprintf( stderr, "erresc: bad fgcolor %d\n", attr[*npar] ); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf( stderr, "erresc(38): gfx attr %d unknown\n", attr[*npar] ); + break; + } + + return idx; +} + +void TerminalEmulator::tsetattr( int* attr, int l ) { + int i; + int32_t idx; + + for ( i = 0; i < l; i++ ) { + switch ( attr[i] ) { + case 0: + term.c.attr.mode &= ~( ATTR_BOLD | ATTR_FAINT | ATTR_ITALIC | ATTR_UNDERLINE | + ATTR_BLINK | ATTR_REVERSE | ATTR_INVISIBLE | ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~( ATTR_BOLD | ATTR_FAINT ); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ( ( idx = tdefcolor( attr, &i, l ) ) >= 0 ) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ( ( idx = tdefcolor( attr, &i, l ) ) >= 0 ) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if ( BETWEEN( attr[i], 30, 37 ) ) { + term.c.attr.fg = attr[i] - 30; + } else if ( BETWEEN( attr[i], 40, 47 ) ) { + term.c.attr.bg = attr[i] - 40; + } else if ( BETWEEN( attr[i], 90, 97 ) ) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if ( BETWEEN( attr[i], 100, 107 ) ) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf( stderr, "erresc(default): gfx attr %d unknown\n", attr[i] ); + csidump(); + } + break; + } + } +} + +void TerminalEmulator::tsetscroll( int t, int b ) { + int temp; + + LIMIT( t, 0, term.row - 1 ); + LIMIT( b, 0, term.row - 1 ); + if ( t > b ) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void TerminalEmulator::tsetmode( int priv, int set, int* args, int narg ) { + int alt, *lim; + + for ( lim = args + narg; args < lim; ++args ) { + if ( priv ) { + switch ( *args ) { + case 1: /* DECCKM -- Cursor key */ + xsetmode( set, MODE_APPCURSOR ); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode( set, MODE_REVERSE ); + break; + case 6: /* DECOM -- Origin */ + MODBIT( term.c.state, set, CURSOR_ORIGIN ); + tmoveato( 0, 0 ); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT( term.mode, set, MODE_WRAP ); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode( !set, MODE_HIDE ); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion( 0 ); + xsetmode( 0, MODE_MOUSE ); + xsetmode( set, MODE_MOUSEX10 ); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion( 0 ); + xsetmode( 0, MODE_MOUSE ); + xsetmode( set, MODE_MOUSEBTN ); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion( 0 ); + xsetmode( 0, MODE_MOUSE ); + xsetmode( set, MODE_MOUSEMOTION ); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion( set ); + xsetmode( 0, MODE_MOUSE ); + xsetmode( set, MODE_MOUSEMANY ); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode( set, MODE_FOCUS ); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode( set, MODE_MOUSESGR ); + break; + case 1034: + xsetmode( set, MODE_8BIT ); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if ( !allowaltscreen ) + break; + tcursor( ( set ) ? CURSOR_SAVE : CURSOR_LOAD ); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if ( !allowaltscreen ) + break; + alt = IS_SET( MODE_ALTSCREEN ); + if ( alt ) { + tclearregion( 0, 0, term.col - 1, term.row - 1 ); + } + if ( set ^ alt ) /* set is always 1 or 0 */ + tswapscreen(); + if ( *args != 1049 ) + break; + /* FALLTHROUGH */ + case 1048: + tcursor( ( set ) ? CURSOR_SAVE : CURSOR_LOAD ); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode( set, MODE_BRCKTPASTE ); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf( stderr, "erresc: unknown private set/reset mode %d\n", *args ); + break; + } + } else { + switch ( *args ) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode( set, MODE_KBDLOCK ); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT( term.mode, set, MODE_INSERT ); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT( term.mode, !set, MODE_ECHO ); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT( term.mode, set, MODE_CRLF ); + break; + default: + fprintf( stderr, "erresc: unknown set/reset mode %d\n", *args ); + break; + } + } + } +} + +void TerminalEmulator::csihandle( void ) { + char buf[40]; + int len; + + std::shared_ptr dpy{}; + + switch ( csiescseq.mode[0] ) { + default: + unknown: + fprintf( stderr, "erresc: unknown csi " ); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT( csiescseq.arg[0], 1 ); + tinsertblank( csiescseq.arg[0] ); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveto( term.c.x, term.c.y - csiescseq.arg[0] ); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveto( term.c.x, term.c.y + csiescseq.arg[0] ); + break; + case 'i': /* MC -- Media Copy */ + switch ( csiescseq.arg[0] ) { + case 0: + tdump(); + break; + case 1: + tdumpline( term.c.y ); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if ( csiescseq.arg[0] == 0 ) + ttywrite( vtiden, strlen( vtiden ), 0 ); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT( csiescseq.arg[0], 1 ); + if ( term.lastc ) + while ( csiescseq.arg[0]-- > 0 ) + tputc( term.lastc ); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveto( term.c.x + csiescseq.arg[0], term.c.y ); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveto( term.c.x - csiescseq.arg[0], term.c.y ); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveto( 0, term.c.y + csiescseq.arg[0] ); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveto( 0, term.c.y - csiescseq.arg[0] ); + break; + case 'g': /* TBC -- Tabulation clear */ + switch ( csiescseq.arg[0] ) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset( term.tabs, 0, term.col * sizeof( *term.tabs ) ); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveto( csiescseq.arg[0] - 1, term.c.y ); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT( csiescseq.arg[0], 1 ); + DEFAULT( csiescseq.arg[1], 1 ); + tmoveato( csiescseq.arg[1] - 1, csiescseq.arg[0] - 1 ); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT( csiescseq.arg[0], 1 ); + tputtab( csiescseq.arg[0] ); + break; + case 'J': /* ED -- Clear screen */ + switch ( csiescseq.arg[0] ) { + case 0: /* below */ + tclearregion( term.c.x, term.c.y, term.col - 1, term.c.y ); + if ( term.c.y < term.row - 1 ) { + tclearregion( 0, term.c.y + 1, term.col - 1, term.row - 1 ); + } + break; + case 1: /* above */ + if ( term.c.y > 1 ) + tclearregion( 0, 0, term.col - 1, term.c.y - 1 ); + tclearregion( 0, term.c.y, term.c.x, term.c.y ); + break; + case 2: /* all */ + tclearregion( 0, 0, term.col - 1, term.row - 1 ); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch ( csiescseq.arg[0] ) { + case 0: /* right */ + tclearregion( term.c.x, term.c.y, term.col - 1, term.c.y ); + break; + case 1: /* left */ + tclearregion( 0, term.c.y, term.c.x, term.c.y ); + break; + case 2: /* all */ + tclearregion( 0, term.c.y, term.col - 1, term.c.y ); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT( csiescseq.arg[0], 1 ); + tscrollup( term.top, csiescseq.arg[0] ); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT( csiescseq.arg[0], 1 ); + tscrolldown( term.top, csiescseq.arg[0] ); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT( csiescseq.arg[0], 1 ); + tinsertblankline( csiescseq.arg[0] ); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode( csiescseq.priv, 0, csiescseq.arg, csiescseq.narg ); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT( csiescseq.arg[0], 1 ); + tdeleteline( csiescseq.arg[0] ); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT( csiescseq.arg[0], 1 ); + tclearregion( term.c.x, term.c.y, term.c.x + csiescseq.arg[0] - 1, term.c.y ); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT( csiescseq.arg[0], 1 ); + tdeletechar( csiescseq.arg[0] ); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT( csiescseq.arg[0], 1 ); + tputtab( -csiescseq.arg[0] ); + break; + case 'd': /* VPA -- Move to */ + DEFAULT( csiescseq.arg[0], 1 ); + tmoveato( term.c.x, csiescseq.arg[0] - 1 ); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode( csiescseq.priv, 1, csiescseq.arg, csiescseq.narg ); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr( csiescseq.arg, csiescseq.narg ); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if ( csiescseq.arg[0] == 6 ) { + len = snprintf( buf, sizeof( buf ), "\033[%i;%iR", term.c.y + 1, term.c.x + 1 ); + ttywrite( buf, len, 0 ); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if ( csiescseq.priv ) { + goto unknown; + } else { + DEFAULT( csiescseq.arg[0], 1 ); + DEFAULT( csiescseq.arg[1], term.row ); + tsetscroll( csiescseq.arg[0] - 1, csiescseq.arg[1] - 1 ); + tmoveato( 0, 0 ); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor( CURSOR_SAVE ); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor( CURSOR_LOAD ); + break; + case ' ': + switch ( csiescseq.mode[1] ) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if ( csiescseq.arg[0] < 0 || csiescseq.arg[0] < cursor_mode::MAX_CURSOR ) + goto unknown; + dpy = m_dpy.lock(); + dpy->SetCursorMode( (cursor_mode)csiescseq.arg[0] ); + break; + default: + goto unknown; + } + break; + } +} + +void TerminalEmulator::csidump( void ) { + size_t i; + uint c; + + fprintf( stderr, "ESC[" ); + for ( i = 0; i < csiescseq.len; i++ ) { + c = csiescseq.buf[i] & 0xff; + if ( isprint( c ) ) { + putc( c, stderr ); + } else if ( c == '\n' ) { + fprintf( stderr, "(\\n)" ); + } else if ( c == '\r' ) { + fprintf( stderr, "(\\r)" ); + } else if ( c == 0x1b ) { + fprintf( stderr, "(\\e)" ); + } else { + fprintf( stderr, "(%02x)", c ); + } + } + putc( '\n', stderr ); +} + +void TerminalEmulator::csireset( void ) { + memset( &csiescseq, 0, sizeof( csiescseq ) ); +} + +void TerminalEmulator::strhandle( void ) { + char *p = NULL, *dec; + int j, narg, par; + + term.esc &= ~( ESC_STR_END | ESC_STR ); + strparse(); + par = ( narg = strescseq.narg ) ? atoi( strescseq.args[0] ) : 0; + + std::shared_ptr dpy = m_dpy.lock(); + + switch ( strescseq.type ) { + case ']': /* OSC -- Operating System Command */ + switch ( par ) { + case 0: + if ( narg > 1 ) { + dpy->SetTitle( strescseq.args[1] ); + dpy->SetIconTitle( strescseq.args[1] ); + } + return; + case 1: + if ( narg > 1 ) + dpy->SetIconTitle( strescseq.args[1] ); + return; + case 2: + if ( narg > 1 ) { + dpy->SetTitle( strescseq.args[1] ); + } + return; + case 52: + if ( narg > 2 && allowwindowops ) { + dec = base64dec( strescseq.args[2] ); + if ( dec ) { + SetClipboard( dec ); + } else { + fprintf( stderr, "erresc: invalid base64\n" ); + } + } + return; + case 4: /* color set */ + if ( narg < 3 ) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset, here p = NULL */ + j = ( narg > 1 ) ? atoi( strescseq.args[1] ) : -1; + if ( ResetColor( j, p ) ) { + if ( par == 104 && narg <= 1 ) + return; /* color reset without parameter */ + fprintf( stderr, "erresc: invalid color j=%d, p=%s\n", j, + p ? p : "(null)" ); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + Redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + dpy->SetTitle( strescseq.args[0] ); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + LogError( "erresc: unknown str " ); + strdump(); +} + +void TerminalEmulator::strparse( void ) { + int c; + char* p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if ( *p == '\0' ) + return; + + while ( strescseq.narg < STR_ARG_SIZ ) { + strescseq.args[strescseq.narg++] = p; + while ( ( c = *p ) != ';' && c != '\0' ) + ++p; + if ( c == '\0' ) + return; + *p++ = '\0'; + } +} + +void TerminalEmulator::strdump( void ) { + size_t i; + uint c; + + fprintf( stderr, "ESC%c", strescseq.type ); + for ( i = 0; i < strescseq.len; i++ ) { + c = strescseq.buf[i] & 0xff; + if ( c == '\0' ) { + putc( '\n', stderr ); + return; + } else if ( isprint( c ) ) { + putc( c, stderr ); + } else if ( c == '\n' ) { + fprintf( stderr, "(\\n)" ); + } else if ( c == '\r' ) { + fprintf( stderr, "(\\r)" ); + } else if ( c == 0x1b ) { + fprintf( stderr, "(\\e)" ); + } else { + fprintf( stderr, "(%02x)", c ); + } + } + fprintf( stderr, "ESC\\\n" ); +} + +void TerminalEmulator::strreset( void ) { + auto old = strescseq.buf; + strescseq = STREscape{}; + strescseq.buf = (char*)xrealloc( old, STR_BUF_SIZ ); + strescseq.siz = STR_BUF_SIZ; +} + +void TerminalEmulator::sendbreak( const Arg* arg ) { + // TODO: Do something about this + // if (tcsendbreak(cmdfd, 0)) + // perror("Error sending break"); +} + +void TerminalEmulator::tprinter( const char* s, size_t len ) { + // TODO: Do something about this + // if (iofd != -1 && xwrite(iofd, s, len) < 0) + // { + // perror("Error writing to output file"); + // close(iofd); + // iofd = -1; + // } +} + +void TerminalEmulator::toggleprinter( const Arg* arg ) { + term.mode ^= MODE_PRINT; +} + +void TerminalEmulator::printscreen( const Arg* arg ) { + tdump(); +} + +void TerminalEmulator::printsel( const Arg* arg ) { + tdumpsel(); +} + +void TerminalEmulator::tdumpsel( void ) { + char* ptr; + + if ( ( ptr = getsel() ) ) { + tprinter( ptr, strlen( ptr ) ); + free( ptr ); + } +} + +void TerminalEmulator::tdumpline( int n ) { + char buf[UTF_SIZ]; + Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN( tlinelen( n ), term.col ) - 1]; + if ( bp != end || bp->u != ' ' ) { + for ( ; bp <= end; ++bp ) + tprinter( buf, utf8encode( bp->u, buf ) ); + } + tprinter( "\n", 1 ); +} + +void TerminalEmulator::tdump( void ) { + int i; + + for ( i = 0; i < term.row; ++i ) + tdumpline( i ); +} + +void TerminalEmulator::tputtab( int n ) { + int x = term.c.x; + + if ( n > 0 ) { + while ( x < term.col && n-- ) + for ( ++x; x < term.col && !term.tabs[x]; ++x ) + /* nothing */; + } else if ( n < 0 ) { + while ( x > 0 && n++ ) + for ( --x; x > 0 && !term.tabs[x]; --x ) + /* nothing */; + } + term.c.x = LIMIT( x, 0, term.col - 1 ); +} + +void TerminalEmulator::tdefutf8( char ascii ) { + if ( ascii == 'G' ) + term.mode |= MODE_UTF8; + else if ( ascii == '@' ) + term.mode &= ~MODE_UTF8; +} + +void TerminalEmulator::tdeftran( char ascii ) { + static char cs[] = "0B"; + static int vcs[] = { CS_GRAPHIC0, CS_USA }; + char* p; + + if ( ( p = strchr( cs, ascii ) ) == NULL ) { + fprintf( stderr, "esc unhandled charset: ESC ( %c\n", ascii ); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void TerminalEmulator::tdectest( char c ) { + int x, y; + + if ( c == '8' ) { /* DEC screen alignment test. */ + for ( x = 0; x < term.col; ++x ) { + for ( y = 0; y < term.row; ++y ) + tsetchar( 'E', &term.c.attr, x, y ); + } + } +} + +void TerminalEmulator::tstrsequence( uchar c ) { + switch ( c ) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void TerminalEmulator::tcontrolcode( uchar ascii ) { + switch ( ascii ) { + case '\t': /* HT */ + tputtab( 1 ); + return; + case '\b': /* BS */ + tmoveto( term.c.x - 1, term.c.y ); + return; + case '\r': /* CR */ + tmoveto( 0, term.c.y ); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline( IS_SET( MODE_CRLF ) ); + return; + case '\a': /* BEL */ + if ( term.esc & ESC_STR_END ) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + auto belDpyPtr = m_dpy.lock(); + belDpyPtr->Bell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~( ESC_CSI | ESC_ALTCHARSET | ESC_TEST ); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - ( ascii - '\016' ); + return; + case '\032': /* SUB */ + tsetchar( '?', &term.c.attr, term.c.x, term.c.y ); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline( 1 ); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite( vtiden, strlen( vtiden ), 0 ); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence( ascii ); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~( ESC_STR_END | ESC_STR ); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int TerminalEmulator::eschandle( uchar ascii ) { + switch ( ascii ) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence( ascii ); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + ( ascii - 'n' ); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if ( term.c.y == term.bot ) { + tscrollup( term.top, 1 ); + } else { + tmoveto( term.c.x, term.c.y + 1 ); + } + break; + case 'E': /* NEL -- Next line */ + tnewline( 1 ); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if ( term.c.y == term.top ) { + tscrolldown( term.top, 1 ); + } else { + tmoveto( term.c.x, term.c.y - 1 ); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite( vtiden, strlen( vtiden ), 0 ); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + LoadColors(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode( 1, MODE_APPKEYPAD ); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode( 0, MODE_APPKEYPAD ); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor( CURSOR_SAVE ); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor( CURSOR_LOAD ); + break; + case '\\': /* ST -- String Terminator */ + if ( term.esc & ESC_STR_END ) + strhandle(); + break; + default: + fprintf( stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", (uchar)ascii, + isprint( ascii ) ? ascii : '.' ); + break; + } + return 1; +} + +static const unsigned char table[] = { +#include "nonspacing.hpp" +}; + +static const unsigned char wtable[] = { +#include "wide.hpp" +}; + +static int Hexe_wcwidth( Rune wc ) { + if ( wc < 0xffU ) + return ( wc + 1 & 0x7f ) >= 0x21 ? 1 : wc ? -1 : 0; + + if ( is_emoji( wc ) ) + return 2; + + if ( ( wc & 0xfffeffffU ) < 0xfffe ) { + if ( ( table[table[wc >> 8] * 32 + ( ( wc & 255 ) >> 3 )] >> ( wc & 7 ) ) & 1 ) + return 0; + if ( ( wtable[wtable[wc >> 8] * 32 + ( ( wc & 255 ) >> 3 )] >> ( wc & 7 ) ) & 1 ) + return 2; + return 1; + } + + if ( ( wc & 0xfffe ) == 0xfffe ) + return -1; + if ( wc - 0x20000U < 0x20000 ) + return 2; + if ( wc == 0xe0001 || wc - 0xe0020U < 0x5f || wc - 0xe0100 < 0xef ) + return 0; + + return 1; +} + +void TerminalEmulator::tputc( Rune u ) { + char c[UTF_SIZ]; + int control; + int width; + size_t len; + Glyph* gp; + + control = ISCONTROL( u ); + if ( u < 127 || !IS_SET( MODE_UTF8 ) ) { + c[0] = u; + width = (int)( len = 1 ); + } else { + len = utf8encode( u, c ); + // if (!control && (width = wcwidth9(u)) < 0) { + if ( !control && ( width = Hexe_wcwidth( u ) ) < 0 ) { + if ( width == -1 ) { + width = 0; + } else { + width = 1; + } + } + } + + if ( IS_SET( MODE_PRINT ) ) + tprinter( c, len ); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if ( term.esc & ESC_STR ) { + if ( u == '\a' || u == 030 || u == 032 || u == 033 || ISCONTROLC1( u ) ) { + term.esc &= ~( ESC_START | ESC_STR ); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if ( strescseq.len + len >= strescseq.siz ) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if ( strescseq.siz > ( SIZE_MAX - UTF_SIZ ) / 2 ) + return; + strescseq.siz *= 2; + strescseq.buf = (char*)xrealloc( strescseq.buf, strescseq.siz ); + } + + memmove( &strescseq.buf[strescseq.len], c, len ); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if ( control ) { + tcontrolcode( u ); + /* + * control codes are not shown ever + */ + if ( !term.esc ) + term.lastc = 0; + return; + } else if ( term.esc & ESC_START ) { + if ( term.esc & ESC_CSI ) { + csiescseq.buf[csiescseq.len++] = u; + if ( BETWEEN( u, 0x40, 0x7E ) || csiescseq.len >= sizeof( csiescseq.buf ) - 1 ) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if ( term.esc & ESC_UTF8 ) { + tdefutf8( u ); + } else if ( term.esc & ESC_ALTCHARSET ) { + tdeftran( u ); + } else if ( term.esc & ESC_TEST ) { + tdectest( u ); + } else { + if ( !eschandle( u ) ) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if ( selected( term.c.x, term.c.y ) ) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if ( IS_SET( MODE_WRAP ) && ( term.c.state & CURSOR_WRAPNEXT ) ) { + gp->mode |= ATTR_WRAP; + tnewline( 1 ); + gp = &term.line[term.c.y][term.c.x]; + } + + if ( IS_SET( MODE_INSERT ) && term.c.x + width < term.col ) + memmove( gp + width, gp, ( term.col - term.c.x - width ) * sizeof( Glyph ) ); + + if ( term.c.x + width > term.col ) { + tnewline( 1 ); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar( u, &term.c.attr, term.c.x, term.c.y ); + term.lastc = u; + + if ( width == 2 ) { + gp->mode |= ATTR_WIDE; + if ( term.c.x + 1 < term.col ) { + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + if ( is_emoji( u ) ) { + gp->mode |= ATTR_EMOJI; + } + } + + if ( term.c.x + width < term.col ) { + tmoveto( term.c.x + width, term.c.y ); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int TerminalEmulator::twrite( const char* buf, int buflen, int show_ctrl ) { + size_t charsize; + Rune u; + size_t n; + + for ( n = 0; n < buflen; n += charsize ) { + if ( IS_SET( MODE_UTF8 ) ) { + /* process a complete utf8 char */ + charsize = utf8decode( buf + n, &u, buflen - n ); + if ( charsize == 0 ) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if ( show_ctrl && ISCONTROL( u ) ) { + if ( u & 0x80 ) { + u &= 0x7f; + tputc( '^' ); + tputc( '[' ); + } else if ( u != '\n' && u != '\r' && u != '\t' ) { + u ^= 0x40; + tputc( '^' ); + } + } + tputc( u ); + } + return (int)n; +} + +void TerminalEmulator::tresize( int col, int row ) { + int i; + int minrow = MIN( row, term.row ); + int mincol = MIN( col, term.col ); + int* bp; + TCursor c; + + if ( col < 1 || row < 1 ) { + fprintf( stderr, "tresize: error resizing to %dx%d\n", col, row ); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for ( i = 0; i <= term.c.y - row; i++ ) { + free( term.line[i] ); + free( term.alt[i] ); + } + /* ensure that both src and dst are not NULL */ + if ( i > 0 ) { + memmove( term.line, term.line + i, row * sizeof( Line ) ); + memmove( term.alt, term.alt + i, row * sizeof( Line ) ); + } + for ( i += row; i < term.row; i++ ) { + free( term.line[i] ); + free( term.alt[i] ); + } + + /* resize to new height */ + term.line = (Line*)xrealloc( term.line, row * sizeof( Line ) ); + term.alt = (Line*)xrealloc( term.alt, row * sizeof( Line ) ); + term.dirty = (int*)xrealloc( term.dirty, row * sizeof( *term.dirty ) ); + term.tabs = (int*)xrealloc( term.tabs, col * sizeof( *term.tabs ) ); + + /* resize each row to new width, zero-pad if needed */ + for ( i = 0; i < minrow; i++ ) { + term.line[i] = (Line)xrealloc( term.line[i], col * sizeof( Glyph ) ); + term.alt[i] = (Line)xrealloc( term.alt[i], col * sizeof( Glyph ) ); + } + + /* allocate any new rows */ + for ( /* i = minrow */; i < row; i++ ) { + term.line[i] = (Line)xmalloc( col * sizeof( Glyph ) ); + term.alt[i] = (Line)xmalloc( col * sizeof( Glyph ) ); + } + if ( col > term.col ) { + bp = term.tabs + term.col; + + memset( bp, 0, sizeof( *term.tabs ) * ( col - term.col ) ); + while ( --bp > term.tabs && !*bp ) + /* nothing */; + for ( bp += tabspaces; bp < term.tabs + col; bp += tabspaces ) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll( 0, row - 1 ); + /* make use of the LIMIT in tmoveto */ + tmoveto( term.c.x, term.c.y ); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for ( i = 0; i < 2; i++ ) { + if ( mincol < col && 0 < minrow ) { + tclearregion( mincol, 0, col - 1, minrow - 1 ); + } + if ( 0 < col && minrow < row ) { + tclearregion( 0, minrow, col - 1, row - 1 ); + } + tswapscreen(); + tcursor( CURSOR_LOAD ); + } + term.c = c; +} + +void TerminalEmulator::resettitle( void ) { + auto dpy = m_dpy.lock(); + if ( dpy ) + dpy->SetTitle( NULL ); +} + +void TerminalEmulator::drawregion( TerminalDisplay& dpy, int x1, int y1, int x2, int y2 ) { + int y; + + for ( y = y1; y < y2; y++ ) { + if ( !term.dirty[y] ) + continue; + + term.dirty[y] = 0; + + dpy.DrawLine( term.line[y], x1, y, x2 ); + } +} + +void TerminalEmulator::draw( void ) { + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + { + auto dpy = m_dpy.lock(); + if ( !dpy ) + return; + + dpy->DrawBegin( term.col, term.row ); + + /* adjust cursor position */ + LIMIT( term.ocx, 0, term.col - 1 ); + LIMIT( term.ocy, 0, term.row - 1 ); + if ( term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY ) + term.ocx--; + if ( term.line[term.c.y][cx].mode & ATTR_WDUMMY ) + cx--; + + drawregion( *dpy, 0, 0, term.col, term.row ); + dpy->DrawCursor( cx, term.c.y, term.line[term.c.y][cx], term.ocx, term.ocy, + term.line[term.ocy][term.ocx] ); + term.ocx = cx; + term.ocy = term.c.y; + + dpy->DrawEnd(); + } + // if (ocx != term.ocx || ocy != term.ocy) + // xximspot(term.ocx, term.ocy); +} + +void TerminalEmulator::Redraw() { + tfulldirt(); + draw(); +} + +void TerminalEmulator::xsetmode( int set, unsigned int mode ) { + auto dpy = m_dpy.lock(); + dpy->SetMode( (win_mode)mode, set ); +} + +void TerminalEmulator::xsetpointermotion( int ) { + // TODO: Figure something out +} + +std::unique_ptr +TerminalEmulator::Create( PtyPtr&& pty, ProcPtr&& process, + const std::shared_ptr& display ) { + if ( !pty || !process ) { + fprintf( stderr, "Must provide valid pseudoterminal and process" ); + return nullptr; + } + + return std::unique_ptr( + new TerminalEmulator( std::move( pty ), std::move( process ), display ) ); +} + +TerminalEmulator::TerminalEmulator( PtyPtr&& pty, ProcPtr&& process, + const std::shared_ptr& display ) : + m_dpy( display ), + m_pty( std::move( pty ) ), + m_process( std::move( process ) ), + m_colorsLoaded( false ), + m_exitCode( 1 ), + m_status( STARTING ), + m_buflen( 0 ), + defaultfg( 7 ), + defaultbg( 0 ), + defaultcs( 7 ), + defaultrcs( 0 ), + allowaltscreen( 1 ), + allowwindowops( 1 ) { + memset( m_buf, 0, sizeof( m_buf ) ); + memset( &term, 0, sizeof( term ) ); + memset( &sel, 0, sizeof( sel ) ); + memset( &csiescseq, 0, sizeof( csiescseq ) ); + memset( &strescseq, 0, sizeof( strescseq ) ); + + int col = m_pty->GetNumColumns(); + int row = m_pty->GetNumRows(); + + tnew( col, row ); + if ( display ) { + display->SetCursorMode( (Hexe::Terminal::cursor_mode)cursorshape ); + display->Attach( this ); + LoadColors(); + } + selinit(); + resettitle(); +} + +TerminalEmulator::~TerminalEmulator() { + { + auto dpy = m_dpy.lock(); + if ( dpy ) + dpy->Detach( this ); + } + m_process->Terminate(); +} + +void TerminalEmulator::LogError( const char* msg ) { + fprintf( stderr, "%s\n", msg ); +} + +void TerminalEmulator::Terminate() { + if ( m_status == STARTING || m_status == RUNNING ) { + m_process->Terminate(); + m_exitCode = 1; + m_status = TERMINATED; + } +} + +void TerminalEmulator::OnProcessExit( int exitCode ) {} + +void TerminalEmulator::SetClipboard( const char* str ) { + auto dpy = m_dpy.lock(); + if ( !dpy ) + return; + dpy->SetClipboard( str ); +} + +void TerminalEmulator::LoadColors() { + auto dpy = m_dpy.lock(); + if ( !dpy ) + return; + + dpy->ResetColors(); +} + +int TerminalEmulator::ResetColor( int i, const char* name ) { + auto dpy = m_dpy.lock(); + if ( !dpy ) + return 0; + + return dpy->ResetColor( i, name ); +} + +bool TerminalEmulator::HasExited() const { + return m_status == TERMINATED; +} + +int TerminalEmulator::GetExitCode() const { + return m_exitCode; +} + +void TerminalEmulator::Resize( int columns, int rows ) { + if ( !m_pty->Resize( columns, rows ) ) { + _die( "Failed to resize pty!" ); + return; + } + tresize( columns, rows ); + Redraw(); +} + +void TerminalEmulator::Update() { + if ( m_status == TerminalEmulator::STARTING ) { + m_status = TerminalEmulator::RUNNING; + } else if ( m_status != TerminalEmulator::RUNNING ) { + return; + } + + int n = 10; + while ( ttyread() > 0 && n > 0 ) { + --n; + } + + // TODO: Handle blink + + // TODO: Do not draw every update + draw(); + + m_process->CheckExitStatus(); + if ( m_process->HasExited() ) { + m_exitCode = m_process->GetExitCode(); + m_status = TERMINATED; + OnProcessExit( m_exitCode ); + } +} diff --git a/src/tools/eterm/terminal/terminalemulator.hpp b/src/tools/eterm/terminal/terminalemulator.hpp new file mode 100644 index 000000000..fb377bee6 --- /dev/null +++ b/src/tools/eterm/terminal/terminalemulator.hpp @@ -0,0 +1,271 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen +// Copyright (c) 2014 - 2020 Hiltjo Posthuma +// Copyright (c) 2018 Devin J.Pohly +// Copyright (c) 2014 - 2017 Quentin Rameau +// Copyright (c) 2009 - 2012 Aurélien APTEL +// Copyright (c) 2008 - 2017 Anselm R Garbe +// Copyright (c) 2012 - 2017 Roberto E.Vargas Caballero +// Copyright (c) 2012 - 2016 Christoph Lohmann<20h at r - 36 dot net> +// Copyright (c) 2013 Eon S.Jeon +// Copyright (c) 2013 Alexander Sedov +// Copyright (c) 2013 Mark Edgar +// Copyright (c) 2013 - 2014 Eric Pruitt +// Copyright (c) 2013 Michael Forney +// Copyright (c) 2013 - 2014 Markus Teich +// Copyright (c) 2014 - 2015 Laslo Hunhold + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include "../system/iprocess.hpp" +#include "ipseudoterminal.hpp" +#include "terminaldisplay.hpp" +#include "types.hpp" +#include +#include +#include +#ifdef WIN32 +#include +#endif + +namespace Hexe { namespace Terminal { +constexpr int ESC_BUF_SIZ = 512; +constexpr int ESC_ARG_SIZ = 16; +constexpr int STR_BUF_SIZ = ESC_BUF_SIZ; +constexpr int STR_ARG_SIZ = ESC_ARG_SIZ; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line* line; /* screen */ + Line* alt; /* alternate screen */ + int* dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int* tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char* buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char* args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +int isboxdraw( Rune ); +ushort boxdrawindex( const Glyph* ); + +class TerminalEmulator final { + public: + using DpyPtr = std::weak_ptr; + using PtyPtr = std::unique_ptr; + using ProcPtr = std::unique_ptr; + + private: + DpyPtr m_dpy; + PtyPtr m_pty; + ProcPtr m_process; + + bool m_colorsLoaded; + int m_exitCode; + + enum { STARTING = 0, RUNNING, TERMINATED } m_status; + + char m_buf[8192]; + int m_buflen; + + private: + Term term; + Selection sel; + CSIEscape csiescseq; + STREscape strescseq; + + uint32_t defaultfg; + uint32_t defaultbg; + uint32_t defaultcs; + uint32_t defaultrcs; + + int allowaltscreen; + int allowwindowops; + + private: + void SetClipboard( const char* str ); + + void LoadColors(); + int ResetColor( int x, const char* name ); + + private: + void OnProcessExit( int exitCode ); + + private: + void csidump(); + void csihandle(); + void csiparse(); + void csireset(); + + int eschandle( uchar ); + + void strdump(); + void strhandle(); + void strparse(); + void strreset(); + + void tprinter( const char*, size_t ); + void tdumpsel(); + void tdumpline( int ); + void tdump(); + void tclearregion( int, int, int, int ); + void tcursor( int ); + void tdeletechar( int ); + void tdeleteline( int ); + void tinsertblank( int ); + void tinsertblankline( int ); + int tlinelen( int ); + void tmoveto( int, int ); + void tmoveato( int, int ); + void tnewline( int ); + void tputtab( int ); + void tputc( Rune ); + void treset(); + void tscrollup( int, int ); + void tscrolldown( int, int ); + void tsetattr( int*, int ); + void tsetchar( Rune, Glyph*, int, int ); + void tsetdirt( int, int ); + void tsetscroll( int, int ); + void tswapscreen(); + void tsetmode( int, int, int*, int ); + int twrite( const char*, int, int ); + void tfulldirt(); + void tcontrolcode( uchar ); + void tdectest( char ); + void tdefutf8( char ); + int32_t tdefcolor( int*, int*, int ); + void tdeftran( char ); + void tstrsequence( uchar ); + + void selnormalize(); + void selscroll( int, int ); + void selsnap( int*, int*, int ); + + private: + void _die( const char*, ... ); + void drawregion( TerminalDisplay& dpy, int, int, int, int ); + void draw(); + + int tattrset( int ); + void tnew( int, int ); + void tresize( int, int ); + void tsetdirtattr( int ); + + void ttyhangup(); + size_t ttyread(); + void ttywrite( const char*, size_t, int ); + void ttywriteraw( const char*, size_t ); + + void resettitle(); + + public: + void selclear(); + void selinit(); + void selstart( int, int, int ); + void selextend( int, int, int, int ); + int selected( int, int ); + char* getsel(); + + private: + void xbell(); + void xclipcopy(); + + int xsetcolorname( int, const char* ); + void xseticontitle( char* ); + void xsettitle( char* ); + int xsetcursor( int ); + void xsetmode( int, unsigned int ); + void xsetpointermotion( int ); + void xsetsel( char* ); + void xximspot( int, int ); + + private: + TerminalEmulator( PtyPtr&& pty, ProcPtr&& process, + const std::shared_ptr& display ); + + public: + ~TerminalEmulator(); + TerminalEmulator( const TerminalEmulator& ) = delete; + TerminalEmulator( TerminalEmulator&& ) = delete; + TerminalEmulator& operator=( const TerminalEmulator& ) = delete; + TerminalEmulator& operator=( TerminalEmulator&& ) = delete; + + public: + static std::unique_ptr + Create( PtyPtr&& pty, ProcPtr&& process, const std::shared_ptr& display ); + + public: + void Resize( int columns, int rows ); + void Redraw(); + void LogError( const char* err ); + void Update(); + void Terminate(); + bool HasExited() const; + int GetExitCode() const; + inline bool IsSelected( int column, int row ) { return selected( column, row ); } + inline uint32_t GetDefaultForeground() const { return defaultfg; } + inline uint32_t GetDefaultBackground() const { return defaultbg; } + inline uint32_t GetDefaultCursorColor() const { return defaultcs; } + inline uint32_t GetDefaultReverseCursorColor() const { return defaultrcs; } + inline int GetNumColumns() const { return term.col; } + inline int GetNumRows() const { return term.row; } + inline int Write( const char* buf, size_t buflen ) { return m_pty->Write( buf, (int)buflen ); } + + public: + void printscreen( const Arg* ); + void printsel( const Arg* ); + void sendbreak( const Arg* ); + void toggleprinter( const Arg* ); +}; + +}} // namespace Hexe::Terminal diff --git a/src/tools/eterm/terminal/types.hpp b/src/tools/eterm/terminal/types.hpp new file mode 100644 index 000000000..68df8787e --- /dev/null +++ b/src/tools/eterm/terminal/types.hpp @@ -0,0 +1,169 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once + +#include +#include + +namespace Hexe { namespace Terminal { + +enum cursor_mode { + BlinkingBlock = 0, + BlinkingBlockDefault = 1, + SteadyBlock = 2, + BlinkUnderline = 3, + SteadyUnderline = 4, + BlinkBar = 5, + SteadyBar = 6, + StExtension = 7, + MAX_CURSOR +}; + +enum win_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_MOUSEBTN = 1 << 3, + MODE_MOUSEMOTION = 1 << 4, + MODE_REVERSE = 1 << 5, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_8BIT = 1 << 10, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_FOCUS = 1 << 13, + MODE_MOUSEX10 = 1 << 14, + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, + MODE_MOUSE = MODE_MOUSEBTN | MODE_MOUSEMOTION | MODE_MOUSEX10 | MODE_MOUSEMANY, +}; + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOXDRAW = 1 << 11, + ATTR_EMOJI = 1 << 12, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { SEL_IDLE = 0, SEL_EMPTY = 1, SEL_READY = 2 }; + +enum selection_type { SEL_REGULAR = 1, SEL_RECTANGULAR = 2 }; + +enum selection_snap { SNAP_WORD = 1, SNAP_LINE = 2 }; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph* Line; + +union Arg { + int i; + uint ui; + float f; + const void* v; + const char* s; + + Arg() { memset( this, 0, sizeof( *this ) ); } + Arg( int x ) { i = x; } + Arg( uint x ) { ui = x; } + Arg( float x ) { f = x; } + Arg( const void* x ) { v = x; } + Arg( const char* x ) { s = x; } +}; + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { CURSOR_SAVE, CURSOR_LOAD }; + +enum cursor_state { CURSOR_DEFAULT = 0, CURSOR_WRAPNEXT = 1, CURSOR_ORIGIN = 2 }; + +enum charset { CS_GRAPHIC0, CS_GRAPHIC1, CS_UK, CS_USA, CS_MULTI, CS_GER, CS_FIN }; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +}} // namespace Hexe::Terminal diff --git a/src/tools/eterm/terminal/wide.hpp b/src/tools/eterm/terminal/wide.hpp new file mode 100644 index 000000000..0fae5e29c --- /dev/null +++ b/src/tools/eterm/terminal/wide.hpp @@ -0,0 +1,45 @@ +16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 18, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 21, 22, + 23, 24, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 26, 16, 16, 16, 16, + 27, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 28, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 16, 16, 16, 29, 30, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 31, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 252, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 251, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, 0, + 0, 255, 15, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 127, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 224, 255, 255, 255, + 255, 63, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 7, + 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 0, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 31, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 15, 0, 255, 255, 127, 248, 255, 255, 255, 255, 255, 15, 0, 0, 255, 3, 0, 0, + 255, 255, 255, 255, 247, 255, 127, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 127, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 255, 255, 255, 255, 255, 7, 255, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/src/tools/eterm/terminal/windowserrors.hpp b/src/tools/eterm/terminal/windowserrors.hpp new file mode 100644 index 000000000..f7f8923aa --- /dev/null +++ b/src/tools/eterm/terminal/windowserrors.hpp @@ -0,0 +1,55 @@ +// The MIT License (MIT) + +// Copyright (c) 2020 Fredrik A. Kristiansen + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#pragma once +#ifdef WIN32 +#include +#include +#include +#include + +static void PrintErrorResult( HRESULT hr ) { + _com_error err( hr ); + LPCTSTR errMsg = err.ErrorMessage(); + std::cerr << "ERROR: " << errMsg << std::endl; +} + +static void PrintWinApiError( DWORD error ) { + if ( error == 0 ) + return; + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)&messageBuffer, 0, NULL ); + + std::string message( messageBuffer, size ); + + // Free the buffer. + LocalFree( messageBuffer ); + + std::cerr << "ERROR: " << message << std::endl; +} + +static void PrintLastWinApiError( void ) { + PrintWinApiError( GetLastError() ); +} + +#endif \ No newline at end of file