Display error messages in top-line overlay.

This commit is contained in:
Keith Winstein
2011-09-30 16:13:43 -04:00
parent 56b4064686
commit 7068e26847
11 changed files with 420 additions and 30 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
proto = userinput.proto transportinstruction.proto
source = parse.cpp parserstate.cpp parser.cpp templates.cpp terminal.cpp termemu.cpp parseraction.cpp terminalfunctions.cpp swrite.cpp terminalframebuffer.cpp terminaldispatcher.cpp terminaluserinput.cpp terminaldisplay.cpp network.cpp ntester.cpp ocb.cpp base64.cpp encrypt.cpp decrypt.cpp crypto.cpp networktransport.cpp transportfragment.cpp user.cpp userinput.pb.cc completeterminal.cpp stm-server.cpp stm.cpp transportinstruction.pb.cc transportsender.cpp stmclient.cpp
objects = parserstate.o parser.o templates.o terminal.o parseraction.o terminalfunctions.o swrite.o terminalframebuffer.o terminaldispatcher.o terminaluserinput.o terminaldisplay.o network.o ocb.o base64.o crypto.o networktransport.o transportfragment.o user.o userinput.pb.o completeterminal.o transportinstruction.pb.o transportsender.o stmclient.o
source = parse.cpp parserstate.cpp parser.cpp templates.cpp terminal.cpp termemu.cpp parseraction.cpp terminalfunctions.cpp swrite.cpp terminalframebuffer.cpp terminaldispatcher.cpp terminaluserinput.cpp terminaldisplay.cpp network.cpp ntester.cpp ocb.cpp base64.cpp encrypt.cpp decrypt.cpp crypto.cpp networktransport.cpp transportfragment.cpp user.cpp userinput.pb.cc completeterminal.cpp stm-server.cpp stm.cpp transportinstruction.pb.cc transportsender.cpp stmclient.cpp terminaloverlay.cpp
objects = parserstate.o parser.o templates.o terminal.o parseraction.o terminalfunctions.o swrite.o terminalframebuffer.o terminaldispatcher.o terminaluserinput.o terminaldisplay.o network.o ocb.o base64.o crypto.o networktransport.o transportfragment.o user.o userinput.pb.o completeterminal.o transportinstruction.pb.o transportsender.o stmclient.o terminaloverlay.o
repos = templates.rpo
executables = parse termemu ntester encrypt decrypt stm-server stm
+1
View File
@@ -19,6 +19,7 @@ namespace Terminal {
std::string act( const Parser::Action *act );
const Framebuffer & get_fb( void ) const { return terminal.get_fb(); }
bool parser_grounded( void ) const { return parser.is_grounded(); }
/* interface for Network::Transport */
void subtract( const Complete * ) {}
+5 -1
View File
@@ -18,7 +18,7 @@
#endif
namespace Parser {
static StateFamily family;
static const StateFamily family;
class Parser {
private:
@@ -37,6 +37,8 @@ namespace Parser {
{
return state == x.state;
}
bool is_grounded( void ) const { return state == &family.s_Ground; }
};
static const size_t BUF_SIZE = 8;
@@ -57,6 +59,8 @@ namespace Parser {
{
return parser == x.parser;
}
bool is_grounded( void ) const { return parser.is_grounded(); }
};
}
+38 -14
View File
@@ -11,12 +11,14 @@
#include <pwd.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <time.h>
#include "stmclient.hpp"
#include "swrite.hpp"
#include "networktransport.hpp"
#include "completeterminal.hpp"
#include "user.hpp"
#include "network.hpp"
void STMClient::init( void )
{
@@ -100,33 +102,44 @@ void STMClient::main_init( void )
}
/* local state */
local_terminal = new Terminal::Complete( window_size.ws_col, window_size.ws_row );
local_framebuffer = new Terminal::Framebuffer( window_size.ws_col, window_size.ws_row );
/* initialize screen */
string init = Terminal::Display::new_frame( false, local_terminal->get_fb(), local_terminal->get_fb() );
string init = Terminal::Display::new_frame( false, *local_framebuffer, *local_framebuffer );
swrite( STDOUT_FILENO, init.data(), init.size() );
/* open network */
Network::UserStream blank;
network = new Network::Transport< Network::UserStream, Terminal::Complete >( blank, *local_terminal,
Terminal::Complete local_terminal( window_size.ws_col, window_size.ws_row );
network = new Network::Transport< Network::UserStream, Terminal::Complete >( blank, local_terminal,
key.c_str(), ip.c_str(), port );
/* tell server the size of the terminal */
network->get_current_state().push_back( Parser::Resize( window_size.ws_col, window_size.ws_row ) );
}
void STMClient::output_new_frame( void )
{
/* fetch target state */
Terminal::Framebuffer new_state( network->get_latest_remote_state().state.get_fb() );
/* apply local overlays */
overlays.get_notification_engine().render_notification();
overlays.apply( new_state );
/* calculate minimal difference from where we are */
string diff = Terminal::Display::new_frame( true,
*local_framebuffer,
new_state );
swrite( STDOUT_FILENO, diff.data(), diff.size() );
*local_framebuffer = new_state;
}
bool STMClient::process_network_input( void )
{
network->recv();
/* is a new frame available from the terminal? */
if ( network->get_remote_state_num() != last_remote_num ) {
string diff = Terminal::Display::new_frame( true,
local_terminal->get_fb(),
network->get_latest_remote_state().state.get_fb() );
swrite( STDOUT_FILENO, diff.data(), diff.size() );
*local_terminal = network->get_latest_remote_state().state;
}
overlays.get_notification_engine().server_ping( network->get_latest_remote_state().timestamp );
return true;
}
@@ -147,7 +160,8 @@ bool STMClient::process_user_input( int fd )
if ( !network->shutdown_in_progress() ) {
for ( int i = 0; i < bytes_read; i++ ) {
network->get_current_state().push_back( Parser::UserByte( buf[ i ] ) );
char the_byte = buf[ i ];
network->get_current_state().push_back( Parser::UserByte( the_byte ) );
}
}
@@ -207,6 +221,8 @@ void STMClient::main( void )
while ( 1 ) {
try {
output_new_frame();
int active_fds = poll( pollfds, 4, network->wait_time() );
if ( active_fds < 0 ) {
perror( "poll" );
@@ -240,6 +256,7 @@ void STMClient::main( void )
}
if ( network->attached() && (!network->shutdown_in_progress()) ) {
overlays.get_notification_engine().set_notification_string( wstring( L"Signal received, shutting down..." ) );
network->start_shutdown();
} else {
break;
@@ -256,6 +273,7 @@ void STMClient::main( void )
& (POLLERR | POLLHUP | POLLNVAL) ) {
/* user problem */
if ( network->attached() && (!network->shutdown_in_progress()) ) {
overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ) );
network->start_shutdown();
} else {
break;
@@ -279,8 +297,14 @@ void STMClient::main( void )
network->tick();
} catch ( Network::NetworkException e ) {
fprintf( stderr, "%s: %s\r\n", e.function.c_str(), strerror( e.the_errno ) );
sleep( 1 );
wchar_t tmp[ 128 ];
swprintf( tmp, 128, L"%s: %s\r\n", e.function.c_str(), strerror( e.the_errno ) );
overlays.get_notification_engine().set_notification_string( wstring( tmp ) );
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = 200000000; /* 0.2 sec */
nanosleep( &req, NULL );
}
}
}
+9 -4
View File
@@ -8,6 +8,7 @@
#include "completeterminal.hpp"
#include "networktransport.hpp"
#include "user.hpp"
#include "terminaloverlay.hpp"
class STMClient {
private:
@@ -20,7 +21,8 @@ private:
int winch_fd, shutdown_signal_fd;
struct winsize window_size;
Terminal::Complete *local_terminal;
Terminal::Framebuffer *local_framebuffer;
Overlay::OverlayManager overlays;
Network::Transport< Network::UserStream, Terminal::Complete > *network;
uint64_t last_remote_num;
@@ -29,13 +31,16 @@ private:
bool process_user_input( int fd );
bool process_resize( void );
void output_new_frame( void );
public:
STMClient( const char *s_ip, int s_port, const char *s_key )
: ip( s_ip ), port( s_port ), key( s_key ),
saved_termios(), raw_termios(),
winch_fd(), shutdown_signal_fd(),
window_size(),
local_terminal( NULL ),
local_framebuffer( NULL ),
overlays(),
network( NULL ),
last_remote_num( -1 )
{}
@@ -46,8 +51,8 @@ public:
~STMClient()
{
if ( local_terminal != NULL ) {
delete local_terminal;
if ( local_framebuffer != NULL ) {
delete local_framebuffer;
}
if ( network != NULL ) {
+3
View File
@@ -6,6 +6,7 @@
#include "terminal.hpp"
#include "completeterminal.hpp"
#include "terminaloverlay.hpp"
#include "user.hpp"
#include "networktransport.cpp"
@@ -19,6 +20,7 @@ namespace Parser {
using namespace std;
using namespace Terminal;
using namespace Network;
using namespace Overlay;
template class list<Parser::Action *>;
template class vector<Cell>;
@@ -38,3 +40,4 @@ template class TransportSender<UserStream>;
template class TransportSender<Complete>;
template class deque<UserEvent>;
template class list<OverlayElement *>;
+18 -9
View File
@@ -84,11 +84,7 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const
if ( lines_scrolled ) {
if ( frame.cursor_y != f.ds.get_height() - 1 ) {
snprintf( tmp, 64, "\033[%d;%dH", f.ds.get_height(), 1 );
frame.append( tmp );
frame.cursor_y = f.ds.get_height() - 1;
frame.cursor_x = 0;
frame.append_silent_move( f.ds.get_height() - 1, 0 );
}
if ( frame.current_rendition_string != "\033[0m" ) {
@@ -194,10 +190,7 @@ void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &
}
if ( (frame.x != frame.cursor_x) || (frame.y != frame.cursor_y) ) {
snprintf( tmp, 64, "\033[%d;%dH", frame.y + 1, frame.x + 1 );
frame.append( tmp );
frame.cursor_x = frame.x;
frame.cursor_y = frame.y;
frame.append_silent_move( frame.y, frame.x );
}
std::string rendition_str = cell->renditions.sgr();
@@ -243,3 +236,19 @@ void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &
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;
}
+2
View File
@@ -17,6 +17,8 @@ namespace Terminal {
FrameState( const Framebuffer &s_last ) : x(0), y(0), str(), cursor_x(0), cursor_y(0), current_rendition_string(), last_frame( s_last ) {}
void append( std::string s ) { str.append( s ); }
void append_silent_move( int y, int x );
};
class Display {
+234
View File
@@ -0,0 +1,234 @@
#include <algorithm>
#include <wchar.h>
#include <list>
#include "terminaloverlay.hpp"
using namespace Overlay;
Validity OverlayElement::get_validity( const Framebuffer & ) const
{
return (timestamp() < expiration_time) ? Pending : IncorrectOrExpired;
}
void OverlayCell::apply( Framebuffer &fb ) const
{
if ( (row >= fb.ds.get_height())
|| (col >= fb.ds.get_width()) ) {
return;
}
*(fb.get_mutable_cell( row, col )) = replacement;
}
Validity ConditionalOverlayCell::get_validity( const Framebuffer &fb ) const
{
if ( (row >= fb.ds.get_height())
|| (col >= fb.ds.get_width()) ) {
return IncorrectOrExpired;
}
const Cell &current = *( fb.get_cell( row, col ) );
if ( (timestamp() < expiration_time) && (current == original_contents) ) {
return Pending;
}
if ( current == replacement ) {
return Correct;
} else {
return IncorrectOrExpired;
}
}
void CursorMove::apply( Framebuffer &fb ) const
{
assert( new_row < fb.ds.get_height() );
assert( new_col < fb.ds.get_width() );
assert( !fb.ds.origin_mode );
fb.ds.move_row( new_row, false );
fb.ds.move_col( new_col, false, false );
}
Validity ConditionalCursorMove::get_validity( const Framebuffer &fb ) const
{
if ( timestamp() < expiration_time ) {
return Pending;
}
if ( (fb.ds.get_cursor_col() == new_col)
&& (fb.ds.get_cursor_row() == new_row) ) {
return Correct;
} else {
return IncorrectOrExpired;
}
}
void OverlayEngine::clear( void )
{
for_each( elements.begin(), elements.end(), []( OverlayElement *x ){ delete x; } );
elements.clear();
}
OverlayEngine::~OverlayEngine()
{
clear();
}
void OverlayEngine::apply( Framebuffer &fb ) const
{
for_each( elements.begin(), elements.end(),
[&fb]( OverlayElement *x ) { x->apply( fb ); } );
}
void OverlayEngine::cull( const Framebuffer &fb )
{
elements.remove_if( [fb]( OverlayElement *x ) { return IncorrectOrExpired == x->get_validity( fb ); } );
}
OverlayCell::OverlayCell( uint64_t expiration_time, int s_row, int s_col, int background_color )
: OverlayElement( expiration_time ), row( s_row ), col( s_col ), replacement( background_color )
{}
NotificationEngine::NotificationEngine()
: needs_render( true ),
last_word( timestamp() ),
last_render( 0 ),
message(),
message_expiration( 0 )
{}
void NotificationEngine::server_ping( uint64_t s_last_word )
{
if ( s_last_word - last_word > 4000 ) {
needs_render = true;
}
last_word = s_last_word;
}
void NotificationEngine::set_notification_string( const wstring s_message )
{
message = s_message;
message_expiration = timestamp() + 1100;
needs_render = true;
}
void NotificationEngine::render_notification( void )
{
uint64_t now = timestamp();
if ( (now - last_render < 250) && (!needs_render) ) {
return;
}
needs_render = false;
last_render = now;
clear();
/* determine string to draw */
if ( now >= message_expiration ) {
message.clear();
}
bool time_expired = now - last_word > 5000;
wchar_t tmp[ 128 ];
if ( message.empty() && (!time_expired) ) {
return;
} else if ( message.empty() && time_expired ) {
swprintf( tmp, 128, L"[stm] No contact for %.0f seconds.", (double)(now - last_word) / 1000.0 );
} else if ( (!message.empty()) && (!time_expired) ) {
swprintf( tmp, 128, L"[stm] %ls", message.c_str() );
} else {
swprintf( tmp, 128, L"[stm] %ls (No contact for %.0f seconds.)", message.c_str(),
(double)(now - last_word) / 1000.0 );
}
wstring string_to_draw( tmp );
int overlay_col = 0;
bool dirty = false;
OverlayCell template_cell( now + 1100, 0 /* row */, -1 /* col */, 0 /* background_color */ );
template_cell.replacement.renditions.inverse = true;
OverlayCell current( template_cell );
for ( wstring::const_iterator i = string_to_draw.begin(); i != string_to_draw.end(); i++ ) {
wchar_t ch = *i;
int chwidth = ch == L'\0' ? -1 : wcwidth( ch );
switch ( chwidth ) {
case 1: /* normal character */
case 2: /* wide character */
/* finish current cell */
if ( dirty ) {
elements.push_back( new OverlayCell( current ) );
dirty = false;
}
/* initialize new cell */
current = template_cell;
current.col = overlay_col;
current.replacement.contents.push_back( ch );
current.replacement.width = chwidth;
overlay_col += chwidth;
dirty = true;
break;
case 0: /* combining character */
if ( current.replacement.contents.empty() ) {
/* string starts with combining character?? */
/* emulate fallback rendering */
current = template_cell;
current.col = overlay_col;
current.replacement.contents.push_back( 0xA0 ); /* no-break space */
current.replacement.width = 1;
overlay_col++;
dirty = true;
}
current.replacement.contents.push_back( ch );
break;
case -1:
break;
default:
assert( false );
}
}
if ( dirty ) {
elements.push_back( new OverlayCell( current ) );
}
}
void NotificationEngine::apply( Framebuffer &fb ) const
{
if ( elements.empty() ) {
return;
}
assert( fb.ds.get_width() > 0 );
assert( fb.ds.get_height() > 0 );
Cell notification_bar( 0 );
notification_bar.renditions.inverse = true;
notification_bar.contents.push_back( 0x20 );
for ( int i = 0; i < fb.ds.get_width(); i++ ) {
*(fb.get_mutable_cell( 0, i )) = notification_bar;
}
OverlayEngine::apply( fb );
}
void OverlayManager::apply( Framebuffer &fb ) const
{
notifications.apply( fb );
}
+107
View File
@@ -0,0 +1,107 @@
#ifndef TERMINAL_OVERLAY_HPP
#define TERMINAL_OVERLAY_HPP
#include "terminalframebuffer.hpp"
#include "network.hpp"
#include <list>
namespace Overlay {
using namespace Terminal;
using namespace Network;
using namespace std;
enum Validity {
Pending,
Correct,
IncorrectOrExpired
};
/* The individual elements of an overlay -- cursor movements and replaced cells */
class OverlayElement {
public:
uint64_t expiration_time;
virtual void apply( Framebuffer &fb ) const = 0;
virtual Validity get_validity( const Framebuffer & ) const;
OverlayElement( uint64_t s_expiration_time ) : expiration_time( s_expiration_time ) {}
virtual ~OverlayElement() {}
};
class OverlayCell : public OverlayElement {
public:
int row, col;
Cell replacement;
OverlayCell( uint64_t expiration_time, int s_row, int s_col, int background_color );
void apply( Framebuffer &fb ) const;
};
class ConditionalOverlayCell : public OverlayCell {
public:
Cell original_contents;
Validity get_validity( const Framebuffer &fb ) const;
};
class CursorMove : public OverlayElement {
public:
int new_row, new_col;
void apply( Framebuffer &fb ) const;
};
class ConditionalCursorMove : public CursorMove {
public:
Validity get_validity( const Framebuffer &fb ) const;
};
/* the various overlays -- some predictive and some for local notifications */
class OverlayEngine {
protected:
list<OverlayElement *> elements;
public:
void cull( const Framebuffer &fb );
virtual void apply( Framebuffer &fb ) const;
void clear( void );
OverlayEngine() : elements() {}
virtual ~OverlayEngine();
};
class NotificationEngine : public OverlayEngine {
private:
bool needs_render;
uint64_t last_word;
uint64_t last_render;
wstring message;
uint64_t message_expiration;
public:
void apply( Framebuffer &fb ) const;
void set_notification_string( const wstring s_message );
void server_ping( uint64_t s_last_word );
void render_notification( void );
NotificationEngine();
};
/* the overlay manager */
class OverlayManager {
private:
NotificationEngine notifications;
public:
void apply( Framebuffer &fb ) const;
NotificationEngine & get_notification_engine( void ) { return notifications; }
OverlayManager() : notifications() {}
};
}
#endif
+1
View File
@@ -1,4 +1,5 @@
#include <algorithm>
#include <list>
#include "transportsender.hpp"
#include "transportfragment.hpp"