Separate modules by subdirectory
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
AM_CPPFLAGS = -I$(srcdir)/../util
|
||||
AM_CXXFLAGS = --std=c++0x -pedantic -Werror -Wall -Wextra -Weffc++ -fno-default-inline -pipe
|
||||
|
||||
noinst_LIBRARIES = libmoshterminal.a
|
||||
|
||||
libmoshterminal_a_SOURCES = parseraction.cc parseraction.h parser.cc parser.h parserstate.cc parserstatefamily.h parserstate.h parsertransition.h terminal.cc terminaldispatcher.cc terminaldispatcher.h terminaldisplay.cc terminaldisplay.h terminalframebuffer.cc terminalframebuffer.h terminalfunctions.cc terminal.h terminaluserinput.cc terminaluserinput.h
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#include <assert.h>
|
||||
#include <typeinfo>
|
||||
#include <langinfo.h>
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
static void append_or_delete( Parser::Action *act,
|
||||
std::list<Parser::Action *>&vec )
|
||||
{
|
||||
assert( act );
|
||||
|
||||
if ( typeid( *act ) != typeid( Parser::Ignore ) ) {
|
||||
vec.push_back( act );
|
||||
} else {
|
||||
delete act;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<Parser::Action *> Parser::Parser::input( wchar_t ch )
|
||||
{
|
||||
std::list<Action *> ret;
|
||||
|
||||
Transition tx = state->input( ch );
|
||||
|
||||
if ( tx.next_state != NULL ) {
|
||||
append_or_delete( state->exit(), ret );
|
||||
}
|
||||
|
||||
append_or_delete( tx.action, ret );
|
||||
|
||||
if ( tx.next_state != NULL ) {
|
||||
append_or_delete( tx.next_state->enter(), ret );
|
||||
state = tx.next_state;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Parser::UTF8Parser::UTF8Parser()
|
||||
: parser(), buf_len( 0 )
|
||||
{
|
||||
assert( BUF_SIZE >= MB_CUR_MAX );
|
||||
}
|
||||
|
||||
std::list<Parser::Action *> Parser::UTF8Parser::input( char c )
|
||||
{
|
||||
assert( buf_len < BUF_SIZE );
|
||||
|
||||
buf[ buf_len++ ] = c;
|
||||
|
||||
/* This function will only work in a UTF-8 locale. */
|
||||
/* This is asserted in the constructor. */
|
||||
|
||||
wchar_t pwc;
|
||||
mbstate_t ps;
|
||||
memset( &ps, 0, sizeof( ps ) );
|
||||
|
||||
size_t total_bytes_parsed = 0;
|
||||
size_t orig_buf_len = buf_len;
|
||||
std::list<Action *> ret;
|
||||
|
||||
/* this routine is somewhat complicated in order to comply with
|
||||
Unicode 6.0, section 3.9, "Best Practices for using U+FFFD" */
|
||||
|
||||
while ( total_bytes_parsed != orig_buf_len ) {
|
||||
assert( total_bytes_parsed < orig_buf_len );
|
||||
assert( buf_len > 0 );
|
||||
size_t bytes_parsed = mbrtowc( &pwc, buf, buf_len, &ps );
|
||||
|
||||
/* this returns 0 when n = 0! */
|
||||
|
||||
/* This function annoying returns a size_t so we have to check
|
||||
the negative values first before the "> 0" branch */
|
||||
|
||||
if ( bytes_parsed == 0 ) {
|
||||
/* character was NUL, accept and clear buffer */
|
||||
assert( buf_len == 1 );
|
||||
buf_len = 0;
|
||||
pwc = L'\0';
|
||||
bytes_parsed = 1;
|
||||
} else if ( bytes_parsed == (size_t) -1 ) {
|
||||
/* invalid sequence, use replacement character and try again with last char */
|
||||
assert( errno == EILSEQ );
|
||||
if ( buf_len > 1 ) {
|
||||
buf[ 0 ] = buf[ buf_len - 1 ];
|
||||
bytes_parsed = buf_len - 1;
|
||||
buf_len = 1;
|
||||
} else {
|
||||
buf_len = 0;
|
||||
bytes_parsed = 1;
|
||||
}
|
||||
pwc = (wchar_t) 0xFFFD;
|
||||
} else if ( bytes_parsed == (size_t) -2 ) {
|
||||
/* can't parse incomplete multibyte character */
|
||||
total_bytes_parsed += buf_len;
|
||||
continue;
|
||||
} else if ( bytes_parsed > 0 ) {
|
||||
/* parsed into pwc, accept */
|
||||
assert( bytes_parsed <= buf_len );
|
||||
memcpy( buf, buf + bytes_parsed, buf_len - bytes_parsed );
|
||||
buf_len = buf_len - bytes_parsed;
|
||||
} else {
|
||||
throw std::string( "Unknown return value from mbrtowc" );
|
||||
}
|
||||
|
||||
if ( (pwc < 0) || (pwc > 0x10FFFF) ) { /* outside Unicode range */
|
||||
pwc = (wchar_t) 0xFFFD;
|
||||
}
|
||||
|
||||
std::list<Action *> vec = parser.input( pwc );
|
||||
ret.insert( ret.end(), vec.begin(), vec.end() );
|
||||
|
||||
total_bytes_parsed += bytes_parsed;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Parser::Parser::Parser( const Parser &other )
|
||||
: state( other.state )
|
||||
{}
|
||||
|
||||
Parser::Parser & Parser::Parser::operator=( const Parser &other )
|
||||
{
|
||||
state = other.state;
|
||||
return *this;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef PARSER_HPP
|
||||
#define PARSER_HPP
|
||||
|
||||
/* Based on Paul Williams's parser,
|
||||
http://www.vt100.net/emu/dec_ansi_parser */
|
||||
|
||||
#include <wchar.h>
|
||||
#include <list>
|
||||
#include <string.h>
|
||||
|
||||
#include "parsertransition.h"
|
||||
#include "parseraction.h"
|
||||
#include "parserstate.h"
|
||||
#include "parserstatefamily.h"
|
||||
|
||||
#ifndef __STDC_ISO_10646__
|
||||
#error "Must have __STDC_ISO_10646__"
|
||||
#endif
|
||||
|
||||
namespace Parser {
|
||||
static const StateFamily family;
|
||||
|
||||
class Parser {
|
||||
private:
|
||||
State const *state;
|
||||
|
||||
public:
|
||||
Parser() : state( &family.s_Ground ) {}
|
||||
|
||||
Parser( const Parser &other );
|
||||
Parser & operator=( const Parser & );
|
||||
~Parser() {}
|
||||
|
||||
std::list<Action *> input( wchar_t ch );
|
||||
|
||||
bool operator==( const Parser &x ) const
|
||||
{
|
||||
return state == x.state;
|
||||
}
|
||||
|
||||
bool is_grounded( void ) const { return state == &family.s_Ground; }
|
||||
};
|
||||
|
||||
static const size_t BUF_SIZE = 8;
|
||||
|
||||
class UTF8Parser {
|
||||
private:
|
||||
Parser parser;
|
||||
|
||||
char buf[ BUF_SIZE ];
|
||||
size_t buf_len;
|
||||
|
||||
public:
|
||||
UTF8Parser();
|
||||
|
||||
std::list<Action *> input( char c );
|
||||
|
||||
bool operator==( const UTF8Parser &x ) const
|
||||
{
|
||||
return parser == x.parser;
|
||||
}
|
||||
|
||||
bool is_grounded( void ) const { return parser.is_grounded(); }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <stdio.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#include "parseraction.h"
|
||||
#include "terminal.h"
|
||||
|
||||
using namespace Parser;
|
||||
|
||||
std::string Action::str( void )
|
||||
{
|
||||
char thechar[ 10 ] = { 0 };
|
||||
if ( char_present ) {
|
||||
snprintf( thechar, 10, iswprint( ch ) ? "(%lc)" : "(0x%x)", ch );
|
||||
}
|
||||
|
||||
return name() + std::string( thechar );
|
||||
}
|
||||
|
||||
void Print::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->print( this );
|
||||
}
|
||||
|
||||
void Execute::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->execute( this );
|
||||
}
|
||||
|
||||
void Clear::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->dispatch.clear( this );
|
||||
}
|
||||
|
||||
void Param::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->dispatch.newparamchar( this );
|
||||
}
|
||||
|
||||
void Collect::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->dispatch.collect( this );
|
||||
}
|
||||
|
||||
void CSI_Dispatch::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->CSI_dispatch( this );
|
||||
}
|
||||
|
||||
void Esc_Dispatch::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->Esc_dispatch( this );
|
||||
}
|
||||
|
||||
void OSC_Put::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->dispatch.OSC_put( this );
|
||||
}
|
||||
|
||||
void OSC_Start::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->dispatch.OSC_start( this );
|
||||
}
|
||||
|
||||
void OSC_End::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->OSC_end( this );
|
||||
}
|
||||
|
||||
void UserByte::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->dispatch.terminal_to_host.append( emu->user.input( this,
|
||||
emu->fb.ds.application_mode_cursor_keys ) );
|
||||
}
|
||||
|
||||
void Resize::act_on_terminal( Terminal::Emulator *emu ) const
|
||||
{
|
||||
emu->resize( width, height );
|
||||
handled = true;
|
||||
}
|
||||
|
||||
bool Action::operator==( const Action &other ) const
|
||||
{
|
||||
return ( char_present == other.char_present )
|
||||
&& ( ch == other.ch )
|
||||
&& ( handled == other.handled );
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
#ifndef PARSERACTION_HPP
|
||||
#define PARSERACTION_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Terminal {
|
||||
class Emulator;
|
||||
}
|
||||
|
||||
namespace Parser {
|
||||
class Action
|
||||
{
|
||||
public:
|
||||
bool char_present;
|
||||
wchar_t ch;
|
||||
mutable bool handled;
|
||||
|
||||
std::string str( void );
|
||||
|
||||
virtual std::string name( void ) = 0;
|
||||
|
||||
virtual void act_on_terminal( Terminal::Emulator * ) const {};
|
||||
|
||||
Action() : char_present( false ), ch( -1 ), handled( false ) {};
|
||||
virtual ~Action() {};
|
||||
|
||||
virtual bool operator==( const Action &other ) const;
|
||||
};
|
||||
|
||||
class Ignore : public Action {
|
||||
public: std::string name( void ) { return std::string( "Ignore" ); }
|
||||
};
|
||||
class Print : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "Print" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class Execute : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "Execute" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class Clear : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "Clear" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class Collect : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "Collect" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class Param : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "Param" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class Esc_Dispatch : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "Esc_Dispatch" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class CSI_Dispatch : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "CSI_Dispatch" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class Hook : public Action {
|
||||
public: std::string name( void ) { return std::string( "Hook" ); }
|
||||
};
|
||||
class Put : public Action {
|
||||
public: std::string name( void ) { return std::string( "Put" ); }
|
||||
};
|
||||
class Unhook : public Action {
|
||||
public: std::string name( void ) { return std::string( "Unhook" ); }
|
||||
};
|
||||
class OSC_Start : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "OSC_Start" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class OSC_Put : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "OSC_Put" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
class OSC_End : public Action {
|
||||
public:
|
||||
std::string name( void ) { return std::string( "OSC_End" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
};
|
||||
|
||||
class UserByte : public Action {
|
||||
/* user keystroke -- not part of the host-source state machine*/
|
||||
public:
|
||||
char c; /* The user-source byte. We don't try to interpret the charset */
|
||||
|
||||
std::string name( void ) { return std::string( "UserByte" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
|
||||
UserByte( int s_c ) : c( s_c ) {}
|
||||
|
||||
bool operator==( const UserByte &other ) const
|
||||
{
|
||||
return c == other.c;
|
||||
}
|
||||
};
|
||||
|
||||
class Resize : public Action {
|
||||
/* resize event -- not part of the host-source state machine*/
|
||||
public:
|
||||
size_t width, height;
|
||||
|
||||
std::string name( void ) { return std::string( "Resize" ); }
|
||||
void act_on_terminal( Terminal::Emulator *emu ) const;
|
||||
|
||||
Resize( size_t s_width, size_t s_height )
|
||||
: width( s_width ),
|
||||
height( s_height )
|
||||
{}
|
||||
|
||||
bool operator==( const Resize &other ) const
|
||||
{
|
||||
return ( width == other.width ) && ( height == other.height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,356 @@
|
||||
#include "parserstate.h"
|
||||
#include "parserstatefamily.h"
|
||||
|
||||
using namespace Parser;
|
||||
|
||||
Transition State::anywhere_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( (ch == 0x18) || (ch == 0x1A)
|
||||
|| ((0x80 <= ch) && (ch <= 0x8F))
|
||||
|| ((0x91 <= ch) && (ch <= 0x97))
|
||||
|| (ch == 0x99) || (ch == 0x9A) ) {
|
||||
return Transition( new Execute, &family->s_Ground );
|
||||
} else if ( ch == 0x9C ) {
|
||||
return Transition( &family->s_Ground );
|
||||
} else if ( ch == 0x1B ) {
|
||||
return Transition( &family->s_Escape );
|
||||
} else if ( (ch == 0x98) || (ch == 0x9E) || (ch == 0x9F) ) {
|
||||
return Transition( &family->s_SOS_PM_APC_String );
|
||||
} else if ( ch == 0x90 ) {
|
||||
return Transition( &family->s_DCS_Entry );
|
||||
} else if ( ch == 0x9D ) {
|
||||
return Transition( &family->s_OSC_String );
|
||||
} else if ( ch == 0x9B ) {
|
||||
return Transition( &family->s_CSI_Entry );
|
||||
}
|
||||
|
||||
return Transition( NULL, NULL ); /* don't allocate an Ignore action */
|
||||
}
|
||||
|
||||
Transition State::input( wchar_t ch ) const
|
||||
{
|
||||
Transition ret = anywhere_rule( ch );
|
||||
if ( !ret.next_state ) {
|
||||
if ( ch >= 0xA0 ) {
|
||||
ret = this->input_state_rule( 0x41 );
|
||||
} else {
|
||||
ret = this->input_state_rule( ch );
|
||||
}
|
||||
}
|
||||
|
||||
ret.action->char_present = true;
|
||||
ret.action->ch = ch;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool C0_prime( wchar_t ch )
|
||||
{
|
||||
return ( (ch <= 0x17)
|
||||
|| (ch == 0x19)
|
||||
|| ( (0x1C <= ch) && (ch <= 0x1F) ) );
|
||||
}
|
||||
|
||||
static bool GLGR ( wchar_t ch )
|
||||
{
|
||||
return ( ( (0x20 <= ch) && (ch <= 0x7F) ) /* GL area */
|
||||
|| ( (0xA0 <= ch) && (ch <= 0xFF) ) ); /* GR area */
|
||||
}
|
||||
|
||||
Transition Ground::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) ) {
|
||||
return Transition( new Execute );
|
||||
}
|
||||
|
||||
if ( GLGR( ch ) ) {
|
||||
return Transition( new Print );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Action *Escape::enter( void ) const
|
||||
{
|
||||
return new Clear;
|
||||
}
|
||||
|
||||
Transition Escape::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) ) {
|
||||
return Transition( new Execute );
|
||||
}
|
||||
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect, &family->s_Escape_Intermediate );
|
||||
}
|
||||
|
||||
if ( ( (0x30 <= ch) && (ch <= 0x4F) )
|
||||
|| ( (0x51 <= ch) && (ch <= 0x57) )
|
||||
|| ( ch == 0x59 )
|
||||
|| ( ch == 0x5A )
|
||||
|| ( ch == 0x5C )
|
||||
|| ( (0x60 <= ch) && (ch <= 0x7E) ) ) {
|
||||
return Transition( new Esc_Dispatch, &family->s_Ground );
|
||||
}
|
||||
|
||||
if ( ch == 0x5B ) {
|
||||
return Transition( &family->s_CSI_Entry );
|
||||
}
|
||||
|
||||
if ( ch == 0x5D ) {
|
||||
return Transition( &family->s_OSC_String );
|
||||
}
|
||||
|
||||
if ( ch == 0x50 ) {
|
||||
return Transition( &family->s_DCS_Entry );
|
||||
}
|
||||
|
||||
if ( (ch == 0x58) || (ch == 0x5E) || (ch == 0x5F) ) {
|
||||
return Transition( &family->s_SOS_PM_APC_String );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition Escape_Intermediate::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) ) {
|
||||
return Transition( new Execute );
|
||||
}
|
||||
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect );
|
||||
}
|
||||
|
||||
if ( (0x30 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( new Esc_Dispatch, &family->s_Ground );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Action *CSI_Entry::enter( void ) const
|
||||
{
|
||||
return new Clear;
|
||||
}
|
||||
|
||||
Transition CSI_Entry::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) ) {
|
||||
return Transition( new Execute );
|
||||
}
|
||||
|
||||
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( new CSI_Dispatch, &family->s_Ground );
|
||||
}
|
||||
|
||||
if ( ( (0x30 <= ch) && (ch <= 0x39) )
|
||||
|| ( ch == 0x3B ) ) {
|
||||
return Transition( new Param, &family->s_CSI_Param );
|
||||
}
|
||||
|
||||
if ( (0x3C <= ch) && (ch <= 0x3F) ) {
|
||||
return Transition( new Collect, &family->s_CSI_Param );
|
||||
}
|
||||
|
||||
if ( ch == 0x3A ) {
|
||||
return Transition( &family->s_CSI_Ignore );
|
||||
}
|
||||
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect, &family->s_CSI_Intermediate );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition CSI_Param::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) ) {
|
||||
return Transition( new Execute );
|
||||
}
|
||||
|
||||
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
|
||||
return Transition( new Param );
|
||||
}
|
||||
|
||||
if ( ( ch == 0x3A ) || ( (0x3C <= ch) && (ch <= 0x3F) ) ) {
|
||||
return Transition( &family->s_CSI_Ignore );
|
||||
}
|
||||
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect, &family->s_CSI_Intermediate );
|
||||
}
|
||||
|
||||
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( new CSI_Dispatch, &family->s_Ground );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition CSI_Intermediate::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) ) {
|
||||
return Transition( new Execute );
|
||||
}
|
||||
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect );
|
||||
}
|
||||
|
||||
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( new CSI_Dispatch, &family->s_Ground );
|
||||
}
|
||||
|
||||
if ( (0x30 <= ch) && (ch <= 0x3F) ) {
|
||||
return Transition( &family->s_CSI_Ignore );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition CSI_Ignore::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) ) {
|
||||
return Transition( new Execute );
|
||||
}
|
||||
|
||||
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( &family->s_Ground );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Action *DCS_Entry::enter( void ) const
|
||||
{
|
||||
return new Clear;
|
||||
}
|
||||
|
||||
Transition DCS_Entry::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect, &family->s_DCS_Intermediate );
|
||||
}
|
||||
|
||||
if ( ch == 0x3A ) {
|
||||
return Transition( &family->s_DCS_Ignore );
|
||||
}
|
||||
|
||||
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
|
||||
return Transition( new Param, &family->s_DCS_Param );
|
||||
}
|
||||
|
||||
if ( (0x3C <= ch) && (ch <= 0x3F) ) {
|
||||
return Transition( new Collect, &family->s_DCS_Param );
|
||||
}
|
||||
|
||||
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( &family->s_DCS_Passthrough );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition DCS_Param::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
|
||||
return Transition( new Param );
|
||||
}
|
||||
|
||||
if ( ( ch == 0x3A ) || ( (0x3C <= ch) && (ch <= 0x3F) ) ) {
|
||||
return Transition( &family->s_DCS_Ignore );
|
||||
}
|
||||
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect, &family->s_DCS_Intermediate );
|
||||
}
|
||||
|
||||
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( &family->s_DCS_Passthrough );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition DCS_Intermediate::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
|
||||
return Transition( new Collect );
|
||||
}
|
||||
|
||||
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
|
||||
return Transition( &family->s_DCS_Passthrough );
|
||||
}
|
||||
|
||||
if ( (0x30 <= ch) && (ch <= 0x3F) ) {
|
||||
return Transition( &family->s_DCS_Ignore );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Action *DCS_Passthrough::enter( void ) const
|
||||
{
|
||||
return new Hook;
|
||||
}
|
||||
|
||||
Action *DCS_Passthrough::exit( void ) const
|
||||
{
|
||||
return new Unhook;
|
||||
}
|
||||
|
||||
Transition DCS_Passthrough::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( C0_prime( ch ) || ( (0x20 <= ch) && (ch <= 0x7E) ) ) {
|
||||
return Transition( new Put );
|
||||
}
|
||||
|
||||
if ( ch == 0x9C ) {
|
||||
return Transition( &family->s_Ground );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition DCS_Ignore::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( ch == 0x9C ) {
|
||||
return Transition( &family->s_Ground );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Action *OSC_String::enter( void ) const
|
||||
{
|
||||
return new OSC_Start;
|
||||
}
|
||||
|
||||
Action *OSC_String::exit( void ) const
|
||||
{
|
||||
return new OSC_End;
|
||||
}
|
||||
|
||||
Transition OSC_String::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( (0x20 <= ch) && (ch <= 0x7F) ) {
|
||||
return Transition( new OSC_Put );
|
||||
}
|
||||
|
||||
if ( (ch == 0x9C) || (ch == 0x07) ) { /* 0x07 is xterm non-ANSI variant */
|
||||
return Transition( &family->s_Ground );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
|
||||
Transition SOS_PM_APC_String::input_state_rule( wchar_t ch ) const
|
||||
{
|
||||
if ( ch == 0x9C ) {
|
||||
return Transition( &family->s_Ground );
|
||||
}
|
||||
|
||||
return Transition();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#ifndef PARSERSTATE_HPP
|
||||
#define PARSERSTATE_HPP
|
||||
|
||||
#include "parsertransition.h"
|
||||
|
||||
namespace Parser {
|
||||
class StateFamily;
|
||||
|
||||
class State
|
||||
{
|
||||
protected:
|
||||
virtual Transition input_state_rule( wchar_t ch ) const = 0;
|
||||
StateFamily *family;
|
||||
|
||||
private:
|
||||
Transition anywhere_rule( wchar_t ch ) const;
|
||||
|
||||
public:
|
||||
void setfamily( StateFamily *s_family ) { family = s_family; }
|
||||
Transition input( wchar_t ch ) const;
|
||||
virtual Action *enter( void ) const { return new Ignore; }
|
||||
virtual Action *exit( void ) const { return new Ignore; }
|
||||
|
||||
State() : family( NULL ) {};
|
||||
virtual ~State() {};
|
||||
|
||||
State( const State & );
|
||||
State & operator=( const State & );
|
||||
};
|
||||
|
||||
class Ground : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
|
||||
class Escape : public State {
|
||||
Action *enter( void ) const;
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
|
||||
class Escape_Intermediate : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
|
||||
class CSI_Entry : public State {
|
||||
Action *enter( void ) const;
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
class CSI_Param : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
class CSI_Intermediate : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
class CSI_Ignore : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
|
||||
class DCS_Entry : public State {
|
||||
Action *enter( void ) const;
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
class DCS_Param : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
class DCS_Intermediate : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
class DCS_Passthrough : public State {
|
||||
Action *enter( void ) const;
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
Action *exit( void ) const;
|
||||
};
|
||||
class DCS_Ignore : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
|
||||
class OSC_String : public State {
|
||||
Action *enter( void ) const;
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
Action *exit( void ) const;
|
||||
};
|
||||
class SOS_PM_APC_String : public State {
|
||||
Transition input_state_rule( wchar_t ch ) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef PARSERSTATEFAMILY_HPP
|
||||
#define PARSERSTATEFAMILY_HPP
|
||||
|
||||
#include "parserstate.h"
|
||||
|
||||
namespace Parser {
|
||||
class StateFamily
|
||||
{
|
||||
public:
|
||||
Ground s_Ground;
|
||||
|
||||
Escape s_Escape;
|
||||
Escape_Intermediate s_Escape_Intermediate;
|
||||
|
||||
CSI_Entry s_CSI_Entry;
|
||||
CSI_Param s_CSI_Param;
|
||||
CSI_Intermediate s_CSI_Intermediate;
|
||||
CSI_Ignore s_CSI_Ignore;
|
||||
|
||||
DCS_Entry s_DCS_Entry;
|
||||
DCS_Param s_DCS_Param;
|
||||
DCS_Intermediate s_DCS_Intermediate;
|
||||
DCS_Passthrough s_DCS_Passthrough;
|
||||
DCS_Ignore s_DCS_Ignore;
|
||||
|
||||
OSC_String s_OSC_String;
|
||||
SOS_PM_APC_String s_SOS_PM_APC_String;
|
||||
|
||||
StateFamily()
|
||||
: s_Ground(), s_Escape(), s_Escape_Intermediate(),
|
||||
s_CSI_Entry(), s_CSI_Param(), s_CSI_Intermediate(), s_CSI_Ignore(),
|
||||
s_DCS_Entry(), s_DCS_Param(), s_DCS_Intermediate(),
|
||||
s_DCS_Passthrough(), s_DCS_Ignore(),
|
||||
s_OSC_String(), s_SOS_PM_APC_String()
|
||||
{
|
||||
s_Ground.setfamily( this );
|
||||
s_Escape.setfamily( this );
|
||||
s_Escape_Intermediate.setfamily( this );
|
||||
s_CSI_Entry.setfamily( this );
|
||||
s_CSI_Param.setfamily( this );
|
||||
s_CSI_Intermediate.setfamily( this );
|
||||
s_CSI_Ignore.setfamily( this );
|
||||
s_DCS_Entry.setfamily( this );
|
||||
s_DCS_Param.setfamily( this );
|
||||
s_DCS_Intermediate.setfamily( this );
|
||||
s_DCS_Passthrough.setfamily( this );
|
||||
s_DCS_Ignore.setfamily( this );
|
||||
s_OSC_String.setfamily( this );
|
||||
s_SOS_PM_APC_String.setfamily( this );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef PARSERTRANSITION_HPP
|
||||
#define PARSERTRANSITION_HPP
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "parseraction.h"
|
||||
|
||||
namespace Parser {
|
||||
class State;
|
||||
|
||||
class Transition
|
||||
{
|
||||
public:
|
||||
Action *action;
|
||||
State *next_state;
|
||||
|
||||
Transition( const Transition &x )
|
||||
: action( x.action ),
|
||||
next_state( x.next_state ) {}
|
||||
Transition & operator=( const Transition &t )
|
||||
{
|
||||
action = t.action;
|
||||
next_state = t.next_state;
|
||||
|
||||
return *this;
|
||||
}
|
||||
virtual ~Transition() {}
|
||||
|
||||
Transition( Action *s_action=new Ignore, State *s_next_state=NULL )
|
||||
: action( s_action ), next_state( s_next_state )
|
||||
{}
|
||||
|
||||
Transition( State *s_next_state )
|
||||
: action( new Ignore ), next_state( s_next_state )
|
||||
{}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,155 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "terminal.h"
|
||||
#include "swrite.h"
|
||||
|
||||
using namespace Terminal;
|
||||
|
||||
Emulator::Emulator( size_t s_width, size_t s_height )
|
||||
: fb( s_width, s_height ), dispatch(), user()
|
||||
{}
|
||||
|
||||
std::string Emulator::read_octets_to_host( void )
|
||||
{
|
||||
std::string ret = dispatch.terminal_to_host;
|
||||
dispatch.terminal_to_host.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Emulator::execute( const Parser::Execute *act )
|
||||
{
|
||||
dispatch.dispatch( CONTROL, act, &fb );
|
||||
}
|
||||
|
||||
void Emulator::print( const Parser::Print *act )
|
||||
{
|
||||
assert( act->char_present );
|
||||
|
||||
int chwidth = act->ch == L'\0' ? -1 : wcwidth( act->ch );
|
||||
|
||||
Cell *this_cell = fb.get_mutable_cell();
|
||||
|
||||
Cell *combining_cell = fb.get_combining_cell(); /* can be null if we were resized */
|
||||
|
||||
switch ( chwidth ) {
|
||||
case 1: /* normal character */
|
||||
case 2: /* wide character */
|
||||
if ( fb.ds.auto_wrap_mode && fb.ds.next_print_will_wrap ) {
|
||||
fb.get_mutable_row( -1 )->wrap = true;
|
||||
fb.ds.move_col( 0 );
|
||||
fb.move_rows_autoscroll( 1 );
|
||||
}
|
||||
|
||||
/* wrap 2-cell chars if no room, even without will-wrap flag */
|
||||
if ( fb.ds.auto_wrap_mode
|
||||
&& (chwidth == 2)
|
||||
&& (fb.ds.get_cursor_col() == fb.ds.get_width() - 1) ) {
|
||||
fb.reset_cell( this_cell );
|
||||
fb.get_mutable_row( -1 )->wrap = false;
|
||||
/* There doesn't seem to be a consistent way to get the
|
||||
downstream terminal emulator to set the wrap-around
|
||||
copy-and-paste flag on a row that ends with an empty cell
|
||||
because a wide char was wrapped to the next line. */
|
||||
fb.ds.move_col( 0 );
|
||||
fb.move_rows_autoscroll( 1 );
|
||||
}
|
||||
|
||||
if ( fb.ds.insert_mode ) {
|
||||
for ( int i = 0; i < chwidth; i++ ) {
|
||||
fb.insert_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() );
|
||||
}
|
||||
}
|
||||
|
||||
this_cell = fb.get_mutable_cell();
|
||||
|
||||
fb.reset_cell( this_cell );
|
||||
this_cell->contents.push_back( act->ch );
|
||||
this_cell->width = chwidth;
|
||||
fb.apply_renditions_to_current_cell();
|
||||
|
||||
if ( chwidth == 2 ) { /* erase overlapped cell */
|
||||
if ( fb.ds.get_cursor_col() + 1 < fb.ds.get_width() ) {
|
||||
fb.reset_cell( fb.get_mutable_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() + 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
fb.ds.move_col( chwidth, true, true );
|
||||
|
||||
act->handled = true;
|
||||
break;
|
||||
case 0: /* combining character */
|
||||
if ( combining_cell == NULL ) { /* character is now offscreen */
|
||||
act->handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( combining_cell->contents.size() == 0 ) {
|
||||
/* cell starts with combining character */
|
||||
assert( this_cell == combining_cell );
|
||||
assert( combining_cell->width == 1 );
|
||||
combining_cell->fallback = true;
|
||||
fb.ds.move_col( 1, true, true );
|
||||
}
|
||||
|
||||
if ( combining_cell->contents.size() < 16 ) {
|
||||
/* seems like a reasonable limit on combining characters */
|
||||
combining_cell->contents.push_back( act->ch );
|
||||
}
|
||||
act->handled = true;
|
||||
break;
|
||||
case -1: /* unprintable character */
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::CSI_dispatch( const Parser::CSI_Dispatch *act )
|
||||
{
|
||||
dispatch.dispatch( CSI, act, &fb );
|
||||
}
|
||||
|
||||
void Emulator::OSC_end( const Parser::OSC_End *act )
|
||||
{
|
||||
dispatch.OSC_dispatch( act, &fb );
|
||||
}
|
||||
|
||||
void Emulator::Esc_dispatch( const Parser::Esc_Dispatch *act )
|
||||
{
|
||||
/* handle 7-bit ESC-encoding of C1 control characters */
|
||||
if ( (dispatch.get_dispatch_chars().size() == 0)
|
||||
&& (0x40 <= act->ch)
|
||||
&& (act->ch <= 0x5F) ) {
|
||||
Parser::Esc_Dispatch act2 = *act;
|
||||
act2.ch += 0x40;
|
||||
dispatch.dispatch( CONTROL, &act2, &fb );
|
||||
} else {
|
||||
dispatch.dispatch( ESCAPE, act, &fb );
|
||||
}
|
||||
}
|
||||
|
||||
std::string Emulator::open( void )
|
||||
{
|
||||
char appmode[ 6 ] = { 0x1b, '[', '?', '1', 'h', 0 };
|
||||
return std::string( appmode );
|
||||
}
|
||||
|
||||
std::string Emulator::close( void )
|
||||
{
|
||||
return std::string( "\033[?1l\033[!p" );
|
||||
}
|
||||
|
||||
void Emulator::resize( size_t s_width, size_t s_height )
|
||||
{
|
||||
fb.resize( s_width, s_height );
|
||||
}
|
||||
|
||||
bool Emulator::operator==( Emulator const &x ) const
|
||||
{
|
||||
/* dispatcher and user are irrelevant for us */
|
||||
return ( fb == x.fb );
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef TERMINAL_CPP
|
||||
#define TERMINAL_CPP
|
||||
|
||||
#include <wchar.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#include "parseraction.h"
|
||||
#include "terminalframebuffer.h"
|
||||
#include "terminaldispatcher.h"
|
||||
#include "terminaluserinput.h"
|
||||
#include "terminaldisplay.h"
|
||||
|
||||
namespace Terminal {
|
||||
class Emulator {
|
||||
friend void Parser::Print::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::Execute::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::Clear::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::Param::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::Collect::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::CSI_Dispatch::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::Esc_Dispatch::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::OSC_Start::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::OSC_Put::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::OSC_End::act_on_terminal( Emulator * ) const;
|
||||
|
||||
friend void Parser::UserByte::act_on_terminal( Emulator * ) const;
|
||||
friend void Parser::Resize::act_on_terminal( Emulator * ) const;
|
||||
|
||||
private:
|
||||
Framebuffer fb;
|
||||
Dispatcher dispatch;
|
||||
UserInput user;
|
||||
|
||||
/* action methods */
|
||||
void print( const Parser::Print *act );
|
||||
void execute( const Parser::Execute *act );
|
||||
void CSI_dispatch( const Parser::CSI_Dispatch *act );
|
||||
void Esc_dispatch( const Parser::Esc_Dispatch *act );
|
||||
void OSC_end( const Parser::OSC_End *act );
|
||||
void resize( size_t s_width, size_t s_height );
|
||||
|
||||
public:
|
||||
Emulator( size_t s_width, size_t s_height );
|
||||
|
||||
std::string read_octets_to_host( void );
|
||||
|
||||
static std::string open( void ); /* put user cursor keys in application mode */
|
||||
static std::string close( void ); /* restore user cursor keys */
|
||||
|
||||
const Framebuffer & get_fb( void ) const { return fb; }
|
||||
|
||||
bool operator==( Emulator const &x ) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,211 @@
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "terminaldispatcher.h"
|
||||
#include "parseraction.h"
|
||||
#include "terminalframebuffer.h"
|
||||
|
||||
using namespace Terminal;
|
||||
|
||||
Dispatcher::Dispatcher()
|
||||
: params(), parsed_params(), parsed( false ), dispatch_chars(),
|
||||
OSC_string(), terminal_to_host()
|
||||
{}
|
||||
|
||||
void Dispatcher::newparamchar( const Parser::Param *act )
|
||||
{
|
||||
assert( act->char_present );
|
||||
assert( (act->ch == ';') || ( (act->ch >= '0') && (act->ch <= '9') ) );
|
||||
if ( params.length() < 100 ) {
|
||||
/* enough for 16 five-char params plus 15 semicolons */
|
||||
params.push_back( act->ch );
|
||||
act->handled = true;
|
||||
}
|
||||
parsed = false;
|
||||
}
|
||||
|
||||
void Dispatcher::collect( const Parser::Collect *act )
|
||||
{
|
||||
assert( act->char_present );
|
||||
if ( ( dispatch_chars.length() < 8 ) /* never should need more than 2 */
|
||||
&& ( act->ch <= 255 ) ) { /* ignore non-8-bit */
|
||||
dispatch_chars.push_back( act->ch );
|
||||
act->handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Dispatcher::clear( const Parser::Clear *act )
|
||||
{
|
||||
params.clear();
|
||||
dispatch_chars.clear();
|
||||
parsed = false;
|
||||
act->handled = true;
|
||||
}
|
||||
|
||||
void Dispatcher::parse_params( void )
|
||||
{
|
||||
if ( parsed ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parsed_params.clear();
|
||||
const char *str = params.c_str();
|
||||
const char *segment_begin = str;
|
||||
|
||||
while ( 1 ) {
|
||||
const char *segment_end = strchr( segment_begin, ';' );
|
||||
if ( segment_end == NULL ) {
|
||||
break;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
char *endptr;
|
||||
int val = strtol( segment_begin, &endptr, 10 );
|
||||
if ( endptr == segment_begin ) {
|
||||
val = -1;
|
||||
}
|
||||
if ( errno == 0 ) {
|
||||
parsed_params.push_back( val );
|
||||
}
|
||||
|
||||
segment_begin = segment_end + 1;
|
||||
}
|
||||
|
||||
/* get last param */
|
||||
errno = 0;
|
||||
char *endptr;
|
||||
int val = strtol( segment_begin, &endptr, 10 );
|
||||
if ( endptr == segment_begin ) {
|
||||
val = -1;
|
||||
}
|
||||
if ( errno == 0 ) {
|
||||
parsed_params.push_back( val );
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
int Dispatcher::getparam( size_t N, int defaultval )
|
||||
{
|
||||
int ret = defaultval;
|
||||
if ( !parsed ) {
|
||||
parse_params();
|
||||
}
|
||||
|
||||
if ( parsed_params.size() > N ) {
|
||||
ret = parsed_params[ N ];
|
||||
}
|
||||
if ( ret < 1 ) ret = defaultval;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Dispatcher::param_count( void )
|
||||
{
|
||||
if ( !parsed ) {
|
||||
parse_params();
|
||||
}
|
||||
|
||||
return parsed_params.size();
|
||||
}
|
||||
|
||||
std::string Dispatcher::str( void )
|
||||
{
|
||||
char assum[ 64 ];
|
||||
snprintf( assum, 64, "[dispatch=\"%s\" params=\"%s\"]",
|
||||
dispatch_chars.c_str(), params.c_str() );
|
||||
return std::string( assum );
|
||||
}
|
||||
|
||||
/* construct on first use to avoid static initialization order crash */
|
||||
DispatchRegistry & Terminal::get_global_dispatch_registry( void )
|
||||
{
|
||||
static DispatchRegistry global_dispatch_registry;
|
||||
return global_dispatch_registry;
|
||||
}
|
||||
|
||||
static void register_function( Function_Type type,
|
||||
std::string dispatch_chars,
|
||||
Function f )
|
||||
{
|
||||
switch ( type ) {
|
||||
case ESCAPE:
|
||||
get_global_dispatch_registry().escape.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
|
||||
break;
|
||||
case CSI:
|
||||
get_global_dispatch_registry().CSI.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
|
||||
break;
|
||||
case CONTROL:
|
||||
get_global_dispatch_registry().control.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Function::Function( Function_Type type, std::string dispatch_chars,
|
||||
void (*s_function)( Framebuffer *, Dispatcher * ),
|
||||
bool s_clears_wrap_state )
|
||||
: function( s_function ), clears_wrap_state( s_clears_wrap_state )
|
||||
{
|
||||
register_function( type, dispatch_chars, *this );
|
||||
}
|
||||
|
||||
void Dispatcher::dispatch( Function_Type type, const Parser::Action *act, Framebuffer *fb )
|
||||
{
|
||||
/* add final char to dispatch key */
|
||||
if ( (type == ESCAPE) || (type == CSI) ) {
|
||||
assert( act->char_present );
|
||||
Parser::Collect act2;
|
||||
act2.char_present = true;
|
||||
act2.ch = act->ch;
|
||||
collect( &act2 );
|
||||
}
|
||||
|
||||
dispatch_map_t *map = NULL;
|
||||
switch ( type ) {
|
||||
case ESCAPE: map = &get_global_dispatch_registry().escape; break;
|
||||
case CSI: map = &get_global_dispatch_registry().CSI; break;
|
||||
case CONTROL: map = &get_global_dispatch_registry().control; break;
|
||||
}
|
||||
|
||||
std::string key = dispatch_chars;
|
||||
if ( type == CONTROL ) {
|
||||
assert( act->ch <= 255 );
|
||||
char ctrlstr[ 2 ] = { (char)act->ch, 0 };
|
||||
key = std::string( ctrlstr, 1 );
|
||||
}
|
||||
|
||||
dispatch_map_t::const_iterator i = map->find( key );
|
||||
if ( i == map->end() ) {
|
||||
/* unknown function */
|
||||
fb->ds.next_print_will_wrap = false;
|
||||
return;
|
||||
} else {
|
||||
act->handled = true;
|
||||
if ( i->second.clears_wrap_state ) {
|
||||
fb->ds.next_print_will_wrap = false;
|
||||
}
|
||||
return i->second.function( fb, this );
|
||||
}
|
||||
}
|
||||
|
||||
void Dispatcher::OSC_put( const Parser::OSC_Put *act )
|
||||
{
|
||||
assert( act->char_present );
|
||||
if ( OSC_string.size() < 256 ) { /* should be a long enough window title */
|
||||
OSC_string.push_back( act->ch );
|
||||
act->handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Dispatcher::OSC_start( const Parser::OSC_Start *act )
|
||||
{
|
||||
OSC_string.clear();
|
||||
act->handled = true;
|
||||
}
|
||||
|
||||
bool Dispatcher::operator==( const Dispatcher &x ) const
|
||||
{
|
||||
return ( params == x.params ) && ( parsed_params == x.parsed_params ) && ( parsed == x.parsed )
|
||||
&& ( dispatch_chars == x.dispatch_chars ) && ( OSC_string == x.OSC_string ) && ( terminal_to_host == x.terminal_to_host );
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef TERMINALDISPATCHER_HPP
|
||||
#define TERMINALDISPATCHER_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace Parser {
|
||||
class Action;
|
||||
class Param;
|
||||
class Collect;
|
||||
class Clear;
|
||||
class Esc_Dispatch;
|
||||
class CSI_Dispatch;
|
||||
class Execute;
|
||||
class OSC_Start;
|
||||
class OSC_Put;
|
||||
class OSC_End;
|
||||
}
|
||||
|
||||
namespace Terminal {
|
||||
class Framebuffer;
|
||||
class Dispatcher;
|
||||
|
||||
enum Function_Type { ESCAPE, CSI, CONTROL };
|
||||
|
||||
class Function {
|
||||
public:
|
||||
Function() : function( NULL ), clears_wrap_state( true ) {}
|
||||
Function( Function_Type type, std::string dispatch_chars,
|
||||
void (*s_function)( Framebuffer *, Dispatcher * ),
|
||||
bool s_clears_wrap_state = true );
|
||||
void (*function)( Framebuffer *, Dispatcher * );
|
||||
bool clears_wrap_state;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, Function> dispatch_map_t;
|
||||
|
||||
class DispatchRegistry {
|
||||
public:
|
||||
dispatch_map_t escape;
|
||||
dispatch_map_t CSI;
|
||||
dispatch_map_t control;
|
||||
|
||||
DispatchRegistry() : escape(), CSI(), control() {}
|
||||
};
|
||||
|
||||
DispatchRegistry & get_global_dispatch_registry( void );
|
||||
|
||||
class Dispatcher {
|
||||
private:
|
||||
std::string params;
|
||||
std::vector<int> parsed_params;
|
||||
bool parsed;
|
||||
|
||||
std::string dispatch_chars;
|
||||
std::vector<wchar_t> OSC_string; /* only used to set the window title */
|
||||
|
||||
void parse_params( void );
|
||||
|
||||
public:
|
||||
std::string terminal_to_host; /* this is the reply string */
|
||||
|
||||
Dispatcher();
|
||||
int getparam( size_t N, int defaultval );
|
||||
int param_count( void );
|
||||
|
||||
void newparamchar( const Parser::Param *act );
|
||||
void collect( const Parser::Collect *act );
|
||||
void clear( const Parser::Clear *act );
|
||||
|
||||
std::string str( void );
|
||||
|
||||
void dispatch( Function_Type type, const Parser::Action *act, Framebuffer *fb );
|
||||
std::string get_dispatch_chars( void ) { return dispatch_chars; }
|
||||
std::vector<wchar_t> get_OSC_string( void ) { return OSC_string; }
|
||||
|
||||
void OSC_put( const Parser::OSC_Put *act );
|
||||
void OSC_start( const Parser::OSC_Start *act );
|
||||
void OSC_dispatch( const Parser::OSC_End *act, Framebuffer *fb );
|
||||
|
||||
bool operator==( const Dispatcher &x ) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,259 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "terminaldisplay.h"
|
||||
|
||||
using namespace Terminal;
|
||||
|
||||
/* Print a new "frame" to the terminal, using ANSI/ECMA-48 escape codes. */
|
||||
|
||||
std::string Display::new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f )
|
||||
{
|
||||
FrameState frame( last );
|
||||
|
||||
char tmp[ 64 ];
|
||||
|
||||
/* has bell been rung? */
|
||||
if ( f.get_bell_count() != frame.last_frame.get_bell_count() ) {
|
||||
frame.append( "\x07" );
|
||||
}
|
||||
|
||||
/* has window title changed? */
|
||||
if ( (!initialized)
|
||||
|| (f.get_window_title() != frame.last_frame.get_window_title()) ) {
|
||||
/* set window title */
|
||||
frame.append( "\033]0;" );
|
||||
const std::deque<wchar_t> &window_title( f.get_window_title() );
|
||||
for ( auto i = window_title.begin();
|
||||
i != window_title.end();
|
||||
i++ ) {
|
||||
snprintf( tmp, 64, "%lc", *i );
|
||||
frame.append( tmp );
|
||||
}
|
||||
frame.append( "\033\\" );
|
||||
}
|
||||
|
||||
/* has reverse video state changed? */
|
||||
if ( (!initialized)
|
||||
|| (f.ds.reverse_video != frame.last_frame.ds.reverse_video) ) {
|
||||
/* set reverse video */
|
||||
snprintf( tmp, 64, "\033[?5%c", (f.ds.reverse_video ? 'h' : 'l') );
|
||||
frame.append( tmp );
|
||||
}
|
||||
|
||||
/* has size changed? */
|
||||
if ( (!initialized)
|
||||
|| (f.ds.get_width() != frame.last_frame.ds.get_width())
|
||||
|| (f.ds.get_height() != frame.last_frame.ds.get_height()) ) {
|
||||
/* clear screen */
|
||||
frame.append( "\033[0m\033[H\033[2J" );
|
||||
initialized = false;
|
||||
frame.cursor_x = frame.cursor_y = 0;
|
||||
frame.current_rendition_string = "\033[0m";
|
||||
} else {
|
||||
frame.cursor_x = frame.last_frame.ds.get_cursor_col();
|
||||
frame.cursor_y = frame.last_frame.ds.get_cursor_row();
|
||||
frame.current_rendition_string = frame.last_frame.ds.get_renditions().sgr();
|
||||
}
|
||||
|
||||
/* shortcut -- has display moved up by a certain number of lines? */
|
||||
frame.y = 0;
|
||||
|
||||
if ( initialized ) {
|
||||
int lines_scrolled = 0;
|
||||
int scroll_height = 0;
|
||||
|
||||
for ( int row = 0; row < f.ds.get_height(); row++ ) {
|
||||
if ( *(f.get_row( 0 )) == *(frame.last_frame.get_row( row )) ) {
|
||||
/* found a scroll */
|
||||
lines_scrolled = row;
|
||||
scroll_height = 1;
|
||||
|
||||
/* how big is the region that was scrolled? */
|
||||
for ( int region_height = 1;
|
||||
lines_scrolled + region_height < f.ds.get_height();
|
||||
region_height++ ) {
|
||||
if ( *(f.get_row( region_height ))
|
||||
== *(frame.last_frame.get_row( lines_scrolled + region_height )) ) {
|
||||
scroll_height = region_height + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( scroll_height ) {
|
||||
frame.y = scroll_height;
|
||||
|
||||
if ( lines_scrolled ) {
|
||||
if ( frame.cursor_y != f.ds.get_height() - 1 ) {
|
||||
frame.append_silent_move( f.ds.get_height() - 1, 0 );
|
||||
}
|
||||
|
||||
if ( frame.current_rendition_string != "\033[0m" ) {
|
||||
frame.append( "\033[0m" );
|
||||
frame.current_rendition_string = "\033[0m";
|
||||
}
|
||||
|
||||
for ( int i = 0; i < lines_scrolled; i++ ) {
|
||||
frame.append( "\n" );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < f.ds.get_height(); i++ ) {
|
||||
if ( i + lines_scrolled < f.ds.get_height() ) {
|
||||
*(frame.last_frame.get_mutable_row( i )) = *(frame.last_frame.get_row( i + lines_scrolled ));
|
||||
} else {
|
||||
frame.last_frame.get_mutable_row( i )->reset( 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* iterate for every cell */
|
||||
for ( ; frame.y < f.ds.get_height(); frame.y++ ) {
|
||||
int last_x = 0;
|
||||
for ( frame.x = 0;
|
||||
frame.x < f.ds.get_width(); /* let put_cell() handle advance */ ) {
|
||||
last_x = frame.x;
|
||||
put_cell( initialized, frame, f );
|
||||
|
||||
/* To hint that a word-select should group the end of one line
|
||||
with the beginning of the next, we let the real cursor
|
||||
actually wrap around in cases where it wrapped around for us. */
|
||||
|
||||
if ( (frame.cursor_x >= f.ds.get_width())
|
||||
&& (frame.y < f.ds.get_height() - 1)
|
||||
&& f.get_row( frame.y )->wrap
|
||||
&& (!initialized || !frame.last_frame.get_row( frame.y )->wrap) ) {
|
||||
/* next write will wrap */
|
||||
frame.cursor_x = 0;
|
||||
frame.cursor_y++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Turn off wrap */
|
||||
if ( (frame.y < f.ds.get_height() - 1)
|
||||
&& (!f.get_row( frame.y )->wrap)
|
||||
&& (!initialized || frame.last_frame.get_row( frame.y )->wrap) ) {
|
||||
frame.x = last_x;
|
||||
if ( initialized ) {
|
||||
frame.last_frame.reset_cell( frame.last_frame.get_mutable_cell( frame.y, frame.x ) );
|
||||
}
|
||||
|
||||
snprintf( tmp, 64, "\033[%d;%dH\033[K", frame.y + 1, frame.x + 1 );
|
||||
frame.append( tmp );
|
||||
frame.cursor_x = frame.x;
|
||||
|
||||
put_cell( initialized, frame, f );
|
||||
}
|
||||
}
|
||||
|
||||
/* has cursor location changed? */
|
||||
if ( (!initialized)
|
||||
|| (f.ds.get_cursor_row() != frame.cursor_y)
|
||||
|| (f.ds.get_cursor_col() != frame.cursor_x) ) {
|
||||
snprintf( tmp, 64, "\033[%d;%dH", f.ds.get_cursor_row() + 1,
|
||||
f.ds.get_cursor_col() + 1 );
|
||||
frame.append( tmp );
|
||||
frame.cursor_x = f.ds.get_cursor_col();
|
||||
frame.cursor_y = f.ds.get_cursor_row();
|
||||
}
|
||||
|
||||
/* has cursor visibility changed? */
|
||||
if ( (!initialized)
|
||||
|| (f.ds.cursor_visible != frame.last_frame.ds.cursor_visible) ) {
|
||||
if ( f.ds.cursor_visible ) {
|
||||
frame.append( "\033[?25h" );
|
||||
} else {
|
||||
frame.append( "\033[?25l" );
|
||||
}
|
||||
}
|
||||
|
||||
/* have renditions changed? */
|
||||
if ( (!initialized)
|
||||
|| (f.ds.get_renditions().sgr() != frame.current_rendition_string) ) {
|
||||
frame.appendstring( f.ds.get_renditions().sgr() );
|
||||
frame.current_rendition_string = f.ds.get_renditions().sgr();
|
||||
}
|
||||
|
||||
return frame.str;
|
||||
}
|
||||
|
||||
void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &f )
|
||||
{
|
||||
char tmp[ 64 ];
|
||||
|
||||
const Cell *cell = f.get_cell( frame.y, frame.x );
|
||||
|
||||
if ( initialized
|
||||
&& ( *cell == *(frame.last_frame.get_cell( frame.y, frame.x )) ) ) {
|
||||
frame.x += cell->width;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( (frame.x != frame.cursor_x) || (frame.y != frame.cursor_y) ) {
|
||||
frame.append_silent_move( frame.y, frame.x );
|
||||
}
|
||||
|
||||
std::string rendition_str = cell->renditions.sgr();
|
||||
|
||||
if ( frame.current_rendition_string != rendition_str ) {
|
||||
/* print renditions */
|
||||
frame.appendstring( rendition_str );
|
||||
frame.current_rendition_string = rendition_str;
|
||||
}
|
||||
|
||||
if ( cell->contents.empty() ) {
|
||||
/* see how far we can stretch a clear */
|
||||
int clear_count = 0;
|
||||
for ( int col = frame.x; col < f.ds.get_width(); col++ ) {
|
||||
const Cell *other_cell = f.get_cell( frame.y, col );
|
||||
if ( (cell->renditions == other_cell->renditions)
|
||||
&& (other_cell->contents.empty()) ) {
|
||||
clear_count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
snprintf( tmp, 64, "\033[%dX", clear_count );
|
||||
frame.append( tmp );
|
||||
|
||||
frame.x += clear_count;
|
||||
return;
|
||||
}
|
||||
|
||||
/* cells that begin with combining character get combiner attached to no-break space */
|
||||
if ( cell->fallback ) {
|
||||
snprintf( tmp, 64, "%lc", 0xA0 );
|
||||
frame.append( tmp );
|
||||
}
|
||||
|
||||
for ( std::vector<wchar_t>::const_iterator i = cell->contents.begin();
|
||||
i != cell->contents.end();
|
||||
i++ ) {
|
||||
snprintf( tmp, 64, "%lc", *i );
|
||||
frame.append( tmp );
|
||||
}
|
||||
|
||||
frame.x += cell->width;
|
||||
frame.cursor_x += cell->width;
|
||||
}
|
||||
|
||||
void FrameState::append_silent_move( int y, int x )
|
||||
{
|
||||
char tmp[ 64 ];
|
||||
|
||||
/* turn off cursor if necessary before moving cursor */
|
||||
if ( last_frame.ds.cursor_visible ) {
|
||||
append( "\033[?25l" );
|
||||
last_frame.ds.cursor_visible = false;
|
||||
}
|
||||
|
||||
snprintf( tmp, 64, "\033[%d;%dH", y + 1, x + 1 );
|
||||
append( tmp );
|
||||
cursor_x = x;
|
||||
cursor_y = y;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef TERMINALDISPLAY_HPP
|
||||
#define TERMINALDISPLAY_HPP
|
||||
|
||||
#include "terminalframebuffer.h"
|
||||
|
||||
namespace Terminal {
|
||||
/* variables used within a new_frame */
|
||||
class FrameState {
|
||||
public:
|
||||
int x, y;
|
||||
std::string str;
|
||||
|
||||
int cursor_x, cursor_y;
|
||||
std::string current_rendition_string;
|
||||
|
||||
Framebuffer last_frame;
|
||||
|
||||
FrameState( const Framebuffer &s_last )
|
||||
: x(0), y(0),
|
||||
str(), cursor_x(0), cursor_y(0), current_rendition_string(),
|
||||
last_frame( s_last )
|
||||
{
|
||||
str.reserve( 1024 );
|
||||
}
|
||||
|
||||
void append( const char * s ) { str.append( s ); }
|
||||
void appendstring( const std::string s ) { str.append( s ); }
|
||||
|
||||
void append_silent_move( int y, int x );
|
||||
};
|
||||
|
||||
class Display {
|
||||
private:
|
||||
static void put_cell( bool initialized, FrameState &frame, const Framebuffer &f );
|
||||
|
||||
public:
|
||||
static std::string new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f );
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,425 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "terminalframebuffer.h"
|
||||
|
||||
using namespace Terminal;
|
||||
|
||||
void Cell::reset( int background_color )
|
||||
{
|
||||
contents.clear();
|
||||
fallback = false;
|
||||
width = 1;
|
||||
renditions = Renditions( background_color );
|
||||
}
|
||||
|
||||
DrawState::DrawState( int s_width, int s_height )
|
||||
: width( s_width ), height( s_height ),
|
||||
cursor_col( 0 ), cursor_row( 0 ),
|
||||
combining_char_col( 0 ), combining_char_row( 0 ), tabs( s_width ),
|
||||
scrolling_region_top_row( 0 ), scrolling_region_bottom_row( height - 1 ),
|
||||
renditions( 0 ), save(),
|
||||
next_print_will_wrap( false ), origin_mode( false ), auto_wrap_mode( true ),
|
||||
insert_mode( false ), cursor_visible( true ), reverse_video( false ),
|
||||
application_mode_cursor_keys( false )
|
||||
{
|
||||
for ( int i = 0; i < width; i++ ) {
|
||||
tabs[ i ] = ( (i % 8) == 0 );
|
||||
}
|
||||
}
|
||||
|
||||
Framebuffer::Framebuffer( int s_width, int s_height )
|
||||
: rows( s_height, Row( s_width, 0 ) ), window_title(), bell_count( 0 ), ds( s_width, s_height )
|
||||
{
|
||||
assert( s_height > 0 );
|
||||
assert( s_width > 0 );
|
||||
}
|
||||
|
||||
void Framebuffer::scroll( int N )
|
||||
{
|
||||
if ( N >= 0 ) {
|
||||
for ( int i = 0; i < N; i++ ) {
|
||||
delete_line( ds.get_scrolling_region_top_row() );
|
||||
ds.move_row( -1, true );
|
||||
}
|
||||
} else {
|
||||
N = -N;
|
||||
|
||||
for ( int i = 0; i < N; i++ ) {
|
||||
rows.insert( rows.begin() + ds.get_scrolling_region_top_row(), newrow() );
|
||||
rows.erase( rows.begin() + ds.get_scrolling_region_bottom_row() + 1 );
|
||||
ds.move_row( 1, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawState::new_grapheme( void )
|
||||
{
|
||||
combining_char_col = cursor_col;
|
||||
combining_char_row = cursor_row;
|
||||
}
|
||||
|
||||
void DrawState::snap_cursor_to_border( void )
|
||||
{
|
||||
if ( cursor_row < limit_top() ) cursor_row = limit_top();
|
||||
if ( cursor_row > limit_bottom() ) cursor_row = limit_bottom();
|
||||
if ( cursor_col < 0 ) cursor_col = 0;
|
||||
if ( cursor_col >= width ) cursor_col = width - 1;
|
||||
}
|
||||
|
||||
void DrawState::move_row( int N, bool relative )
|
||||
{
|
||||
if ( relative ) {
|
||||
cursor_row += N;
|
||||
} else {
|
||||
cursor_row = N + limit_top();
|
||||
}
|
||||
|
||||
snap_cursor_to_border();
|
||||
new_grapheme();
|
||||
next_print_will_wrap = false;
|
||||
}
|
||||
|
||||
void DrawState::move_col( int N, bool relative, bool implicit )
|
||||
{
|
||||
if ( implicit ) {
|
||||
new_grapheme();
|
||||
}
|
||||
|
||||
if ( relative ) {
|
||||
cursor_col += N;
|
||||
} else {
|
||||
cursor_col = N;
|
||||
}
|
||||
|
||||
if ( implicit && (cursor_col >= width) ) {
|
||||
next_print_will_wrap = true;
|
||||
}
|
||||
|
||||
snap_cursor_to_border();
|
||||
if ( !implicit ) {
|
||||
new_grapheme();
|
||||
next_print_will_wrap = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Framebuffer::move_rows_autoscroll( int rows )
|
||||
{
|
||||
/* don't scroll if outside the scrolling region */
|
||||
if ( (ds.get_cursor_row() < ds.get_scrolling_region_top_row())
|
||||
|| (ds.get_cursor_row() > ds.get_scrolling_region_bottom_row()) ) {
|
||||
ds.move_row( rows, true );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ds.get_cursor_row() + rows > ds.get_scrolling_region_bottom_row() ) {
|
||||
scroll( ds.get_cursor_row() + rows - ds.get_scrolling_region_bottom_row() );
|
||||
} else if ( ds.get_cursor_row() + rows < ds.get_scrolling_region_top_row() ) {
|
||||
scroll( ds.get_cursor_row() + rows - ds.get_scrolling_region_top_row() );
|
||||
}
|
||||
|
||||
ds.move_row( rows, true );
|
||||
}
|
||||
|
||||
Cell *Framebuffer::get_combining_cell( void )
|
||||
{
|
||||
if ( (ds.get_combining_char_col() < 0)
|
||||
|| (ds.get_combining_char_row() < 0)
|
||||
|| (ds.get_combining_char_col() >= ds.get_width())
|
||||
|| (ds.get_combining_char_row() >= ds.get_height()) ) {
|
||||
return NULL;
|
||||
} /* can happen if a resize came in between */
|
||||
|
||||
return &rows[ ds.get_combining_char_row() ].cells[ ds.get_combining_char_col() ];
|
||||
}
|
||||
|
||||
void DrawState::set_tab( void )
|
||||
{
|
||||
tabs[ cursor_col ] = true;
|
||||
}
|
||||
|
||||
void DrawState::clear_tab( int col )
|
||||
{
|
||||
tabs[ col ] = false;
|
||||
}
|
||||
|
||||
int DrawState::get_next_tab( void )
|
||||
{
|
||||
for ( int i = cursor_col + 1; i < width; i++ ) {
|
||||
if ( tabs[ i ] ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void DrawState::set_scrolling_region( int top, int bottom )
|
||||
{
|
||||
if ( height < 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrolling_region_top_row = top;
|
||||
scrolling_region_bottom_row = bottom;
|
||||
|
||||
if ( scrolling_region_top_row < 0 ) scrolling_region_top_row = 0;
|
||||
if ( scrolling_region_bottom_row >= height ) scrolling_region_bottom_row = height - 1;
|
||||
|
||||
if ( scrolling_region_bottom_row < scrolling_region_top_row )
|
||||
scrolling_region_bottom_row = scrolling_region_top_row;
|
||||
/* real rule requires TWO-line scrolling region */
|
||||
|
||||
if ( origin_mode ) {
|
||||
snap_cursor_to_border();
|
||||
new_grapheme();
|
||||
}
|
||||
}
|
||||
|
||||
int DrawState::limit_top( void )
|
||||
{
|
||||
return origin_mode ? scrolling_region_top_row : 0;
|
||||
}
|
||||
|
||||
int DrawState::limit_bottom( void )
|
||||
{
|
||||
return origin_mode ? scrolling_region_bottom_row : height - 1;
|
||||
}
|
||||
|
||||
std::vector<int> DrawState::get_tabs( void )
|
||||
{
|
||||
std::vector<int> ret;
|
||||
|
||||
for ( int i = 0; i < width; i++ ) {
|
||||
if ( tabs[ i ] ) {
|
||||
ret.push_back( i );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Framebuffer::apply_renditions_to_current_cell( void )
|
||||
{
|
||||
get_mutable_cell()->renditions = ds.get_renditions();
|
||||
}
|
||||
|
||||
SavedCursor::SavedCursor()
|
||||
: cursor_col( 0 ), cursor_row( 0 ),
|
||||
renditions( 0 ),
|
||||
auto_wrap_mode( true ),
|
||||
origin_mode( false )
|
||||
{}
|
||||
|
||||
void DrawState::save_cursor( void )
|
||||
{
|
||||
save.cursor_col = cursor_col;
|
||||
save.cursor_row = cursor_row;
|
||||
save.renditions = renditions;
|
||||
save.auto_wrap_mode = auto_wrap_mode;
|
||||
save.origin_mode = origin_mode;
|
||||
}
|
||||
|
||||
void DrawState::restore_cursor( void )
|
||||
{
|
||||
cursor_col = save.cursor_col;
|
||||
cursor_row = save.cursor_row;
|
||||
renditions = save.renditions;
|
||||
auto_wrap_mode = save.auto_wrap_mode;
|
||||
origin_mode = save.origin_mode;
|
||||
|
||||
snap_cursor_to_border(); /* we could have resized in between */
|
||||
new_grapheme();
|
||||
}
|
||||
|
||||
void Framebuffer::insert_line( int before_row )
|
||||
{
|
||||
if ( (before_row < ds.get_scrolling_region_top_row())
|
||||
|| (before_row > ds.get_scrolling_region_bottom_row() + 1) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
rows.insert( rows.begin() + before_row, newrow() );
|
||||
rows.erase( rows.begin() + ds.get_scrolling_region_bottom_row() + 1 );
|
||||
}
|
||||
|
||||
void Framebuffer::delete_line( int row )
|
||||
{
|
||||
if ( (row < ds.get_scrolling_region_top_row())
|
||||
|| (row > ds.get_scrolling_region_bottom_row()) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
int insertbefore = ds.get_scrolling_region_bottom_row() + 1;
|
||||
if ( insertbefore == ds.get_height() ) {
|
||||
rows.push_back( newrow() );
|
||||
} else {
|
||||
rows.insert( rows.begin() + insertbefore, newrow() );
|
||||
}
|
||||
|
||||
rows.erase( rows.begin() + row );
|
||||
}
|
||||
|
||||
void Row::insert_cell( int col, int background_color )
|
||||
{
|
||||
cells.insert( cells.begin() + col, Cell( background_color ) );
|
||||
cells.pop_back();
|
||||
}
|
||||
|
||||
void Row::delete_cell( int col, int background_color )
|
||||
{
|
||||
cells.push_back( Cell( background_color ) );
|
||||
cells.erase( cells.begin() + col );
|
||||
}
|
||||
|
||||
void Framebuffer::insert_cell( int row, int col )
|
||||
{
|
||||
rows[ row ].insert_cell( col, ds.get_background_rendition() );
|
||||
}
|
||||
|
||||
void Framebuffer::delete_cell( int row, int col )
|
||||
{
|
||||
rows[ row ].delete_cell( col, ds.get_background_rendition() );
|
||||
}
|
||||
|
||||
void Framebuffer::reset( void )
|
||||
{
|
||||
int width = ds.get_width(), height = ds.get_height();
|
||||
ds = DrawState( width, height );
|
||||
rows = std::deque<Row>( height, newrow() );
|
||||
window_title.clear();
|
||||
/* do not reset bell_count */
|
||||
}
|
||||
|
||||
void Framebuffer::soft_reset( void )
|
||||
{
|
||||
ds.insert_mode = false;
|
||||
ds.origin_mode = false;
|
||||
ds.cursor_visible = true; /* per xterm and gnome-terminal */
|
||||
ds.application_mode_cursor_keys = false;
|
||||
ds.set_scrolling_region( 0, ds.get_height() - 1 );
|
||||
ds.add_rendition( 0 );
|
||||
ds.clear_saved_cursor();
|
||||
}
|
||||
|
||||
void Framebuffer::resize( int s_width, int s_height )
|
||||
{
|
||||
assert( s_width > 0 );
|
||||
assert( s_height > 0 );
|
||||
|
||||
rows.resize( s_height, newrow() );
|
||||
|
||||
for ( std::deque<Row>::iterator i = rows.begin();
|
||||
i != rows.end();
|
||||
i++ ) {
|
||||
(*i).cells.resize( s_width, Cell( ds.get_background_rendition() ) );
|
||||
}
|
||||
|
||||
ds.resize( s_width, s_height );
|
||||
}
|
||||
|
||||
void DrawState::resize( int s_width, int s_height )
|
||||
{
|
||||
if ( (width != s_width)
|
||||
|| (height != s_height) ) {
|
||||
/* reset entire scrolling region on any resize */
|
||||
/* xterm and rxvt-unicode do this. gnome-terminal only
|
||||
resets scrolling region if it has to become smaller in resize */
|
||||
scrolling_region_top_row = 0;
|
||||
scrolling_region_bottom_row = s_height - 1;
|
||||
}
|
||||
|
||||
width = s_width;
|
||||
height = s_height;
|
||||
|
||||
snap_cursor_to_border();
|
||||
|
||||
tabs.resize( width );
|
||||
|
||||
/* saved cursor will be snapped to border on restore */
|
||||
|
||||
/* invalidate combining char cell if necessary */
|
||||
if ( (combining_char_col >= width)
|
||||
|| (combining_char_row >= height) ) {
|
||||
combining_char_col = combining_char_row = -1;
|
||||
}
|
||||
}
|
||||
|
||||
Renditions::Renditions( int s_background )
|
||||
: bold( false ), underlined( false ), blink( false ),
|
||||
inverse( false ), invisible( false ), foreground_color( 0 ),
|
||||
background_color( s_background )
|
||||
{}
|
||||
|
||||
void Renditions::set_rendition( int num )
|
||||
{
|
||||
if ( num == 0 ) {
|
||||
bold = underlined = blink = inverse = invisible = false;
|
||||
foreground_color = background_color = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( (30 <= num) && (num <= 39) ) { /* foreground color */
|
||||
foreground_color = num;
|
||||
return;
|
||||
} else if ( (40 <= num) && (num <= 49) ) { /* background color */
|
||||
background_color = num;
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( num ) {
|
||||
case 1: case 22: bold = (num == 1); break;
|
||||
case 4: case 24: underlined = (num == 4); break;
|
||||
case 5: case 25: blink = (num == 5); break;
|
||||
case 7: case 27: inverse = (num == 7); break;
|
||||
case 8: case 28: invisible = (num == 8); break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Renditions::sgr( void ) const
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
ret.append( "\033[0" );
|
||||
if ( bold ) ret.append( ";1" );
|
||||
if ( underlined ) ret.append( ";4" );
|
||||
if ( blink ) ret.append( ";5" );
|
||||
if ( inverse ) ret.append( ";7" );
|
||||
if ( invisible ) ret.append( ";8" );
|
||||
if ( foreground_color ) {
|
||||
char col[ 8 ];
|
||||
snprintf( col, 8, ";%d", foreground_color );
|
||||
ret.append( col );
|
||||
}
|
||||
if ( background_color ) {
|
||||
char col[ 8 ];
|
||||
snprintf( col, 8, ";%d", background_color );
|
||||
ret.append( col );
|
||||
}
|
||||
ret.append( "m" );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Row::reset( int background_color )
|
||||
{
|
||||
for ( std::vector<Cell>::iterator i = cells.begin();
|
||||
i != cells.end();
|
||||
i++ ) {
|
||||
i->reset( background_color );
|
||||
}
|
||||
}
|
||||
|
||||
void Framebuffer::prefix_window_title( const std::deque<wchar_t> &s )
|
||||
{
|
||||
for ( auto i = s.rbegin(); i != s.rend(); i++ ) {
|
||||
window_title.push_front( *i );
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t Cell::debug_contents( void ) const
|
||||
{
|
||||
if ( contents.empty() ) {
|
||||
return '_';
|
||||
} else {
|
||||
return contents.front();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
#ifndef TERMINALFB_HPP
|
||||
#define TERMINALFB_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <assert.h>
|
||||
|
||||
/* Terminal framebuffer */
|
||||
|
||||
namespace Terminal {
|
||||
class Renditions {
|
||||
public:
|
||||
bool bold, underlined, blink, inverse, invisible;
|
||||
int foreground_color;
|
||||
int background_color;
|
||||
|
||||
Renditions( int s_background );
|
||||
void set_rendition( int num );
|
||||
std::string sgr( void ) const;
|
||||
|
||||
bool operator==( const Renditions &x ) const
|
||||
{
|
||||
return (bold == x.bold) && (underlined == x.underlined)
|
||||
&& (blink == x.blink) && (inverse == x.inverse)
|
||||
&& (invisible == x.invisible) && (foreground_color == x.foreground_color)
|
||||
&& (background_color == x.background_color);
|
||||
}
|
||||
};
|
||||
|
||||
class Cell {
|
||||
public:
|
||||
std::vector<wchar_t> contents;
|
||||
char fallback; /* first character is combining character */
|
||||
int width;
|
||||
Renditions renditions;
|
||||
|
||||
Cell( int background_color )
|
||||
: contents(),
|
||||
fallback( false ),
|
||||
width( 1 ),
|
||||
renditions( background_color )
|
||||
{}
|
||||
|
||||
Cell() /* default constructor required by C++11 STL */
|
||||
: contents(),
|
||||
fallback( false ),
|
||||
width( 1 ),
|
||||
renditions( 0 )
|
||||
{
|
||||
assert( false );
|
||||
}
|
||||
|
||||
void reset( int background_color );
|
||||
|
||||
bool operator==( const Cell &x ) const
|
||||
{
|
||||
return ( (contents == x.contents)
|
||||
&& (fallback == x.fallback)
|
||||
&& (width == x.width)
|
||||
&& (renditions == x.renditions) );
|
||||
}
|
||||
|
||||
wchar_t debug_contents( void ) const;
|
||||
|
||||
bool is_blank( void ) const
|
||||
{
|
||||
return ( contents.empty()
|
||||
|| ( (contents.size() == 1) && ( (contents.front() == 0x20)
|
||||
|| (contents.front() == 0xA0) ) ) );
|
||||
}
|
||||
};
|
||||
|
||||
class Row {
|
||||
public:
|
||||
std::vector<Cell> cells;
|
||||
bool wrap;
|
||||
|
||||
Row( size_t s_width, int background_color )
|
||||
: cells( s_width, Cell( background_color ) ), wrap( false )
|
||||
{}
|
||||
|
||||
Row() /* default constructor required by C++11 STL */
|
||||
: cells( 1, Cell() ), wrap( false )
|
||||
{
|
||||
assert( false );
|
||||
}
|
||||
|
||||
void insert_cell( int col, int background_color );
|
||||
void delete_cell( int col, int background_color );
|
||||
|
||||
void reset( int background_color );
|
||||
|
||||
bool operator==( const Row &x ) const
|
||||
{
|
||||
return ( (cells == x.cells) && (wrap == x.wrap) );
|
||||
}
|
||||
};
|
||||
|
||||
class SavedCursor {
|
||||
public:
|
||||
int cursor_col, cursor_row;
|
||||
Renditions renditions;
|
||||
/* not implemented: character set shift state */
|
||||
bool auto_wrap_mode;
|
||||
bool origin_mode;
|
||||
/* not implemented: state of selective erase */
|
||||
|
||||
SavedCursor();
|
||||
};
|
||||
|
||||
class DrawState {
|
||||
private:
|
||||
int width, height;
|
||||
|
||||
void new_grapheme( void );
|
||||
void snap_cursor_to_border( void );
|
||||
|
||||
int cursor_col, cursor_row;
|
||||
int combining_char_col, combining_char_row;
|
||||
|
||||
std::vector<bool> tabs;
|
||||
|
||||
int scrolling_region_top_row, scrolling_region_bottom_row;
|
||||
|
||||
Renditions renditions;
|
||||
|
||||
SavedCursor save;
|
||||
|
||||
public:
|
||||
bool next_print_will_wrap;
|
||||
bool origin_mode;
|
||||
bool auto_wrap_mode;
|
||||
bool insert_mode;
|
||||
bool cursor_visible;
|
||||
bool reverse_video;
|
||||
|
||||
bool application_mode_cursor_keys;
|
||||
|
||||
/* bold, etc. */
|
||||
|
||||
void move_row( int N, bool relative = false );
|
||||
void move_col( int N, bool relative = false, bool implicit = false );
|
||||
|
||||
int get_cursor_col( void ) const { return cursor_col; }
|
||||
int get_cursor_row( void ) const { return cursor_row; }
|
||||
int get_combining_char_col( void ) const { return combining_char_col; }
|
||||
int get_combining_char_row( void ) const { return combining_char_row; }
|
||||
int get_width( void ) const { return width; }
|
||||
int get_height( void ) const { return height; }
|
||||
|
||||
void set_tab( void );
|
||||
void clear_tab( int col );
|
||||
int get_next_tab( void );
|
||||
|
||||
std::vector<int> get_tabs( void );
|
||||
|
||||
void set_scrolling_region( int top, int bottom );
|
||||
|
||||
int get_scrolling_region_top_row( void ) const { return scrolling_region_top_row; }
|
||||
int get_scrolling_region_bottom_row( void ) const { return scrolling_region_bottom_row; }
|
||||
|
||||
int limit_top( void );
|
||||
int limit_bottom( void );
|
||||
|
||||
void add_rendition( int x ) { renditions.set_rendition( x ); }
|
||||
Renditions get_renditions( void ) const { return renditions; }
|
||||
int get_background_rendition( void ) { return renditions.background_color; }
|
||||
|
||||
void save_cursor( void );
|
||||
void restore_cursor( void );
|
||||
void clear_saved_cursor( void ) { save = SavedCursor(); }
|
||||
|
||||
void resize( int s_width, int s_height );
|
||||
|
||||
DrawState( int s_width, int s_height );
|
||||
|
||||
bool operator==( const DrawState &x ) const
|
||||
{
|
||||
/* only compare fields that affect display */
|
||||
return ( width == x.width ) && ( height == x.height ) && ( cursor_col == x.cursor_col )
|
||||
&& ( cursor_row == x.cursor_row ) && ( cursor_visible == x.cursor_visible ) &&
|
||||
( reverse_video == x.reverse_video ) && ( renditions == x.renditions );
|
||||
}
|
||||
};
|
||||
|
||||
class Framebuffer {
|
||||
private:
|
||||
std::deque<Row> rows;
|
||||
std::deque<wchar_t> window_title;
|
||||
unsigned int bell_count;
|
||||
|
||||
Row newrow( void ) { return Row( ds.get_width(), ds.get_background_rendition() ); }
|
||||
|
||||
public:
|
||||
Framebuffer( int s_width, int s_height );
|
||||
DrawState ds;
|
||||
|
||||
void scroll( int N );
|
||||
void move_rows_autoscroll( int rows );
|
||||
|
||||
const Row *get_row( int row ) const
|
||||
{
|
||||
if ( row == -1 ) row = ds.get_cursor_row();
|
||||
|
||||
return &rows[ row ];
|
||||
}
|
||||
|
||||
inline const Cell *get_cell( void ) const
|
||||
{
|
||||
return &rows[ ds.get_cursor_row() ].cells[ ds.get_cursor_col() ];
|
||||
}
|
||||
|
||||
inline const Cell *get_cell( int row, int col ) const
|
||||
{
|
||||
if ( row == -1 ) row = ds.get_cursor_row();
|
||||
if ( col == -1 ) col = ds.get_cursor_col();
|
||||
|
||||
return &rows[ row ].cells[ col ];
|
||||
}
|
||||
|
||||
Row *get_mutable_row( int row )
|
||||
{
|
||||
if ( row == -1 ) row = ds.get_cursor_row();
|
||||
|
||||
return &rows[ row ];
|
||||
}
|
||||
|
||||
inline Cell *get_mutable_cell( void )
|
||||
{
|
||||
return &rows[ ds.get_cursor_row() ].cells[ ds.get_cursor_col() ];
|
||||
}
|
||||
|
||||
inline Cell *get_mutable_cell( int row, int col )
|
||||
{
|
||||
if ( row == -1 ) row = ds.get_cursor_row();
|
||||
if ( col == -1 ) col = ds.get_cursor_col();
|
||||
|
||||
return &rows[ row ].cells[ col ];
|
||||
}
|
||||
|
||||
Cell *get_combining_cell( void );
|
||||
|
||||
void apply_renditions_to_current_cell( void );
|
||||
|
||||
void insert_line( int before_row );
|
||||
void delete_line( int row );
|
||||
|
||||
void insert_cell( int row, int col );
|
||||
void delete_cell( int row, int col );
|
||||
|
||||
void reset( void );
|
||||
void soft_reset( void );
|
||||
|
||||
void set_window_title( const std::deque<wchar_t> &s ) { window_title = s; }
|
||||
const std::deque<wchar_t> & get_window_title( void ) const { return window_title; }
|
||||
|
||||
void prefix_window_title( const std::deque<wchar_t> &s );
|
||||
|
||||
void resize( int s_width, int s_height );
|
||||
|
||||
void reset_cell( Cell *c ) { c->reset( ds.get_background_rendition() ); }
|
||||
void reset_row( Row *r ) { r->reset( ds.get_background_rendition() ); }
|
||||
|
||||
void ring_bell( void ) { bell_count++; }
|
||||
unsigned int get_bell_count( void ) const { return bell_count; }
|
||||
|
||||
bool operator==( const Framebuffer &x ) const
|
||||
{
|
||||
return ( rows == x.rows ) && ( window_title == x.window_title ) && ( bell_count == x.bell_count ) && ( ds == x.ds );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,493 @@
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
|
||||
#include "terminaldispatcher.h"
|
||||
#include "terminalframebuffer.h"
|
||||
#include "parseraction.h"
|
||||
|
||||
using namespace Terminal;
|
||||
|
||||
/* Terminal functions -- routines activated by CSI, escape or a control char */
|
||||
|
||||
static void clearline( Framebuffer *fb, int row, int start, int end )
|
||||
{
|
||||
for ( int col = start; col <= end; col++ ) {
|
||||
fb->reset_cell( fb->get_mutable_cell( row, col ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* erase in line */
|
||||
void CSI_EL( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
switch ( dispatch->getparam( 0, 0 ) ) {
|
||||
case 0: /* default: active position to end of line, inclusive */
|
||||
clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
|
||||
break;
|
||||
case 1: /* start of screen to active position, inclusive */
|
||||
clearline( fb, -1, 0, fb->ds.get_cursor_col() );
|
||||
break;
|
||||
case 2: /* all of line */
|
||||
fb->reset_row( fb->get_mutable_row( -1 ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_EL( CSI, "K", CSI_EL );
|
||||
|
||||
/* erase in display */
|
||||
void CSI_ED( Framebuffer *fb, Dispatcher *dispatch ) {
|
||||
switch ( dispatch->getparam( 0, 0 ) ) {
|
||||
case 0: /* active position to end of screen, inclusive */
|
||||
clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
|
||||
for ( int y = fb->ds.get_cursor_row() + 1; y < fb->ds.get_height(); y++ ) {
|
||||
fb->reset_row( fb->get_mutable_row( y ) );
|
||||
}
|
||||
break;
|
||||
case 1: /* start of screen to active position, inclusive */
|
||||
for ( int y = 0; y < fb->ds.get_cursor_row(); y++ ) {
|
||||
fb->reset_row( fb->get_mutable_row( y ) );
|
||||
}
|
||||
clearline( fb, -1, 0, fb->ds.get_cursor_col() );
|
||||
break;
|
||||
case 2: /* entire screen */
|
||||
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
|
||||
fb->reset_row( fb->get_mutable_row( y ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_ED( CSI, "J", CSI_ED );
|
||||
|
||||
/* cursor movement -- relative and absolute */
|
||||
void CSI_cursormove( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int num = dispatch->getparam( 0, 1 );
|
||||
|
||||
switch ( dispatch->get_dispatch_chars()[ 0 ] ) {
|
||||
case 'A':
|
||||
fb->ds.move_row( -num, true );
|
||||
break;
|
||||
case 'B':
|
||||
fb->ds.move_row( num, true );
|
||||
break;
|
||||
case 'C':
|
||||
fb->ds.move_col( num, true );
|
||||
break;
|
||||
case 'D':
|
||||
fb->ds.move_col( -num, true );
|
||||
break;
|
||||
case 'H':
|
||||
case 'f':
|
||||
int x = dispatch->getparam( 0, 1 );
|
||||
int y = dispatch->getparam( 1, 1 );
|
||||
fb->ds.move_row( x - 1 );
|
||||
fb->ds.move_col( y - 1 );
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_cursormove_A( CSI, "A", CSI_cursormove );
|
||||
static Function func_CSI_cursormove_B( CSI, "B", CSI_cursormove );
|
||||
static Function func_CSI_cursormove_C( CSI, "C", CSI_cursormove );
|
||||
static Function func_CSI_cursormove_D( CSI, "D", CSI_cursormove );
|
||||
static Function func_CSI_cursormove_H( CSI, "H", CSI_cursormove );
|
||||
static Function func_CSI_cursormove_f( CSI, "f", CSI_cursormove );
|
||||
|
||||
/* device attributes */
|
||||
void CSI_DA( Framebuffer *fb __attribute((unused)), Dispatcher *dispatch )
|
||||
{
|
||||
dispatch->terminal_to_host.append( "\033[?62c" ); /* plain vt220 */
|
||||
}
|
||||
|
||||
static Function func_CSI_DA( CSI, "c", CSI_DA );
|
||||
|
||||
/* secondary device attributes */
|
||||
void CSI_SDA( Framebuffer *fb __attribute((unused)), Dispatcher *dispatch )
|
||||
{
|
||||
dispatch->terminal_to_host.append( "\033[>1;10;0c" ); /* plain vt220 */
|
||||
}
|
||||
|
||||
static Function func_CSI_SDA( CSI, ">c", CSI_SDA );
|
||||
|
||||
/* screen alignment diagnostic */
|
||||
void Esc_DECALN( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
|
||||
for ( int x = 0; x < fb->ds.get_width(); x++ ) {
|
||||
fb->reset_cell( fb->get_mutable_cell( y, x ) );
|
||||
fb->get_mutable_cell( y, x )->contents.push_back( L'E' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_Esc_DECALN( ESCAPE, "#8", Esc_DECALN );
|
||||
|
||||
/* line feed */
|
||||
void Ctrl_LF( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->move_rows_autoscroll( 1 );
|
||||
}
|
||||
|
||||
/* same procedure for index, vertical tab, and form feed control codes */
|
||||
static Function func_Ctrl_LF( CONTROL, "\x0a", Ctrl_LF );
|
||||
static Function func_Ctrl_IND( CONTROL, "\x84", Ctrl_LF );
|
||||
static Function func_Ctrl_VT( CONTROL, "\x0b", Ctrl_LF );
|
||||
static Function func_Ctrl_FF( CONTROL, "\x0c", Ctrl_LF );
|
||||
|
||||
/* carriage return */
|
||||
void Ctrl_CR( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->ds.move_col( 0 );
|
||||
}
|
||||
|
||||
static Function func_Ctrl_CR( CONTROL, "\x0d", Ctrl_CR );
|
||||
|
||||
/* backspace */
|
||||
void Ctrl_BS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->ds.move_col( -1, true );
|
||||
}
|
||||
|
||||
static Function func_Ctrl_BS( CONTROL, "\x08", Ctrl_BS );
|
||||
|
||||
/* reverse index -- like a backwards line feed */
|
||||
void Ctrl_RI( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->move_rows_autoscroll( -1 );
|
||||
}
|
||||
|
||||
static Function func_Ctrl_RI( CONTROL, "\x8D", Ctrl_RI );
|
||||
|
||||
/* newline */
|
||||
void Ctrl_NEL( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->ds.move_col( 0 );
|
||||
fb->move_rows_autoscroll( 1 );
|
||||
}
|
||||
|
||||
static Function func_Ctrl_NEL( CONTROL, "\x85", Ctrl_NEL );
|
||||
|
||||
/* horizontal tab */
|
||||
void Ctrl_HT( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
int col = fb->ds.get_next_tab();
|
||||
if ( col == -1 ) { /* no tabs, go to end of line */
|
||||
fb->ds.move_col( fb->ds.get_width() - 1 );
|
||||
} else {
|
||||
fb->ds.move_col( col );
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_Ctrl_HT( CONTROL, "\x09", Ctrl_HT );
|
||||
|
||||
/* horizontal tab set */
|
||||
void Ctrl_HTS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->ds.set_tab();
|
||||
}
|
||||
|
||||
static Function func_Ctrl_HTS( CONTROL, "\x88", Ctrl_HTS );
|
||||
|
||||
/* tabulation clear */
|
||||
void CSI_TBC( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int param = dispatch->getparam( 0, 0 );
|
||||
switch ( param ) {
|
||||
case 0: /* clear this tab stop */
|
||||
fb->ds.clear_tab( fb->ds.get_cursor_col() );
|
||||
break;
|
||||
case 3: /* clear all tab stops */
|
||||
for ( int x = 0; x < fb->ds.get_width(); x++ ) {
|
||||
fb->ds.clear_tab( x );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_TBC( CSI, "g", CSI_TBC );
|
||||
|
||||
static bool *get_DEC_mode( int param, Framebuffer *fb ) {
|
||||
switch ( param ) {
|
||||
case 1: /* cursor key mode */
|
||||
return &(fb->ds.application_mode_cursor_keys);
|
||||
case 3: /* 80/132. Ignore but clear screen. */
|
||||
/* clear screen */
|
||||
fb->ds.move_row( 0 );
|
||||
fb->ds.move_col( 0 );
|
||||
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
|
||||
fb->reset_row( fb->get_mutable_row( y ) );
|
||||
}
|
||||
return NULL;
|
||||
case 5: /* reverse video */
|
||||
return &(fb->ds.reverse_video);
|
||||
case 6: /* origin */
|
||||
fb->ds.move_row( 0 );
|
||||
fb->ds.move_col( 0 );
|
||||
return &(fb->ds.origin_mode);
|
||||
case 7: /* auto wrap */
|
||||
return &(fb->ds.auto_wrap_mode);
|
||||
case 25:
|
||||
return &(fb->ds.cursor_visible);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* set private mode */
|
||||
void CSI_DECSM( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
for ( int i = 0; i < dispatch->param_count(); i++ ) {
|
||||
bool *mode = get_DEC_mode( dispatch->getparam( i, 0 ), fb );
|
||||
if ( mode ) {
|
||||
*mode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* clear private mode */
|
||||
void CSI_DECRM( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
for ( int i = 0; i < dispatch->param_count(); i++ ) {
|
||||
bool *mode = get_DEC_mode( dispatch->getparam( i, 0 ), fb );
|
||||
if ( mode ) {
|
||||
*mode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_DECSM( CSI, "?h", CSI_DECSM );
|
||||
static Function func_CSI_DECRM( CSI, "?l", CSI_DECRM );
|
||||
|
||||
static bool *get_ANSI_mode( int param, Framebuffer *fb ) {
|
||||
switch ( param ) {
|
||||
case 4: /* insert/replace mode */
|
||||
return &(fb->ds.insert_mode);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* set mode */
|
||||
void CSI_SM( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
for ( int i = 0; i < dispatch->param_count(); i++ ) {
|
||||
bool *mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
|
||||
if ( mode ) {
|
||||
*mode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* clear mode */
|
||||
void CSI_RM( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
for ( int i = 0; i < dispatch->param_count(); i++ ) {
|
||||
bool *mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
|
||||
if ( mode ) {
|
||||
*mode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_SM( CSI, "h", CSI_SM );
|
||||
static Function func_CSI_RM( CSI, "l", CSI_RM );
|
||||
|
||||
/* set top and bottom margins */
|
||||
void CSI_DECSTBM( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int top = dispatch->getparam( 0, 1 );
|
||||
int bottom = dispatch->getparam( 1, fb->ds.get_height() );
|
||||
|
||||
fb->ds.set_scrolling_region( top - 1, bottom - 1 );
|
||||
fb->ds.move_row( 0 );
|
||||
fb->ds.move_col( 0 );
|
||||
}
|
||||
|
||||
static Function func_CSI_DECSTMB( CSI, "r", CSI_DECSTBM );
|
||||
|
||||
/* terminal bell */
|
||||
void Ctrl_BEL( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) ) {
|
||||
fb->ring_bell();
|
||||
}
|
||||
|
||||
static Function func_Ctrl_BEL( CONTROL, "\x07", Ctrl_BEL );
|
||||
|
||||
/* select graphics rendition -- e.g., bold, blinking, etc. */
|
||||
void CSI_SGR( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
for ( int i = 0; i < dispatch->param_count(); i++ ) {
|
||||
int rendition = dispatch->getparam( i, 0 );
|
||||
fb->ds.add_rendition( rendition );
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_SGR( CSI, "m", CSI_SGR, false ); /* changing renditions doesn't clear wrap flag */
|
||||
|
||||
/* save and restore cursor */
|
||||
void Esc_DECSC( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->ds.save_cursor();
|
||||
}
|
||||
|
||||
void Esc_DECRC( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->ds.restore_cursor();
|
||||
}
|
||||
|
||||
static Function func_Esc_DECSC( ESCAPE, "7", Esc_DECSC );
|
||||
static Function func_Esc_DECRC( ESCAPE, "8", Esc_DECRC );
|
||||
|
||||
/* device status report -- e.g., cursor position (used by resize) */
|
||||
void CSI_DSR( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int param = dispatch->getparam( 0, 0 );
|
||||
|
||||
switch ( param ) {
|
||||
case 5: /* device status report requested */
|
||||
dispatch->terminal_to_host.append( "\033[0n" );
|
||||
break;
|
||||
case 6: /* report of active position requested */
|
||||
char cpr[ 32 ];
|
||||
snprintf( cpr, 32, "\033[%d;%dR",
|
||||
fb->ds.get_cursor_row() + 1,
|
||||
fb->ds.get_cursor_col() + 1 );
|
||||
dispatch->terminal_to_host.append( cpr );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_DSR( CSI, "n", CSI_DSR );
|
||||
|
||||
/* insert line */
|
||||
void CSI_IL( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int lines = dispatch->getparam( 0, 1 );
|
||||
|
||||
for ( int i = 0; i < lines; i++ ) {
|
||||
fb->insert_line( fb->ds.get_cursor_row() );
|
||||
}
|
||||
|
||||
/* vt220 manual and Ecma-48 say to move to first column */
|
||||
/* but xterm and gnome-terminal don't */
|
||||
fb->ds.move_col( 0 );
|
||||
}
|
||||
|
||||
static Function func_CSI_IL( CSI, "L", CSI_IL );
|
||||
|
||||
/* delete line */
|
||||
void CSI_DL( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int lines = dispatch->getparam( 0, 1 );
|
||||
|
||||
for ( int i = 0; i < lines; i++ ) {
|
||||
fb->delete_line( fb->ds.get_cursor_row() );
|
||||
}
|
||||
|
||||
/* same story -- xterm and gnome-terminal don't
|
||||
move to first column */
|
||||
fb->ds.move_col( 0 );
|
||||
}
|
||||
|
||||
static Function func_CSI_DL( CSI, "M", CSI_DL );
|
||||
|
||||
/* insert characters */
|
||||
void CSI_ICH( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int cells = dispatch->getparam( 0, 1 );
|
||||
|
||||
for ( int i = 0; i < cells; i++ ) {
|
||||
fb->insert_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_ICH( CSI, "@", CSI_ICH );
|
||||
|
||||
/* delete character */
|
||||
void CSI_DCH( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int cells = dispatch->getparam( 0, 1 );
|
||||
|
||||
for ( int i = 0; i < cells; i++ ) {
|
||||
fb->delete_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
|
||||
}
|
||||
}
|
||||
|
||||
static Function func_CSI_DCH( CSI, "P", CSI_DCH );
|
||||
|
||||
/* line position absolute */
|
||||
void CSI_VPA( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int row = dispatch->getparam( 0, 1 );
|
||||
fb->ds.move_row( row - 1 );
|
||||
}
|
||||
|
||||
static Function func_CSI_VPA( CSI, "d", CSI_VPA );
|
||||
|
||||
/* character position absolute */
|
||||
void CSI_HPA( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int col = dispatch->getparam( 0, 1 );
|
||||
fb->ds.move_col( col - 1 );
|
||||
}
|
||||
|
||||
static Function func_CSI_CHA( CSI, "G", CSI_HPA ); /* ECMA-48 name: CHA */
|
||||
static Function func_CSI_HPA( CSI, "\x60", CSI_HPA ); /* ECMA-48 name: HPA */
|
||||
|
||||
/* erase character */
|
||||
void CSI_ECH( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
int num = dispatch->getparam( 0, 1 );
|
||||
int limit = fb->ds.get_cursor_col() + num - 1;
|
||||
if ( limit >= fb->ds.get_width() ) {
|
||||
limit = fb->ds.get_width() - 1;
|
||||
}
|
||||
|
||||
clearline( fb, -1, fb->ds.get_cursor_col(), limit );
|
||||
}
|
||||
|
||||
static Function func_CSI_ECH( CSI, "X", CSI_ECH );
|
||||
|
||||
/* reset to initial state */
|
||||
void Esc_RIS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->reset();
|
||||
}
|
||||
|
||||
static Function func_Esc_RIS( ESCAPE, "c", Esc_RIS );
|
||||
|
||||
/* soft reset */
|
||||
void CSI_DECSTR( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
|
||||
{
|
||||
fb->soft_reset();
|
||||
}
|
||||
|
||||
static Function func_CSI_DECSTR( CSI, "!p", CSI_DECSTR );
|
||||
|
||||
/* xterm uses an Operating System Command to set the window title */
|
||||
void Dispatcher::OSC_dispatch( const Parser::OSC_End *act, Framebuffer *fb )
|
||||
{
|
||||
if ( OSC_string.size() >= 2 ) {
|
||||
if ( (OSC_string[ 0 ] == L'0')
|
||||
&& (OSC_string[ 1 ] == L';') ) {
|
||||
std::deque<wchar_t> newtitle( OSC_string.begin(), OSC_string.end() );
|
||||
newtitle.erase( newtitle.begin() );
|
||||
newtitle.erase( newtitle.begin() );
|
||||
fb->set_window_title( newtitle );
|
||||
act->handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* scroll down or terminfo indn */
|
||||
void CSI_SD( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
fb->scroll( dispatch->getparam( 0, 1 ) );
|
||||
}
|
||||
|
||||
static Function func_CSI_SD( CSI, "S", CSI_SD );
|
||||
|
||||
/* scroll up or terminfo rin */
|
||||
void CSI_SU( Framebuffer *fb, Dispatcher *dispatch )
|
||||
{
|
||||
fb->scroll( -dispatch->getparam( 0, 1 ) );
|
||||
}
|
||||
|
||||
static Function func_CSI_SU( CSI, "T", CSI_SU );
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "terminaluserinput.h"
|
||||
|
||||
using namespace Terminal;
|
||||
|
||||
std::string UserInput::input( const Parser::UserByte *act,
|
||||
bool application_mode_cursor_keys )
|
||||
{
|
||||
char translated_str[ 2 ] = { act->c, 0 };
|
||||
|
||||
/* The user will always be in application mode. If stm is not in
|
||||
application mode, convert user's cursor control function to an
|
||||
ANSI cursor control sequence */
|
||||
|
||||
/* We don't need lookahead to do this for 7-bit. */
|
||||
|
||||
if ( (!application_mode_cursor_keys)
|
||||
&& (last_byte == 0x1b) /* ESC */
|
||||
&& (act->c == 'O') ) { /* ESC O = 7-bit SS3 = application mode */
|
||||
translated_str[ 0 ] = '[';
|
||||
}
|
||||
|
||||
/* This doesn't handle the 8-bit SS3 C1 control, which would be
|
||||
two octets in UTF-8. Fortunately nobody seems to send this. */
|
||||
|
||||
last_byte = act->c;
|
||||
|
||||
act->handled = true;
|
||||
|
||||
return std::string( translated_str, 1 );
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef TERMINALUSERINPUT_HPP
|
||||
#define TERMINALUSERINPUT_HPP
|
||||
|
||||
#include <string>
|
||||
#include "parseraction.h"
|
||||
|
||||
namespace Terminal {
|
||||
class UserInput {
|
||||
private:
|
||||
wchar_t last_byte;
|
||||
|
||||
public:
|
||||
UserInput()
|
||||
: last_byte( -1 )
|
||||
{}
|
||||
|
||||
std::string input( const Parser::UserByte *act,
|
||||
bool application_mode_cursor_keys );
|
||||
|
||||
bool operator==( const UserInput &x ) const { return last_byte == x.last_byte; }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user