Terminal: multiple issues with the copy/paste operations fixed. Now uses the primary selection when corresponds (middle mouse click, shift+insert). Fixed paste operation, it does not hang the app, pastes instantly contents and behaves exactly the same as other terminals.

Incorporated new features into eterm.
This commit is contained in:
Martín Lucas Golini
2025-12-20 01:58:21 -03:00
parent e5a67727c4
commit 35fbf7c733
12 changed files with 195 additions and 49 deletions

View File

@@ -79,6 +79,8 @@ class PseudoTerminal final : public IPseudoTerminal {
AutoHandle mMaster;
AutoHandle mSlave;
std::string mWriteBuffer;
PseudoTerminal( int columns, int rows, AutoHandle&& master, AutoHandle&& slave );
#endif
};

View File

@@ -14,7 +14,6 @@
#include <eterm/terminal/terminalcolorscheme.hpp>
#include <eterm/terminal/terminalemulator.hpp>
#include <atomic>
#include <memory>
#include <unordered_map>
#include <vector>
@@ -33,6 +32,7 @@ namespace eterm { namespace Terminal {
enum class TerminalShortcutAction {
PASTE,
PASTE_SELECTION,
COPY,
SCROLLUP_ROW,
SCROLLDOWN_ROW,
@@ -283,7 +283,6 @@ class TerminalDisplay : public ITerminalDisplay {
std::vector<TerminalGlyph> mBuffer;
std::vector<Color> mColors;
std::shared_ptr<TerminalEmulator> mTerminal;
mutable String mClipboard;
mutable std::string mClipboardUtf8;
Uint32 mNumCallBacks;
std::map<Uint32, EventFunc> mCallbacks;
@@ -371,6 +370,8 @@ class TerminalDisplay : public ITerminalDisplay {
void drawBg( bool toFBO = false );
void sanitizeInput( std::string& input );
};
}} // namespace eterm::Terminal

View File

@@ -27,6 +27,7 @@
#include <string.h>
#include <string>
#include <string_view>
#include <unordered_map>
using namespace std::literals;
@@ -82,6 +83,17 @@ struct TerminalCursorHelper {
}
return "steady_underline";
}
static std::unordered_map<std::string, TerminalCursorMode> getTerminalCursorModeMap() {
return {
{ "blinking_block", TerminalCursorMode::BlinkingBlock },
{ "steady_block", TerminalCursorMode::SteadyBlock },
{ "blink_underline", TerminalCursorMode::BlinkUnderline },
{ "steady_underline", TerminalCursorMode::SteadyUnderline },
{ "blink_bar", TerminalCursorMode::BlinkBar },
{ "steady_bar", TerminalCursorMode::SteadyBar },
};
}
};
enum TerminalWinMode {

View File

@@ -42,11 +42,13 @@
#include <unistd.h>
using namespace EE::System;
#if defined( EE_PLATFORM_POSIX )
#include <fcntl.h>
#endif
namespace eterm { namespace Terminal {
#if EE_PLATFORM == EE_PLATFORM_ANDROID
#include <fcntl.h>
int openpty( int* amaster, int* aslave, char* name, struct termios* termp, struct winsize* winp ) {
int master, slave;
char* name_slave;
@@ -129,20 +131,29 @@ int PseudoTerminal::getNumRows() const {
}
int PseudoTerminal::write( const char* s, size_t n ) {
size_t c = n;
while ( n > 0 ) {
ssize_t r = ::write( (int)mMaster, s, n );
if ( r < 0 ) {
return -1;
}
n -= r;
s += r;
if ( !mWriteBuffer.empty() ) {
mWriteBuffer.append( s, n );
return n;
}
return c;
ssize_t r = ::write( (int)mMaster, s, n );
if ( r < 0 ) {
if ( errno == EAGAIN || errno == EWOULDBLOCK ) {
mWriteBuffer.append( s, n );
return n;
}
return -1;
}
if ( (size_t)r < n ) {
mWriteBuffer.append( s + r, n - r );
}
return n;
}
int PseudoTerminal::read( char* s, size_t n, bool block ) {
if ( !block ) {
if ( mWriteBuffer.empty() && !block ) {
struct pollfd pfd;
pfd.fd = mMaster.handle();
pfd.events = POLLIN;
@@ -155,7 +166,36 @@ int PseudoTerminal::read( char* s, size_t n, bool block ) {
perror( "PseudoTerminal::read(poll)" );
return -1;
}
} else if ( !mWriteBuffer.empty() || block ) {
struct pollfd pfd;
pfd.fd = mMaster.handle();
pfd.events = POLLIN;
if ( !mWriteBuffer.empty() )
pfd.events |= POLLOUT;
pfd.revents = 0;
auto i = poll( &pfd, 1, block ? -1 : 0 );
if ( i < 0 && errno != EAGAIN && errno != EINTR ) {
perror( "PseudoTerminal::read(poll)" );
return -1;
}
if ( ( pfd.revents & POLLOUT ) && !mWriteBuffer.empty() ) {
ssize_t r = ::write( (int)mMaster, mWriteBuffer.c_str(), mWriteBuffer.size() );
if ( r > 0 ) {
if ( (size_t)r == mWriteBuffer.size() ) {
mWriteBuffer.clear();
} else {
mWriteBuffer = mWriteBuffer.substr( r );
}
} else if ( r < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) {
perror( "PseudoTerminal::read(write)" ); // Log error but don't fail read
}
}
if ( !( pfd.revents & POLLIN ) )
return 0;
}
ssize_t r = ::read( mMaster.handle(), s, n );
return (int)r;
}
@@ -180,6 +220,9 @@ std::unique_ptr<PseudoTerminal> PseudoTerminal::create( int columns, int rows )
return nullptr;
}
int flags = fcntl( master, F_GETFL, 0 );
fcntl( master, F_SETFL, flags | O_NONBLOCK );
return std::unique_ptr<PseudoTerminal>(
new PseudoTerminal( columns, rows, std::move( master ), std::move( slave ) ) );
#endif

View File

@@ -75,7 +75,7 @@ TerminalKeyMap::TerminalKeyMap( const TerminalKey keys[], size_t keysLen,
}
static TerminalShortcut shortcuts[] = {
{ KEY_INSERT, KEYMOD_SHIFT, TerminalShortcutAction::PASTE, 0, 0 },
{ KEY_INSERT, KEYMOD_SHIFT, TerminalShortcutAction::PASTE_SELECTION, 0, 0 },
{ KEY_V, KEYMOD_SHIFT | KEYMOD_CTRL, TerminalShortcutAction::PASTE, 0, 0 },
{ KEY_C, KEYMOD_SHIFT | KEYMOD_CTRL, TerminalShortcutAction::COPY, 0, 0 },
{ KEY_PAGEUP, KEYMOD_SHIFT, TerminalShortcutAction::SCROLLUP_SCREEN, 0, 0, -1 },
@@ -92,7 +92,9 @@ static TerminalMouseShortcut mouseShortcuts[] = {
{ EE_BUTTON_WUMASK, 0, TerminalShortcutAction::SCROLLUP_ROW, 0, 0, -1 },
{ EE_BUTTON_WDMASK, 0, TerminalShortcutAction::SCROLLDOWN_ROW, 0, 0, -1 },
{ EE_BUTTON_WUMASK, KEYMOD_CTRL, TerminalShortcutAction::FONTSIZE_GROW, 0, 0, 0 },
{ EE_BUTTON_WDMASK, KEYMOD_CTRL, TerminalShortcutAction::FONTSIZE_SHRINK, 0, 0, 0 } };
{ EE_BUTTON_WDMASK, KEYMOD_CTRL, TerminalShortcutAction::FONTSIZE_SHRINK, 0, 0, 0 },
{ EE_BUTTON_MMASK, 0, TerminalShortcutAction::PASTE_SELECTION, 0, 0, -1 },
};
static TerminalKey keys[] = {
/* keysym mask string appkey appcursor */
@@ -678,8 +680,29 @@ void TerminalDisplay::action( TerminalShortcutAction action ) {
switch ( action ) {
case TerminalShortcutAction::PASTE: {
getClipboard();
if ( !mClipboard.empty() )
mTerminal->ttywrite( mClipboardUtf8.c_str(), mClipboardUtf8.size(), 1 );
if ( !mClipboardUtf8.empty() ) {
if ( mMode & MODE_BRCKTPASTE ) {
mTerminal->write( "\033[200~", 6 );
mTerminal->write( mClipboardUtf8.c_str(), mClipboardUtf8.size() );
mTerminal->write( "\033[201~", 6 );
} else {
mTerminal->write( mClipboardUtf8.c_str(), mClipboardUtf8.size() );
}
}
break;
}
case TerminalShortcutAction::PASTE_SELECTION: {
std::string selection = mWindow->getClipboard()->getPrimarySelectionText();
sanitizeInput( selection );
if ( !selection.empty() ) {
if ( mMode & MODE_BRCKTPASTE ) {
mTerminal->write( "\033[200~", 6 );
mTerminal->write( selection.c_str(), selection.size() );
mTerminal->write( "\033[201~", 6 );
} else {
mTerminal->write( selection.c_str(), selection.size() );
}
}
break;
}
case TerminalShortcutAction::COPY: {
@@ -752,24 +775,37 @@ void TerminalDisplay::setIconTitle( const char* title ) {
void TerminalDisplay::setClipboard( const char* text ) {
if ( text == nullptr )
return;
mClipboard = text;
mClipboardUtf8 = mClipboard.toUtf8();
mWindow->getClipboard()->setText( mClipboard );
mWindow->getClipboard()->setText( text );
}
const char* TerminalDisplay::getClipboard() const {
mClipboard = mWindow->getClipboard()->getText();
#ifdef _WIN32
if ( mPasteNewlineFix ) {
size_t pos;
while ( ( pos = mClipboard.find( '\r' ) ) != std::string::npos )
mClipboard.erase( pos, 1 );
}
#endif
mClipboardUtf8 = mClipboard.toUtf8();
mClipboardUtf8 = mWindow->getClipboard()->getText();
const_cast<TerminalDisplay*>( this )->sanitizeInput( mClipboardUtf8 );
return mClipboardUtf8.c_str();
}
void TerminalDisplay::sanitizeInput( std::string& input ) {
if ( !mPasteNewlineFix )
return;
// Replace isolated \n (not part of \r\n) with \r
// This handles Unix-style clipboard text (\n) → simulate Enter as \r
// Preserves \r\n (Windows) as a single line ending → \r
// Keeps lone \r as-is
size_t pos = 0;
while ( ( pos = input.find( '\n', pos ) ) != std::string::npos ) {
if ( pos == 0 || input[pos - 1] != '\r' ) {
input.replace( pos, 1, "\r" );
} else {
// Part of \r\n: remove the \n, keep the preceding \r
input.erase( pos, 1 );
++pos; // Skip over the kept \r if needed
}
++pos;
}
}
bool TerminalDisplay::drawBegin( Uint32 columns, Uint32 rows ) {
if ( columns != mColumns || rows != mRows ) {
TerminalGlyph defaultGlyph{};
@@ -832,12 +868,13 @@ void TerminalDisplay::onMouseDoubleClick( const Vector2i& pos, const Uint32& fla
void TerminalDisplay::onMouseMove( const Vector2i& pos, const Uint32& flags ) {
bool shiftPressed = ( mWindow->getInput()->getModState() & KEYMOD_SHIFT ) != 0;
auto mousePos = mWindow->getInput()->getRelativeMousePos();
bool isCapturingMouse = isAppCapturingMouse() && !shiftPressed;
if ( !isAltScr() && !isCapturingMouse && ( flags & EE_BUTTON_LMASK ) &&
mAlreadyClickedLButton ) {
Vector2f relPos = { pos.x - mPosition.x - mPadding.Left,
pos.y - mPosition.y - mPadding.Top };
Vector2f relPos = { mousePos.x - mPosition.x - mPadding.Left,
mousePos.y - mPosition.y - mPadding.Top };
if ( mLastAutoScroll.getElapsedTime() >= Milliseconds( 16 ) ) {
if ( relPos.y < 0 ) {
@@ -882,22 +919,8 @@ void TerminalDisplay::onMouseDown( const Vector2i& pos, const Uint32& flags ) {
invalidateLines();
mWindow->getInput()->captureMouse( true );
}
} else if ( flags & EE_BUTTON_MMASK ) {
if ( !mAlreadyClickedMButton ) {
mAlreadyClickedMButton = true;
auto selection = mTerminal->getSelection();
if ( !selection.empty() && selection != "\n" ) {
for ( auto& chr : selection )
onTextInput( chr );
} else {
getClipboard();
if ( !mClipboard.empty() ) {
for ( auto& chr : mClipboard )
onTextInput( chr );
}
}
}
}
if ( ( flags & EE_BUTTON_LMASK ) ) {
if ( !mAlreadyClickedLButton ) {
mAlreadyClickedLButton = true;
@@ -905,6 +928,15 @@ void TerminalDisplay::onMouseDown( const Vector2i& pos, const Uint32& flags ) {
return;
}
}
if ( ( flags & EE_BUTTON_MMASK ) ) {
if ( !mAlreadyClickedMButton ) {
mAlreadyClickedMButton = true;
} else {
return;
}
}
mTerminal->mousereport( TerminalMouseEventType::MouseButtonDown, positionToGrid( pos ), flags,
mWindow->getInput()->getModState() );
}
@@ -914,6 +946,11 @@ void TerminalDisplay::onMouseUp( const Vector2i& pos, const Uint32& flags ) {
mDraggingSel = false;
}
if ( flags & EE_BUTTON_LMASK ) {
auto selection = mTerminal->getSelection();
mWindow->getClipboard()->setPrimarySelectionText( selection );
}
Uint32 smod = sanitizeMod( mWindow->getInput()->getModState() );
if ( flags & EE_BUTTON_LMASK ) {

View File

@@ -103,6 +103,8 @@ UITerminal::UITerminal( const std::shared_ptr<TerminalDisplay>& terminalDisplay
setCommand( "terminal-font-size-shrink",
[this] { mTerm->action( TerminalShortcutAction::FONTSIZE_SHRINK ); } );
setCommand( "terminal-paste", [this] { mTerm->action( TerminalShortcutAction::PASTE ); } );
setCommand( "terminal-paste-selection",
[this] { mTerm->action( TerminalShortcutAction::PASTE_SELECTION ); } );
setCommand( "terminal-copy", [this] { mTerm->action( TerminalShortcutAction::COPY ); } );
setCommand( "terminal-open-link",
[this] { Engine::instance()->openURI( mTerm->getTerminal()->getSelection() ); } );