Rename TerminalController to TerminalSession.

This commit is contained in:
Martín Lucas Golini
2026-08-31 01:15:28 -03:00
parent d0aa310362
commit bfa9b6ab60
6 changed files with 255 additions and 262 deletions
@@ -12,7 +12,7 @@
#include <eepp/window/window.hpp>
#include <eterm/system/iprocessfactory.hpp>
#include <eterm/terminal/terminalcolorscheme.hpp>
#include <eterm/terminal/terminalcontroller.hpp>
#include <eterm/terminal/terminalsession.hpp>
#include <memory>
#include <unordered_map>
@@ -242,7 +242,7 @@ class TerminalDisplay {
void setPadding( const Rectf& padding );
const std::shared_ptr<TerminalController>& getController() const;
const std::shared_ptr<TerminalSession>& getSession() const;
std::string getSelection();
@@ -313,7 +313,7 @@ class TerminalDisplay {
protected:
EE::Window::Window* mWindow;
std::vector<Color> mColors;
std::shared_ptr<TerminalController> mController;
std::shared_ptr<TerminalSession> mSession;
std::shared_ptr<const TerminalSnapshot> mSnapshot;
mutable std::string mClipboardUtf8;
Uint32 mNumCallBacks{ 0 };
@@ -382,7 +382,7 @@ class TerminalDisplay {
void consumeSnapshot();
void drainControllerEvents();
void drainSessionEvents();
TerminalColorPalette makeColorPalette() const;
@@ -1,5 +1,5 @@
#ifndef ETERM_TERMINALCONTROLLER_HPP
#define ETERM_TERMINALCONTROLLER_HPP
#ifndef ETERM_TERMINALSESSION_HPP
#define ETERM_TERMINALSESSION_HPP
#include <eepp/math/vector2.hpp>
#include <eepp/window/keycodes.hpp>
@@ -69,13 +69,13 @@ struct TerminalColorPalette {
};
/**
* Per-terminal worker/controller.
* Per-terminal worker/session.
*
* After create() returns, the worker thread exclusively owns the emulator, PTY, process, parser,
* history, selection, and cursor. UI code may only enqueue commands, drain events, or retain an
* immutable snapshot returned by snapshot(). No controller callback is invoked by the worker.
* immutable snapshot returned by snapshot(). No session callback is invoked by the worker.
*/
class TerminalController final : public std::enable_shared_from_this<TerminalController> {
class TerminalSession final : public std::enable_shared_from_this<TerminalSession> {
public:
using PtyPtr = std::unique_ptr<IPseudoTerminal>;
using ProcPtr = std::unique_ptr<IProcess>;
@@ -104,16 +104,16 @@ class TerminalController final : public std::enable_shared_from_this<TerminalCon
PromptState promptState{ PromptState::Unknown };
};
static std::shared_ptr<TerminalController> create( PtyPtr&& pty, ProcPtr&& process,
size_t historySize,
TerminalColorPalette palette = {} );
static std::shared_ptr<TerminalSession> create( PtyPtr&& pty, ProcPtr&& process,
size_t historySize,
TerminalColorPalette palette = {} );
~TerminalController();
~TerminalSession();
TerminalController( const TerminalController& ) = delete;
TerminalController( TerminalController&& ) = delete;
TerminalController& operator=( const TerminalController& ) = delete;
TerminalController& operator=( TerminalController&& ) = delete;
TerminalSession( const TerminalSession& ) = delete;
TerminalSession( TerminalSession&& ) = delete;
TerminalSession& operator=( const TerminalSession& ) = delete;
TerminalSession& operator=( TerminalSession&& ) = delete;
void write( std::string data, bool mayEcho = true );
void writeRaw( std::string data );
@@ -218,8 +218,8 @@ class TerminalController final : public std::enable_shared_from_this<TerminalCon
PromptEventsCommand, TerminateCommand, RestartCommand, ResetCommand,
SelectionRequestCommand>;
TerminalController( PtyPtr&& pty, ProcPtr&& process, size_t historySize,
TerminalColorPalette palette );
TerminalSession( PtyPtr&& pty, ProcPtr&& process, size_t historySize,
TerminalColorPalette palette );
void start();
bool enqueue( Command&& command );
@@ -246,4 +246,4 @@ class TerminalController final : public std::enable_shared_from_this<TerminalCon
}} // namespace eterm::Terminal
#endif
#endif
@@ -450,15 +450,15 @@ std::shared_ptr<TerminalDisplay> TerminalDisplay::create(
std::shared_ptr<TerminalDisplay> terminal = std::shared_ptr<TerminalDisplay>(
new TerminalDisplay( window, font, fontSize, pixelsSize, useFrameBuffer ) );
terminal->mController =
TerminalController::create( std::move( pseudoTerminal ), std::move( process ), historySize,
terminal->makeColorPalette() );
if ( !terminal->mController ) {
terminal->mSession =
TerminalSession::create( std::move( pseudoTerminal ), std::move( process ), historySize,
terminal->makeColorPalette() );
if ( !terminal->mSession ) {
if ( freeProcessFactory )
eeSAFE_DELETE( processFactory );
return nullptr;
}
terminal->mController->setPresentationRate( presentationRateForWindow( window ) );
terminal->mSession->setPresentationRate( presentationRateForWindow( window ) );
terminal->mProgram = program;
terminal->mArgs = args;
terminal->mEnv = env;
@@ -473,8 +473,8 @@ std::shared_ptr<TerminalDisplay> TerminalDisplay::create(
}
TerminalDisplay::~TerminalDisplay() {
if ( mController )
mController->shutdown();
if ( mSession )
mSession->shutdown();
}
TerminalDisplay::TerminalDisplay( EE::Window::Window* window, Font* font, const Float& fontSize,
@@ -621,8 +621,8 @@ void TerminalDisplay::setPadding( const Rectf& padding ) {
}
}
const std::shared_ptr<TerminalController>& TerminalDisplay::getController() const {
return mController;
const std::shared_ptr<TerminalSession>& TerminalDisplay::getSession() const {
return mSession;
}
int TerminalDisplay::scrollSize() const {
@@ -638,7 +638,7 @@ int TerminalDisplay::scrollPosition() const {
}
Uint64 TerminalDisplay::scrollTo( int position ) {
return mController ? mController->scrollTo( position ) : 0;
return mSession ? mSession->scrollTo( position ) : 0;
}
Uint64 TerminalDisplay::lastAppliedScrollCommand() const {
@@ -679,13 +679,13 @@ const TerminalColorScheme& TerminalDisplay::getColorScheme() const {
void TerminalDisplay::setColorScheme( const TerminalColorScheme& colorScheme ) {
mColorScheme = colorScheme;
resetColors();
if ( mController )
mController->setColorPalette( makeColorPalette() );
if ( mSession )
mSession->setColorPalette( makeColorPalette() );
invalidateLines();
}
bool TerminalDisplay::isAppCapturingMouse() const {
return mController &&
return mSession &&
( mMode & ( MODE_MOUSEX10 | MODE_MOUSEBTN | MODE_MOUSEMOTION | MODE_MOUSEMANY ) );
}
@@ -711,7 +711,7 @@ void TerminalDisplay::setKeepAlive( bool keepAlive ) {
bool TerminalDisplay::update( bool isMouseOverMe ) {
consumeSnapshot();
drainControllerEvents();
drainSessionEvents();
if ( mFocus && isBlinkingCursor() && mClock.getElapsedTime().asSeconds() > 0.7 ) {
mMode ^= MODE_BLINK;
mClock.restart();
@@ -730,9 +730,9 @@ bool TerminalDisplay::update( bool isMouseOverMe ) {
}
void TerminalDisplay::consumeSnapshot() {
if ( !mController )
if ( !mSession )
return;
auto snapshot = mController->snapshot();
auto snapshot = mSession->snapshot();
if ( !snapshot || snapshot->generation == mSnapshotGeneration )
return;
@@ -781,56 +781,56 @@ void TerminalDisplay::consumeSnapshot() {
mDirty = true;
}
void TerminalDisplay::drainControllerEvents() {
if ( !mController )
void TerminalDisplay::drainSessionEvents() {
if ( !mSession )
return;
for ( auto& event : mController->drainEvents() ) {
for ( auto& event : mSession->drainEvents() ) {
switch ( event.type ) {
case TerminalController::EventType::Title:
case TerminalSession::EventType::Title:
sendEvent( { EventType::TITLE, std::move( event.data ) } );
break;
case TerminalController::EventType::IconTitle:
case TerminalSession::EventType::IconTitle:
sendEvent( { EventType::ICON_TITLE, std::move( event.data ) } );
break;
case TerminalController::EventType::ScrollPosition:
case TerminalSession::EventType::ScrollPosition:
sendEvent( { EventType::SCROLL_HISTORY } );
break;
case TerminalController::EventType::Bell:
case TerminalSession::EventType::Bell:
sendEvent( { EventType::BELL } );
break;
case TerminalController::EventType::Clipboard:
case TerminalSession::EventType::Clipboard:
setClipboard( event.data.c_str() );
sendEvent( { EventType::CLIPBOARD } );
break;
case TerminalController::EventType::ProcessExit:
case TerminalSession::EventType::ProcessExit:
onProcessExit( event.value );
break;
case TerminalController::EventType::RestartFailure:
case TerminalSession::EventType::RestartFailure:
sendEvent( { EventType::RESTART_FAILURE, std::move( event.data ) } );
break;
case TerminalController::EventType::Data:
case TerminalSession::EventType::Data:
if ( mDataCallback )
mDataCallback( event.data.data(), event.data.size() );
break;
case TerminalController::EventType::PromptState:
case TerminalSession::EventType::PromptState:
if ( mPromptStateChangedCallback )
mPromptStateChangedCallback( event.promptState, event.data );
break;
case TerminalController::EventType::Color:
case TerminalSession::EventType::Color:
if ( event.value < 0 )
resetColors();
else
resetColor( event.value, event.data.empty() ? nullptr : event.data.c_str() );
invalidateLines();
break;
case TerminalController::EventType::Error:
case TerminalSession::EventType::Error:
Log::error( "Terminal worker error: %s", event.data.c_str() );
sendEvent( { EventType::WORKER_ERROR, std::move( event.data ) } );
break;
case TerminalController::EventType::HistoryLength:
case TerminalSession::EventType::HistoryLength:
sendEvent( { EventType::HISTORY_LENGTH_CHANGE } );
break;
case TerminalController::EventType::SnapshotReady:
case TerminalSession::EventType::SnapshotReady:
break;
}
}
@@ -848,8 +848,8 @@ TerminalColorPalette TerminalDisplay::makeColorPalette() const {
}
std::string TerminalDisplay::getSelection() {
if ( mController ) {
if ( auto selection = mController->requestSelection() )
if ( mSession ) {
if ( auto selection = mSession->requestSelection() )
return std::move( *selection );
}
return mSnapshot ? mSnapshot->selection : std::string{};
@@ -872,33 +872,33 @@ int TerminalDisplay::getExitCode() const {
}
void TerminalDisplay::terminate() {
if ( mController )
mController->terminate();
if ( mSession )
mSession->terminate();
}
void TerminalDisplay::setAllowMemoryTrimming( bool allow ) {
if ( mController )
mController->setAllowMemoryTrimming( allow );
if ( mSession )
mSession->setAllowMemoryTrimming( allow );
}
void TerminalDisplay::setDataCallback( DataFunc callback ) {
mDataCallback = std::move( callback );
if ( mController )
mController->setDataEventsEnabled( static_cast<bool>( mDataCallback ) );
if ( mSession )
mSession->setDataEventsEnabled( static_cast<bool>( mDataCallback ) );
}
void TerminalDisplay::setPromptStateChangedCallback( PromptStateChangedFunc callback ) {
mPromptStateChangedCallback = std::move( callback );
if ( mController )
mController->setPromptEventsEnabled( static_cast<bool>( mPromptStateChangedCallback ) );
if ( mSession )
mSession->setPromptEventsEnabled( static_cast<bool>( mPromptStateChangedCallback ) );
}
void TerminalDisplay::setCursorMode( TerminalCursorMode mode ) {
if ( mCursorMode == mode )
return;
mCursorMode = mode;
if ( mController )
mController->setCursorMode( mode );
if ( mSession )
mSession->setCursorMode( mode );
invalidateCursor();
}
@@ -907,29 +907,29 @@ TerminalCursorMode TerminalDisplay::getCursorMode() const {
}
void TerminalDisplay::executeFile( const std::string& cmd ) {
if ( mController ) {
if ( mSession ) {
std::string rcmd;
#if EE_PLATFORM != EE_PLATFORM_WIN
rcmd.push_back( 0x15 );
#endif
rcmd.append( cmd ).push_back( '\r' );
mController->write( std::move( rcmd ) );
mSession->write( std::move( rcmd ) );
}
}
void TerminalDisplay::executeBinary( const std::string& binaryPath, const std::string& args ) {
if ( mController ) {
if ( mSession ) {
std::string rcmd;
#if EE_PLATFORM != EE_PLATFORM_WIN
rcmd.push_back( 0x15 );
#endif
rcmd.append( "\"" ).append( binaryPath ).append( "\" " ).append( args ).push_back( '\r' );
mController->write( std::move( rcmd ) );
mSession->write( std::move( rcmd ) );
}
}
void TerminalDisplay::action( TerminalShortcutAction action ) {
if ( !mController && action != TerminalShortcutAction::FONTSIZE_GROW &&
if ( !mSession && action != TerminalShortcutAction::FONTSIZE_GROW &&
action != TerminalShortcutAction::FONTSIZE_SHRINK )
return;
switch ( action ) {
@@ -937,11 +937,11 @@ void TerminalDisplay::action( TerminalShortcutAction action ) {
getClipboard();
if ( !mClipboardUtf8.empty() ) {
if ( mMode & MODE_BRCKTPASTE ) {
mController->writeRaw( "\033[200~" );
mController->writeRaw( std::move( mClipboardUtf8 ) );
mController->writeRaw( "\033[201~" );
mSession->writeRaw( "\033[200~" );
mSession->writeRaw( std::move( mClipboardUtf8 ) );
mSession->writeRaw( "\033[201~" );
} else {
mController->writeRaw( std::move( mClipboardUtf8 ) );
mSession->writeRaw( std::move( mClipboardUtf8 ) );
}
}
break;
@@ -955,11 +955,11 @@ void TerminalDisplay::action( TerminalShortcutAction action ) {
sanitizeInput( selection );
if ( !selection.empty() ) {
if ( mMode & MODE_BRCKTPASTE ) {
mController->writeRaw( "\033[200~" );
mController->writeRaw( std::move( selection ) );
mController->writeRaw( "\033[201~" );
mSession->writeRaw( "\033[200~" );
mSession->writeRaw( std::move( selection ) );
mSession->writeRaw( "\033[201~" );
} else {
mController->writeRaw( std::move( selection ) );
mSession->writeRaw( std::move( selection ) );
}
}
break;
@@ -971,27 +971,27 @@ void TerminalDisplay::action( TerminalShortcutAction action ) {
break;
}
case TerminalShortcutAction::SCROLLUP_SCREEN: {
mController->scrollUp( -(int)mClickStep );
mSession->scrollUp( -(int)mClickStep );
break;
}
case TerminalShortcutAction::SCROLLDOWN_SCREEN: {
mController->scrollDown( -(int)mClickStep );
mSession->scrollDown( -(int)mClickStep );
break;
}
case TerminalShortcutAction::SCROLLUP_ROW: {
mController->scrollUp( mClickStep );
mSession->scrollUp( mClickStep );
break;
}
case TerminalShortcutAction::SCROLLDOWN_ROW: {
mController->scrollDown( mClickStep );
mSession->scrollDown( mClickStep );
break;
}
case TerminalShortcutAction::SCROLLUP_HISTORY: {
mController->scrollUp( INT_MAX );
mSession->scrollUp( INT_MAX );
break;
}
case TerminalShortcutAction::SCROLLDOWN_HISTORY: {
mController->scrollDown( INT_MAX );
mSession->scrollDown( INT_MAX );
break;
}
case TerminalShortcutAction::FONTSIZE_GROW: {
@@ -1055,7 +1055,7 @@ void TerminalDisplay::onMouseDoubleClick( const Vector2i& pos, const Uint32& fla
if ( !isAppCapturingMouse() && ( flags & EE_BUTTON_LMASK ) &&
( getSelectionMode() == SEL_EMPTY || getSelectionMode() == SEL_IDLE ) ) {
auto gridPos{ positionToGrid( pos ) };
mController->selectionStart( gridPos.x, gridPos.y, SNAP_WORD );
mSession->selectionStart( gridPos.x, gridPos.y, SNAP_WORD );
}
}
@@ -1083,13 +1083,13 @@ void TerminalDisplay::onMouseMove( const Vector2i& pos, const Uint32& flags ) {
if ( !isCapturingMouse && ( flags & EE_BUTTON_LMASK ) &&
( mDraggingSel || getSelectionMode() == SEL_EMPTY || getSelectionMode() == SEL_READY ) ) {
auto gridPos{ positionToGrid( pos ) };
mController->selectionExtend(
mSession->selectionExtend(
gridPos.x, gridPos.y,
mWindow->getInput()->getModState() & KEYMOD_SHIFT ? SEL_RECTANGULAR : SEL_REGULAR,
false );
}
mController->mouseReport( TerminalMouseEventType::MouseMotion, positionToGrid( pos ), flags,
mWindow->getInput()->getModState() );
mSession->mouseReport( TerminalMouseEventType::MouseMotion, positionToGrid( pos ), flags,
mWindow->getInput()->getModState() );
}
void TerminalDisplay::onMouseDown( const Vector2i& pos, const Uint32& flags ) {
@@ -1103,10 +1103,10 @@ void TerminalDisplay::onMouseDown( const Vector2i& pos, const Uint32& flags ) {
if ( !isCapturingMouse && ( flags & EE_BUTTON_LMASK ) &&
mLastDoubleClick.getElapsedTime() < Milliseconds( 300.f ) ) {
mController->selectionStart( gridPos.x, gridPos.y, SNAP_LINE );
mSession->selectionStart( gridPos.x, gridPos.y, SNAP_LINE );
} else if ( !isCapturingMouse && ( flags & EE_BUTTON_LMASK ) ) {
if ( !mDraggingSel ) {
mController->selectionStart( gridPos.x, gridPos.y, 0 );
mSession->selectionStart( gridPos.x, gridPos.y, 0 );
mDraggingSel = true;
invalidateLines();
mWindow->getInput()->captureMouse( true );
@@ -1129,8 +1129,8 @@ void TerminalDisplay::onMouseDown( const Vector2i& pos, const Uint32& flags ) {
}
}
mController->mouseReport( TerminalMouseEventType::MouseButtonDown, positionToGrid( pos ), flags,
mWindow->getInput()->getModState() );
mSession->mouseReport( TerminalMouseEventType::MouseButtonDown, positionToGrid( pos ), flags,
mWindow->getInput()->getModState() );
}
void TerminalDisplay::onMouseUp( const Vector2i& pos, const Uint32& flags ) {
@@ -1176,8 +1176,8 @@ void TerminalDisplay::onMouseUp( const Vector2i& pos, const Uint32& flags ) {
}
}
mController->mouseReport( TerminalMouseEventType::MouseButtonRelease, positionToGrid( pos ),
flags, mWindow->getInput()->getModState() );
mSession->mouseReport( TerminalMouseEventType::MouseButtonRelease, positionToGrid( pos ), flags,
mWindow->getInput()->getModState() );
}
static inline Color termColor( unsigned int terminalColor, const std::vector<Color>& colors ) {
@@ -1713,7 +1713,7 @@ void TerminalDisplay::drawBg( bool toFBO ) {
}
void TerminalDisplay::draw( const Vector2f& pos ) {
if ( !mController || !mSnapshot )
if ( !mSession || !mSnapshot )
return;
mDrawing = true;
@@ -1777,9 +1777,9 @@ void TerminalDisplay::onSizeChange() {
mFont, mFontSize,
mSize - Vector2f( mPadding.Left + mPadding.Right, mPadding.Top + mPadding.Bottom ) ) );
if ( mController && ( !mSnapshot || gridSize.getWidth() != mSnapshot->columns ||
gridSize.getHeight() != mSnapshot->rows ) ) {
mController->resize( gridSize.getWidth(), gridSize.getHeight() );
if ( mSession && ( !mSnapshot || gridSize.getWidth() != mSnapshot->columns ||
gridSize.getHeight() != mSnapshot->rows ) ) {
mSession->resize( gridSize.getWidth(), gridSize.getHeight() );
mDirtyLines.resize( gridSize.getHeight(), 1 );
}
@@ -1798,7 +1798,7 @@ void TerminalDisplay::onSizeChange() {
void TerminalDisplay::onProcessExit( int exitCode ) {
sendEvent( { EventType::PROCESS_EXIT, String::toString( exitCode ) } );
if ( !mController || mProgram.empty() || exitCode != 0 || !mKeepAlive )
if ( !mSession || mProgram.empty() || exitCode != 0 || !mKeepAlive )
return;
auto processFactory = eeNew( ProcessFactory, () );
@@ -1824,23 +1824,23 @@ void TerminalDisplay::onProcessExit( int exitCode ) {
return;
}
mController->restart( std::move( pseudoTerminal ), std::move( process ) );
mSession->restart( std::move( pseudoTerminal ), std::move( process ) );
eeSAFE_DELETE( processFactory );
}
void TerminalDisplay::onTextInput( const Uint32& chr ) {
if ( !mController )
if ( !mSession )
return;
String input;
input.push_back( chr );
std::string utf8Input( input.toUtf8() );
mController->write( std::move( utf8Input ) );
mSession->write( std::move( utf8Input ) );
mDirty = true;
}
void TerminalDisplay::onTextEditing( const String&, const Int32&, const Int32& ) {
if ( !mController )
if ( !mSession )
return;
invalidateCursor();
updateIMELocation();
@@ -1910,7 +1910,7 @@ void TerminalDisplay::onKeyDown( const Keycode& keyCode, const Uint32& /*chr*/,
}
}
mController->write( std::string( 1, tmp ) );
mSession->write( std::string( 1, tmp ) );
return;
}
}
@@ -1929,7 +1929,7 @@ void TerminalDisplay::onKeyDown( const Keycode& keyCode, const Uint32& /*chr*/,
continue;
if ( k.string.size() > 0 ) {
mController->write( k.string );
mSession->write( k.string );
return;
}
break;
@@ -1951,7 +1951,7 @@ void TerminalDisplay::onKeyDown( const Keycode& keyCode, const Uint32& /*chr*/,
continue;
if ( k.string.size() > 0 ) {
mController->write( k.string );
mSession->write( k.string );
return;
}
break;
@@ -2069,8 +2069,8 @@ void TerminalDisplay::setFocus( bool focus ) {
} else {
mMode &= ~MODE_FOCUSED;
}
if ( mController )
mController->setFocus( focus );
if ( mSession )
mSession->setFocus( focus );
invalidateCursor();
}
@@ -1,4 +1,4 @@
#include <eterm/terminal/terminalcontroller.hpp>
#include <eterm/terminal/terminalsession.hpp>
#include <eepp/system/color.hpp>
#include <eepp/system/log.hpp>
@@ -10,19 +10,17 @@ using namespace EE::System;
namespace eterm { namespace Terminal {
struct TerminalController::SelectionResponse {
struct TerminalSession::SelectionResponse {
std::mutex mutex;
std::condition_variable condition;
std::string selection;
bool ready{ false };
};
class TerminalController::WorkerDisplay final : public ITerminalDisplay {
class TerminalSession::WorkerDisplay final : public ITerminalDisplay {
public:
WorkerDisplay( TerminalController& controller, TerminalColorPalette palette ) :
mController( controller ),
mInitialPalette( std::move( palette ) ),
mPalette( mInitialPalette ) {
WorkerDisplay( TerminalSession& session, TerminalColorPalette palette ) :
mSession( session ), mInitialPalette( std::move( palette ) ), mPalette( mInitialPalette ) {
mMode |= MODE_FOCUSED;
}
@@ -96,18 +94,18 @@ class TerminalController::WorkerDisplay final : public ITerminalDisplay {
mLastHistoryLength = snapshot->historyLength;
Event event{ EventType::HistoryLength };
event.value = mLastHistoryLength;
mController.enqueueEvent( std::move( event ), true );
mSession.enqueueEvent( std::move( event ), true );
}
mController.publishSnapshot( std::move( snapshot ) );
mSession.publishSnapshot( std::move( snapshot ) );
}
void bell() { mController.enqueueEvent( { EventType::Bell }, false ); }
void bell() { mSession.enqueueEvent( { EventType::Bell }, false ); }
void resetColors() {
mPalette = mInitialPalette;
Event event{ EventType::Color };
event.value = -1;
mController.enqueueEvent( std::move( event ), false );
mSession.enqueueEvent( std::move( event ), false );
}
int resetColor( const Uint32& index, const char* name ) {
@@ -149,7 +147,7 @@ class TerminalController::WorkerDisplay final : public ITerminalDisplay {
Event event{ EventType::Color };
event.data = name ? name : "";
event.value = static_cast<int>( index );
mController.enqueueEvent( std::move( event ), false );
mSession.enqueueEvent( std::move( event ), false );
return 0;
}
@@ -174,27 +172,25 @@ class TerminalController::WorkerDisplay final : public ITerminalDisplay {
void setTitle( const char* title ) {
mTitle = title ? title : "";
mController.enqueueEvent( { EventType::Title, mTitle }, true );
mSession.enqueueEvent( { EventType::Title, mTitle }, true );
}
void setIconTitle( const char* title ) {
mController.enqueueEvent( { EventType::IconTitle, title ? title : "" }, true );
mSession.enqueueEvent( { EventType::IconTitle, title ? title : "" }, true );
}
void setClipboard( const char* text ) {
if ( text )
mController.enqueueEvent( { EventType::Clipboard, text }, false );
mSession.enqueueEvent( { EventType::Clipboard, text }, false );
}
void onProcessExit( int exitCode ) {
Event event{ EventType::ProcessExit };
event.value = exitCode;
mController.enqueueEvent( std::move( event ), false );
mSession.enqueueEvent( std::move( event ), false );
}
void onScrollPositionChange() {
mController.enqueueEvent( { EventType::ScrollPosition }, true );
}
void onScrollPositionChange() { mSession.enqueueEvent( { EventType::ScrollPosition }, true ); }
void setPalette( TerminalColorPalette palette ) {
mInitialPalette = palette;
@@ -224,7 +220,7 @@ class TerminalController::WorkerDisplay final : public ITerminalDisplay {
mPalette.background = color;
}
TerminalController& mController;
TerminalSession& mSession;
TerminalColorPalette mInitialPalette;
TerminalColorPalette mPalette;
std::vector<TerminalGlyph> mCells;
@@ -241,35 +237,35 @@ class TerminalController::WorkerDisplay final : public ITerminalDisplay {
bool mCursorVisible{ false };
};
std::shared_ptr<TerminalController> TerminalController::create( PtyPtr&& pty, ProcPtr&& process,
size_t historySize,
TerminalColorPalette palette ) {
std::shared_ptr<TerminalSession> TerminalSession::create( PtyPtr&& pty, ProcPtr&& process,
size_t historySize,
TerminalColorPalette palette ) {
if ( !pty || !process )
return nullptr;
auto controller = std::shared_ptr<TerminalController>( new TerminalController(
auto session = std::shared_ptr<TerminalSession>( new TerminalSession(
std::move( pty ), std::move( process ), historySize, std::move( palette ) ) );
controller->start();
return controller;
session->start();
return session;
}
TerminalController::TerminalController( PtyPtr&& pty, ProcPtr&& process, size_t historySize,
TerminalColorPalette palette ) {
TerminalSession::TerminalSession( PtyPtr&& pty, ProcPtr&& process, size_t historySize,
TerminalColorPalette palette ) {
mWorkerDisplay = std::make_shared<WorkerDisplay>( *this, std::move( palette ) );
mEmulator = TerminalEmulator::create( std::move( pty ), std::move( process ), mWorkerDisplay,
historySize );
// Establish a complete generation before the controller becomes concurrently visible.
// Establish a complete generation before the session becomes concurrently visible.
mEmulator->redraw();
}
TerminalController::~TerminalController() {
TerminalSession::~TerminalSession() {
shutdown();
}
void TerminalController::start() {
void TerminalSession::start() {
mWorker = std::thread( [this] { workerLoop(); } );
}
void TerminalController::shutdown() {
void TerminalSession::shutdown() {
std::lock_guard<std::mutex> shutdownLock( mShutdownMutex );
if ( !mShutdownRequested.exchange( true, std::memory_order_acq_rel ) )
mCommandCondition.notify_all();
@@ -277,7 +273,7 @@ void TerminalController::shutdown() {
mWorker.join();
}
bool TerminalController::enqueue( Command&& command ) {
bool TerminalSession::enqueue( Command&& command ) {
{
std::lock_guard<std::mutex> lock( mCommandMutex );
@@ -291,27 +287,27 @@ bool TerminalController::enqueue( Command&& command ) {
return true;
}
void TerminalController::write( std::string data, bool mayEcho ) {
void TerminalSession::write( std::string data, bool mayEcho ) {
enqueue( WriteCommand{ std::move( data ), mayEcho } );
}
void TerminalController::writeRaw( std::string data ) {
void TerminalSession::writeRaw( std::string data ) {
enqueue( WriteRawCommand{ std::move( data ) } );
}
void TerminalController::resize( int columns, int rows ) {
void TerminalSession::resize( int columns, int rows ) {
enqueue( ResizeCommand{ columns, rows } );
}
void TerminalController::scrollUp( int amount ) {
void TerminalSession::scrollUp( int amount ) {
enqueue( ScrollCommand{ amount, -1 } );
}
void TerminalController::scrollDown( int amount ) {
void TerminalSession::scrollDown( int amount ) {
enqueue( ScrollCommand{ amount, 1 } );
}
Uint64 TerminalController::scrollTo( int position ) {
Uint64 TerminalSession::scrollTo( int position ) {
const Uint64 commandId = mNextScrollCommand.fetch_add( 1, std::memory_order_relaxed ) + 1;
if ( !enqueue( ScrollCommand{ position, 0, commandId } ) )
@@ -320,60 +316,60 @@ Uint64 TerminalController::scrollTo( int position ) {
return commandId;
}
void TerminalController::selectionStart( int column, int row, int snap ) {
void TerminalSession::selectionStart( int column, int row, int snap ) {
enqueue( SelectionStartCommand{ column, row, snap } );
}
void TerminalController::selectionExtend( int column, int row, int type, bool done ) {
void TerminalSession::selectionExtend( int column, int row, int type, bool done ) {
enqueue( SelectionExtendCommand{ column, row, type, done } );
}
void TerminalController::selectionClear() {
void TerminalSession::selectionClear() {
enqueue( SelectionClearCommand{} );
}
void TerminalController::mouseReport( TerminalMouseEventType type, Vector2i position, Uint32 flags,
Uint32 modifiers ) {
void TerminalSession::mouseReport( TerminalMouseEventType type, Vector2i position, Uint32 flags,
Uint32 modifiers ) {
enqueue( MouseCommand{ type, position, flags, modifiers } );
}
void TerminalController::setFocus( bool focus ) {
void TerminalSession::setFocus( bool focus ) {
enqueue( FocusCommand{ { focus } } );
}
void TerminalController::setCursorMode( TerminalCursorMode mode ) {
void TerminalSession::setCursorMode( TerminalCursorMode mode ) {
enqueue( CursorModeCommand{ mode } );
}
void TerminalController::setColorPalette( TerminalColorPalette palette ) {
void TerminalSession::setColorPalette( TerminalColorPalette palette ) {
enqueue( PaletteCommand{ std::move( palette ) } );
}
void TerminalController::setAllowMemoryTrimming( bool allow ) {
void TerminalSession::setAllowMemoryTrimming( bool allow ) {
enqueue( AllowTrimCommand{ { allow } } );
}
void TerminalController::setPresentationRate( Uint32 framesPerSecond ) {
void TerminalSession::setPresentationRate( Uint32 framesPerSecond ) {
enqueue( PresentationRateCommand{ framesPerSecond } );
}
void TerminalController::setDataEventsEnabled( bool enabled ) {
void TerminalSession::setDataEventsEnabled( bool enabled ) {
enqueue( DataEventsCommand{ { enabled } } );
}
void TerminalController::setPromptEventsEnabled( bool enabled ) {
void TerminalSession::setPromptEventsEnabled( bool enabled ) {
enqueue( PromptEventsCommand{ { enabled } } );
}
void TerminalController::reset() {
void TerminalSession::reset() {
enqueue( ResetCommand{} );
}
void TerminalController::terminate() {
void TerminalSession::terminate() {
enqueue( TerminateCommand{} );
}
void TerminalController::restart( PtyPtr&& pty, ProcPtr&& process ) {
void TerminalSession::restart( PtyPtr&& pty, ProcPtr&& process ) {
if ( !pty || !process ) {
enqueueEvent( { EventType::RestartFailure, "Invalid PTY or process" }, false );
return;
@@ -381,13 +377,12 @@ void TerminalController::restart( PtyPtr&& pty, ProcPtr&& process ) {
enqueue( RestartCommand{ std::move( pty ), std::move( process ) } );
}
std::shared_ptr<const TerminalSnapshot> TerminalController::snapshot() const {
std::shared_ptr<const TerminalSnapshot> TerminalSession::snapshot() const {
std::lock_guard<std::mutex> lock( mPublishedSnapshotMutex );
return mPublishedSnapshot;
}
std::optional<std::string>
TerminalController::requestSelection( std::chrono::milliseconds timeout ) {
std::optional<std::string> TerminalSession::requestSelection( std::chrono::milliseconds timeout ) {
if ( mShutdownRequested.load( std::memory_order_acquire ) )
return std::nullopt;
auto response = std::make_shared<SelectionResponse>();
@@ -398,7 +393,7 @@ TerminalController::requestSelection( std::chrono::milliseconds timeout ) {
return std::move( response->selection );
}
std::vector<TerminalController::Event> TerminalController::drainEvents() {
std::vector<TerminalSession::Event> TerminalSession::drainEvents() {
std::vector<Event> events;
std::lock_guard<std::mutex> lock( mEventMutex );
events.reserve( mEvents.size() );
@@ -409,7 +404,7 @@ std::vector<TerminalController::Event> TerminalController::drainEvents() {
return events;
}
void TerminalController::enqueueEvent( Event event, bool coalescable ) {
void TerminalSession::enqueueEvent( Event event, bool coalescable ) {
std::lock_guard<std::mutex> lock( mEventMutex );
if ( coalescable ) {
for ( auto it = mEvents.rbegin(); it != mEvents.rend(); ++it ) {
@@ -428,7 +423,7 @@ void TerminalController::enqueueEvent( Event event, bool coalescable ) {
mEvents.emplace_back( std::move( event ) );
}
void TerminalController::publishSnapshot( std::shared_ptr<const TerminalSnapshot> snapshot ) {
void TerminalSession::publishSnapshot( std::shared_ptr<const TerminalSnapshot> snapshot ) {
const Uint64 generation = snapshot->generation;
{
std::lock_guard<std::mutex> lock( mPublishedSnapshotMutex );
@@ -439,7 +434,7 @@ void TerminalController::publishSnapshot( std::shared_ptr<const TerminalSnapshot
enqueueEvent( std::move( event ), true );
}
void TerminalController::workerLoop() {
void TerminalSession::workerLoop() {
while ( !mShutdownRequested.load( std::memory_order_acquire ) ) {
processCommands();
if ( mShutdownRequested.load( std::memory_order_acquire ) )
@@ -458,7 +453,7 @@ void TerminalController::workerLoop() {
mWorkerDisplay.reset();
}
void TerminalController::processCommands() {
void TerminalSession::processCommands() {
std::deque<Command> commands;
{
std::lock_guard<std::mutex> lock( mCommandMutex );
@@ -470,7 +465,7 @@ void TerminalController::processCommands() {
}
}
void TerminalController::processCommand( Command&& command ) {
void TerminalSession::processCommand( Command&& command ) {
std::visit(
[this]( auto&& value ) {
using T = std::decay_t<decltype( value )>;
@@ -559,4 +554,4 @@ void TerminalController::processCommand( Command&& command ) {
std::move( command ) );
}
}} // namespace eterm::Terminal
}} // namespace eterm::Terminal
+87 -89
View File
@@ -4,8 +4,8 @@
#include <eterm/system/iprocess.hpp>
#include <eterm/terminal/ipseudoterminal.hpp>
#include <eterm/terminal/iterminaldisplay.hpp>
#include <eterm/terminal/terminalcontroller.hpp>
#include <eterm/terminal/terminalemulator.hpp>
#include <eterm/terminal/terminalsession.hpp>
#include <limits>
#include <thread>
@@ -59,12 +59,12 @@ class MockProcess : public IProcess {
};
static std::shared_ptr<const TerminalSnapshot>
waitForSnapshot( const std::shared_ptr<TerminalController>& controller,
waitForSnapshot( const std::shared_ptr<TerminalSession>& session,
const std::function<bool( const TerminalSnapshot& )>& predicate,
std::chrono::milliseconds timeout = std::chrono::milliseconds( 1000 ) ) {
const auto deadline = std::chrono::steady_clock::now() + timeout;
while ( std::chrono::steady_clock::now() < deadline ) {
auto snapshot = controller->snapshot();
auto snapshot = session->snapshot();
if ( snapshot && predicate( *snapshot ) )
return snapshot;
std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
@@ -72,60 +72,59 @@ waitForSnapshot( const std::shared_ptr<TerminalController>& controller,
return nullptr;
}
UTEST( eterm_controller, command_wakeup_and_snapshot_immutability ) {
UTEST( eterm_session, command_wakeup_and_snapshot_immutability ) {
auto pty = std::make_unique<MockPty>();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
ASSERT_TRUE( controller != nullptr );
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
ASSERT_TRUE( session != nullptr );
controller->writeRaw( "ABC" );
auto first = waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
session->writeRaw( "ABC" );
auto first = waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return snapshot.cells.size() >= 3 && snapshot.cells[0].u == 'A' &&
snapshot.cells[1].u == 'B' && snapshot.cells[2].u == 'C';
} );
ASSERT_TRUE( first != nullptr );
const Uint64 firstGeneration = first->generation;
controller->writeRaw( "\rXYZ" );
auto second =
waitForSnapshot( controller, [firstGeneration]( const TerminalSnapshot& snapshot ) {
return snapshot.generation > firstGeneration && snapshot.cells[0].u == 'X';
} );
session->writeRaw( "\rXYZ" );
auto second = waitForSnapshot( session, [firstGeneration]( const TerminalSnapshot& snapshot ) {
return snapshot.generation > firstGeneration && snapshot.cells[0].u == 'X';
} );
ASSERT_TRUE( second != nullptr );
EXPECT_EQ( static_cast<Rune>( 'A' ), first->cells[0].u );
EXPECT_TRUE( second->generation > first->generation );
}
UTEST( eterm_controller, skipped_snapshot_generation_requires_full_redraw ) {
UTEST( eterm_session, skipped_snapshot_generation_requires_full_redraw ) {
TerminalSnapshot snapshot;
snapshot.generation = 42;
EXPECT_TRUE( snapshot.dirtyRowsFollow( 41 ) );
EXPECT_FALSE( snapshot.dirtyRowsFollow( 40 ) );
}
UTEST( eterm_controller, ordered_selection_request ) {
UTEST( eterm_session, ordered_selection_request ) {
auto pty = std::make_unique<MockPty>();
pty->mBuffer = "ordered selection";
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
ASSERT_TRUE( waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
ASSERT_TRUE( waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return !snapshot.cells.empty() && snapshot.cells[0].u == 'o';
} ) != nullptr );
controller->selectionStart( 0, 0, 0 );
controller->selectionExtend( 6, 0, SEL_REGULAR, false );
auto selection = controller->requestSelection();
session->selectionStart( 0, 0, 0 );
session->selectionExtend( 6, 0, SEL_REGULAR, false );
auto selection = session->requestSelection();
ASSERT_TRUE( selection.has_value() );
EXPECT_STDSTREQ( "ordered", *selection );
}
UTEST( eterm_controller, loaded_command_latency_stays_bounded ) {
UTEST( eterm_session, loaded_command_latency_stays_bounded ) {
auto pty = std::make_unique<MockPty>();
pty->mBuffer.assign( 32 * 1024 * 1024, 'L' );
pty->mMaxRead = 64;
MockPty* ptyPtr = pty.get();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
const auto readDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds( 100 );
while ( ptyPtr->mBytesRead.load( std::memory_order_relaxed ) == 0 &&
std::chrono::steady_clock::now() < readDeadline )
@@ -133,19 +132,19 @@ UTEST( eterm_controller, loaded_command_latency_stays_bounded ) {
ASSERT_TRUE( ptyPtr->mBytesRead.load( std::memory_order_relaxed ) > 0 );
const auto start = std::chrono::steady_clock::now();
auto selection = controller->requestSelection();
auto selection = session->requestSelection();
const auto latency = std::chrono::steady_clock::now() - start;
EXPECT_TRUE( selection.has_value() );
EXPECT_TRUE( latency < std::chrono::milliseconds( 50 ) );
}
UTEST( eterm_controller, resize_and_output_are_serialized ) {
UTEST( eterm_session, resize_and_output_are_serialized ) {
auto pty = std::make_unique<MockPty>();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
controller->resize( 40, 12 );
controller->writeRaw( "after resize" );
auto snapshot = waitForSnapshot( controller, []( const TerminalSnapshot& value ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
session->resize( 40, 12 );
session->writeRaw( "after resize" );
auto snapshot = waitForSnapshot( session, []( const TerminalSnapshot& value ) {
return value.columns == 40 && value.rows == 12 && !value.cells.empty() &&
value.cells[0].u == 'a';
} );
@@ -153,59 +152,59 @@ UTEST( eterm_controller, resize_and_output_are_serialized ) {
EXPECT_EQ( static_cast<size_t>( 40 * 12 ), snapshot->cells.size() );
}
UTEST( eterm_controller, scroll_snapshots_acknowledge_the_latest_ordered_command ) {
UTEST( eterm_session, scroll_snapshots_acknowledge_the_latest_ordered_command ) {
auto pty = std::make_unique<MockPty>();
for ( int line = 0; line < 80; ++line )
pty->mBuffer += "history " + std::to_string( line ) + "\r\n";
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
ASSERT_TRUE( waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
ASSERT_TRUE( waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return snapshot.historyLength >= 25;
} ) != nullptr );
const Uint64 firstCommand = controller->scrollTo( 10 );
const Uint64 secondCommand = controller->scrollTo( 25 );
const Uint64 firstCommand = session->scrollTo( 10 );
const Uint64 secondCommand = session->scrollTo( 25 );
EXPECT_EQ( firstCommand + 1, secondCommand );
auto acknowledged =
waitForSnapshot( controller, [secondCommand]( const TerminalSnapshot& snapshot ) {
waitForSnapshot( session, [secondCommand]( const TerminalSnapshot& snapshot ) {
return snapshot.lastAppliedScrollCommand == secondCommand;
} );
ASSERT_TRUE( acknowledged != nullptr );
EXPECT_EQ( 25, acknowledged->scrollPosition );
}
UTEST( eterm_controller, presentation_rate_is_applied_on_the_worker ) {
UTEST( eterm_session, presentation_rate_is_applied_on_the_worker ) {
auto pty = std::make_unique<MockPty>();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
controller->setPresentationRate( 120 );
ASSERT_TRUE( waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
session->setPresentationRate( 120 );
ASSERT_TRUE( waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return snapshot.presentationRate == 120;
} ) != nullptr );
}
UTEST( eterm_controller, focus_reporting_is_ordered_on_worker ) {
UTEST( eterm_session, focus_reporting_is_ordered_on_worker ) {
auto pty = std::make_unique<MockPty>();
pty->mBuffer = "\033[?1004h";
pty->mLoopWrites = false;
MockPty* ptyPtr = pty.get();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
auto enabled = waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
auto enabled = waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return snapshot.windowMode & MODE_FOCUS;
} );
ASSERT_TRUE( enabled != nullptr );
controller->setFocus( false );
auto unfocused = waitForSnapshot( controller, [enabled]( const TerminalSnapshot& snapshot ) {
session->setFocus( false );
auto unfocused = waitForSnapshot( session, [enabled]( const TerminalSnapshot& snapshot ) {
return snapshot.generation > enabled->generation && !( snapshot.windowMode & MODE_FOCUSED );
} );
ASSERT_TRUE( unfocused != nullptr );
ASSERT_TRUE( ptyPtr->mWrites.size() >= 3 );
EXPECT_STDSTREQ( "\033[O", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) );
controller->setFocus( true );
ASSERT_TRUE( waitForSnapshot( controller, [unfocused]( const TerminalSnapshot& snapshot ) {
session->setFocus( true );
ASSERT_TRUE( waitForSnapshot( session, [unfocused]( const TerminalSnapshot& snapshot ) {
return snapshot.generation > unfocused->generation &&
snapshot.windowMode & MODE_FOCUSED;
} ) != nullptr );
@@ -213,20 +212,20 @@ UTEST( eterm_controller, focus_reporting_is_ordered_on_worker ) {
EXPECT_STDSTREQ( "\033[I", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) );
}
UTEST( eterm_controller, replaceable_events_coalesce_without_losing_ordered_events ) {
UTEST( eterm_session, replaceable_events_coalesce_without_losing_ordered_events ) {
auto pty = std::make_unique<MockPty>();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
controller->drainEvents();
controller->writeRaw( "\033]0;first\a\033]0;second\a" );
ASSERT_TRUE( waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
session->drainEvents();
session->writeRaw( "\033]0;first\a\033]0;second\a" );
ASSERT_TRUE( waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return snapshot.title == "second";
} ) != nullptr );
int titleEvents = 0;
std::string title;
for ( auto& event : controller->drainEvents() ) {
if ( event.type == TerminalController::EventType::Title ) {
for ( auto& event : session->drainEvents() ) {
if ( event.type == TerminalSession::EventType::Title ) {
++titleEvents;
title = std::move( event.data );
}
@@ -235,54 +234,54 @@ UTEST( eterm_controller, replaceable_events_coalesce_without_losing_ordered_even
EXPECT_STDSTREQ( "second", title );
}
UTEST( eterm_controller, ordered_events_are_coalescing_barriers ) {
UTEST( eterm_session, ordered_events_are_coalescing_barriers ) {
auto pty = std::make_unique<MockPty>();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
controller->drainEvents();
controller->writeRaw( "\033]0;before\a\a\033]0;after\a" );
ASSERT_TRUE( waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
session->drainEvents();
session->writeRaw( "\033]0;before\a\a\033]0;after\a" );
ASSERT_TRUE( waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return snapshot.title == "after";
} ) != nullptr );
std::vector<TerminalController::EventType> semanticEvents;
for ( const auto& event : controller->drainEvents() ) {
if ( event.type == TerminalController::EventType::Title ||
event.type == TerminalController::EventType::Bell )
std::vector<TerminalSession::EventType> semanticEvents;
for ( const auto& event : session->drainEvents() ) {
if ( event.type == TerminalSession::EventType::Title ||
event.type == TerminalSession::EventType::Bell )
semanticEvents.emplace_back( event.type );
}
ASSERT_EQ( static_cast<size_t>( 3 ), semanticEvents.size() );
EXPECT_EQ( TerminalController::EventType::Title, semanticEvents[0] );
EXPECT_EQ( TerminalController::EventType::Bell, semanticEvents[1] );
EXPECT_EQ( TerminalController::EventType::Title, semanticEvents[2] );
EXPECT_EQ( TerminalSession::EventType::Title, semanticEvents[0] );
EXPECT_EQ( TerminalSession::EventType::Bell, semanticEvents[1] );
EXPECT_EQ( TerminalSession::EventType::Title, semanticEvents[2] );
}
UTEST( eterm_controller, reset_is_ordered_and_publishes_immediately ) {
UTEST( eterm_session, reset_is_ordered_and_publishes_immediately ) {
auto pty = std::make_unique<MockPty>();
pty->mBuffer = "content";
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
auto populated = waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
auto populated = waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return !snapshot.cells.empty() && snapshot.cells[0].u == 'c';
} );
ASSERT_TRUE( populated != nullptr );
controller->reset();
auto reset = waitForSnapshot( controller, [populated]( const TerminalSnapshot& snapshot ) {
session->reset();
auto reset = waitForSnapshot( session, [populated]( const TerminalSnapshot& snapshot ) {
return snapshot.generation > populated->generation && !snapshot.cells.empty() &&
snapshot.cells[0].u == ' ';
} );
ASSERT_TRUE( reset != nullptr );
}
UTEST( eterm_controller, process_exit_follows_buffered_output_and_final_snapshot ) {
UTEST( eterm_session, process_exit_follows_buffered_output_and_final_snapshot ) {
auto pty = std::make_unique<MockPty>();
pty->mMaxRead = 1;
MockPty* ptyPtr = pty.get();
auto process = std::make_unique<MockProcess>();
MockProcess* processPtr = process.get();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
controller->setDataEventsEnabled( true );
controller->writeRaw( std::string( 3 * 1024, 'Q' ) );
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
session->setDataEventsEnabled( true );
session->writeRaw( std::string( 3 * 1024, 'Q' ) );
const auto readDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds( 100 );
while ( ptyPtr->mBytesRead.load( std::memory_order_relaxed ) == 0 &&
std::chrono::steady_clock::now() < readDeadline )
@@ -291,51 +290,50 @@ UTEST( eterm_controller, process_exit_follows_buffered_output_and_final_snapshot
processPtr->mExited.store( true );
auto finalSnapshot = waitForSnapshot(
controller, []( const TerminalSnapshot& snapshot ) { return snapshot.processExited; } );
session, []( const TerminalSnapshot& snapshot ) { return snapshot.processExited; } );
ASSERT_TRUE( finalSnapshot != nullptr );
EXPECT_EQ( 0, finalSnapshot->exitCode );
size_t bytesRead = 0;
bool sawExit = false;
for ( auto& event : controller->drainEvents() ) {
if ( event.type == TerminalController::EventType::Data )
for ( auto& event : session->drainEvents() ) {
if ( event.type == TerminalSession::EventType::Data )
bytesRead += event.data.size();
else if ( event.type == TerminalController::EventType::ProcessExit )
else if ( event.type == TerminalSession::EventType::ProcessExit )
sawExit = true;
}
EXPECT_EQ( static_cast<size_t>( 3 * 1024 ), bytesRead );
EXPECT_TRUE( sawExit );
}
UTEST( eterm_controller, repeated_create_destroy_and_concurrent_workers ) {
UTEST( eterm_session, repeated_create_destroy_and_concurrent_workers ) {
for ( int iteration = 0; iteration < 16; ++iteration ) {
std::vector<std::shared_ptr<TerminalController>> controllers;
std::vector<std::shared_ptr<TerminalSession>> sessions;
for ( int terminal = 0; terminal < 4; ++terminal ) {
auto pty = std::make_unique<MockPty>();
auto process = std::make_unique<MockProcess>();
auto controller =
TerminalController::create( std::move( pty ), std::move( process ), 100 );
controller->writeRaw( "worker" + std::to_string( terminal ) );
controllers.emplace_back( std::move( controller ) );
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
session->writeRaw( "worker" + std::to_string( terminal ) );
sessions.emplace_back( std::move( session ) );
}
for ( const auto& controller : controllers ) {
EXPECT_TRUE( waitForSnapshot( controller, []( const TerminalSnapshot& snapshot ) {
for ( const auto& session : sessions ) {
EXPECT_TRUE( waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
return !snapshot.cells.empty() && snapshot.cells[0].u == 'w';
} ) != nullptr );
}
}
}
UTEST( eterm_controller, concurrent_shutdown_is_idempotent ) {
UTEST( eterm_session, concurrent_shutdown_is_idempotent ) {
auto pty = std::make_unique<MockPty>();
auto process = std::make_unique<MockProcess>();
auto controller = TerminalController::create( std::move( pty ), std::move( process ), 100 );
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
std::vector<std::thread> shutdownThreads;
for ( int thread = 0; thread < 4; ++thread )
shutdownThreads.emplace_back( [controller] { controller->shutdown(); } );
shutdownThreads.emplace_back( [session] { session->shutdown(); } );
for ( auto& thread : shutdownThreads )
thread.join();
controller->shutdown();
session->shutdown();
}
class MockDisplay : public ITerminalDisplay {
+2 -2
View File
@@ -145,8 +145,8 @@ void queueExitedTabs() {
auto* terminal = terminalFromTab( tab );
if ( !terminal || !terminal->getTerm() )
return;
const auto& controller = terminal->getTerm()->getController();
auto snapshot = controller ? controller->snapshot() : nullptr;
const auto& session = terminal->getTerm()->getSession();
auto snapshot = session ? session->snapshot() : nullptr;
if ( snapshot && snapshot->processExited )
queueExitCloseTab( tab );
} );