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 ¤t = *( 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 : }
|