LCOV - code coverage report
Current view: top level - src/frontend - terminaloverlay.cc (source / functions) Hit Total Coverage
Test: mosh-1.3.2 Code Coverage Lines: 298 441 67.6 %
Date: 2022-02-06 20:19:53 Functions: 20 22 90.9 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : #include <algorithm>
      34             : #include <wchar.h>
      35             : #include <list>
      36             : #include <typeinfo>
      37             : #include <limits.h>
      38             : 
      39             : #include "terminaloverlay.h"
      40             : 
      41             : using namespace Overlay;
      42             : 
      43      399120 : void ConditionalOverlayCell::apply( Framebuffer &fb, uint64_t confirmed_epoch, int row, bool flag ) const
      44             : {
      45      399120 :   if ( (!active)
      46       50128 :        || (row >= fb.ds.get_height())
      47      449248 :        || (col >= fb.ds.get_width()) ) {
      48             :     return;
      49             :   }
      50             : 
      51       50128 :   if ( tentative( confirmed_epoch ) ) {
      52             :     return;
      53             :   }
      54             : 
      55       44331 :   if ( replacement.is_blank() && fb.get_cell( row, col )->is_blank() ) {
      56             :     flag = false;
      57             :   }
      58             : 
      59       44331 :   if ( unknown ) {
      60         864 :     if ( flag && ( col != fb.ds.get_width() - 1 ) ) {
      61           0 :       fb.get_mutable_cell( row, col )->get_renditions().set_attribute(Renditions::underlined, true);
      62             :     }
      63         864 :     return;
      64             :   }
      65             : 
      66       43467 :   if ( *fb.get_cell( row, col ) != replacement ) {
      67        1012 :     *(fb.get_mutable_cell( row, col )) = replacement;
      68        1012 :     if ( flag ) {
      69          18 :       fb.get_mutable_cell( row, col )->get_renditions().set_attribute( Renditions::underlined, true );
      70             :     }
      71             :   }
      72             : }
      73             : 
      74      620960 : Validity ConditionalOverlayCell::get_validity( const Framebuffer &fb, int row,
      75             :                                                uint64_t early_ack __attribute__((unused)),
      76             :                                                uint64_t late_ack ) const
      77             : {
      78      620960 :   if ( !active ) {
      79             :     return Inactive;
      80             :   }
      81             : 
      82       89591 :   if ( (row >= fb.ds.get_height())
      83       89591 :        || (col >= fb.ds.get_width()) ) {
      84             :     return IncorrectOrExpired;
      85             :   }
      86             : 
      87       89591 :   const Cell &current = *( fb.get_cell( row, col ) );
      88             : 
      89             :   /* see if it hasn't been updated yet */
      90       89591 :   if ( late_ack < expiration_frame ) {
      91             :     return Pending;
      92             :   }
      93             : 
      94       12833 :   if ( unknown ) {
      95             :     return CorrectNoCredit;
      96             :   }
      97             : 
      98       12593 :   if ( replacement.is_blank() ) { /* too easy for this to trigger falsely */
      99             :     return CorrectNoCredit;
     100             :   }
     101             : 
     102         435 :   if ( current.contents_match( replacement ) ) {
     103         428 :     vector<Cell>::const_iterator it = original_contents.begin();
     104        1716 :     for ( ; it != original_contents.end(); it++ ) {
     105        1288 :       if ( it->contents_match( replacement ) )
     106             :         break;
     107             :     }
     108         428 :     if ( it == original_contents.end() ) {
     109             :       return Correct;
     110             :     }
     111           0 :     return CorrectNoCredit;
     112             :   }
     113             :   return IncorrectOrExpired;
     114             : }
     115             : 
     116        5599 : Validity ConditionalCursorMove::get_validity( const Framebuffer &fb,
     117             :                                               uint64_t early_ack __attribute((unused)),
     118             :                                               uint64_t late_ack ) const
     119             : {
     120        5599 :   if ( !active ) {
     121             :     return Inactive;
     122             :   }
     123             : 
     124        5599 :   if ( (row >= fb.ds.get_height())
     125        5599 :        || (col >= fb.ds.get_width()) ) {
     126             :     //    fprintf( stderr, "Crazy cursor (%d,%d)!\n", row, col );
     127             :     return IncorrectOrExpired;
     128             :   }
     129             : 
     130        5599 :   if ( late_ack >= expiration_frame ) {
     131         507 :     if ( (fb.ds.get_cursor_col() == col)
     132         507 :          && (fb.ds.get_cursor_row() == row) ) {
     133         500 :       return Correct;
     134             :     }
     135             :     return IncorrectOrExpired;
     136             :   }
     137             : 
     138             :   return Pending;
     139             : }
     140             : 
     141        2116 : void ConditionalCursorMove::apply( Framebuffer &fb, uint64_t confirmed_epoch ) const
     142             : {
     143        2116 :   if ( !active ) {
     144             :     return;
     145             :   }
     146             : 
     147        2116 :   if ( tentative( confirmed_epoch ) ) {
     148             :     return;
     149             :   }
     150             : 
     151         864 :   assert( row < fb.ds.get_height() );
     152         864 :   assert( col < fb.ds.get_width() );
     153         864 :   assert( !fb.ds.origin_mode );
     154             : 
     155         864 :   fb.ds.move_row( row, false );
     156         864 :   fb.ds.move_col( col, false, false );
     157             : }
     158             : 
     159         448 : NotificationEngine::NotificationEngine()
     160         448 :   : last_word_from_server( timestamp() ),
     161         448 :     last_acked_state( timestamp() ),
     162         448 :     escape_key_string(),
     163         448 :     message(),
     164         448 :     message_is_network_error( false ),
     165         448 :     message_expiration( -1 ),
     166         448 :     show_quit_keystroke( true )
     167         448 : {}
     168             : 
     169           0 : static std::string human_readable_duration( int num_seconds, const std::string &seconds_abbr ) {
     170           0 :   char tmp[ 128 ];
     171           0 :   if ( num_seconds < 60 ) {
     172           0 :     snprintf( tmp, 128, "%d %s", num_seconds, seconds_abbr.c_str() );
     173           0 :   } else if ( num_seconds < 3600 ) {
     174           0 :     snprintf( tmp, 128, "%d:%02d", num_seconds / 60, num_seconds % 60 );
     175             :   } else {
     176           0 :     snprintf( tmp, 128, "%d:%02d:%02d", num_seconds / 3600,
     177           0 :               (num_seconds / 60) % 60, num_seconds % 60 );
     178             :   }
     179           0 :   return tmp;
     180             : }
     181             : 
     182        6536 : void NotificationEngine::apply( Framebuffer &fb ) const
     183             : {
     184        6536 :   uint64_t now = timestamp();
     185             : 
     186        6536 :   bool time_expired = need_countup( now );
     187             : 
     188        6536 :   if ( message.empty() && !time_expired ) {
     189        6536 :     return;
     190             :   }
     191             : 
     192           0 :   assert( fb.ds.get_width() > 0 );
     193           0 :   assert( fb.ds.get_height() > 0 );
     194             : 
     195             :   /* hide cursor if necessary */
     196           0 :   if ( fb.ds.get_cursor_row() == 0 ) {
     197           0 :     fb.ds.cursor_visible = false;
     198             :   }
     199             : 
     200             :   /* draw bar across top of screen */
     201           0 :   Cell notification_bar( 0 );
     202           0 :   notification_bar.get_renditions().set_foreground_color( 7 );
     203           0 :   notification_bar.get_renditions().set_background_color( 4 );
     204           0 :   notification_bar.append( 0x20 );
     205             : 
     206           0 :   for ( int i = 0; i < fb.ds.get_width(); i++ ) {
     207           0 :     *(fb.get_mutable_cell( 0, i )) = notification_bar;
     208             :   }
     209             : 
     210             :   /* write message */
     211           0 :   wchar_t tmp[ 128 ];
     212             : 
     213             :   /* We want to prefer the "last contact" message if we simply haven't
     214             :      heard from the server in a while, but print the "last reply" message
     215             :      if the problem is uplink-only. */
     216             : 
     217           0 :   double since_heard = (double)(now - last_word_from_server) / 1000.0;
     218           0 :   double since_ack = (double)(now - last_acked_state) / 1000.0;
     219           0 :   const char server_message[] = "contact";
     220           0 :   const char reply_message[] = "reply";
     221             : 
     222           0 :   double time_elapsed = since_heard;
     223           0 :   const char *explanation = server_message;
     224             : 
     225           0 :   if ( reply_late( now ) && (!server_late( now )) ) {
     226             :     time_elapsed = since_ack;
     227             :     explanation = reply_message;
     228             :   }
     229             : 
     230           0 :   const static char blank[] = "";
     231             : 
     232           0 :   const char *keystroke_str = show_quit_keystroke ? escape_key_string.c_str() : blank;
     233             : 
     234           0 :   if ( message.empty() && (!time_expired) ) {
     235           0 :     return;
     236             :   }
     237           0 :   if ( message.empty() && time_expired ) {
     238           0 :     swprintf( tmp, 128, L"mosh: Last %s %s ago.%s", explanation,
     239           0 :               human_readable_duration( static_cast<int>( time_elapsed ),
     240             :                                        "seconds" ).c_str(),
     241             :               keystroke_str );
     242           0 :   } else if ( (!message.empty()) && (!time_expired) ) {
     243           0 :     swprintf( tmp, 128, L"mosh: %ls%s", message.c_str(), keystroke_str );
     244             :   } else {
     245           0 :     swprintf( tmp, 128, L"mosh: %ls (%s without %s.)%s", message.c_str(),
     246           0 :               human_readable_duration( static_cast<int>( time_elapsed ),
     247             :                                        "s" ).c_str(),
     248             :               explanation, keystroke_str );
     249             :   }
     250             : 
     251           0 :   wstring string_to_draw( tmp );
     252             : 
     253           0 :   int overlay_col = 0;
     254             : 
     255           0 :   Cell *combining_cell = fb.get_mutable_cell( 0, 0 );
     256             : 
     257             :   /* We unfortunately duplicate the terminal's logic for how to render a Unicode sequence into graphemes */
     258           0 :   for ( wstring::const_iterator i = string_to_draw.begin(); i != string_to_draw.end(); i++ ) {
     259           0 :     if ( overlay_col >= fb.ds.get_width() ) {
     260             :       break;
     261             :     }
     262             : 
     263           0 :     wchar_t ch = *i;
     264           0 :     int chwidth = ch == L'\0' ? -1 : wcwidth( ch );
     265           0 :     Cell *this_cell = 0;
     266             : 
     267           0 :     switch ( chwidth ) {
     268           0 :     case 1: /* normal character */
     269           0 :     case 2: /* wide character */
     270           0 :       this_cell = fb.get_mutable_cell( 0, overlay_col );
     271           0 :       fb.reset_cell( this_cell );
     272           0 :       this_cell->get_renditions().set_attribute(Renditions::bold, true);
     273           0 :       this_cell->get_renditions().set_foreground_color( 7 );
     274           0 :       this_cell->get_renditions().set_background_color( 4 );
     275             :       
     276           0 :       this_cell->append( ch );
     277           0 :       this_cell->set_wide( chwidth == 2 );
     278           0 :       combining_cell = this_cell;
     279             : 
     280           0 :       overlay_col += chwidth;
     281           0 :       break;
     282           0 :     case 0: /* combining character */
     283           0 :       if ( !combining_cell ) {
     284             :         break;
     285             :       }
     286             : 
     287           0 :       if ( combining_cell->empty() ) {
     288           0 :         assert( !combining_cell->get_wide() );
     289           0 :         combining_cell->set_fallback( true );
     290           0 :         overlay_col++;
     291             :       }
     292             : 
     293           0 :       if ( !combining_cell->full() ) {
     294           0 :         combining_cell->append( ch );
     295             :       }
     296             :       break;
     297             :     case -1: /* unprintable character */
     298             :       break;
     299           0 :     default:
     300           0 :       assert( !"unexpected character width from wcwidth()" );
     301             :     }
     302             :   }
     303        6536 : }
     304             : 
     305        6536 : void NotificationEngine::adjust_message( void )
     306             : {
     307        6536 :   if ( timestamp() >= message_expiration ) {
     308           0 :     message.clear();
     309             :   }  
     310        6536 : }
     311             : 
     312        6088 : int NotificationEngine::wait_time( void ) const
     313             : {
     314        6088 :   uint64_t next_expiry = INT_MAX;
     315             : 
     316        6088 :   uint64_t now = timestamp();
     317             : 
     318        6088 :   next_expiry = std::min( next_expiry, message_expiration - now );
     319             : 
     320        6088 :   if ( need_countup( now ) ) {
     321           0 :     uint64_t countup_interval = 1000;
     322           0 :     if ( ( now - last_word_from_server ) > 60000 ) {
     323             :       /* If we've been disconnected for 60 seconds, save power by updating the
     324             :          display less often.  See #243. */
     325           0 :       countup_interval = Network::ACK_INTERVAL;
     326             :     }
     327           0 :     next_expiry = std::min( next_expiry, countup_interval );
     328             :   }
     329             : 
     330        6088 :   return next_expiry;
     331             : }
     332             : 
     333        6536 : void OverlayManager::apply( Framebuffer &fb )
     334             : {
     335        6536 :   predictions.cull( fb );
     336        6536 :   predictions.apply( fb );
     337        6536 :   notifications.adjust_message();
     338        6536 :   notifications.apply( fb );
     339        6536 :   title.apply( fb );
     340        6536 : }
     341             : 
     342         896 : void TitleEngine::set_prefix( const wstring &s )
     343             : {
     344         896 :   prefix = Terminal::Framebuffer::title_type( s.begin(), s.end() );
     345         896 : }
     346             : 
     347        4989 : void ConditionalOverlayRow::apply( Framebuffer &fb, uint64_t confirmed_epoch, bool flag ) const
     348             : {
     349      404109 :   for ( overlay_cells_type::const_iterator it = overlay_cells.begin();
     350      404109 :         it != overlay_cells.end();
     351      404109 :         it++ ) {
     352      399120 :     it->apply( fb, confirmed_epoch, row_num, flag );
     353             :   }
     354        4989 : }
     355             : 
     356        6536 : void PredictionEngine::apply( Framebuffer &fb ) const
     357             : {
     358        8272 :   if ( (display_preference == Never) || !( srtt_trigger
     359        2970 :                                            || glitch_trigger
     360        2822 :                                            || (display_preference == Always)
     361             :                                            || (display_preference == Experimental) ) ){
     362             :     return;
     363             :   }
     364             : 
     365        6916 :   for ( cursors_type::const_iterator it = cursors.begin();
     366        6916 :         it != cursors.end();
     367        6916 :         it++ ) {
     368        2116 :     it->apply( fb, confirmed_epoch );
     369             :   }
     370             : 
     371        9789 :   for ( overlays_type::const_iterator it = overlays.begin();
     372        9789 :         it != overlays.end();
     373        9789 :         it++ ) {
     374        4989 :     it->apply( fb, confirmed_epoch, flagging );
     375             :   }
     376             : }
     377             : 
     378           7 : void PredictionEngine::kill_epoch( uint64_t epoch, const Framebuffer &fb )
     379             : {
     380          14 :   for( cursors_type::iterator it = cursors.begin(); it != cursors.end(); ) {
     381           7 :     cursors_type::iterator it_next = it;
     382           7 :     it_next++;
     383           7 :     if ( it->tentative( epoch - 1 )) {
     384           7 :       cursors.erase( it );
     385             :     }
     386             :     it = it_next;
     387             :   }
     388             : 
     389           7 :   cursors.push_back( ConditionalCursorMove( local_frame_sent + 1,
     390             :                                             fb.ds.get_cursor_row(),
     391             :                                             fb.ds.get_cursor_col(),
     392             :                                             prediction_epoch ) );
     393           7 :   cursor().active = true;
     394             : 
     395          15 :   for ( overlays_type::iterator i = overlays.begin();
     396          15 :         i != overlays.end();
     397          15 :         i++ ) {
     398           8 :     for ( overlay_cells_type::iterator j = i->overlay_cells.begin();
     399         648 :           j != i->overlay_cells.end();
     400         648 :           j++ ) {
     401         640 :       if ( j->tentative( epoch - 1 ) ) {
     402        1280 :         j->reset();
     403             :       }
     404             :     }
     405             :   }
     406             : 
     407          14 :   become_tentative();
     408           7 : }
     409             : 
     410         538 : void PredictionEngine::reset( void )
     411             : {
     412         538 :   cursors.clear();
     413         538 :   overlays.clear();
     414        1076 :   become_tentative();
     415             : 
     416             :   //  fprintf( stderr, "RESETTING\n" );
     417         538 : }
     418             : 
     419         884 : void PredictionEngine::init_cursor( const Framebuffer &fb )
     420             : {
     421         884 :   if ( cursors.empty() ) {
     422             :     /* initialize new cursor prediction */
     423             :     
     424         452 :     cursors.push_back( ConditionalCursorMove( local_frame_sent + 1,
     425             :                                               fb.ds.get_cursor_row(),
     426             :                                               fb.ds.get_cursor_col(),
     427             :                                               prediction_epoch ) );
     428             : 
     429         452 :     cursor().active = true;
     430         432 :   } else if ( cursor().tentative_until_epoch != prediction_epoch ) {
     431           9 :     cursors.push_back( ConditionalCursorMove( local_frame_sent + 1,
     432             :                                               cursor().row,
     433             :                                               cursor().col,
     434             :                                               prediction_epoch ) );
     435             : 
     436           9 :     cursor().active = true;
     437             :   }
     438         884 : }
     439             : 
     440        7719 : void PredictionEngine::cull( const Framebuffer &fb )
     441             : {
     442        7719 :   if ( display_preference == Never ) {
     443             :     return;
     444             :   }
     445             : 
     446        7719 :   if ( (last_height != fb.ds.get_height())
     447        7719 :        || (last_width != fb.ds.get_width()) ) {
     448         492 :     last_height = fb.ds.get_height();
     449         492 :     last_width = fb.ds.get_width();
     450         492 :     reset();
     451             :   }
     452             : 
     453        7719 :   uint64_t now = timestamp();
     454             : 
     455             :   /* control srtt_trigger with hysteresis */
     456        7719 :   if ( send_interval > SRTT_TRIGGER_HIGH ) {
     457        2024 :     srtt_trigger = true;
     458        2134 :   } else if ( srtt_trigger &&
     459             :               (send_interval <= SRTT_TRIGGER_LOW) /* 20 ms is current minimum value */
     460        5722 :               && (!active()) ) { /* only turn off when no predictions being shown */
     461          10 :     srtt_trigger = false;
     462             :   }
     463             : 
     464             :   /* control underlining with hysteresis */
     465        7719 :   if ( send_interval > FLAG_TRIGGER_HIGH ) {
     466        1421 :     flagging = true;
     467        6298 :   } else if ( send_interval <= FLAG_TRIGGER_LOW ) {
     468        6019 :     flagging = false;
     469             :   }
     470             : 
     471             :   /* really big glitches also activate underlining */
     472        7719 :   if ( glitch_trigger > GLITCH_REPAIR_COUNT ) {
     473           0 :     flagging = true;
     474             :   }
     475             : 
     476             :   /* go through cell predictions */
     477             : 
     478        7719 :   overlays_type::iterator i = overlays.begin();
     479       15481 :   while ( i != overlays.end() ) {
     480        7762 :     overlays_type::iterator inext = i;
     481        7762 :     inext++;
     482        7762 :     if ( (i->row_num < 0) || (i->row_num >= fb.ds.get_height()) ) {
     483           0 :       overlays.erase( i );
     484           0 :       i = inext;
     485           0 :       continue;
     486             :     }
     487             : 
     488        7762 :     for ( overlay_cells_type::iterator j = i->overlay_cells.begin();
     489      628722 :           j != i->overlay_cells.end();
     490      628722 :           j++ ) {
     491      620960 :       switch ( j->get_validity( fb, i->row_num,
     492             :                                 local_frame_acked, local_frame_late_acked ) ) {
     493           7 :       case IncorrectOrExpired:
     494           7 :         if ( j->tentative( confirmed_epoch ) ) {
     495             : 
     496             :           /*
     497             :           fprintf( stderr, "Bad tentative prediction in row %d, col %d (think %lc, actually %lc)\n",
     498             :                    i->row_num, j->col,
     499             :                    j->replacement.debug_contents(),
     500             :                    fb.get_cell( i->row_num, j->col )->debug_contents()
     501             :                    );
     502             :           */
     503             : 
     504           7 :           if ( display_preference == Experimental ) {
     505           0 :             j->reset();
     506             :           } else {
     507           7 :             kill_epoch( j->tentative_until_epoch, fb );
     508             :           }
     509             :           /*
     510             :           if ( j->display_time != uint64_t(-1) ) {
     511             :             fprintf( stderr, "TIMING %ld - %ld (TENT)\n", time(NULL), now - j->display_time );
     512             :           }
     513             :           */
     514             :         } else {
     515             :           /*
     516             :           fprintf( stderr, "[%d=>%d] Killing prediction in row %d, col %d (think %lc, actually %lc)\n",
     517             :                    (int)local_frame_acked, (int)j->expiration_frame,
     518             :                    i->row_num, j->col,
     519             :                    j->replacement.debug_contents(),
     520             :                    fb.get_cell( i->row_num, j->col )->debug_contents() );
     521             :           */
     522             :           /*
     523             :           if ( j->display_time != uint64_t(-1) ) {
     524             :             fprintf( stderr, "TIMING %ld - %ld\n", time(NULL), now - j->display_time );
     525             :           }
     526             :           */
     527             : 
     528           0 :           if ( display_preference == Experimental ) {
     529           0 :             j->reset();
     530             :           } else {
     531           0 :             reset();
     532           0 :             return;
     533             :           }
     534             :         }
     535             :         break;
     536         428 :       case Correct:
     537             :         /*
     538             :         if ( j->display_time != uint64_t(-1) ) {
     539             :           fprintf( stderr, "TIMING %ld + %ld\n", now, now - j->display_time );
     540             :         }
     541             :         */
     542             : 
     543         428 :         if ( j->tentative_until_epoch > confirmed_epoch ) {
     544          10 :           confirmed_epoch = j->tentative_until_epoch;
     545             : 
     546             :           /*
     547             :           fprintf( stderr, "%lc in (%d,%d) confirms epoch %lu (predicting in epoch %lu)\n",
     548             :                    j->replacement.debug_contents(), i->row_num, j->col,
     549             :                    confirmed_epoch, prediction_epoch );
     550             :           */
     551             : 
     552             :         }
     553             : 
     554             :         /* When predictions come in quickly, slowly take away the glitch trigger. */
     555         428 :         if ( now - j->prediction_time < GLITCH_THRESHOLD 
     556         428 :              && ( glitch_trigger > 0 && now - GLITCH_REPAIR_MININTERVAL >= last_quick_confirmation ) ) {
     557           0 :           glitch_trigger--;
     558           0 :           last_quick_confirmation = now;
     559             :         }
     560             : 
     561             :         /* match rest of row to the actual renditions */
     562         428 :         {
     563         428 :           const Renditions &actual_renditions = fb.get_cell( i->row_num, j->col )->get_renditions();
     564       22466 :           for ( overlay_cells_type::iterator k = j;
     565       22894 :                 k != i->overlay_cells.end();
     566       22894 :                 k++ ) {
     567       22466 :             k->replacement.get_renditions() = actual_renditions;
     568             :           }
     569             :         }
     570             : 
     571             :         /* fallthrough */
     572       12826 :       case CorrectNoCredit:
     573      633786 :         j->reset();
     574             : 
     575             :         break;
     576       76758 :       case Pending:
     577             :         /* When a prediction takes a long time to be confirmed, we
     578             :            activate the predictions even if SRTT is low */
     579       76758 :         if ( (now - j->prediction_time) >= GLITCH_FLAG_THRESHOLD ) {
     580           0 :           glitch_trigger = GLITCH_REPAIR_COUNT * 2; /* display and underline */
     581       76758 :         } else if ( ((now - j->prediction_time) >= GLITCH_THRESHOLD)
     582       76758 :                     && (glitch_trigger < GLITCH_REPAIR_COUNT) ) {
     583           2 :           glitch_trigger = GLITCH_REPAIR_COUNT; /* just display */
     584             :         }
     585             : 
     586             :         break;
     587             :       default:
     588             :         break;
     589             :       }
     590             :     }
     591             : 
     592             :     i = inext;
     593             :   }
     594             : 
     595             :   /* go through cursor predictions */
     596        7719 :   if ( !cursors.empty() 
     597        7719 :        && cursor().get_validity( fb,
     598             :                                 local_frame_acked, local_frame_late_acked ) == IncorrectOrExpired ) {
     599             :     /*
     600             :       fprintf( stderr, "Sadly, we're predicting (%d,%d) vs. (%d,%d) [tau: %ld, expiration_time=%ld, now=%ld]\n",
     601             :       cursor().row, cursor().col,
     602             :       fb.ds.get_cursor_row(),
     603             :       fb.ds.get_cursor_col(),
     604             :       cursor().tentative_until_epoch,
     605             :       cursor().expiration_time,
     606             :       now );
     607             :     */
     608           2 :     if ( display_preference == Experimental ) {
     609           0 :       cursors.clear();
     610             :     } else {
     611           2 :       reset();
     612           2 :       return;
     613             :     }
     614             :   }
     615             : 
     616             :   /* NB: switching from list to another STL container could break this code.
     617             :      So we don't use the cursors_type typedef. */
     618        7717 :   for ( list<ConditionalCursorMove>::iterator it = cursors.begin();
     619       10535 :         it != cursors.end(); ) {
     620        2818 :     if ( it->get_validity( fb, local_frame_acked, local_frame_late_acked ) != Pending ) {
     621         255 :       it = cursors.erase( it );
     622             :     } else {
     623       13098 :       it++;
     624             :     }
     625             :   }
     626             : }
     627             : 
     628         666 : ConditionalOverlayRow & PredictionEngine::get_or_make_row( int row_num, int num_cols )
     629             : {
     630         666 :   overlays_type::iterator it;
     631             : 
     632        2292 :   for ( it = overlays.begin(); it != overlays.end(); it++ ) {
     633        2274 :     if ( it->row_num == row_num ) {
     634             :       break;
     635             :     }
     636             :   }
     637             : 
     638         666 :   if ( it != overlays.end() ) {
     639         648 :     return *it;
     640             :   }
     641             :   /* make row */
     642          18 :   ConditionalOverlayRow r( row_num );
     643          18 :   r.overlay_cells.reserve( num_cols );
     644        1458 :   for ( int i = 0; i < num_cols; i++ ) {
     645        1440 :     r.overlay_cells.push_back( ConditionalOverlayCell( 0, i, prediction_epoch ) );
     646        1440 :     assert( r.overlay_cells[ i ].col == i );
     647             :   }
     648          18 :   overlays.push_back( r );
     649          18 :   return overlays.back();
     650         666 : }
     651             : 
     652        1183 : void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
     653             : {
     654        1183 :   if ( display_preference == Never ) {
     655           0 :     return;
     656             :   }
     657        1183 :   if ( display_preference == Experimental ) {
     658           0 :     prediction_epoch = confirmed_epoch;
     659             :   }
     660             : 
     661        1183 :   cull( fb );
     662             : 
     663        1183 :   uint64_t now = timestamp();
     664             : 
     665             :   /* translate application-mode cursor control function to ANSI cursor control sequence */
     666        1183 :   if ( (last_byte == 0x1b)
     667           0 :        && (the_byte == 'O') ) {
     668           0 :     the_byte = '[';
     669             :   }
     670        1183 :   last_byte = the_byte;
     671             : 
     672        1183 :   Parser::Actions actions;
     673        1183 :   parser.input( the_byte, actions );
     674             : 
     675        1183 :   for ( Parser::Actions::iterator it = actions.begin();
     676        2266 :         it != actions.end();
     677        2266 :         it++ ) {
     678        1083 :     Parser::Action& act = **it;
     679             : 
     680             :     /*
     681             :     fprintf( stderr, "Action: %s (%lc)\n",
     682             :              act->name().c_str(), act->char_present ? act->ch : L'_' );
     683             :     */
     684             : 
     685        1083 :     const std::type_info& type_act = typeid( act );
     686        1083 :     if ( type_act == typeid( Parser::Print ) ) {
     687             :       /* make new prediction */
     688             : 
     689         662 :       init_cursor( fb );
     690             : 
     691         662 :       assert( act.char_present );
     692             : 
     693         662 :       wchar_t ch = act.ch;
     694             :       /* XXX handle wide characters */
     695             : 
     696         662 :       if ( ch == 0x7f ) { /* backspace */
     697             :         //      fprintf( stderr, "Backspace.\n" );
     698           0 :         ConditionalOverlayRow &the_row = get_or_make_row( cursor().row, fb.ds.get_width() );
     699             : 
     700           0 :         if ( cursor().col > 0 ) {
     701           0 :           cursor().col--;
     702           0 :           cursor().expire( local_frame_sent + 1, now );
     703             : 
     704           0 :           if ( predict_overwrite ) {
     705           0 :             ConditionalOverlayCell &cell = the_row.overlay_cells[ cursor().col ];
     706           0 :             cell.reset_with_orig();
     707           0 :             cell.active = true;
     708           0 :             cell.tentative_until_epoch = prediction_epoch;
     709           0 :             cell.expire( local_frame_sent + 1, now );
     710           0 :             const Cell orig_cell = *fb.get_cell();
     711           0 :             cell.original_contents.push_back( orig_cell );
     712           0 :             cell.replacement = orig_cell;
     713           0 :             cell.replacement.clear();
     714           0 :             cell.replacement.append(' ');
     715           0 :           } else {
     716           0 :             for ( int i = cursor().col; i < fb.ds.get_width(); i++ ) {
     717           0 :               ConditionalOverlayCell &cell = the_row.overlay_cells[ i ];
     718             : 
     719           0 :               cell.reset_with_orig();
     720           0 :               cell.active = true;
     721           0 :               cell.tentative_until_epoch = prediction_epoch;
     722           0 :               cell.expire( local_frame_sent + 1, now );
     723           0 :               cell.original_contents.push_back( *fb.get_cell( cursor().row, i ) );
     724             :           
     725           0 :               if ( i + 2 < fb.ds.get_width() ) {
     726           0 :                 ConditionalOverlayCell &next_cell = the_row.overlay_cells[ i + 1 ];
     727           0 :                 const Cell *next_cell_actual = fb.get_cell( cursor().row, i + 1 );
     728             : 
     729           0 :                 if ( next_cell.active ) {
     730           0 :                   if ( next_cell.unknown ) {
     731           0 :                     cell.unknown = true;
     732             :                   } else {
     733           0 :                     cell.unknown = false;
     734           0 :                     cell.replacement = next_cell.replacement;
     735             :                   }
     736             :                 } else {
     737           0 :                   cell.unknown = false;
     738           0 :                   cell.replacement = *next_cell_actual;
     739             :                 }
     740             :               } else {
     741           0 :                 cell.unknown = true;
     742             :               }
     743             :             }
     744             :           }
     745             :         }
     746         662 :       } else if ( (ch < 0x20) || (wcwidth( ch ) != 1) ) {
     747             :         /* unknown print */
     748        1083 :         become_tentative();
     749             :         //      fprintf( stderr, "Unknown print 0x%x\n", ch );
     750             :       } else {
     751         662 :         assert( cursor().row >= 0 );
     752         662 :         assert( cursor().col >= 0 );
     753         662 :         assert( cursor().row < fb.ds.get_height() );
     754         662 :         assert( cursor().col < fb.ds.get_width() );
     755             : 
     756         662 :         ConditionalOverlayRow &the_row = get_or_make_row( cursor().row, fb.ds.get_width() );
     757             : 
     758         662 :         if ( cursor().col + 1 >= fb.ds.get_width() ) {
     759             :           /* prediction in the last column is tricky */
     760             :           /* e.g., emacs will show wrap character, shell will just put the character there */
     761         662 :           become_tentative();
     762             :         }
     763             : 
     764             :         /* do the insert */
     765         662 :         int rightmost_column = predict_overwrite ? cursor().col : fb.ds.get_width() - 1;
     766       35636 :         for ( int i = rightmost_column; i > cursor().col; i-- ) {
     767       34974 :           ConditionalOverlayCell &cell = the_row.overlay_cells[ i ];
     768       34974 :           cell.reset_with_orig();
     769       34974 :           cell.active = true;
     770       34974 :           cell.tentative_until_epoch = prediction_epoch;
     771       34974 :           cell.expire( local_frame_sent + 1, now );
     772       34974 :           cell.original_contents.push_back( *fb.get_cell( cursor().row, i ) );
     773             : 
     774       34974 :           ConditionalOverlayCell &prev_cell = the_row.overlay_cells[ i - 1 ];
     775       34974 :           const Cell *prev_cell_actual = fb.get_cell( cursor().row, i - 1 );
     776             : 
     777       34974 :           if ( i == fb.ds.get_width() - 1 ) {
     778         662 :             cell.unknown = true;
     779       34312 :           } else if ( prev_cell.active ) {
     780       21606 :             if ( prev_cell.unknown ) {
     781           0 :               cell.unknown = true;
     782             :             } else {
     783       21606 :               cell.unknown = false;
     784       21606 :               cell.replacement = prev_cell.replacement;
     785             :             }
     786             :           } else {
     787       12706 :             cell.unknown = false;
     788       12706 :             cell.replacement = *prev_cell_actual;
     789             :           }
     790             :         }
     791             :         
     792         662 :         ConditionalOverlayCell &cell = the_row.overlay_cells[ cursor().col ];
     793         662 :         cell.reset_with_orig();
     794         662 :         cell.active = true;
     795         662 :         cell.tentative_until_epoch = prediction_epoch;
     796         662 :         cell.expire( local_frame_sent + 1, now );
     797         662 :         cell.replacement.get_renditions() = fb.ds.get_renditions();
     798             : 
     799             :         /* heuristic: match renditions of character to the left */
     800         662 :         if ( cursor().col > 0 ) {
     801         650 :           ConditionalOverlayCell &prev_cell = the_row.overlay_cells[ cursor().col - 1 ];
     802         650 :           const Cell *prev_cell_actual = fb.get_cell( cursor().row, cursor().col - 1 );
     803         650 :           if ( prev_cell.active && (!prev_cell.unknown) ) {
     804         415 :             cell.replacement.get_renditions() = prev_cell.replacement.get_renditions();
     805             :           } else {
     806         235 :             cell.replacement.get_renditions() = prev_cell_actual->get_renditions();
     807             :           }
     808             :         }
     809             : 
     810         662 :         cell.replacement.clear();
     811         662 :         cell.replacement.append( ch );
     812         662 :         cell.original_contents.push_back( *fb.get_cell( cursor().row, cursor().col ) );
     813             : 
     814             :         /*
     815             :         fprintf( stderr, "[%d=>%d] Predicting %lc in row %d, col %d [tue: %lu]\n",
     816             :                  (int)local_frame_acked, (int)cell.expiration_frame,
     817             :                  ch, cursor().row, cursor().col,
     818             :                  cell.tentative_until_epoch );
     819             :         */
     820             : 
     821         662 :         cursor().expire( local_frame_sent + 1, now );
     822             : 
     823             :         /* do we need to wrap? */
     824         662 :         if ( cursor().col < fb.ds.get_width() - 1 ) {
     825         662 :           cursor().col++;
     826             :         } else {
     827           0 :           become_tentative();
     828           0 :           newline_carriage_return( fb );
     829             :         }
     830             :       }
     831         421 :     } else if ( type_act == typeid( Parser::Execute ) ) {
     832         421 :       if ( act.char_present && (act.ch == 0x0d) /* CR */ ) {
     833         444 :         become_tentative();
     834         222 :         newline_carriage_return( fb );
     835             :       } else {
     836             :         //      fprintf( stderr, "Execute 0x%x\n", act.ch );
     837        1282 :         become_tentative();     
     838             :       }
     839           0 :     } else if ( type_act == typeid( Parser::Esc_Dispatch ) ) {
     840             :       //      fprintf( stderr, "Escape sequence\n" );
     841        1083 :       become_tentative();
     842           0 :     } else if ( type_act == typeid( Parser::CSI_Dispatch ) ) {
     843           0 :       if ( act.char_present && (act.ch == L'C') ) { /* right arrow */
     844           0 :         init_cursor( fb );
     845           0 :         if ( cursor().col < fb.ds.get_width() - 1 ) {
     846           0 :           cursor().col++;
     847           0 :           cursor().expire( local_frame_sent + 1, now );
     848             :         }
     849           0 :       } else if ( act.char_present && (act.ch == L'D') ) { /* left arrow */
     850           0 :         init_cursor( fb );
     851             :         
     852           0 :         if ( cursor().col > 0 ) {
     853           0 :           cursor().col--;
     854           0 :           cursor().expire( local_frame_sent + 1, now );
     855             :         }
     856             :       } else {
     857             :         //      fprintf( stderr, "CSI sequence %lc\n", act.ch );
     858        1083 :         become_tentative();
     859             :       }
     860             :     }
     861             :   }
     862        1183 : }
     863             : 
     864         222 : void PredictionEngine::newline_carriage_return( const Framebuffer &fb )
     865             : {
     866         222 :   uint64_t now = timestamp();
     867         222 :   init_cursor( fb );
     868         222 :   cursor().col = 0;
     869         222 :   if ( cursor().row == fb.ds.get_height() - 1 ) {
     870             :     /* Don't try to predict scroll until we have versioned cell predictions */
     871             :     /*
     872             :     for ( overlays_type::iterator i = overlays.begin();
     873             :           i != overlays.end();
     874             :           i++ ) {
     875             :       i->row_num--;
     876             :       for ( overlay_cells_type::iterator j = i->overlay_cells.begin();
     877             :             j != i->overlay_cells.end();
     878             :             j++ ) {
     879             :         if ( j->active ) {
     880             :           j->expire( local_frame_sent + 1, now );
     881             :         }
     882             :       }
     883             :     }
     884             :     */
     885             : 
     886             :     /* make blank prediction for last row */
     887           4 :     ConditionalOverlayRow &the_row = get_or_make_row( cursor().row, fb.ds.get_width() );
     888         324 :     for ( overlay_cells_type::iterator j = the_row.overlay_cells.begin();
     889         324 :           j != the_row.overlay_cells.end();
     890         324 :           j++ ) {
     891         320 :       j->active = true;
     892         320 :       j->tentative_until_epoch = prediction_epoch;
     893         320 :       j->expire( local_frame_sent + 1, now );
     894         320 :       j->replacement.clear();
     895             :     }
     896             :   } else {
     897         218 :     cursor().row++;
     898             :   }
     899         222 : }
     900             : 
     901         966 : void PredictionEngine::become_tentative( void )
     902             : {
     903         966 :   if ( display_preference != Experimental ) {
     904         966 :     prediction_epoch++;
     905             :   }
     906             : 
     907             :   /*
     908             :   fprintf( stderr, "Now tentative in epoch %lu (confirmed=%lu)\n",
     909             :            prediction_epoch, confirmed_epoch );
     910             :   */
     911           0 : }
     912             : 
     913        6075 : bool PredictionEngine::active( void ) const
     914             : {
     915        6075 :   if ( !cursors.empty() ) {
     916             :     return true;
     917             :   }
     918             : 
     919        5364 :   for ( overlays_type::const_iterator i = overlays.begin();
     920        5364 :         i != overlays.end();
     921        5364 :         i++ ) {
     922       96795 :     for ( overlay_cells_type::const_iterator j = i->overlay_cells.begin();
     923       96795 :           j != i->overlay_cells.end();
     924       96795 :           j++ ) {
     925       95600 :       if ( j->active ) {
     926        6075 :         return true;
     927             :       }
     928             :     }
     929             :   }
     930             : 
     931             :   return false;
     932             : }

Generated by: LCOV version 1.14