Coverage Report

Created: 2026-07-16 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mosh/src/terminal/terminalfunctions.cc
Line
Count
Source
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 <cstdio>
35
#include <string>
36
#include <utility>
37
#include <vector>
38
39
#include <unistd.h>
40
41
#include "src/terminal/parseraction.h"
42
#include "src/terminal/terminalframebuffer.h"
43
#include "terminaldispatcher.h"
44
45
using namespace Terminal;
46
47
/* Terminal functions -- routines activated by CSI, escape or a control char */
48
49
static void clearline( Framebuffer* fb, int row, int start, int end )
50
0
{
51
0
  for ( int col = start; col <= end; col++ ) {
52
0
    fb->reset_cell( fb->get_mutable_cell( row, col ) );
53
0
  }
54
0
}
55
56
/* erase in line */
57
static void CSI_EL( Framebuffer* fb, Dispatcher* dispatch )
58
0
{
59
0
  switch ( dispatch->getparam( 0, 0 ) ) {
60
0
    case 0: /* default: active position to end of line, inclusive */
61
0
      clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
62
0
      break;
63
0
    case 1: /* start of screen to active position, inclusive */
64
0
      clearline( fb, -1, 0, fb->ds.get_cursor_col() );
65
0
      break;
66
0
    case 2: /* all of line */
67
0
      fb->reset_row( fb->get_mutable_row( -1 ) );
68
0
      break;
69
0
    default:
70
0
      break;
71
0
  }
72
0
}
73
74
static Function func_CSI_EL( CSI, "K", CSI_EL );
75
76
/* erase in display */
77
static void CSI_ED( Framebuffer* fb, Dispatcher* dispatch )
78
0
{
79
0
  switch ( dispatch->getparam( 0, 0 ) ) {
80
0
    case 0: /* active position to end of screen, inclusive */
81
0
      clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
82
0
      for ( int y = fb->ds.get_cursor_row() + 1; y < fb->ds.get_height(); y++ ) {
83
0
        fb->reset_row( fb->get_mutable_row( y ) );
84
0
      }
85
0
      break;
86
0
    case 1: /* start of screen to active position, inclusive */
87
0
      for ( int y = 0; y < fb->ds.get_cursor_row(); y++ ) {
88
0
        fb->reset_row( fb->get_mutable_row( y ) );
89
0
      }
90
0
      clearline( fb, -1, 0, fb->ds.get_cursor_col() );
91
0
      break;
92
0
    case 2: /* entire screen */
93
0
      for ( int y = 0; y < fb->ds.get_height(); y++ ) {
94
0
        fb->reset_row( fb->get_mutable_row( y ) );
95
0
      }
96
0
      break;
97
0
    default:
98
0
      break;
99
0
  }
100
0
}
101
102
static Function func_CSI_ED( CSI, "J", CSI_ED );
103
104
/* cursor movement -- relative and absolute */
105
static void CSI_cursormove( Framebuffer* fb, Dispatcher* dispatch )
106
0
{
107
0
  int num = dispatch->getparam( 0, 1 );
108
109
0
  switch ( dispatch->get_dispatch_chars()[0] ) {
110
0
    case 'A':
111
0
      fb->ds.move_row( -num, true );
112
0
      break;
113
0
    case 'B':
114
0
      fb->ds.move_row( num, true );
115
0
      break;
116
0
    case 'C':
117
0
      fb->ds.move_col( num, true );
118
0
      break;
119
0
    case 'D':
120
0
      fb->ds.move_col( -num, true );
121
0
      break;
122
0
    case 'H':
123
0
    case 'f':
124
0
      fb->ds.move_row( dispatch->getparam( 0, 1 ) - 1 );
125
0
      fb->ds.move_col( dispatch->getparam( 1, 1 ) - 1 );
126
0
      break;
127
0
    default:
128
0
      break;
129
0
  }
130
0
}
131
132
static Function func_CSI_cursormove_A( CSI, "A", CSI_cursormove );
133
static Function func_CSI_cursormove_B( CSI, "B", CSI_cursormove );
134
static Function func_CSI_cursormove_C( CSI, "C", CSI_cursormove );
135
static Function func_CSI_cursormove_D( CSI, "D", CSI_cursormove );
136
static Function func_CSI_cursormove_H( CSI, "H", CSI_cursormove );
137
static Function func_CSI_cursormove_f( CSI, "f", CSI_cursormove );
138
139
/* device attributes */
140
static void CSI_DA( Framebuffer* fb __attribute( ( unused ) ), Dispatcher* dispatch )
141
0
{
142
0
  dispatch->terminal_to_host.append( "\033[?62c" ); /* plain vt220 */
143
0
}
144
145
static Function func_CSI_DA( CSI, "c", CSI_DA );
146
147
/* secondary device attributes */
148
static void CSI_SDA( Framebuffer* fb __attribute( ( unused ) ), Dispatcher* dispatch )
149
0
{
150
0
  dispatch->terminal_to_host.append( "\033[>1;10;0c" ); /* plain vt220 */
151
0
}
152
153
static Function func_CSI_SDA( CSI, ">c", CSI_SDA );
154
155
/* screen alignment diagnostic */
156
static void Esc_DECALN( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
157
0
{
158
0
  for ( int y = 0; y < fb->ds.get_height(); y++ ) {
159
0
    for ( int x = 0; x < fb->ds.get_width(); x++ ) {
160
0
      fb->reset_cell( fb->get_mutable_cell( y, x ) );
161
0
      fb->get_mutable_cell( y, x )->append( 'E' );
162
0
    }
163
0
  }
164
0
}
165
166
static Function func_Esc_DECALN( ESCAPE, "#8", Esc_DECALN );
167
168
/* line feed */
169
static void Ctrl_LF( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
170
0
{
171
0
  fb->move_rows_autoscroll( 1 );
172
0
}
173
174
/* same procedure for index, vertical tab, and form feed control codes */
175
static Function func_Ctrl_LF( CONTROL, "\x0a", Ctrl_LF );
176
static Function func_Ctrl_IND( CONTROL, "\x84", Ctrl_LF );
177
static Function func_Ctrl_VT( CONTROL, "\x0b", Ctrl_LF );
178
static Function func_Ctrl_FF( CONTROL, "\x0c", Ctrl_LF );
179
180
/* carriage return */
181
static void Ctrl_CR( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
182
0
{
183
0
  fb->ds.move_col( 0 );
184
0
}
185
186
static Function func_Ctrl_CR( CONTROL, "\x0d", Ctrl_CR );
187
188
/* backspace */
189
static void Ctrl_BS( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
190
0
{
191
0
  fb->ds.move_col( -1, true );
192
0
}
193
194
static Function func_Ctrl_BS( CONTROL, "\x08", Ctrl_BS );
195
196
/* reverse index -- like a backwards line feed */
197
static void Ctrl_RI( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
198
0
{
199
0
  fb->move_rows_autoscroll( -1 );
200
0
}
201
202
static Function func_Ctrl_RI( CONTROL, "\x8D", Ctrl_RI );
203
204
/* newline */
205
static void Ctrl_NEL( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
206
0
{
207
0
  fb->ds.move_col( 0 );
208
0
  fb->move_rows_autoscroll( 1 );
209
0
}
210
211
static Function func_Ctrl_NEL( CONTROL, "\x85", Ctrl_NEL );
212
213
/* horizontal tab */
214
static void HT_n( Framebuffer* fb, size_t count )
215
0
{
216
0
  int col = fb->ds.get_next_tab( count );
217
0
  if ( col == -1 ) { /* no tabs, go to end of line */
218
0
    col = fb->ds.get_width() - 1;
219
0
  }
220
221
  /* A horizontal tab is the only operation that preserves but
222
     does not set the wrap state. It also starts a new grapheme. */
223
224
0
  bool wrap_state_save = fb->ds.next_print_will_wrap;
225
0
  fb->ds.move_col( col, false );
226
0
  fb->ds.next_print_will_wrap = wrap_state_save;
227
0
}
228
229
static void Ctrl_HT( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
230
0
{
231
0
  HT_n( fb, 1 );
232
0
}
233
static Function func_Ctrl_HT( CONTROL, "\x09", Ctrl_HT, false );
234
235
static void CSI_CxT( Framebuffer* fb, Dispatcher* dispatch )
236
0
{
237
0
  int param = dispatch->getparam( 0, 1 );
238
0
  if ( dispatch->get_dispatch_chars()[0] == 'Z' ) {
239
0
    param = -param;
240
0
  }
241
0
  if ( param == 0 ) {
242
0
    return;
243
0
  }
244
0
  HT_n( fb, param );
245
0
}
246
247
static Function func_CSI_CHT( CSI, "I", CSI_CxT, false );
248
static Function func_CSI_CBT( CSI, "Z", CSI_CxT, false );
249
250
/* horizontal tab set */
251
static void Ctrl_HTS( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
252
0
{
253
0
  fb->ds.set_tab();
254
0
}
255
256
static Function func_Ctrl_HTS( CONTROL, "\x88", Ctrl_HTS );
257
258
/* tabulation clear */
259
static void CSI_TBC( Framebuffer* fb, Dispatcher* dispatch )
260
0
{
261
0
  int param = dispatch->getparam( 0, 0 );
262
0
  switch ( param ) {
263
0
    case 0: /* clear this tab stop */
264
0
      fb->ds.clear_tab( fb->ds.get_cursor_col() );
265
0
      break;
266
0
    case 3: /* clear all tab stops */
267
0
      fb->ds.clear_default_tabs();
268
0
      for ( int x = 0; x < fb->ds.get_width(); x++ ) {
269
0
        fb->ds.clear_tab( x );
270
0
      }
271
0
      break;
272
0
    default:
273
0
      break;
274
0
  }
275
0
}
276
277
/* TBC preserves wrap state */
278
static Function func_CSI_TBC( CSI, "g", CSI_TBC, false );
279
280
static bool* get_DEC_mode( int param, Framebuffer* fb )
281
0
{
282
0
  switch ( param ) {
283
0
    case 1: /* cursor key mode */
284
0
      return &( fb->ds.application_mode_cursor_keys );
285
0
    case 3: /* 80/132. Ignore but clear screen. */
286
      /* clear screen */
287
0
      fb->ds.move_row( 0 );
288
0
      fb->ds.move_col( 0 );
289
0
      for ( int y = 0; y < fb->ds.get_height(); y++ ) {
290
0
        fb->reset_row( fb->get_mutable_row( y ) );
291
0
      }
292
0
      return NULL;
293
0
    case 5: /* reverse video */
294
0
      return &( fb->ds.reverse_video );
295
0
    case 6: /* origin */
296
0
      fb->ds.move_row( 0 );
297
0
      fb->ds.move_col( 0 );
298
0
      return &( fb->ds.origin_mode );
299
0
    case 7: /* auto wrap */
300
0
      return &( fb->ds.auto_wrap_mode );
301
0
    case 25:
302
0
      return &( fb->ds.cursor_visible );
303
0
    case 1004: /* xterm mouse focus event */
304
0
      return &( fb->ds.mouse_focus_event );
305
0
    case 1007: /* xterm mouse alternate scroll */
306
0
      return &( fb->ds.mouse_alternate_scroll );
307
0
    case 2004: /* bracketed paste */
308
0
      return &( fb->ds.bracketed_paste );
309
0
    default:
310
0
      break;
311
0
  }
312
0
  return NULL;
313
0
}
314
315
/* helper for CSI_DECSM and CSI_DECRM */
316
static void set_if_available( bool* mode, bool value )
317
0
{
318
0
  if ( mode ) {
319
0
    *mode = value;
320
0
  }
321
0
}
322
323
/* set private mode */
324
static void CSI_DECSM( Framebuffer* fb, Dispatcher* dispatch )
325
0
{
326
0
  for ( int i = 0; i < dispatch->param_count(); i++ ) {
327
0
    int param = dispatch->getparam( i, 0 );
328
0
    if ( param == 9 || ( param >= 1000 && param <= 1003 ) ) {
329
0
      fb->ds.mouse_reporting_mode = (Terminal::DrawState::MouseReportingMode)param;
330
0
    } else if ( param == 1005 || param == 1006 || param == 1015 ) {
331
0
      fb->ds.mouse_encoding_mode = (Terminal::DrawState::MouseEncodingMode)param;
332
0
    } else {
333
0
      set_if_available( get_DEC_mode( param, fb ), true );
334
0
    }
335
0
  }
336
0
}
337
338
/* clear private mode */
339
static void CSI_DECRM( Framebuffer* fb, Dispatcher* dispatch )
340
0
{
341
0
  for ( int i = 0; i < dispatch->param_count(); i++ ) {
342
0
    int param = dispatch->getparam( i, 0 );
343
0
    if ( param == 9 || ( param >= 1000 && param <= 1003 ) ) {
344
0
      fb->ds.mouse_reporting_mode = Terminal::DrawState::MOUSE_REPORTING_NONE;
345
0
    } else if ( param == 1005 || param == 1006 || param == 1015 ) {
346
0
      fb->ds.mouse_encoding_mode = Terminal::DrawState::MOUSE_ENCODING_DEFAULT;
347
0
    } else {
348
0
      set_if_available( get_DEC_mode( param, fb ), false );
349
0
    }
350
0
  }
351
0
}
352
353
/* These functions don't clear wrap state. */
354
static Function func_CSI_DECSM( CSI, "?h", CSI_DECSM, false );
355
static Function func_CSI_DECRM( CSI, "?l", CSI_DECRM, false );
356
357
static bool* get_ANSI_mode( int param, Framebuffer* fb )
358
0
{
359
0
  if ( param == 4 ) { /* insert/replace mode */
360
0
    return &( fb->ds.insert_mode );
361
0
  }
362
0
  return NULL;
363
0
}
364
365
/* set mode */
366
static void CSI_SM( Framebuffer* fb, Dispatcher* dispatch )
367
0
{
368
0
  for ( int i = 0; i < dispatch->param_count(); i++ ) {
369
0
    bool* mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
370
0
    if ( mode ) {
371
0
      *mode = true;
372
0
    }
373
0
  }
374
0
}
375
376
/* clear mode */
377
static void CSI_RM( Framebuffer* fb, Dispatcher* dispatch )
378
0
{
379
0
  for ( int i = 0; i < dispatch->param_count(); i++ ) {
380
0
    bool* mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
381
0
    if ( mode ) {
382
0
      *mode = false;
383
0
    }
384
0
  }
385
0
}
386
387
static Function func_CSI_SM( CSI, "h", CSI_SM );
388
static Function func_CSI_RM( CSI, "l", CSI_RM );
389
390
/* set top and bottom margins */
391
static void CSI_DECSTBM( Framebuffer* fb, Dispatcher* dispatch )
392
0
{
393
0
  int top = dispatch->getparam( 0, 1 );
394
0
  int bottom = dispatch->getparam( 1, fb->ds.get_height() );
395
396
0
  if ( ( bottom <= top ) || ( top > fb->ds.get_height() ) || ( top == 0 && bottom == 1 ) ) {
397
0
    return; /* invalid, xterm ignores */
398
0
  }
399
400
0
  fb->ds.set_scrolling_region( top - 1, bottom - 1 );
401
0
  fb->ds.move_row( 0 );
402
0
  fb->ds.move_col( 0 );
403
0
}
404
405
static Function func_CSI_DECSTMB( CSI, "r", CSI_DECSTBM );
406
407
/* terminal bell */
408
static void Ctrl_BEL( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
409
0
{
410
0
  fb->ring_bell();
411
0
}
412
413
static Function func_Ctrl_BEL( CONTROL, "\x07", Ctrl_BEL );
414
415
/* select graphics rendition -- e.g., bold, blinking, etc. */
416
static void CSI_SGR( Framebuffer* fb, Dispatcher* dispatch )
417
0
{
418
0
  for ( int i = 0; i < dispatch->param_count(); i++ ) {
419
0
    int rendition = dispatch->getparam( i, 0 );
420
    /* We need to special-case the handling of [34]8 ; 5 ; Ps,
421
       because Ps of 0 in that case does not mean reset to default, even
422
       though it means that otherwise (as usually renditions are applied
423
       in order). */
424
0
    if ( ( rendition == 38 || rendition == 48 ) && ( dispatch->param_count() - i >= 3 )
425
0
         && ( dispatch->getparam( i + 1, -1 ) == 5 ) ) {
426
0
      ( rendition == 38 ) ? fb->ds.set_foreground_color( dispatch->getparam( i + 2, 0 ) )
427
0
                          : fb->ds.set_background_color( dispatch->getparam( i + 2, 0 ) );
428
0
      i += 2;
429
0
      continue;
430
0
    }
431
432
    /* True color support: ESC[ ... [34]8;2;<r>;<g>;<b> ... m */
433
0
    if ( ( rendition == 38 || rendition == 48 ) && ( dispatch->param_count() - i >= 5 )
434
0
         && ( dispatch->getparam( i + 1, -1 ) == 2 ) ) {
435
0
      unsigned int red = dispatch->getparam( i + 2, 0 );
436
0
      unsigned int green = dispatch->getparam( i + 3, 0 );
437
0
      unsigned int blue = dispatch->getparam( i + 4, 0 );
438
0
      unsigned int color;
439
440
0
      color = Renditions::make_true_color( red, green, blue );
441
442
0
      if ( rendition == 38 ) {
443
0
        fb->ds.set_foreground_color( color );
444
0
      } else {
445
0
        fb->ds.set_background_color( color );
446
0
      }
447
0
      i += 4;
448
0
      continue;
449
0
    }
450
451
0
    fb->ds.add_rendition( rendition );
452
0
  }
453
0
}
454
455
static Function func_CSI_SGR( CSI, "m", CSI_SGR, false ); /* changing renditions doesn't clear wrap flag */
456
457
/* save and restore cursor */
458
static void Esc_DECSC( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
459
0
{
460
0
  fb->ds.save_cursor();
461
0
}
462
463
static void Esc_DECRC( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
464
0
{
465
0
  fb->ds.restore_cursor();
466
0
}
467
468
static Function func_Esc_DECSC( ESCAPE, "7", Esc_DECSC );
469
static Function func_Esc_DECRC( ESCAPE, "8", Esc_DECRC );
470
471
/* device status report -- e.g., cursor position (used by resize) */
472
static void CSI_DSR( Framebuffer* fb, Dispatcher* dispatch )
473
0
{
474
0
  int param = dispatch->getparam( 0, 0 );
475
476
0
  switch ( param ) {
477
0
    case 5: /* device status report requested */
478
0
      dispatch->terminal_to_host.append( "\033[0n" );
479
0
      break;
480
0
    case 6: /* report of active position requested */
481
0
      char cpr[32];
482
0
      snprintf( cpr, 32, "\033[%d;%dR", fb->ds.get_cursor_row() + 1, fb->ds.get_cursor_col() + 1 );
483
0
      dispatch->terminal_to_host.append( cpr );
484
0
      break;
485
0
    default:
486
0
      break;
487
0
  }
488
0
}
489
490
static Function func_CSI_DSR( CSI, "n", CSI_DSR );
491
492
/* insert line */
493
static void CSI_IL( Framebuffer* fb, Dispatcher* dispatch )
494
0
{
495
0
  int lines = dispatch->getparam( 0, 1 );
496
497
0
  fb->insert_line( fb->ds.get_cursor_row(), lines );
498
499
  /* vt220 manual and Ecma-48 say to move to first column */
500
  /* but xterm and gnome-terminal don't */
501
0
  fb->ds.move_col( 0 );
502
0
}
503
504
static Function func_CSI_IL( CSI, "L", CSI_IL );
505
506
/* delete line */
507
static void CSI_DL( Framebuffer* fb, Dispatcher* dispatch )
508
0
{
509
0
  int lines = dispatch->getparam( 0, 1 );
510
511
0
  fb->delete_line( fb->ds.get_cursor_row(), lines );
512
513
  /* same story -- xterm and gnome-terminal don't
514
     move to first column */
515
0
  fb->ds.move_col( 0 );
516
0
}
517
518
static Function func_CSI_DL( CSI, "M", CSI_DL );
519
520
/* insert characters */
521
static void CSI_ICH( Framebuffer* fb, Dispatcher* dispatch )
522
0
{
523
0
  int cells = dispatch->getparam( 0, 1 );
524
525
0
  for ( int i = 0; i < cells; i++ ) {
526
0
    fb->insert_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
527
0
  }
528
0
}
529
530
static Function func_CSI_ICH( CSI, "@", CSI_ICH );
531
532
/* delete character */
533
static void CSI_DCH( Framebuffer* fb, Dispatcher* dispatch )
534
0
{
535
0
  int cells = dispatch->getparam( 0, 1 );
536
537
0
  for ( int i = 0; i < cells; i++ ) {
538
0
    fb->delete_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
539
0
  }
540
0
}
541
542
static Function func_CSI_DCH( CSI, "P", CSI_DCH );
543
544
/* line position absolute */
545
static void CSI_VPA( Framebuffer* fb, Dispatcher* dispatch )
546
0
{
547
0
  int row = dispatch->getparam( 0, 1 );
548
0
  fb->ds.move_row( row - 1 );
549
0
}
550
551
static Function func_CSI_VPA( CSI, "d", CSI_VPA );
552
553
/* character position absolute */
554
static void CSI_HPA( Framebuffer* fb, Dispatcher* dispatch )
555
0
{
556
0
  int col = dispatch->getparam( 0, 1 );
557
0
  fb->ds.move_col( col - 1 );
558
0
}
559
560
static Function func_CSI_CHA( CSI, "G", CSI_HPA );    /* ECMA-48 name: CHA */
561
static Function func_CSI_HPA( CSI, "\x60", CSI_HPA ); /* ECMA-48 name: HPA */
562
563
/* erase character */
564
static void CSI_ECH( Framebuffer* fb, Dispatcher* dispatch )
565
0
{
566
0
  int num = dispatch->getparam( 0, 1 );
567
0
  int limit = fb->ds.get_cursor_col() + num - 1;
568
0
  if ( limit >= fb->ds.get_width() ) {
569
0
    limit = fb->ds.get_width() - 1;
570
0
  }
571
572
0
  clearline( fb, -1, fb->ds.get_cursor_col(), limit );
573
0
}
574
575
static Function func_CSI_ECH( CSI, "X", CSI_ECH );
576
577
/* reset to initial state */
578
static void Esc_RIS( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
579
0
{
580
0
  fb->reset();
581
0
}
582
583
static Function func_Esc_RIS( ESCAPE, "c", Esc_RIS );
584
585
/* soft reset */
586
static void CSI_DECSTR( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unused ) ) )
587
0
{
588
0
  fb->soft_reset();
589
0
}
590
591
static Function func_CSI_DECSTR( CSI, "!p", CSI_DECSTR );
592
593
static bool Parse_OSC_8( const std::vector<wchar_t>& osc8_vector, std::string& osc8_str )
594
0
{
595
0
  osc8_str.reserve( osc8_vector.size() );
596
0
  for ( wchar_t wide_char : osc8_vector ) {
597
    // Valid char range is 32-126, per
598
    // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#encodings
599
0
    if ( wide_char < 32 || wide_char > 126 ) {
600
0
      return false;
601
0
    }
602
0
    osc8_str.append( 1, static_cast<char>( wide_char ) );
603
0
  }
604
0
  return true;
605
0
}
606
607
static void OSC_8( const std::string& OSC_string, Framebuffer* fb )
608
0
{
609
  // OSC of the form "\033]8;params;url\007"
610
0
  assert( OSC_string[0] == '8' );
611
0
  if ( OSC_string.size() <= 2 || OSC_string[1] != ';' ) {
612
    // Bail early if the string is malformed.
613
0
    return;
614
0
  }
615
616
0
  size_t second_semicolon = OSC_string.find_first_of( ';', 2 );
617
0
  if ( second_semicolon == std::string::npos ) {
618
    // Missing the second semicolon, malformed.
619
0
    return;
620
0
  }
621
622
0
  std::string url = OSC_string.substr( second_semicolon + 1 );
623
0
  fb->ds.set_hyperlink( Hyperlink( OSC_string.substr( 2, second_semicolon - 2 ), std::move( url ) ) );
624
0
}
625
626
/* xterm uses an Operating System Command to set the window title */
627
void Dispatcher::OSC_dispatch( const Parser::OSC_End* act __attribute( ( unused ) ), Framebuffer* fb )
628
0
{
629
  /* handle osc copy clipboard sequence 52;c; */
630
0
  if ( OSC_string.size() >= 5 && OSC_string[0] == L'5' && OSC_string[1] == L'2' && OSC_string[2] == L';'
631
0
       && OSC_string[3] == L'c' && OSC_string[4] == L';' ) {
632
0
    Terminal::Framebuffer::title_type clipboard( OSC_string.begin() + 5, OSC_string.end() );
633
0
    fb->set_clipboard( clipboard );
634
    /* handle osc terminal title sequence */
635
0
  } else if ( OSC_string.size() >= 1 ) {
636
0
    long cmd_num = -1;
637
0
    int offset = 0;
638
0
    if ( OSC_string[0] == L';' ) {
639
      /* OSC of the form "\033];<title>\007" */
640
0
      cmd_num = 0; /* treat it as as a zero */
641
0
      offset = 1;
642
0
    } else if ( ( OSC_string.size() >= 2 ) && ( OSC_string[1] == L';' ) ) {
643
      /* OSC of the form "\033]X;<title>\007" where X can be:
644
       * 0: set icon name and window title
645
       * 1: set icon name
646
       * 2: set window title */
647
0
      cmd_num = OSC_string[0] - L'0';
648
0
      offset = 2;
649
0
    }
650
0
    if ( cmd_num == 8 ) {
651
      // Handle OSC8 hyperlinks separately
652
0
      std::string osc_8_str;
653
0
      if ( !Parse_OSC_8( OSC_string, osc_8_str ) ) {
654
        //
655
0
        return;
656
0
      }
657
0
      OSC_8( osc_8_str, fb );
658
0
      return;
659
0
    }
660
0
    bool set_icon = cmd_num == 0 || cmd_num == 1;
661
0
    bool set_title = cmd_num == 0 || cmd_num == 2;
662
0
    if ( set_icon || set_title ) {
663
0
      fb->set_title_initialized();
664
0
      int title_length = std::min( OSC_string.size(), (size_t)256 );
665
0
      Terminal::Framebuffer::title_type newtitle( OSC_string.begin() + offset, OSC_string.begin() + title_length );
666
0
      if ( set_icon ) {
667
0
        fb->set_icon_name( newtitle );
668
0
      }
669
0
      if ( set_title ) {
670
0
        fb->set_window_title( newtitle );
671
0
      }
672
0
    }
673
0
  }
674
0
}
675
676
/* scroll down or terminfo indn */
677
static void CSI_SD( Framebuffer* fb, Dispatcher* dispatch )
678
0
{
679
0
  fb->scroll( dispatch->getparam( 0, 1 ) );
680
0
}
681
682
static Function func_CSI_SD( CSI, "S", CSI_SD );
683
684
/* scroll up or terminfo rin */
685
static void CSI_SU( Framebuffer* fb, Dispatcher* dispatch )
686
0
{
687
0
  fb->scroll( -dispatch->getparam( 0, 1 ) );
688
0
}
689
690
static Function func_CSI_SU( CSI, "T", CSI_SU );