/* Mosh: the mobile shell Copyright 2012 Keith Winstein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TERMINAL_OVERLAY_HPP #define TERMINAL_OVERLAY_HPP #include "terminalframebuffer.h" #include "network.h" #include "parser.h" #include namespace Overlay { using namespace Terminal; using namespace Network; using std::deque; using std::list; using std::vector; using std::wstring; enum Validity { Pending, Correct, CorrectNoCredit, IncorrectOrExpired, Inactive }; class ConditionalOverlay { public: uint64_t expiration_frame; int col; bool active; /* represents a prediction at all */ uint64_t tentative_until_epoch; /* when to show */ uint64_t prediction_time; /* used to find long-pending predictions */ ConditionalOverlay( uint64_t s_exp, int s_col, uint64_t s_tentative ) : expiration_frame( s_exp ), col( s_col ), active( false ), tentative_until_epoch( s_tentative ), prediction_time( uint64_t( -1 ) ) {} virtual ~ConditionalOverlay() {} bool tentative( uint64_t confirmed_epoch ) const { return tentative_until_epoch > confirmed_epoch; } void reset( void ) { expiration_frame = tentative_until_epoch = -1; active = false; } void expire( uint64_t s_exp, uint64_t now ) { expiration_frame = s_exp; prediction_time = now; } }; class ConditionalCursorMove : public ConditionalOverlay { public: int row; void apply( Framebuffer &fb, uint64_t confirmed_epoch ) const; Validity get_validity( const Framebuffer &fb, uint64_t early_ack, uint64_t late_ack ) const; ConditionalCursorMove( uint64_t s_exp, int s_row, int s_col, uint64_t s_tentative ) : ConditionalOverlay( s_exp, s_col, s_tentative ), row( s_row ) {} }; class ConditionalOverlayCell : public ConditionalOverlay { public: Cell replacement; bool unknown; vector original_contents; /* we don't give credit for correct predictions that match the original contents */ void apply( Framebuffer &fb, uint64_t confirmed_epoch, int row, bool flag ) const; Validity get_validity( const Framebuffer &fb, int row, uint64_t early_ack, uint64_t late_ack ) const; ConditionalOverlayCell( uint64_t s_exp, int s_col, uint64_t s_tentative ) : ConditionalOverlay( s_exp, s_col, s_tentative ), replacement( 0 ), unknown( false ), original_contents() {} void reset( void ) { unknown = false; original_contents.clear(); ConditionalOverlay::reset(); } void reset_with_orig( void ) { if ( (!active) || unknown ) { reset(); return; } original_contents.push_back( replacement ); ConditionalOverlay::reset(); } }; class ConditionalOverlayRow { public: int row_num; typedef vector overlay_cells_t; overlay_cells_t overlay_cells; void apply( Framebuffer &fb, uint64_t confirmed_epoch, bool flag ) const; ConditionalOverlayRow( int s_row_num ) : row_num( s_row_num ), overlay_cells() {} }; /* the various overlays */ class NotificationEngine { private: uint64_t last_word_from_server; wstring message; uint64_t message_expiration; public: bool need_countup( uint64_t ts ) const { return ts - last_word_from_server > 6500; } void adjust_message( void ); void apply( Framebuffer &fb ) const; void set_notification_string( const wstring s_message ) { message = s_message; message_expiration = timestamp() + 1000; } const wstring &get_notification_string( void ) const { return message; } void server_heard( uint64_t s_last_word ) { last_word_from_server = s_last_word; } uint64_t get_message_expiration( void ) const { return message_expiration; } NotificationEngine(); }; class PredictionEngine { private: static const uint64_t SRTT_TRIGGER_LOW = 20; /* <= ms cures SRTT trigger to show predictions */ static const uint64_t SRTT_TRIGGER_HIGH = 30; /* > ms starts SRTT trigger */ static const uint64_t FLAG_TRIGGER_LOW = 50; /* <= ms cures flagging */ static const uint64_t FLAG_TRIGGER_HIGH = 80; /* > ms starts flagging */ static const uint64_t GLITCH_THRESHOLD = 250; /* prediction outstanding this long is glitch */ static const uint64_t GLITCH_REPAIR_COUNT = 10; /* non-glitches required to cure glitch trigger */ static const uint64_t GLITCH_REPAIR_MININTERVAL = 150; /* required time in between non-glitches */ static const uint64_t GLITCH_FLAG_THRESHOLD = 5000; /* prediction outstanding this long => underline */ char last_byte; Parser::UTF8Parser parser; typedef list overlays_t; overlays_t overlays; typedef list cursors_t; cursors_t cursors; typedef ConditionalOverlayRow::overlay_cells_t overlay_cells_t; uint64_t local_frame_sent, local_frame_acked, local_frame_late_acked; ConditionalOverlayRow & get_or_make_row( int row_num, int num_cols ); uint64_t prediction_epoch; uint64_t confirmed_epoch; void become_tentative( void ); void newline_carriage_return( const Framebuffer &fb ); bool flagging; /* whether we are underlining predictions */ bool srtt_trigger; /* show predictions because of slow round trip time */ unsigned int glitch_trigger; /* show predictions temporarily because of long-pending prediction */ uint64_t last_quick_confirmation; ConditionalCursorMove & cursor( void ) { assert( !cursors.empty() ); return cursors.back(); } void kill_epoch( uint64_t epoch, const Framebuffer &fb ); void init_cursor( const Framebuffer &fb ); unsigned int send_interval; public: enum DisplayPreference { Always, Never, Adaptive }; private: DisplayPreference display_preference; public: void set_display_preference( DisplayPreference s_pref ) { display_preference = s_pref; } void apply( Framebuffer &fb ) const; void new_user_byte( char the_byte, const Framebuffer &fb ); void cull( const Framebuffer &fb ); void reset( void ); bool active( void ) const; void set_local_frame_sent( uint64_t x ) { local_frame_sent = x; } void set_local_frame_acked( uint64_t x ) { local_frame_acked = x; } void set_local_frame_late_acked( uint64_t x ) { local_frame_late_acked = x; } void set_send_interval( unsigned int x ) { send_interval = x; } PredictionEngine( void ) : last_byte( 0 ), parser(), overlays(), cursors(), local_frame_sent( 0 ), local_frame_acked( 0 ), local_frame_late_acked( 0 ), prediction_epoch( 1 ), confirmed_epoch( 0 ), flagging( false ), srtt_trigger( false ), glitch_trigger( 0 ), last_quick_confirmation( 0 ), send_interval( 250 ), display_preference( Adaptive ) { } }; class TitleEngine { private: deque prefix; public: void apply( Framebuffer &fb ) const { fb.prefix_window_title( prefix ); } void set_prefix( const wstring s ); TitleEngine() : prefix() {} }; /* the overlay manager */ class OverlayManager { private: NotificationEngine notifications; PredictionEngine predictions; TitleEngine title; public: void apply( Framebuffer &fb ); NotificationEngine & get_notification_engine( void ) { return notifications; } PredictionEngine & get_prediction_engine( void ) { return predictions; } void set_title_prefix( const wstring s ) { title.set_prefix( s ); } OverlayManager() : notifications(), predictions(), title() {} int wait_time( void ); }; } #endif