1 /* 2 Mosh: the mobile shell 3 Copyright 2012 Keith Winstein 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation, either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 In addition, as a special exception, the copyright holders give 19 permission to link the code of portions of this program with the 20 OpenSSL library under certain conditions as described in each 21 individual source file, and distribute linked combinations including 22 the two. 23 24 You must obey the GNU General Public License in all respects for all 25 of the code used other than OpenSSL. If you modify file(s) with this 26 exception, you may extend this exception to your version of the 27 file(s), but you are not obligated to do so. If you do not wish to do 28 so, delete this exception statement from your version. If you delete 29 this exception statement from all source files in the program, then 30 also delete it here. 31 */ 32 33 #ifndef TERMINAL_OVERLAY_HPP 34 #define TERMINAL_OVERLAY_HPP 35 36 #include "terminalframebuffer.h" 37 #include "network.h" 38 #include "transportsender.h" 39 #include "parser.h" 40 41 #include <vector> 42 #include <limits.h> 43 44 namespace Overlay { 45 using namespace Terminal; 46 using namespace Network; 47 using std::deque; 48 using std::list; 49 using std::vector; 50 using std::wstring; 51 52 enum Validity { 53 Pending, 54 Correct, 55 CorrectNoCredit, 56 IncorrectOrExpired, 57 Inactive 58 }; 59 60 class ConditionalOverlay { 61 public: 62 uint64_t expiration_frame; 63 int col; 64 bool active; /* represents a prediction at all */ 65 uint64_t tentative_until_epoch; /* when to show */ 66 uint64_t prediction_time; /* used to find long-pending predictions */ 67 ConditionalOverlay(uint64_t s_exp,int s_col,uint64_t s_tentative)68 ConditionalOverlay( uint64_t s_exp, int s_col, uint64_t s_tentative ) 69 : expiration_frame( s_exp ), col( s_col ), 70 active( false ), 71 tentative_until_epoch( s_tentative ), prediction_time( uint64_t( -1 ) ) 72 {} 73 ~ConditionalOverlay()74 virtual ~ConditionalOverlay() {} 75 tentative(uint64_t confirmed_epoch)76 bool tentative( uint64_t confirmed_epoch ) const { return tentative_until_epoch > confirmed_epoch; } reset(void)77 void reset( void ) { expiration_frame = tentative_until_epoch = -1; active = false; } expire(uint64_t s_exp,uint64_t now)78 void expire( uint64_t s_exp, uint64_t now ) 79 { 80 expiration_frame = s_exp; prediction_time = now; 81 } 82 }; 83 84 class ConditionalCursorMove : public ConditionalOverlay { 85 public: 86 int row; 87 88 void apply( Framebuffer &fb, uint64_t confirmed_epoch ) const; 89 90 Validity get_validity( const Framebuffer &fb, uint64_t early_ack, uint64_t late_ack ) const; 91 ConditionalCursorMove(uint64_t s_exp,int s_row,int s_col,uint64_t s_tentative)92 ConditionalCursorMove( uint64_t s_exp, int s_row, int s_col, uint64_t s_tentative ) 93 : ConditionalOverlay( s_exp, s_col, s_tentative ), row( s_row ) 94 {} 95 }; 96 97 class ConditionalOverlayCell : public ConditionalOverlay { 98 public: 99 Cell replacement; 100 bool unknown; 101 102 vector<Cell> original_contents; /* we don't give credit for correct predictions 103 that match the original contents */ 104 105 void apply( Framebuffer &fb, uint64_t confirmed_epoch, int row, bool flag ) const; 106 Validity get_validity( const Framebuffer &fb, int row, uint64_t early_ack, uint64_t late_ack ) const; 107 ConditionalOverlayCell(uint64_t s_exp,int s_col,uint64_t s_tentative)108 ConditionalOverlayCell( uint64_t s_exp, int s_col, uint64_t s_tentative ) 109 : ConditionalOverlay( s_exp, s_col, s_tentative ), 110 replacement( 0 ), 111 unknown( false ), 112 original_contents() 113 {} 114 reset(void)115 void reset( void ) { unknown = false; original_contents.clear(); ConditionalOverlay::reset(); } reset_with_orig(void)116 void reset_with_orig( void ) { 117 if ( (!active) || unknown ) { 118 reset(); 119 return; 120 } 121 122 original_contents.push_back( replacement ); 123 ConditionalOverlay::reset(); 124 } 125 }; 126 127 class ConditionalOverlayRow { 128 public: 129 int row_num; 130 131 typedef vector<ConditionalOverlayCell> overlay_cells_type; 132 overlay_cells_type overlay_cells; 133 134 void apply( Framebuffer &fb, uint64_t confirmed_epoch, bool flag ) const; 135 136 /* For use with find_if */ row_num_eq(int v)137 bool row_num_eq( int v ) const { return row_num == v; } 138 ConditionalOverlayRow(int s_row_num)139 ConditionalOverlayRow( int s_row_num ) : row_num( s_row_num ), overlay_cells() {} 140 }; 141 142 /* the various overlays */ 143 class NotificationEngine { 144 private: 145 uint64_t last_word_from_server; 146 uint64_t last_acked_state; 147 string escape_key_string; 148 wstring message; 149 bool message_is_network_error; 150 uint64_t message_expiration; 151 bool show_quit_keystroke; 152 server_late(uint64_t ts)153 bool server_late( uint64_t ts ) const { return (ts - last_word_from_server) > 6500; } reply_late(uint64_t ts)154 bool reply_late( uint64_t ts ) const { return (ts - last_acked_state) > 10000; } need_countup(uint64_t ts)155 bool need_countup( uint64_t ts ) const { return server_late( ts ) || reply_late( ts ); } 156 157 public: 158 void adjust_message( void ); 159 void apply( Framebuffer &fb ) const; get_notification_string(void)160 const wstring &get_notification_string( void ) const { return message; } server_heard(uint64_t s_last_word)161 void server_heard( uint64_t s_last_word ) { last_word_from_server = s_last_word; } server_acked(uint64_t s_last_acked)162 void server_acked( uint64_t s_last_acked ) { last_acked_state = s_last_acked; } 163 int wait_time( void ) const; 164 165 void set_notification_string( const wstring &s_message, bool permanent = false, bool s_show_quit_keystroke = true ) 166 { 167 message = s_message; 168 if ( permanent ) { 169 message_expiration = -1; 170 } else { 171 message_expiration = timestamp() + 1000; 172 } 173 message_is_network_error = false; 174 show_quit_keystroke = s_show_quit_keystroke; 175 } 176 set_escape_key_string(const string & s_name)177 void set_escape_key_string( const string &s_name ) 178 { 179 char tmp[ 128 ]; 180 snprintf( tmp, sizeof tmp, " [To quit: %s .]", s_name.c_str() ); 181 escape_key_string = tmp; 182 } 183 set_network_error(const std::string & s)184 void set_network_error( const std::string &s ) 185 { 186 wchar_t tmp[ 128 ]; 187 swprintf( tmp, 128, L"%s", s.c_str() ); 188 189 message = tmp; 190 message_is_network_error = true; 191 message_expiration = timestamp() + Network::ACK_INTERVAL + 100; 192 } 193 clear_network_error()194 void clear_network_error() 195 { 196 if ( message_is_network_error ) { 197 message_expiration = std::min( message_expiration, timestamp() + 1000 ); 198 } 199 } 200 201 NotificationEngine(); 202 }; 203 204 class PredictionEngine { 205 private: 206 static const uint64_t SRTT_TRIGGER_LOW = 20; /* <= ms cures SRTT trigger to show predictions */ 207 static const uint64_t SRTT_TRIGGER_HIGH = 30; /* > ms starts SRTT trigger */ 208 209 static const uint64_t FLAG_TRIGGER_LOW = 50; /* <= ms cures flagging */ 210 static const uint64_t FLAG_TRIGGER_HIGH = 80; /* > ms starts flagging */ 211 212 static const uint64_t GLITCH_THRESHOLD = 250; /* prediction outstanding this long is glitch */ 213 static const uint64_t GLITCH_REPAIR_COUNT = 10; /* non-glitches required to cure glitch trigger */ 214 static const uint64_t GLITCH_REPAIR_MININTERVAL = 150; /* required time in between non-glitches */ 215 216 static const uint64_t GLITCH_FLAG_THRESHOLD = 5000; /* prediction outstanding this long => underline */ 217 218 char last_byte; 219 Parser::UTF8Parser parser; 220 221 typedef list<ConditionalOverlayRow> overlays_type; 222 overlays_type overlays; 223 224 typedef list<ConditionalCursorMove> cursors_type; 225 cursors_type cursors; 226 227 typedef ConditionalOverlayRow::overlay_cells_type overlay_cells_type; 228 229 uint64_t local_frame_sent, local_frame_acked, local_frame_late_acked; 230 231 ConditionalOverlayRow & get_or_make_row( int row_num, int num_cols ); 232 233 uint64_t prediction_epoch; 234 uint64_t confirmed_epoch; 235 236 void become_tentative( void ); 237 238 void newline_carriage_return( const Framebuffer &fb ); 239 240 bool flagging; /* whether we are underlining predictions */ 241 bool srtt_trigger; /* show predictions because of slow round trip time */ 242 unsigned int glitch_trigger; /* show predictions temporarily because of long-pending prediction */ 243 uint64_t last_quick_confirmation; 244 cursor(void)245 ConditionalCursorMove & cursor( void ) { assert( !cursors.empty() ); return cursors.back(); } 246 247 void kill_epoch( uint64_t epoch, const Framebuffer &fb ); 248 249 void init_cursor( const Framebuffer &fb ); 250 251 unsigned int send_interval; 252 253 int last_height, last_width; 254 255 public: 256 enum DisplayPreference { 257 Always, 258 Never, 259 Adaptive, 260 Experimental 261 }; 262 263 private: 264 DisplayPreference display_preference; 265 266 bool active( void ) const; 267 timing_tests_necessary(void)268 bool timing_tests_necessary( void ) const { 269 /* Are there any timing-based triggers that haven't fired yet? */ 270 return !( glitch_trigger && flagging ); 271 } 272 273 public: set_display_preference(DisplayPreference s_pref)274 void set_display_preference( DisplayPreference s_pref ) { display_preference = s_pref; } 275 276 void apply( Framebuffer &fb ) const; 277 void new_user_byte( char the_byte, const Framebuffer &fb ); 278 void cull( const Framebuffer &fb ); 279 280 void reset( void ); 281 set_local_frame_sent(uint64_t x)282 void set_local_frame_sent( uint64_t x ) { local_frame_sent = x; } set_local_frame_acked(uint64_t x)283 void set_local_frame_acked( uint64_t x ) { local_frame_acked = x; } set_local_frame_late_acked(uint64_t x)284 void set_local_frame_late_acked( uint64_t x ) { local_frame_late_acked = x; } 285 set_send_interval(unsigned int x)286 void set_send_interval( unsigned int x ) { send_interval = x; } 287 wait_time(void)288 int wait_time( void ) const 289 { 290 return ( timing_tests_necessary() && active() ) 291 ? 50 292 : INT_MAX; 293 } 294 PredictionEngine(void)295 PredictionEngine( void ) : last_byte( 0 ), parser(), overlays(), cursors(), 296 local_frame_sent( 0 ), local_frame_acked( 0 ), 297 local_frame_late_acked( 0 ), 298 prediction_epoch( 1 ), confirmed_epoch( 0 ), 299 flagging( false ), 300 srtt_trigger( false ), 301 glitch_trigger( 0 ), 302 last_quick_confirmation( 0 ), 303 send_interval( 250 ), 304 last_height( 0 ), last_width( 0 ), 305 display_preference( Adaptive ) 306 { 307 } 308 }; 309 310 class TitleEngine { 311 private: 312 Terminal::Framebuffer::title_type prefix; 313 314 public: apply(Framebuffer & fb)315 void apply( Framebuffer &fb ) const { fb.prefix_window_title( prefix ); } TitleEngine()316 TitleEngine() : prefix() {} 317 void set_prefix( const wstring &s ); 318 }; 319 320 /* the overlay manager */ 321 class OverlayManager { 322 private: 323 NotificationEngine notifications; 324 PredictionEngine predictions; 325 TitleEngine title; 326 327 public: 328 void apply( Framebuffer &fb ); 329 get_notification_engine(void)330 NotificationEngine & get_notification_engine( void ) { return notifications; } get_prediction_engine(void)331 PredictionEngine & get_prediction_engine( void ) { return predictions; } 332 set_title_prefix(const wstring & s)333 void set_title_prefix( const wstring &s ) { title.set_prefix( s ); } 334 OverlayManager()335 OverlayManager() : notifications(), predictions(), title() {} 336 wait_time(void)337 int wait_time( void ) const 338 { 339 return std::min( notifications.wait_time(), predictions.wait_time() ); 340 } 341 }; 342 } 343 344 #endif 345