1 #include "vterm_internal.h"
2
3 #include <stdio.h>
4 #include <string.h>
5
6 #define strneq(a,b,n) (strncmp(a,b,n)==0)
7
8 #if defined(DEBUG) && DEBUG > 1
9 # define DEBUG_GLYPH_COMBINE
10 #endif
11
12 static int on_resize(int rows, int cols, void *user);
13
14 /* Some convenient wrappers to make callback functions easier */
15
putglyph(VTermState * state,const uint32_t chars[],int width,VTermPos pos)16 static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
17 {
18 VTermGlyphInfo info;
19
20 info.chars = chars;
21 info.width = width;
22 info.protected_cell = state->protected_cell;
23 info.dwl = state->lineinfo[pos.row].doublewidth;
24 info.dhl = state->lineinfo[pos.row].doubleheight;
25
26 if(state->callbacks && state->callbacks->putglyph)
27 if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
28 return;
29
30 DEBUG_LOG3("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
31 }
32
updatecursor(VTermState * state,VTermPos * oldpos,int cancel_phantom)33 static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
34 {
35 if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
36 return;
37
38 if(cancel_phantom)
39 state->at_phantom = 0;
40
41 if(state->callbacks && state->callbacks->movecursor)
42 if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
43 return;
44 }
45
erase(VTermState * state,VTermRect rect,int selective)46 static void erase(VTermState *state, VTermRect rect, int selective)
47 {
48 if(rect.end_col == state->cols) {
49 int row;
50 /* If we're erasing the final cells of any lines, cancel the continuation
51 * marker on the subsequent line
52 */
53 for(row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++)
54 state->lineinfo[row].continuation = 0;
55 }
56
57 if(state->callbacks && state->callbacks->erase)
58 if((*state->callbacks->erase)(rect, selective, state->cbdata))
59 return;
60 }
61
vterm_state_new(VTerm * vt)62 static VTermState *vterm_state_new(VTerm *vt)
63 {
64 VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
65
66 if (state == NULL)
67 return NULL;
68 state->vt = vt;
69
70 state->rows = vt->rows;
71 state->cols = vt->cols;
72
73 state->mouse_col = 0;
74 state->mouse_row = 0;
75 state->mouse_buttons = 0;
76
77 state->mouse_protocol = MOUSE_X10;
78
79 state->callbacks = NULL;
80 state->cbdata = NULL;
81
82 state->selection.callbacks = NULL;
83 state->selection.user = NULL;
84 state->selection.buffer = NULL;
85
86 vterm_state_newpen(state);
87
88 state->bold_is_highbright = 0;
89
90 state->combine_chars_size = 16;
91 state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
92
93 state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
94
95 state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
96 /* TODO: Make an 'enable' function */
97 state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
98 state->lineinfo = state->lineinfos[BUFIDX_PRIMARY];
99
100 state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
101 if(state->encoding_utf8.enc->init)
102 (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
103
104 return state;
105 }
106
vterm_state_free(VTermState * state)107 INTERNAL void vterm_state_free(VTermState *state)
108 {
109 vterm_allocator_free(state->vt, state->tabstops);
110 vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);
111 if(state->lineinfos[BUFIDX_ALTSCREEN])
112 vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);
113 vterm_allocator_free(state->vt, state->combine_chars);
114 vterm_allocator_free(state->vt, state);
115 }
116
scroll(VTermState * state,VTermRect rect,int downward,int rightward)117 static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
118 {
119 int rows;
120 int cols;
121 if(!downward && !rightward)
122 return;
123
124 rows = rect.end_row - rect.start_row;
125 if(downward > rows)
126 downward = rows;
127 else if(downward < -rows)
128 downward = -rows;
129
130 cols = rect.end_col - rect.start_col;
131 if(rightward > cols)
132 rightward = cols;
133 else if(rightward < -cols)
134 rightward = -cols;
135
136 // Update lineinfo if full line
137 if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
138 int height = rect.end_row - rect.start_row - abs(downward);
139 int row;
140 VTermLineInfo zeroLineInfo = {0x0};
141
142 if(downward > 0) {
143 memmove(state->lineinfo + rect.start_row,
144 state->lineinfo + rect.start_row + downward,
145 height * sizeof(state->lineinfo[0]));
146 for(row = rect.end_row - downward; row < rect.end_row; row++)
147 state->lineinfo[row] = zeroLineInfo;
148 }
149 else {
150 memmove(state->lineinfo + rect.start_row - downward,
151 state->lineinfo + rect.start_row,
152 height * sizeof(state->lineinfo[0]));
153 for(row = rect.start_row; row < rect.start_row - downward; row++)
154 state->lineinfo[row] = zeroLineInfo;
155 }
156 }
157
158 if(state->callbacks && state->callbacks->scrollrect)
159 if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
160 return;
161
162 if(state->callbacks)
163 vterm_scroll_rect(rect, downward, rightward,
164 state->callbacks->moverect, state->callbacks->erase, state->cbdata);
165 }
166
linefeed(VTermState * state)167 static void linefeed(VTermState *state)
168 {
169 if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
170 VTermRect rect;
171 rect.start_row = state->scrollregion_top;
172 rect.end_row = SCROLLREGION_BOTTOM(state);
173 rect.start_col = SCROLLREGION_LEFT(state);
174 rect.end_col = SCROLLREGION_RIGHT(state);
175
176 scroll(state, rect, 1, 0);
177 }
178 else if(state->pos.row < state->rows-1)
179 state->pos.row++;
180 }
181
grow_combine_buffer(VTermState * state)182 static void grow_combine_buffer(VTermState *state)
183 {
184 size_t new_size = state->combine_chars_size * 2;
185 uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
186
187 memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
188
189 vterm_allocator_free(state->vt, state->combine_chars);
190
191 state->combine_chars = new_chars;
192 state->combine_chars_size = new_size;
193 }
194
set_col_tabstop(VTermState * state,int col)195 static void set_col_tabstop(VTermState *state, int col)
196 {
197 unsigned char mask = 1 << (col & 7);
198 state->tabstops[col >> 3] |= mask;
199 }
200
clear_col_tabstop(VTermState * state,int col)201 static void clear_col_tabstop(VTermState *state, int col)
202 {
203 unsigned char mask = 1 << (col & 7);
204 state->tabstops[col >> 3] &= ~mask;
205 }
206
is_col_tabstop(VTermState * state,int col)207 static int is_col_tabstop(VTermState *state, int col)
208 {
209 unsigned char mask = 1 << (col & 7);
210 return state->tabstops[col >> 3] & mask;
211 }
212
is_cursor_in_scrollregion(const VTermState * state)213 static int is_cursor_in_scrollregion(const VTermState *state)
214 {
215 if(state->pos.row < state->scrollregion_top ||
216 state->pos.row >= SCROLLREGION_BOTTOM(state))
217 return 0;
218 if(state->pos.col < SCROLLREGION_LEFT(state) ||
219 state->pos.col >= SCROLLREGION_RIGHT(state))
220 return 0;
221
222 return 1;
223 }
224
tab(VTermState * state,int count,int direction)225 static void tab(VTermState *state, int count, int direction)
226 {
227 while(count > 0) {
228 if(direction > 0) {
229 if(state->pos.col >= THISROWWIDTH(state)-1)
230 return;
231
232 state->pos.col++;
233 }
234 else if(direction < 0) {
235 if(state->pos.col < 1)
236 return;
237
238 state->pos.col--;
239 }
240
241 if(is_col_tabstop(state, state->pos.col))
242 count--;
243 }
244 }
245
246 #define NO_FORCE 0
247 #define FORCE 1
248
249 #define DWL_OFF 0
250 #define DWL_ON 1
251
252 #define DHL_OFF 0
253 #define DHL_TOP 1
254 #define DHL_BOTTOM 2
255
set_lineinfo(VTermState * state,int row,int force,int dwl,int dhl)256 static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
257 {
258 VTermLineInfo info = state->lineinfo[row];
259
260 if(dwl == DWL_OFF)
261 info.doublewidth = DWL_OFF;
262 else if(dwl == DWL_ON)
263 info.doublewidth = DWL_ON;
264 // else -1 to ignore
265
266 if(dhl == DHL_OFF)
267 info.doubleheight = DHL_OFF;
268 else if(dhl == DHL_TOP)
269 info.doubleheight = DHL_TOP;
270 else if(dhl == DHL_BOTTOM)
271 info.doubleheight = DHL_BOTTOM;
272
273 if((state->callbacks &&
274 state->callbacks->setlineinfo &&
275 (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
276 || force)
277 state->lineinfo[row] = info;
278 }
279
on_text(const char bytes[],size_t len,void * user)280 static int on_text(const char bytes[], size_t len, void *user)
281 {
282 VTermState *state = user;
283 uint32_t *codepoints;
284 int npoints = 0;
285 size_t eaten = 0;
286 VTermEncodingInstance *encoding;
287 int i = 0;
288
289 VTermPos oldpos = state->pos;
290
291 // We'll have at most len codepoints, plus one from a previous incomplete
292 // sequence.
293 codepoints = vterm_allocator_malloc(state->vt, (len + 1) * sizeof(uint32_t));
294 if (codepoints == NULL)
295 return 0;
296
297 encoding =
298 state->gsingle_set ? &state->encoding[state->gsingle_set] :
299 !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
300 state->vt->mode.utf8 ? &state->encoding_utf8 :
301 &state->encoding[state->gr_set];
302
303 (*encoding->enc->decode)(encoding->enc, encoding->data,
304 codepoints, &npoints, state->gsingle_set ? 1 : (int)len,
305 bytes, &eaten, len);
306
307 /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
308 * for even a single codepoint
309 */
310 if(!npoints)
311 {
312 vterm_allocator_free(state->vt, codepoints);
313 return (int)eaten;
314 }
315
316 if(state->gsingle_set && npoints)
317 state->gsingle_set = 0;
318
319 /* This is a combining char. that needs to be merged with the previous
320 * glyph output */
321 if(vterm_unicode_is_combining(codepoints[i])) {
322 /* See if the cursor has moved since */
323 if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
324 #ifdef DEBUG_GLYPH_COMBINE
325 int printpos;
326 printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
327 for(printpos = 0; state->combine_chars[printpos]; printpos++)
328 printf("U+%04x ", state->combine_chars[printpos]);
329 printf("} + {");
330 #endif
331
332 /* Find where we need to append these combining chars */
333 int saved_i = 0;
334 while(state->combine_chars[saved_i])
335 saved_i++;
336
337 /* Add extra ones */
338 while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
339 if(saved_i >= (int)state->combine_chars_size)
340 grow_combine_buffer(state);
341 state->combine_chars[saved_i++] = codepoints[i++];
342 }
343 if(saved_i >= (int)state->combine_chars_size)
344 grow_combine_buffer(state);
345 state->combine_chars[saved_i] = 0;
346
347 #ifdef DEBUG_GLYPH_COMBINE
348 for(; state->combine_chars[printpos]; printpos++)
349 printf("U+%04x ", state->combine_chars[printpos]);
350 printf("}\n");
351 #endif
352
353 /* Now render it */
354 putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
355 }
356 else {
357 DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
358 }
359 }
360
361 for(; i < npoints; i++) {
362 // Try to find combining characters following this
363 int glyph_starts = i;
364 int glyph_ends;
365 int width = 0;
366 uint32_t *chars;
367
368 for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
369 if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
370 break;
371
372 chars = vterm_allocator_malloc(state->vt, (glyph_ends - glyph_starts + 1) * sizeof(uint32_t));
373 if (chars == NULL)
374 break;
375
376 for( ; i < glyph_ends; i++) {
377 int this_width;
378 if(vterm_get_special_pty_type() == 2) {
379 state->vt->in_backspace -= (state->vt->in_backspace > 0) ? 1 : 0;
380 if(state->vt->in_backspace == 1)
381 codepoints[i] = 0; // codepoints under this condition must be 0
382 }
383 chars[i - glyph_starts] = codepoints[i];
384 this_width = vterm_unicode_width(codepoints[i]);
385 #ifdef DEBUG
386 if(this_width < 0) {
387 fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]);
388 abort();
389 }
390 #endif
391 if (i == glyph_starts || this_width > width)
392 width = this_width;
393 }
394
395 chars[glyph_ends - glyph_starts] = 0;
396 i--;
397
398 #ifdef DEBUG_GLYPH_COMBINE
399 int printpos;
400 printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
401 for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
402 printf("U+%04x ", chars[printpos]);
403 printf("}, onscreen width %d\n", width);
404 #endif
405
406 if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
407 linefeed(state);
408 state->pos.col = 0;
409 state->at_phantom = 0;
410 state->lineinfo[state->pos.row].continuation = 1;
411 }
412
413 if(state->mode.insert) {
414 // TODO: This will be a little inefficient for large bodies of text, as
415 // it'll have to 'ICH' effectively before every glyph. We should scan
416 // ahead and ICH as many times as required
417 VTermRect rect;
418 rect.start_row = state->pos.row;
419 rect.end_row = state->pos.row + 1;
420 rect.start_col = state->pos.col;
421 rect.end_col = THISROWWIDTH(state);
422 scroll(state, rect, 0, -1);
423 }
424
425 putglyph(state, chars, width, state->pos);
426
427 if(i == npoints - 1) {
428 /* End of the buffer. Save the chars in case we have to combine with
429 * more on the next call */
430 int save_i;
431 for(save_i = 0; chars[save_i]; save_i++) {
432 if(save_i >= (int)state->combine_chars_size)
433 grow_combine_buffer(state);
434 state->combine_chars[save_i] = chars[save_i];
435 }
436 if(save_i >= (int)state->combine_chars_size)
437 grow_combine_buffer(state);
438 state->combine_chars[save_i] = 0;
439 state->combine_width = width;
440 state->combine_pos = state->pos;
441 }
442
443 if(state->pos.col + width >= THISROWWIDTH(state)) {
444 if(state->mode.autowrap)
445 state->at_phantom = 1;
446 }
447 else {
448 state->pos.col += width;
449 }
450 vterm_allocator_free(state->vt, chars);
451 }
452
453 updatecursor(state, &oldpos, 0);
454
455 #ifdef DEBUG
456 if(state->pos.row < 0 || state->pos.row >= state->rows ||
457 state->pos.col < 0 || state->pos.col >= state->cols) {
458 fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
459 state->pos.row, state->pos.col);
460 abort();
461 }
462 #endif
463
464 vterm_allocator_free(state->vt, codepoints);
465 return (int)eaten;
466 }
467
on_control(unsigned char control,void * user)468 static int on_control(unsigned char control, void *user)
469 {
470 VTermState *state = user;
471
472 VTermPos oldpos = state->pos;
473
474 VTermScreenCell cell;
475
476 // Preparing to see the leading byte
477 VTermPos leadpos = state->pos;
478 leadpos.col -= (leadpos.col >= 2 ? 2 : 0);
479
480 switch(control) {
481 case 0x07: // BEL - ECMA-48 8.3.3
482 if(state->callbacks && state->callbacks->bell)
483 (*state->callbacks->bell)(state->cbdata);
484 break;
485
486 case 0x08: // BS - ECMA-48 8.3.5
487 if(state->pos.col > 0)
488 state->pos.col--;
489 if(vterm_get_special_pty_type() == 2) {
490 // In 2 cell letters, go back 2 cells
491 vterm_screen_get_cell(state->vt->screen, leadpos, &cell);
492 if(vterm_unicode_width(cell.chars[0]) == 2)
493 state->pos.col--;
494 }
495 break;
496
497 case 0x09: // HT - ECMA-48 8.3.60
498 tab(state, 1, +1);
499 break;
500
501 case 0x0a: // LF - ECMA-48 8.3.74
502 case 0x0b: // VT
503 case 0x0c: // FF
504 linefeed(state);
505 if(state->mode.newline)
506 state->pos.col = 0;
507 break;
508
509 case 0x0d: // CR - ECMA-48 8.3.15
510 state->pos.col = 0;
511 break;
512
513 case 0x0e: // LS1 - ECMA-48 8.3.76
514 state->gl_set = 1;
515 break;
516
517 case 0x0f: // LS0 - ECMA-48 8.3.75
518 state->gl_set = 0;
519 break;
520
521 case 0x84: // IND - DEPRECATED but implemented for completeness
522 linefeed(state);
523 break;
524
525 case 0x85: // NEL - ECMA-48 8.3.86
526 linefeed(state);
527 state->pos.col = 0;
528 break;
529
530 case 0x88: // HTS - ECMA-48 8.3.62
531 set_col_tabstop(state, state->pos.col);
532 break;
533
534 case 0x8d: // RI - ECMA-48 8.3.104
535 if(state->pos.row == state->scrollregion_top) {
536 VTermRect rect;
537 rect.start_row = state->scrollregion_top;
538 rect.end_row = SCROLLREGION_BOTTOM(state);
539 rect.start_col = SCROLLREGION_LEFT(state);
540 rect.end_col = SCROLLREGION_RIGHT(state);
541
542 scroll(state, rect, -1, 0);
543 }
544 else if(state->pos.row > 0)
545 state->pos.row--;
546 break;
547
548 case 0x8e: // SS2 - ECMA-48 8.3.141
549 state->gsingle_set = 2;
550 break;
551
552 case 0x8f: // SS3 - ECMA-48 8.3.142
553 state->gsingle_set = 3;
554 break;
555
556 default:
557 if(state->fallbacks && state->fallbacks->control)
558 if((*state->fallbacks->control)(control, state->fbdata))
559 return 1;
560
561 return 0;
562 }
563
564 updatecursor(state, &oldpos, 1);
565
566 #ifdef DEBUG
567 if(state->pos.row < 0 || state->pos.row >= state->rows ||
568 state->pos.col < 0 || state->pos.col >= state->cols) {
569 fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
570 control, state->pos.row, state->pos.col);
571 abort();
572 }
573 #endif
574
575 return 1;
576 }
577
settermprop_bool(VTermState * state,VTermProp prop,int v)578 static int settermprop_bool(VTermState *state, VTermProp prop, int v)
579 {
580 VTermValue val;
581 val.boolean = v;
582 return vterm_state_set_termprop(state, prop, &val);
583 }
584
settermprop_int(VTermState * state,VTermProp prop,int v)585 static int settermprop_int(VTermState *state, VTermProp prop, int v)
586 {
587 VTermValue val;
588 val.number = v;
589 return vterm_state_set_termprop(state, prop, &val);
590 }
591
settermprop_string(VTermState * state,VTermProp prop,VTermStringFragment frag)592 static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag)
593 {
594 VTermValue val;
595
596 val.string = frag;
597 return vterm_state_set_termprop(state, prop, &val);
598 }
599
savecursor(VTermState * state,int save)600 static void savecursor(VTermState *state, int save)
601 {
602 if(save) {
603 state->saved.pos = state->pos;
604 state->saved.mode.cursor_visible = state->mode.cursor_visible;
605 state->saved.mode.cursor_blink = state->mode.cursor_blink;
606 state->saved.mode.cursor_shape = state->mode.cursor_shape;
607
608 vterm_state_savepen(state, 1);
609 }
610 else {
611 VTermPos oldpos = state->pos;
612
613 state->pos = state->saved.pos;
614
615 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
616 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink);
617 settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape);
618
619 vterm_state_savepen(state, 0);
620
621 updatecursor(state, &oldpos, 1);
622 }
623 }
624
on_escape(const char * bytes,size_t len,void * user)625 static int on_escape(const char *bytes, size_t len, void *user)
626 {
627 VTermState *state = user;
628
629 /* Easier to decode this from the first byte, even though the final
630 * byte terminates it
631 */
632 switch(bytes[0]) {
633 case ' ':
634 if(len != 2)
635 return 0;
636
637 switch(bytes[1]) {
638 case 'F': // S7C1T
639 state->vt->mode.ctrl8bit = 0;
640 break;
641
642 case 'G': // S8C1T
643 state->vt->mode.ctrl8bit = 1;
644 break;
645
646 default:
647 return 0;
648 }
649 return 2;
650
651 case '#':
652 if(len != 2)
653 return 0;
654
655 switch(bytes[1]) {
656 case '3': // DECDHL top
657 if(state->mode.leftrightmargin)
658 break;
659 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
660 break;
661
662 case '4': // DECDHL bottom
663 if(state->mode.leftrightmargin)
664 break;
665 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
666 break;
667
668 case '5': // DECSWL
669 if(state->mode.leftrightmargin)
670 break;
671 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
672 break;
673
674 case '6': // DECDWL
675 if(state->mode.leftrightmargin)
676 break;
677 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
678 break;
679
680 case '8': // DECALN
681 {
682 VTermPos pos;
683 uint32_t E[] = { 'E', 0 };
684 for(pos.row = 0; pos.row < state->rows; pos.row++)
685 for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
686 putglyph(state, E, 1, pos);
687 break;
688 }
689
690 default:
691 return 0;
692 }
693 return 2;
694
695 case '(': case ')': case '*': case '+': // SCS
696 if(len != 2)
697 return 0;
698
699 {
700 int setnum = bytes[0] - 0x28;
701 VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
702
703 if(newenc) {
704 state->encoding[setnum].enc = newenc;
705
706 if(newenc->init)
707 (*newenc->init)(newenc, state->encoding[setnum].data);
708 }
709 }
710
711 return 2;
712
713 case '7': // DECSC
714 savecursor(state, 1);
715 return 1;
716
717 case '8': // DECRC
718 savecursor(state, 0);
719 return 1;
720
721 case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
722 return 1;
723
724 case '=': // DECKPAM
725 state->mode.keypad = 1;
726 return 1;
727
728 case '>': // DECKPNM
729 state->mode.keypad = 0;
730 return 1;
731
732 case 'c': // RIS - ECMA-48 8.3.105
733 {
734 VTermPos oldpos = state->pos;
735 vterm_state_reset(state, 1);
736 if(state->callbacks && state->callbacks->movecursor)
737 (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
738 return 1;
739 }
740
741 case 'n': // LS2 - ECMA-48 8.3.78
742 state->gl_set = 2;
743 return 1;
744
745 case 'o': // LS3 - ECMA-48 8.3.80
746 state->gl_set = 3;
747 return 1;
748
749 case '~': // LS1R - ECMA-48 8.3.77
750 state->gr_set = 1;
751 return 1;
752
753 case '}': // LS2R - ECMA-48 8.3.79
754 state->gr_set = 2;
755 return 1;
756
757 case '|': // LS3R - ECMA-48 8.3.81
758 state->gr_set = 3;
759 return 1;
760
761 default:
762 return 0;
763 }
764 }
765
set_mode(VTermState * state,int num,int val)766 static void set_mode(VTermState *state, int num, int val)
767 {
768 switch(num) {
769 case 4: // IRM - ECMA-48 7.2.10
770 state->mode.insert = val;
771 break;
772
773 case 20: // LNM - ANSI X3.4-1977
774 state->mode.newline = val;
775 break;
776
777 default:
778 DEBUG_LOG1("libvterm: Unknown mode %d\n", num);
779 return;
780 }
781 }
782
set_dec_mode(VTermState * state,int num,int val)783 static void set_dec_mode(VTermState *state, int num, int val)
784 {
785 switch(num) {
786 case 1:
787 state->mode.cursor = val;
788 break;
789
790 case 5: // DECSCNM - screen mode
791 settermprop_bool(state, VTERM_PROP_REVERSE, val);
792 break;
793
794 case 6: // DECOM - origin mode
795 {
796 VTermPos oldpos = state->pos;
797 state->mode.origin = val;
798 state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
799 state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
800 updatecursor(state, &oldpos, 1);
801 }
802 break;
803
804 case 7:
805 state->mode.autowrap = val;
806 break;
807
808 case 12:
809 settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
810 break;
811
812 case 25:
813 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
814 break;
815
816 case 69: // DECVSSM - vertical split screen mode
817 // DECLRMM - left/right margin mode
818 state->mode.leftrightmargin = val;
819 if(val) {
820 int row;
821
822 // Setting DECVSSM must clear doublewidth/doubleheight state of every line
823 for(row = 0; row < state->rows; row++)
824 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
825 }
826
827 break;
828
829 case 1000:
830 case 1002:
831 case 1003:
832 settermprop_int(state, VTERM_PROP_MOUSE,
833 !val ? VTERM_PROP_MOUSE_NONE :
834 (num == 1000) ? VTERM_PROP_MOUSE_CLICK :
835 (num == 1002) ? VTERM_PROP_MOUSE_DRAG :
836 VTERM_PROP_MOUSE_MOVE);
837 break;
838
839 case 1004:
840 state->mode.report_focus = val;
841 break;
842
843 case 1005:
844 state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
845 break;
846
847 case 1006:
848 state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
849 break;
850
851 case 1015:
852 state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
853 break;
854
855 case 1047:
856 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
857 break;
858
859 case 1048:
860 savecursor(state, val);
861 break;
862
863 case 1049:
864 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
865 savecursor(state, val);
866 break;
867
868 case 2004:
869 state->mode.bracketpaste = val;
870 break;
871
872 default:
873 DEBUG_LOG1("libvterm: Unknown DEC mode %d\n", num);
874 return;
875 }
876 }
877
request_dec_mode(VTermState * state,int num)878 static void request_dec_mode(VTermState *state, int num)
879 {
880 int reply;
881
882 switch(num) {
883 case 1:
884 reply = state->mode.cursor;
885 break;
886
887 case 5:
888 reply = state->mode.screen;
889 break;
890
891 case 6:
892 reply = state->mode.origin;
893 break;
894
895 case 7:
896 reply = state->mode.autowrap;
897 break;
898
899 case 12:
900 reply = state->mode.cursor_blink;
901 break;
902
903 case 25:
904 reply = state->mode.cursor_visible;
905 break;
906
907 case 69:
908 reply = state->mode.leftrightmargin;
909 break;
910
911 case 1000:
912 reply = state->mouse_flags == MOUSE_WANT_CLICK;
913 break;
914
915 case 1002:
916 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
917 break;
918
919 case 1003:
920 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
921 break;
922
923 case 1004:
924 reply = state->mode.report_focus;
925 break;
926
927 case 1005:
928 reply = state->mouse_protocol == MOUSE_UTF8;
929 break;
930
931 case 1006:
932 reply = state->mouse_protocol == MOUSE_SGR;
933 break;
934
935 case 1015:
936 reply = state->mouse_protocol == MOUSE_RXVT;
937 break;
938
939 case 1047:
940 reply = state->mode.alt_screen;
941 break;
942
943 case 2004:
944 reply = state->mode.bracketpaste;
945 break;
946
947 default:
948 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
949 return;
950 }
951
952 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
953 }
954
on_csi(const char * leader,const long args[],int argcount,const char * intermed,char command,void * user)955 static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
956 {
957 VTermState *state = user;
958 int leader_byte = 0;
959 int intermed_byte = 0;
960 int cancel_phantom = 1;
961 VTermPos oldpos = state->pos;
962 int handled = 1;
963
964 // Some temporaries for later code
965 int count, val;
966 int row, col;
967 VTermRect rect;
968 int selective;
969
970 if(leader && leader[0]) {
971 if(leader[1]) // longer than 1 char
972 return 0;
973
974 switch(leader[0]) {
975 case '?':
976 case '>':
977 leader_byte = leader[0];
978 break;
979 default:
980 return 0;
981 }
982 }
983
984 if(intermed && intermed[0]) {
985 if(intermed[1]) // longer than 1 char
986 return 0;
987
988 switch(intermed[0]) {
989 case ' ':
990 case '"':
991 case '$':
992 case '\'':
993 intermed_byte = intermed[0];
994 break;
995 default:
996 return 0;
997 }
998 }
999
1000 oldpos = state->pos;
1001
1002 #define LBOUND(v,min) if((v) < (min)) (v) = (min)
1003 #define UBOUND(v,max) if((v) > (max)) (v) = (max)
1004
1005 #define LEADER(l,b) ((l << 8) | b)
1006 #define INTERMED(i,b) ((i << 16) | b)
1007
1008 switch(intermed_byte << 16 | leader_byte << 8 | command) {
1009 case 0x40: // ICH - ECMA-48 8.3.64
1010 count = CSI_ARG_COUNT(args[0]);
1011
1012 if(!is_cursor_in_scrollregion(state))
1013 break;
1014
1015 rect.start_row = state->pos.row;
1016 rect.end_row = state->pos.row + 1;
1017 rect.start_col = state->pos.col;
1018 if(state->mode.leftrightmargin)
1019 rect.end_col = SCROLLREGION_RIGHT(state);
1020 else
1021 rect.end_col = THISROWWIDTH(state);
1022
1023 scroll(state, rect, 0, -count);
1024
1025 break;
1026
1027 case 0x41: // CUU - ECMA-48 8.3.22
1028 count = CSI_ARG_COUNT(args[0]);
1029 state->pos.row -= count;
1030 state->at_phantom = 0;
1031 break;
1032
1033 case 0x42: // CUD - ECMA-48 8.3.19
1034 count = CSI_ARG_COUNT(args[0]);
1035 state->pos.row += count;
1036 state->at_phantom = 0;
1037 break;
1038
1039 case 0x43: // CUF - ECMA-48 8.3.20
1040 count = CSI_ARG_COUNT(args[0]);
1041 state->pos.col += count;
1042 state->at_phantom = 0;
1043 break;
1044
1045 case 0x44: // CUB - ECMA-48 8.3.18
1046 count = CSI_ARG_COUNT(args[0]);
1047 state->pos.col -= count;
1048 state->at_phantom = 0;
1049 break;
1050
1051 case 0x45: // CNL - ECMA-48 8.3.12
1052 count = CSI_ARG_COUNT(args[0]);
1053 state->pos.col = 0;
1054 state->pos.row += count;
1055 state->at_phantom = 0;
1056 break;
1057
1058 case 0x46: // CPL - ECMA-48 8.3.13
1059 count = CSI_ARG_COUNT(args[0]);
1060 state->pos.col = 0;
1061 state->pos.row -= count;
1062 state->at_phantom = 0;
1063 break;
1064
1065 case 0x47: // CHA - ECMA-48 8.3.9
1066 val = CSI_ARG_OR(args[0], 1);
1067 state->pos.col = val-1;
1068 state->at_phantom = 0;
1069 break;
1070
1071 case 0x48: // CUP - ECMA-48 8.3.21
1072 row = CSI_ARG_OR(args[0], 1);
1073 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
1074 // zero-based
1075 if(vterm_get_special_pty_type() == 2) {
1076 // Fix a sequence that is not correct right now
1077 if(state->pos.row == row - 1) {
1078 int cnt, ptr = 0;
1079 for(cnt = 0; cnt < col - 1; ++cnt) {
1080 VTermPos p;
1081 VTermScreenCell c0, c1;
1082 p.row = row - 1;
1083 p.col = ptr;
1084 vterm_screen_get_cell(state->vt->screen, p, &c0);
1085 p.col++;
1086 vterm_screen_get_cell(state->vt->screen, p, &c1);
1087 ptr += (c1.chars[0] == (uint32_t)-1) // double cell?
1088 ? (vterm_unicode_is_ambiguous(c0.chars[0])) // is ambiguous?
1089 ? vterm_unicode_width(0x00a1) : 1 // &ambiwidth
1090 : 1; // not ambiguous
1091 }
1092 col = ptr + 1;
1093 }
1094 }
1095 state->pos.row = row-1;
1096 state->pos.col = col-1;
1097 if(state->mode.origin) {
1098 state->pos.row += state->scrollregion_top;
1099 state->pos.col += SCROLLREGION_LEFT(state);
1100 }
1101 state->at_phantom = 0;
1102 break;
1103
1104 case 0x49: // CHT - ECMA-48 8.3.10
1105 count = CSI_ARG_COUNT(args[0]);
1106 tab(state, count, +1);
1107 break;
1108
1109 case 0x4a: // ED - ECMA-48 8.3.39
1110 case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
1111 selective = (leader_byte == '?');
1112 switch(CSI_ARG(args[0])) {
1113 case CSI_ARG_MISSING:
1114 case 0:
1115 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
1116 rect.start_col = state->pos.col; rect.end_col = state->cols;
1117 if(rect.end_col > rect.start_col)
1118 erase(state, rect, selective);
1119
1120 rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
1121 rect.start_col = 0;
1122 for(row = rect.start_row; row < rect.end_row; row++)
1123 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1124 if(rect.end_row > rect.start_row)
1125 erase(state, rect, selective);
1126 break;
1127
1128 case 1:
1129 rect.start_row = 0; rect.end_row = state->pos.row;
1130 rect.start_col = 0; rect.end_col = state->cols;
1131 for(row = rect.start_row; row < rect.end_row; row++)
1132 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1133 if(rect.end_col > rect.start_col)
1134 erase(state, rect, selective);
1135
1136 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
1137 rect.end_col = state->pos.col + 1;
1138 if(rect.end_row > rect.start_row)
1139 erase(state, rect, selective);
1140 break;
1141
1142 case 2:
1143 rect.start_row = 0; rect.end_row = state->rows;
1144 rect.start_col = 0; rect.end_col = state->cols;
1145 for(row = rect.start_row; row < rect.end_row; row++)
1146 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1147 erase(state, rect, selective);
1148 break;
1149 }
1150 break;
1151
1152 case 0x4b: // EL - ECMA-48 8.3.41
1153 case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
1154 selective = (leader_byte == '?');
1155 rect.start_row = state->pos.row;
1156 rect.end_row = state->pos.row + 1;
1157
1158 switch(CSI_ARG(args[0])) {
1159 case CSI_ARG_MISSING:
1160 case 0:
1161 rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
1162 case 1:
1163 rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
1164 case 2:
1165 rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
1166 default:
1167 return 0;
1168 }
1169
1170 if(rect.end_col > rect.start_col)
1171 erase(state, rect, selective);
1172
1173 break;
1174
1175 case 0x4c: // IL - ECMA-48 8.3.67
1176 count = CSI_ARG_COUNT(args[0]);
1177
1178 if(!is_cursor_in_scrollregion(state))
1179 break;
1180
1181 rect.start_row = state->pos.row;
1182 rect.end_row = SCROLLREGION_BOTTOM(state);
1183 rect.start_col = SCROLLREGION_LEFT(state);
1184 rect.end_col = SCROLLREGION_RIGHT(state);
1185
1186 scroll(state, rect, -count, 0);
1187
1188 break;
1189
1190 case 0x4d: // DL - ECMA-48 8.3.32
1191 count = CSI_ARG_COUNT(args[0]);
1192
1193 if(!is_cursor_in_scrollregion(state))
1194 break;
1195
1196 rect.start_row = state->pos.row;
1197 rect.end_row = SCROLLREGION_BOTTOM(state);
1198 rect.start_col = SCROLLREGION_LEFT(state);
1199 rect.end_col = SCROLLREGION_RIGHT(state);
1200
1201 scroll(state, rect, count, 0);
1202
1203 break;
1204
1205 case 0x50: // DCH - ECMA-48 8.3.26
1206 count = CSI_ARG_COUNT(args[0]);
1207
1208 if(!is_cursor_in_scrollregion(state))
1209 break;
1210
1211 rect.start_row = state->pos.row;
1212 rect.end_row = state->pos.row + 1;
1213 rect.start_col = state->pos.col;
1214 if(state->mode.leftrightmargin)
1215 rect.end_col = SCROLLREGION_RIGHT(state);
1216 else
1217 rect.end_col = THISROWWIDTH(state);
1218
1219 scroll(state, rect, 0, count);
1220
1221 break;
1222
1223 case 0x53: // SU - ECMA-48 8.3.147
1224 count = CSI_ARG_COUNT(args[0]);
1225
1226 rect.start_row = state->scrollregion_top;
1227 rect.end_row = SCROLLREGION_BOTTOM(state);
1228 rect.start_col = SCROLLREGION_LEFT(state);
1229 rect.end_col = SCROLLREGION_RIGHT(state);
1230
1231 scroll(state, rect, count, 0);
1232
1233 break;
1234
1235 case 0x54: // SD - ECMA-48 8.3.113
1236 count = CSI_ARG_COUNT(args[0]);
1237
1238 rect.start_row = state->scrollregion_top;
1239 rect.end_row = SCROLLREGION_BOTTOM(state);
1240 rect.start_col = SCROLLREGION_LEFT(state);
1241 rect.end_col = SCROLLREGION_RIGHT(state);
1242
1243 scroll(state, rect, -count, 0);
1244
1245 break;
1246
1247 case 0x58: // ECH - ECMA-48 8.3.38
1248 count = CSI_ARG_COUNT(args[0]);
1249
1250 rect.start_row = state->pos.row;
1251 rect.end_row = state->pos.row + 1;
1252 rect.start_col = state->pos.col;
1253 rect.end_col = state->pos.col + count;
1254 UBOUND(rect.end_col, THISROWWIDTH(state));
1255
1256 erase(state, rect, 0);
1257 break;
1258
1259 case 0x5a: // CBT - ECMA-48 8.3.7
1260 count = CSI_ARG_COUNT(args[0]);
1261 tab(state, count, -1);
1262 break;
1263
1264 case 0x60: // HPA - ECMA-48 8.3.57
1265 col = CSI_ARG_OR(args[0], 1);
1266 state->pos.col = col-1;
1267 state->at_phantom = 0;
1268 break;
1269
1270 case 0x61: // HPR - ECMA-48 8.3.59
1271 count = CSI_ARG_COUNT(args[0]);
1272 state->pos.col += count;
1273 state->at_phantom = 0;
1274 break;
1275
1276 case 0x62: { // REP - ECMA-48 8.3.103
1277 const int row_width = THISROWWIDTH(state);
1278 count = CSI_ARG_COUNT(args[0]);
1279 col = state->pos.col + count;
1280 UBOUND(col, row_width);
1281 while (state->pos.col < col) {
1282 putglyph(state, state->combine_chars, state->combine_width, state->pos);
1283 state->pos.col += state->combine_width;
1284 }
1285 if (state->pos.col + state->combine_width >= row_width) {
1286 if (state->mode.autowrap) {
1287 state->at_phantom = 1;
1288 cancel_phantom = 0;
1289 }
1290 }
1291 break;
1292 }
1293
1294 case 0x63: // DA - ECMA-48 8.3.24
1295 val = CSI_ARG_OR(args[0], 0);
1296 if(val == 0)
1297 // DEC VT100 response
1298 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c");
1299 break;
1300
1301 case LEADER('>', 0x63): // DEC secondary Device Attributes
1302 // This returns xterm version number 100.
1303 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
1304 break;
1305
1306 case 0x64: // VPA - ECMA-48 8.3.158
1307 row = CSI_ARG_OR(args[0], 1);
1308 state->pos.row = row-1;
1309 if(state->mode.origin)
1310 state->pos.row += state->scrollregion_top;
1311 state->at_phantom = 0;
1312 break;
1313
1314 case 0x65: // VPR - ECMA-48 8.3.160
1315 count = CSI_ARG_COUNT(args[0]);
1316 state->pos.row += count;
1317 state->at_phantom = 0;
1318 break;
1319
1320 case 0x66: // HVP - ECMA-48 8.3.63
1321 row = CSI_ARG_OR(args[0], 1);
1322 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
1323 // zero-based
1324 state->pos.row = row-1;
1325 state->pos.col = col-1;
1326 if(state->mode.origin) {
1327 state->pos.row += state->scrollregion_top;
1328 state->pos.col += SCROLLREGION_LEFT(state);
1329 }
1330 state->at_phantom = 0;
1331 break;
1332
1333 case 0x67: // TBC - ECMA-48 8.3.154
1334 val = CSI_ARG_OR(args[0], 0);
1335
1336 switch(val) {
1337 case 0:
1338 clear_col_tabstop(state, state->pos.col);
1339 break;
1340 case 3:
1341 case 5:
1342 for(col = 0; col < state->cols; col++)
1343 clear_col_tabstop(state, col);
1344 break;
1345 case 1:
1346 case 2:
1347 case 4:
1348 break;
1349 /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
1350 default:
1351 return 0;
1352 }
1353 break;
1354
1355 case 0x68: // SM - ECMA-48 8.3.125
1356 if(!CSI_ARG_IS_MISSING(args[0]))
1357 set_mode(state, CSI_ARG(args[0]), 1);
1358 break;
1359
1360 case LEADER('?', 0x68): // DEC private mode set
1361 if(!CSI_ARG_IS_MISSING(args[0]))
1362 set_dec_mode(state, CSI_ARG(args[0]), 1);
1363 break;
1364
1365 case 0x6a: // HPB - ECMA-48 8.3.58
1366 count = CSI_ARG_COUNT(args[0]);
1367 state->pos.col -= count;
1368 state->at_phantom = 0;
1369 break;
1370
1371 case 0x6b: // VPB - ECMA-48 8.3.159
1372 count = CSI_ARG_COUNT(args[0]);
1373 state->pos.row -= count;
1374 state->at_phantom = 0;
1375 break;
1376
1377 case 0x6c: // RM - ECMA-48 8.3.106
1378 if(!CSI_ARG_IS_MISSING(args[0]))
1379 set_mode(state, CSI_ARG(args[0]), 0);
1380 break;
1381
1382 case LEADER('?', 0x6c): // DEC private mode reset
1383 if(!CSI_ARG_IS_MISSING(args[0]))
1384 set_dec_mode(state, CSI_ARG(args[0]), 0);
1385 break;
1386
1387 case 0x6d: // SGR - ECMA-48 8.3.117
1388 vterm_state_setpen(state, args, argcount);
1389 break;
1390
1391 case LEADER('>', 0x6d): // xterm resource modifyOtherKeys
1392 if (argcount == 2 && args[0] == 4)
1393 state->mode.modify_other_keys = args[1] == 2;
1394 break;
1395
1396 case 0x6e: // DSR - ECMA-48 8.3.35
1397 case LEADER('?', 0x6e): // DECDSR
1398 val = CSI_ARG_OR(args[0], 0);
1399
1400 {
1401 char *qmark = (leader_byte == '?') ? "?" : "";
1402
1403 switch(val) {
1404 case 0: case 1: case 2: case 3: case 4:
1405 // ignore - these are replies
1406 break;
1407 case 5:
1408 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
1409 break;
1410 case 6: // CPR - cursor position report
1411 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1);
1412 break;
1413 }
1414 }
1415 break;
1416
1417
1418 case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
1419 vterm_state_reset(state, 0);
1420 break;
1421
1422 case LEADER('?', INTERMED('$', 0x70)):
1423 request_dec_mode(state, CSI_ARG(args[0]));
1424 break;
1425
1426 case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
1427 val = CSI_ARG_OR(args[0], 1);
1428
1429 switch(val) {
1430 case 0: case 1:
1431 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1432 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1433 break;
1434 case 2:
1435 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1436 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1437 break;
1438 case 3:
1439 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1440 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
1441 break;
1442 case 4:
1443 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1444 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
1445 break;
1446 case 5:
1447 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1448 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
1449 break;
1450 case 6:
1451 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1452 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
1453 break;
1454 }
1455
1456 break;
1457
1458 case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
1459 val = CSI_ARG_OR(args[0], 0);
1460
1461 switch(val) {
1462 case 0: case 2:
1463 state->protected_cell = 0;
1464 break;
1465 case 1:
1466 state->protected_cell = 1;
1467 break;
1468 }
1469
1470 break;
1471
1472 case 0x72: // DECSTBM - DEC custom
1473 state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
1474 state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
1475 LBOUND(state->scrollregion_top, 0);
1476 UBOUND(state->scrollregion_top, state->rows);
1477 LBOUND(state->scrollregion_bottom, -1);
1478 if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
1479 state->scrollregion_bottom = -1;
1480 else
1481 UBOUND(state->scrollregion_bottom, state->rows);
1482
1483 if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
1484 // Invalid
1485 state->scrollregion_top = 0;
1486 state->scrollregion_bottom = -1;
1487 }
1488
1489 // Setting the scrolling region restores the cursor to the home position
1490 state->pos.row = 0;
1491 state->pos.col = 0;
1492 if(state->mode.origin) {
1493 state->pos.row += state->scrollregion_top;
1494 state->pos.col += SCROLLREGION_LEFT(state);
1495 }
1496
1497 break;
1498
1499 case 0x73: // DECSLRM - DEC custom
1500 // Always allow setting these margins, just they won't take effect without DECVSSM
1501 state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
1502 state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
1503 LBOUND(state->scrollregion_left, 0);
1504 UBOUND(state->scrollregion_left, state->cols);
1505 LBOUND(state->scrollregion_right, -1);
1506 if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
1507 state->scrollregion_right = -1;
1508 else
1509 UBOUND(state->scrollregion_right, state->cols);
1510
1511 if(state->scrollregion_right > -1 &&
1512 state->scrollregion_right <= state->scrollregion_left) {
1513 // Invalid
1514 state->scrollregion_left = 0;
1515 state->scrollregion_right = -1;
1516 }
1517
1518 // Setting the scrolling region restores the cursor to the home position
1519 state->pos.row = 0;
1520 state->pos.col = 0;
1521 if(state->mode.origin) {
1522 state->pos.row += state->scrollregion_top;
1523 state->pos.col += SCROLLREGION_LEFT(state);
1524 }
1525
1526 break;
1527
1528 case 0x74:
1529 switch(CSI_ARG(args[0])) {
1530 case 8: // CSI 8 ; rows ; cols t set size
1531 if (argcount == 3)
1532 on_resize(CSI_ARG(args[1]), CSI_ARG(args[2]), state);
1533 break;
1534 default:
1535 handled = 0;
1536 break;
1537 }
1538 break;
1539
1540 case INTERMED('\'', 0x7D): // DECIC
1541 count = CSI_ARG_COUNT(args[0]);
1542
1543 if(!is_cursor_in_scrollregion(state))
1544 break;
1545
1546 rect.start_row = state->scrollregion_top;
1547 rect.end_row = SCROLLREGION_BOTTOM(state);
1548 rect.start_col = state->pos.col;
1549 rect.end_col = SCROLLREGION_RIGHT(state);
1550
1551 scroll(state, rect, 0, -count);
1552
1553 break;
1554
1555 case INTERMED('\'', 0x7E): // DECDC
1556 count = CSI_ARG_COUNT(args[0]);
1557
1558 if(!is_cursor_in_scrollregion(state))
1559 break;
1560
1561 rect.start_row = state->scrollregion_top;
1562 rect.end_row = SCROLLREGION_BOTTOM(state);
1563 rect.start_col = state->pos.col;
1564 rect.end_col = SCROLLREGION_RIGHT(state);
1565
1566 scroll(state, rect, 0, count);
1567
1568 break;
1569
1570 default:
1571 handled = 0;
1572 break;
1573 }
1574
1575 if (!handled) {
1576 if(state->fallbacks && state->fallbacks->csi)
1577 if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata))
1578 return 1;
1579
1580 return 0;
1581 }
1582
1583 if(state->mode.origin) {
1584 LBOUND(state->pos.row, state->scrollregion_top);
1585 UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1);
1586 LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
1587 UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);
1588 }
1589 else {
1590 LBOUND(state->pos.row, 0);
1591 UBOUND(state->pos.row, state->rows-1);
1592 LBOUND(state->pos.col, 0);
1593 UBOUND(state->pos.col, THISROWWIDTH(state)-1);
1594 }
1595
1596 updatecursor(state, &oldpos, cancel_phantom);
1597
1598 #ifdef DEBUG
1599 if(state->pos.row < 0 || state->pos.row >= state->rows ||
1600 state->pos.col < 0 || state->pos.col >= state->cols) {
1601 fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
1602 command, state->pos.row, state->pos.col);
1603 abort();
1604 }
1605
1606 if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
1607 fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
1608 command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
1609 abort();
1610 }
1611
1612 if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
1613 fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
1614 command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
1615 abort();
1616 }
1617 #endif
1618
1619 return 1;
1620 }
1621
base64_one(uint8_t b)1622 static char base64_one(uint8_t b)
1623 {
1624 if(b < 26)
1625 return 'A' + b;
1626 else if(b < 52)
1627 return 'a' + b - 26;
1628 else if(b < 62)
1629 return '0' + b - 52;
1630 else if(b == 62)
1631 return '+';
1632 else if(b == 63)
1633 return '/';
1634 return 0;
1635 }
1636
unbase64one(char c)1637 static uint8_t unbase64one(char c)
1638 {
1639 if(c >= 'A' && c <= 'Z')
1640 return c - 'A';
1641 else if(c >= 'a' && c <= 'z')
1642 return c - 'a' + 26;
1643 else if(c >= '0' && c <= '9')
1644 return c - '0' + 52;
1645 else if(c == '+')
1646 return 62;
1647 else if(c == '/')
1648 return 63;
1649
1650 return 0xFF;
1651 }
1652
osc_selection(VTermState * state,VTermStringFragment frag)1653 static void osc_selection(VTermState *state, VTermStringFragment frag)
1654 {
1655 if(frag.initial) {
1656 state->tmp.selection.mask = 0;
1657 state->tmp.selection.state = SELECTION_INITIAL;
1658 }
1659
1660 while(!state->tmp.selection.state && frag.len) {
1661 /* Parse selection parameter */
1662 switch(frag.str[0]) {
1663 case 'c':
1664 state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;
1665 break;
1666 case 'p':
1667 state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;
1668 break;
1669 case 'q':
1670 state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;
1671 break;
1672 case 's':
1673 state->tmp.selection.mask |= VTERM_SELECTION_SELECT;
1674 break;
1675 case '0':
1676 case '1':
1677 case '2':
1678 case '3':
1679 case '4':
1680 case '5':
1681 case '6':
1682 case '7':
1683 state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));
1684 break;
1685
1686 case ';':
1687 state->tmp.selection.state = SELECTION_SELECTED;
1688 if(!state->tmp.selection.mask)
1689 state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;
1690 break;
1691 }
1692
1693 frag.str++;
1694 frag.len--;
1695 }
1696
1697 if(!frag.len)
1698 return;
1699
1700 if(state->tmp.selection.state == SELECTION_SELECTED) {
1701 if(frag.str[0] == '?') {
1702 state->tmp.selection.state = SELECTION_QUERY;
1703 }
1704 else {
1705 state->tmp.selection.state = SELECTION_SET_INITIAL;
1706 state->tmp.selection.recvpartial = 0;
1707 }
1708 }
1709
1710 if(state->tmp.selection.state == SELECTION_QUERY) {
1711 if(state->selection.callbacks->query)
1712 (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);
1713 return;
1714 }
1715
1716 if(state->selection.callbacks->set) {
1717 size_t bufcur = 0;
1718 char *buffer = state->selection.buffer;
1719
1720 uint32_t x = 0; /* Current decoding value */
1721 int n = 0; /* Number of sextets consumed */
1722
1723 if(state->tmp.selection.recvpartial) {
1724 n = state->tmp.selection.recvpartial >> 24;
1725 x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */
1726
1727 state->tmp.selection.recvpartial = 0;
1728 }
1729
1730 while((state->selection.buflen - bufcur) >= 3 && frag.len) {
1731 if(frag.str[0] == '=') {
1732 if(n == 2) {
1733 buffer[0] = (x >> 4) & 0xFF;
1734 buffer += 1, bufcur += 1;
1735 }
1736 if(n == 3) {
1737 buffer[0] = (x >> 10) & 0xFF;
1738 buffer[1] = (x >> 2) & 0xFF;
1739 buffer += 2, bufcur += 2;
1740 }
1741
1742 while(frag.len && frag.str[0] == '=')
1743 frag.str++, frag.len--;
1744
1745 n = 0;
1746 }
1747 else {
1748 uint8_t b = unbase64one(frag.str[0]);
1749 if(b == 0xFF) {
1750 DEBUG_LOG1("base64decode bad input %02X\n", (uint8_t)frag.str[0]);
1751 }
1752 else {
1753 x = (x << 6) | b;
1754 n++;
1755 }
1756 frag.str++, frag.len--;
1757
1758 if(n == 4) {
1759 buffer[0] = (x >> 16) & 0xFF;
1760 buffer[1] = (x >> 8) & 0xFF;
1761 buffer[2] = (x >> 0) & 0xFF;
1762
1763 buffer += 3, bufcur += 3;
1764 x = 0;
1765 n = 0;
1766 }
1767 }
1768
1769 if(!frag.len || (state->selection.buflen - bufcur) < 3) {
1770 if(bufcur) {
1771 VTermStringFragment setfrag = {
1772 state->selection.buffer, // str
1773 bufcur, // len
1774 state->tmp.selection.state == SELECTION_SET_INITIAL, // initial
1775 frag.final // final
1776 };
1777 (*state->selection.callbacks->set)(state->tmp.selection.mask,
1778 setfrag, state->selection.user);
1779 state->tmp.selection.state = SELECTION_SET;
1780 }
1781
1782 buffer = state->selection.buffer;
1783 bufcur = 0;
1784 }
1785 }
1786
1787 if(n)
1788 state->tmp.selection.recvpartial = (n << 24) | x;
1789 }
1790 }
1791
on_osc(int command,VTermStringFragment frag,void * user)1792 static int on_osc(int command, VTermStringFragment frag, void *user)
1793 {
1794 VTermState *state = user;
1795
1796 switch(command) {
1797 case 0:
1798 settermprop_string(state, VTERM_PROP_ICONNAME, frag);
1799 settermprop_string(state, VTERM_PROP_TITLE, frag);
1800 return 1;
1801
1802 case 1:
1803 settermprop_string(state, VTERM_PROP_ICONNAME, frag);
1804 return 1;
1805
1806 case 2:
1807 settermprop_string(state, VTERM_PROP_TITLE, frag);
1808 return 1;
1809
1810 case 10:
1811 {
1812 // request foreground color: <Esc>]10;?<0x07>
1813 int red = state->default_fg.red;
1814 int blue = state->default_fg.blue;
1815 int green = state->default_fg.green;
1816 vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "10;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
1817 return 1;
1818 }
1819
1820 case 11:
1821 {
1822 // request background color: <Esc>]11;?<0x07>
1823 int red = state->default_bg.red;
1824 int blue = state->default_bg.blue;
1825 int green = state->default_bg.green;
1826 vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "11;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
1827 return 1;
1828 }
1829 case 12:
1830 settermprop_string(state, VTERM_PROP_CURSORCOLOR, frag);
1831 return 1;
1832
1833 case 52:
1834 if(state->selection.callbacks)
1835 osc_selection(state, frag);
1836
1837 return 1;
1838
1839 default:
1840 if(state->fallbacks && state->fallbacks->osc)
1841 if((*state->fallbacks->osc)(command, frag, state->fbdata))
1842 return 1;
1843 }
1844
1845 return 0;
1846 }
1847
request_status_string(VTermState * state,VTermStringFragment frag)1848 static void request_status_string(VTermState *state, VTermStringFragment frag)
1849 {
1850 VTerm *vt = state->vt;
1851
1852 char *tmp = state->tmp.decrqss;
1853 size_t i = 0;
1854
1855 if(frag.initial)
1856 tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
1857
1858 while(i < sizeof(state->tmp.decrqss)-1 && tmp[i])
1859 i++;
1860 while(i < sizeof(state->tmp.decrqss)-1 && frag.len--)
1861 tmp[i++] = (frag.str++)[0];
1862 tmp[i] = 0;
1863
1864 if(!frag.final)
1865 return;
1866
1867 switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) {
1868 case 'm': {
1869 // Query SGR
1870 long args[20];
1871 int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
1872 size_t cur = 0;
1873 int argi;
1874
1875 cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
1876 vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ...
1877 if(cur >= vt->tmpbuffer_len)
1878 return;
1879
1880 for(argi = 0; argi < argc; argi++) {
1881 cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
1882 argi == argc - 1 ? "%ld" :
1883 CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" :
1884 "%ld;",
1885 CSI_ARG(args[argi]));
1886 if(cur >= vt->tmpbuffer_len)
1887 return;
1888 }
1889
1890 cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
1891 vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST
1892 if(cur >= vt->tmpbuffer_len)
1893 return;
1894
1895 vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
1896 return;
1897 }
1898
1899 case 'r':
1900 // Query DECSTBM
1901 vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
1902 "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
1903 return;
1904
1905 case 's':
1906 // Query DECSLRM
1907 vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
1908 "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
1909 return;
1910
1911 case ' '|('q'<<8): {
1912 // Query DECSCUSR
1913 int reply;
1914 switch(state->mode.cursor_shape) {
1915 case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break;
1916 case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
1917 default: /* VTERM_PROP_CURSORSHAPE_BAR_LEFT */ reply = 6; break;
1918 }
1919 if(state->mode.cursor_blink)
1920 reply--;
1921 vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
1922 "1$r%d q", reply);
1923 return;
1924 }
1925
1926 case '\"'|('q'<<8):
1927 // Query DECSCA
1928 vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
1929 "1$r%d\"q", state->protected_cell ? 1 : 2);
1930 return;
1931 }
1932
1933 vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, "0$r%s", tmp);
1934 }
1935
on_dcs(const char * command,size_t commandlen,VTermStringFragment frag,void * user)1936 static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
1937 {
1938 VTermState *state = user;
1939
1940 if(commandlen == 2 && strneq(command, "$q", 2)) {
1941 request_status_string(state, frag);
1942 return 1;
1943 }
1944 else if(state->fallbacks && state->fallbacks->dcs)
1945 if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata))
1946 return 1;
1947
1948 DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command);
1949 return 0;
1950 }
1951
on_apc(VTermStringFragment frag,void * user)1952 static int on_apc(VTermStringFragment frag, void *user)
1953 {
1954 VTermState *state = user;
1955
1956 if(state->fallbacks && state->fallbacks->apc)
1957 if((*state->fallbacks->apc)(frag, state->fbdata))
1958 return 1;
1959
1960 /* No DEBUG_LOG because all APCs are unhandled */
1961 return 0;
1962 }
1963
on_pm(VTermStringFragment frag,void * user)1964 static int on_pm(VTermStringFragment frag, void *user)
1965 {
1966 VTermState *state = user;
1967
1968 if(state->fallbacks && state->fallbacks->pm)
1969 if((*state->fallbacks->pm)(frag, state->fbdata))
1970 return 1;
1971
1972 /* No DEBUG_LOG because all PMs are unhandled */
1973 return 0;
1974 }
1975
on_sos(VTermStringFragment frag,void * user)1976 static int on_sos(VTermStringFragment frag, void *user)
1977 {
1978 VTermState *state = user;
1979
1980 if(state->fallbacks && state->fallbacks->sos)
1981 if((*state->fallbacks->sos)(frag, state->fbdata))
1982 return 1;
1983
1984 /* No DEBUG_LOG because all SOSs are unhandled */
1985 return 0;
1986 }
1987
on_resize(int rows,int cols,void * user)1988 static int on_resize(int rows, int cols, void *user)
1989 {
1990 VTermState *state = user;
1991 VTermPos oldpos = state->pos;
1992 VTermStateFields fields;
1993
1994 if(cols != state->cols) {
1995 int col;
1996 unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
1997 if (newtabstops == NULL)
1998 return 0;
1999
2000 /* TODO: This can all be done much more efficiently bytewise */
2001 for(col = 0; col < state->cols && col < cols; col++) {
2002 unsigned char mask = 1 << (col & 7);
2003 if(state->tabstops[col >> 3] & mask)
2004 newtabstops[col >> 3] |= mask;
2005 else
2006 newtabstops[col >> 3] &= ~mask;
2007 }
2008
2009 for( ; col < cols; col++) {
2010 unsigned char mask = 1 << (col & 7);
2011 if(col % 8 == 0)
2012 newtabstops[col >> 3] |= mask;
2013 else
2014 newtabstops[col >> 3] &= ~mask;
2015 }
2016
2017 vterm_allocator_free(state->vt, state->tabstops);
2018 state->tabstops = newtabstops;
2019 }
2020
2021 if(rows != state->rows) {
2022 int bufidx;
2023 for(bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
2024 int row;
2025 VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
2026 VTermLineInfo *newlineinfo;
2027 if(!oldlineinfo)
2028 continue;
2029
2030 newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
2031
2032 for(row = 0; row < state->rows && row < rows; row++) {
2033 newlineinfo[row] = oldlineinfo[row];
2034 }
2035
2036 for( ; row < rows; row++) {
2037 VTermLineInfo lineInfo = {0x0};
2038 newlineinfo[row] = lineInfo;
2039 }
2040
2041 vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
2042 state->lineinfos[bufidx] = newlineinfo;
2043 }
2044
2045 state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
2046 }
2047
2048 state->rows = rows;
2049 state->cols = cols;
2050
2051 if(state->scrollregion_bottom > -1)
2052 UBOUND(state->scrollregion_bottom, state->rows);
2053 if(state->scrollregion_right > -1)
2054 UBOUND(state->scrollregion_right, state->cols);
2055
2056 fields.pos = state->pos;
2057
2058 if(state->callbacks && state->callbacks->resize)
2059 (*state->callbacks->resize)(rows, cols, &fields, state->cbdata);
2060
2061 state->pos = fields.pos;
2062
2063 if(state->at_phantom && state->pos.col < cols-1) {
2064 state->at_phantom = 0;
2065 state->pos.col++;
2066 }
2067
2068 if(state->pos.row < 0)
2069 state->pos.row = 0;
2070 if(state->pos.row >= rows)
2071 state->pos.row = rows - 1;
2072 if(state->pos.col < 0)
2073 state->pos.col = 0;
2074 if(state->pos.col >= cols)
2075 state->pos.col = cols - 1;
2076
2077 updatecursor(state, &oldpos, 1);
2078
2079 return 1;
2080 }
2081
2082 static const VTermParserCallbacks parser_callbacks = {
2083 on_text, // text
2084 on_control, // control
2085 on_escape, // escape
2086 on_csi, // csi
2087 on_osc, // osc
2088 on_dcs, // dcs
2089 on_apc, // apc
2090 on_pm, // pm
2091 on_sos, // sos
2092 on_resize // resize
2093 };
2094
2095 /*
2096 * Return the existing state or create a new one.
2097 * Returns NULL when out of memory.
2098 */
vterm_obtain_state(VTerm * vt)2099 VTermState *vterm_obtain_state(VTerm *vt)
2100 {
2101 VTermState *state;
2102 if(vt->state)
2103 return vt->state;
2104
2105 state = vterm_state_new(vt);
2106 if (state == NULL)
2107 return NULL;
2108 vt->state = state;
2109
2110 vterm_parser_set_callbacks(vt, &parser_callbacks, state);
2111
2112 return state;
2113 }
2114
vterm_state_reset(VTermState * state,int hard)2115 void vterm_state_reset(VTermState *state, int hard)
2116 {
2117 VTermEncoding *default_enc;
2118
2119 state->scrollregion_top = 0;
2120 state->scrollregion_bottom = -1;
2121 state->scrollregion_left = 0;
2122 state->scrollregion_right = -1;
2123
2124 state->mode.keypad = 0;
2125 state->mode.cursor = 0;
2126 state->mode.autowrap = 1;
2127 state->mode.insert = 0;
2128 state->mode.newline = 0;
2129 state->mode.alt_screen = 0;
2130 state->mode.origin = 0;
2131 state->mode.leftrightmargin = 0;
2132 state->mode.bracketpaste = 0;
2133 state->mode.report_focus = 0;
2134
2135 state->mouse_flags = 0;
2136
2137 state->vt->mode.ctrl8bit = 0;
2138
2139 {
2140 int col;
2141 for(col = 0; col < state->cols; col++)
2142 if(col % 8 == 0)
2143 set_col_tabstop(state, col);
2144 else
2145 clear_col_tabstop(state, col);
2146 }
2147
2148 {
2149 int row;
2150 for(row = 0; row < state->rows; row++)
2151 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
2152 }
2153
2154 if(state->callbacks && state->callbacks->initpen)
2155 (*state->callbacks->initpen)(state->cbdata);
2156
2157 vterm_state_resetpen(state);
2158
2159 default_enc = state->vt->mode.utf8 ?
2160 vterm_lookup_encoding(ENC_UTF8, 'u') :
2161 vterm_lookup_encoding(ENC_SINGLE_94, 'B');
2162
2163 {
2164 int i;
2165 for(i = 0; i < 4; i++) {
2166 state->encoding[i].enc = default_enc;
2167 if(default_enc->init)
2168 (*default_enc->init)(default_enc, state->encoding[i].data);
2169 }
2170 }
2171
2172 state->gl_set = 0;
2173 state->gr_set = 1;
2174 state->gsingle_set = 0;
2175
2176 state->protected_cell = 0;
2177
2178 // Initialise the props
2179 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
2180 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
2181 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
2182
2183 if(hard) {
2184 VTermRect rect = { 0, 0, 0, 0 };
2185
2186 state->pos.row = 0;
2187 state->pos.col = 0;
2188 state->at_phantom = 0;
2189
2190 rect.end_row = state->rows;
2191 rect.end_col = state->cols;
2192 erase(state, rect, 0);
2193 }
2194 }
2195
vterm_state_get_cursorpos(const VTermState * state,VTermPos * cursorpos)2196 void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
2197 {
2198 *cursorpos = state->pos;
2199 }
2200
vterm_state_get_mousestate(const VTermState * state,VTermMouseState * mousestate)2201 void vterm_state_get_mousestate(const VTermState *state, VTermMouseState *mousestate)
2202 {
2203 mousestate->pos.col = state->mouse_col;
2204 mousestate->pos.row = state->mouse_row;
2205 mousestate->buttons = state->mouse_buttons;
2206 mousestate->flags = state->mouse_flags;
2207 }
2208
vterm_state_set_callbacks(VTermState * state,const VTermStateCallbacks * callbacks,void * user)2209 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
2210 {
2211 if(callbacks) {
2212 state->callbacks = callbacks;
2213 state->cbdata = user;
2214
2215 if(state->callbacks && state->callbacks->initpen)
2216 (*state->callbacks->initpen)(state->cbdata);
2217 }
2218 else {
2219 state->callbacks = NULL;
2220 state->cbdata = NULL;
2221 }
2222 }
2223
vterm_state_get_cbdata(VTermState * state)2224 void *vterm_state_get_cbdata(VTermState *state)
2225 {
2226 return state->cbdata;
2227 }
2228
vterm_state_set_unrecognised_fallbacks(VTermState * state,const VTermStateFallbacks * fallbacks,void * user)2229 void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user)
2230 {
2231 if(fallbacks) {
2232 state->fallbacks = fallbacks;
2233 state->fbdata = user;
2234 }
2235 else {
2236 state->fallbacks = NULL;
2237 state->fbdata = NULL;
2238 }
2239 }
2240
vterm_state_get_unrecognised_fbdata(VTermState * state)2241 void *vterm_state_get_unrecognised_fbdata(VTermState *state)
2242 {
2243 return state->fbdata;
2244 }
2245
vterm_state_set_termprop(VTermState * state,VTermProp prop,VTermValue * val)2246 int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
2247 {
2248 /* Only store the new value of the property if usercode said it was happy.
2249 * This is especially important for altscreen switching */
2250 if(state->callbacks && state->callbacks->settermprop)
2251 if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))
2252 return 0;
2253
2254 switch(prop) {
2255 case VTERM_PROP_TITLE:
2256 case VTERM_PROP_ICONNAME:
2257 case VTERM_PROP_CURSORCOLOR:
2258 // we don't store these, just transparently pass through
2259 return 1;
2260 case VTERM_PROP_CURSORVISIBLE:
2261 state->mode.cursor_visible = val->boolean;
2262 return 1;
2263 case VTERM_PROP_CURSORBLINK:
2264 state->mode.cursor_blink = val->boolean;
2265 return 1;
2266 case VTERM_PROP_CURSORSHAPE:
2267 state->mode.cursor_shape = val->number;
2268 return 1;
2269 case VTERM_PROP_REVERSE:
2270 state->mode.screen = val->boolean;
2271 return 1;
2272 case VTERM_PROP_ALTSCREEN:
2273 state->mode.alt_screen = val->boolean;
2274 state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
2275 if(state->mode.alt_screen) {
2276 VTermRect rect = {0, 0, 0, 0};
2277 rect.end_row = state->rows;
2278 rect.end_col = state->cols;
2279 erase(state, rect, 0);
2280 }
2281 return 1;
2282 case VTERM_PROP_MOUSE:
2283 state->mouse_flags = 0;
2284 if(val->number)
2285 state->mouse_flags |= MOUSE_WANT_CLICK;
2286 if(val->number == VTERM_PROP_MOUSE_DRAG)
2287 state->mouse_flags |= MOUSE_WANT_DRAG;
2288 if(val->number == VTERM_PROP_MOUSE_MOVE)
2289 state->mouse_flags |= MOUSE_WANT_MOVE;
2290 return 1;
2291
2292 case VTERM_N_PROPS:
2293 return 0;
2294 }
2295
2296 return 0;
2297 }
2298
vterm_state_focus_in(VTermState * state)2299 void vterm_state_focus_in(VTermState *state)
2300 {
2301 if(state->mode.report_focus)
2302 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
2303 }
2304
vterm_state_focus_out(VTermState * state)2305 void vterm_state_focus_out(VTermState *state)
2306 {
2307 if(state->mode.report_focus)
2308 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
2309 }
2310
vterm_state_get_lineinfo(const VTermState * state,int row)2311 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
2312 {
2313 return state->lineinfo + row;
2314 }
2315
vterm_state_set_selection_callbacks(VTermState * state,const VTermSelectionCallbacks * callbacks,void * user,char * buffer,size_t buflen)2316 void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
2317 char *buffer, size_t buflen)
2318 {
2319 if(buflen && !buffer)
2320 buffer = vterm_allocator_malloc(state->vt, buflen);
2321
2322 state->selection.callbacks = callbacks;
2323 state->selection.user = user;
2324 state->selection.buffer = buffer;
2325 state->selection.buflen = buflen;
2326 }
2327
vterm_state_send_selection(VTermState * state,VTermSelectionMask mask,VTermStringFragment frag)2328 void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag)
2329 {
2330 VTerm *vt = state->vt;
2331
2332 if(frag.initial) {
2333 /* TODO: support sending more than one mask bit */
2334 static char selection_chars[] = "cpqs";
2335 int idx;
2336 for(idx = 0; idx < 4; idx++)
2337 if(mask & (1 << idx))
2338 break;
2339
2340 vterm_push_output_sprintf_str(vt, C1_OSC, FALSE, "52;%c;", selection_chars[idx]);
2341
2342 state->tmp.selection.sendpartial = 0;
2343 }
2344
2345 if(frag.len) {
2346 size_t bufcur = 0;
2347 char *buffer = state->selection.buffer;
2348
2349 uint32_t x = 0;
2350 int n = 0;
2351
2352 if(state->tmp.selection.sendpartial) {
2353 n = state->tmp.selection.sendpartial >> 24;
2354 x = state->tmp.selection.sendpartial & 0xFFFFFF;
2355
2356 state->tmp.selection.sendpartial = 0;
2357 }
2358
2359 while((state->selection.buflen - bufcur) >= 4 && frag.len) {
2360 x = (x << 8) | frag.str[0];
2361 n++;
2362 frag.str++, frag.len--;
2363
2364 if(n == 3) {
2365 buffer[0] = base64_one((x >> 18) & 0x3F);
2366 buffer[1] = base64_one((x >> 12) & 0x3F);
2367 buffer[2] = base64_one((x >> 6) & 0x3F);
2368 buffer[3] = base64_one((x >> 0) & 0x3F);
2369
2370 buffer += 4, bufcur += 4;
2371 x = 0;
2372 n = 0;
2373 }
2374
2375 if(!frag.len || (state->selection.buflen - bufcur) < 4) {
2376 if(bufcur)
2377 vterm_push_output_bytes(vt, state->selection.buffer, bufcur);
2378
2379 buffer = state->selection.buffer;
2380 bufcur = 0;
2381 }
2382 }
2383
2384 if(n)
2385 state->tmp.selection.sendpartial = (n << 24) | x;
2386 }
2387
2388 if(frag.final) {
2389 if(state->tmp.selection.sendpartial) {
2390 int n = state->tmp.selection.sendpartial >> 24;
2391 uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF;
2392 char *buffer = state->selection.buffer;
2393
2394 /* n is either 1 or 2 now */
2395 x <<= (n == 1) ? 16 : 8;
2396
2397 buffer[0] = base64_one((x >> 18) & 0x3F);
2398 buffer[1] = base64_one((x >> 12) & 0x3F);
2399 buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F);
2400 buffer[3] = '=';
2401
2402 vterm_push_output_sprintf_str(vt, 0, TRUE, "%.*s", 4, buffer);
2403 }
2404 else
2405 vterm_push_output_sprintf_str(vt, 0, TRUE, "");
2406 }
2407 }
2408