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 /* Some convenient wrappers to make callback functions easier */
13
putglyph(VTermState * state,const uint32_t chars[],int width,VTermPos pos)14 static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
15 {
16 VTermGlyphInfo info = {
17 .chars = chars,
18 .width = width,
19 .protected_cell = state->protected_cell,
20 .dwl = state->lineinfo[pos.row].doublewidth,
21 .dhl = state->lineinfo[pos.row].doubleheight,
22 };
23
24 if(state->callbacks && state->callbacks->putglyph)
25 if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
26 return;
27
28 DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
29 }
30
updatecursor(VTermState * state,VTermPos * oldpos,int cancel_phantom)31 static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
32 {
33 if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
34 return;
35
36 if(cancel_phantom)
37 state->at_phantom = 0;
38
39 if(state->callbacks && state->callbacks->movecursor)
40 if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
41 return;
42 }
43
erase(VTermState * state,VTermRect rect,int selective)44 static void erase(VTermState *state, VTermRect rect, int selective)
45 {
46 if(state->callbacks && state->callbacks->erase)
47 if((*state->callbacks->erase)(rect, selective, state->cbdata))
48 return;
49 }
50
vterm_state_new(VTerm * vt)51 static VTermState *vterm_state_new(VTerm *vt)
52 {
53 VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
54
55 state->vt = vt;
56
57 state->rows = vt->rows;
58 state->cols = vt->cols;
59
60 state->mouse_col = 0;
61 state->mouse_row = 0;
62 state->mouse_buttons = 0;
63
64 state->mouse_protocol = MOUSE_X10;
65
66 state->callbacks = NULL;
67 state->cbdata = NULL;
68
69 vterm_state_newpen(state);
70
71 state->bold_is_highbright = 0;
72
73 return state;
74 }
75
vterm_state_free(VTermState * state)76 INTERNAL void vterm_state_free(VTermState *state)
77 {
78 vterm_allocator_free(state->vt, state->tabstops);
79 vterm_allocator_free(state->vt, state->lineinfo);
80 vterm_allocator_free(state->vt, state->combine_chars);
81 vterm_allocator_free(state->vt, state);
82 }
83
scroll(VTermState * state,VTermRect rect,int downward,int rightward)84 static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
85 {
86 if(!downward && !rightward)
87 return;
88
89 int rows = rect.end_row - rect.start_row;
90 if(downward > rows)
91 downward = rows;
92 else if(downward < -rows)
93 downward = -rows;
94
95 int cols = rect.end_col - rect.start_col;
96 if(rightward > cols)
97 rightward = cols;
98 else if(rightward < -cols)
99 rightward = -cols;
100
101 // Update lineinfo if full line
102 if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
103 int height = rect.end_row - rect.start_row - abs(downward);
104
105 if(downward > 0)
106 memmove(state->lineinfo + rect.start_row,
107 state->lineinfo + rect.start_row + downward,
108 height * sizeof(state->lineinfo[0]));
109 else
110 memmove(state->lineinfo + rect.start_row - downward,
111 state->lineinfo + rect.start_row,
112 height * sizeof(state->lineinfo[0]));
113 }
114
115 if(state->callbacks && state->callbacks->scrollrect)
116 if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
117 return;
118
119 if(state->callbacks)
120 vterm_scroll_rect(rect, downward, rightward,
121 state->callbacks->moverect, state->callbacks->erase, state->cbdata);
122 }
123
linefeed(VTermState * state)124 static void linefeed(VTermState *state)
125 {
126 if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
127 VTermRect rect = {
128 .start_row = state->scrollregion_top,
129 .end_row = SCROLLREGION_BOTTOM(state),
130 .start_col = SCROLLREGION_LEFT(state),
131 .end_col = SCROLLREGION_RIGHT(state),
132 };
133
134 scroll(state, rect, 1, 0);
135 }
136 else if(state->pos.row < state->rows-1)
137 state->pos.row++;
138 }
139
grow_combine_buffer(VTermState * state)140 static void grow_combine_buffer(VTermState *state)
141 {
142 size_t new_size = state->combine_chars_size * 2;
143 uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
144
145 memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
146
147 vterm_allocator_free(state->vt, state->combine_chars);
148
149 state->combine_chars = new_chars;
150 state->combine_chars_size = new_size;
151 }
152
set_col_tabstop(VTermState * state,int col)153 static void set_col_tabstop(VTermState *state, int col)
154 {
155 unsigned char mask = 1 << (col & 7);
156 state->tabstops[col >> 3] |= mask;
157 }
158
clear_col_tabstop(VTermState * state,int col)159 static void clear_col_tabstop(VTermState *state, int col)
160 {
161 unsigned char mask = 1 << (col & 7);
162 state->tabstops[col >> 3] &= ~mask;
163 }
164
is_col_tabstop(VTermState * state,int col)165 static int is_col_tabstop(VTermState *state, int col)
166 {
167 unsigned char mask = 1 << (col & 7);
168 return state->tabstops[col >> 3] & mask;
169 }
170
is_cursor_in_scrollregion(const VTermState * state)171 static int is_cursor_in_scrollregion(const VTermState *state)
172 {
173 if(state->pos.row < state->scrollregion_top ||
174 state->pos.row >= SCROLLREGION_BOTTOM(state))
175 return 0;
176 if(state->pos.col < SCROLLREGION_LEFT(state) ||
177 state->pos.col >= SCROLLREGION_RIGHT(state))
178 return 0;
179
180 return 1;
181 }
182
tab(VTermState * state,int count,int direction)183 static void tab(VTermState *state, int count, int direction)
184 {
185 while(count > 0) {
186 if(direction > 0) {
187 if(state->pos.col >= THISROWWIDTH(state)-1)
188 return;
189
190 state->pos.col++;
191 }
192 else if(direction < 0) {
193 if(state->pos.col < 1)
194 return;
195
196 state->pos.col--;
197 }
198
199 if(is_col_tabstop(state, state->pos.col))
200 count--;
201 }
202 }
203
204 #define NO_FORCE 0
205 #define FORCE 1
206
207 #define DWL_OFF 0
208 #define DWL_ON 1
209
210 #define DHL_OFF 0
211 #define DHL_TOP 1
212 #define DHL_BOTTOM 2
213
set_lineinfo(VTermState * state,int row,int force,int dwl,int dhl)214 static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
215 {
216 VTermLineInfo info = state->lineinfo[row];
217
218 if(dwl == DWL_OFF)
219 info.doublewidth = DWL_OFF;
220 else if(dwl == DWL_ON)
221 info.doublewidth = DWL_ON;
222 // else -1 to ignore
223
224 if(dhl == DHL_OFF)
225 info.doubleheight = DHL_OFF;
226 else if(dhl == DHL_TOP)
227 info.doubleheight = DHL_TOP;
228 else if(dhl == DHL_BOTTOM)
229 info.doubleheight = DHL_BOTTOM;
230
231 if((state->callbacks &&
232 state->callbacks->setlineinfo &&
233 (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
234 || force)
235 state->lineinfo[row] = info;
236 }
237
on_text(const char bytes[],size_t len,void * user)238 static int on_text(const char bytes[], size_t len, void *user)
239 {
240 VTermState *state = user;
241
242 VTermPos oldpos = state->pos;
243
244 // We'll have at most len codepoints
245 uint32_t codepoints[len];
246 int npoints = 0;
247 size_t eaten = 0;
248
249 VTermEncodingInstance *encoding =
250 state->gsingle_set ? &state->encoding[state->gsingle_set] :
251 !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
252 state->vt->mode.utf8 ? &state->encoding_utf8 :
253 &state->encoding[state->gr_set];
254
255 (*encoding->enc->decode)(encoding->enc, encoding->data,
256 codepoints, &npoints, state->gsingle_set ? 1 : len,
257 bytes, &eaten, len);
258
259 /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
260 * for even a single codepoint
261 */
262 if(!npoints)
263 return eaten;
264
265 if(state->gsingle_set && npoints)
266 state->gsingle_set = 0;
267
268 int i = 0;
269
270 /* This is a combining char. that needs to be merged with the previous
271 * glyph output */
272 if(vterm_unicode_is_combining(codepoints[i])) {
273 /* See if the cursor has moved since */
274 if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
275 #ifdef DEBUG_GLYPH_COMBINE
276 int printpos;
277 printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
278 for(printpos = 0; state->combine_chars[printpos]; printpos++)
279 printf("U+%04x ", state->combine_chars[printpos]);
280 printf("} + {");
281 #endif
282
283 /* Find where we need to append these combining chars */
284 int saved_i = 0;
285 while(state->combine_chars[saved_i])
286 saved_i++;
287
288 /* Add extra ones */
289 while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
290 if(saved_i >= state->combine_chars_size)
291 grow_combine_buffer(state);
292 state->combine_chars[saved_i++] = codepoints[i++];
293 }
294 if(saved_i >= state->combine_chars_size)
295 grow_combine_buffer(state);
296 state->combine_chars[saved_i] = 0;
297
298 #ifdef DEBUG_GLYPH_COMBINE
299 for(; state->combine_chars[printpos]; printpos++)
300 printf("U+%04x ", state->combine_chars[printpos]);
301 printf("}\n");
302 #endif
303
304 /* Now render it */
305 putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
306 }
307 else {
308 DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
309 }
310 }
311
312 for(; i < npoints; i++) {
313 // Try to find combining characters following this
314 int glyph_starts = i;
315 int glyph_ends;
316 for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
317 if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
318 break;
319
320 int width = 0;
321
322 uint32_t chars[glyph_ends - glyph_starts + 1];
323
324 for( ; i < glyph_ends; i++) {
325 chars[i - glyph_starts] = codepoints[i];
326 int this_width = vterm_unicode_width(codepoints[i]);
327 #ifdef DEBUG
328 if(this_width < 0) {
329 fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]);
330 abort();
331 }
332 #endif
333 width += this_width;
334 }
335
336 chars[glyph_ends - glyph_starts] = 0;
337 i--;
338
339 #ifdef DEBUG_GLYPH_COMBINE
340 int printpos;
341 printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
342 for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
343 printf("U+%04x ", chars[printpos]);
344 printf("}, onscreen width %d\n", width);
345 #endif
346
347 if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
348 linefeed(state);
349 state->pos.col = 0;
350 state->at_phantom = 0;
351 }
352
353 if(state->mode.insert) {
354 /* TODO: This will be a little inefficient for large bodies of text, as
355 * it'll have to 'ICH' effectively before every glyph. We should scan
356 * ahead and ICH as many times as required
357 */
358 VTermRect rect = {
359 .start_row = state->pos.row,
360 .end_row = state->pos.row + 1,
361 .start_col = state->pos.col,
362 .end_col = THISROWWIDTH(state),
363 };
364 scroll(state, rect, 0, -1);
365 }
366
367 putglyph(state, chars, width, state->pos);
368
369 if(i == npoints - 1) {
370 /* End of the buffer. Save the chars in case we have to combine with
371 * more on the next call */
372 int save_i;
373 for(save_i = 0; chars[save_i]; save_i++) {
374 if(save_i >= state->combine_chars_size)
375 grow_combine_buffer(state);
376 state->combine_chars[save_i] = chars[save_i];
377 }
378 if(save_i >= state->combine_chars_size)
379 grow_combine_buffer(state);
380 state->combine_chars[save_i] = 0;
381 state->combine_width = width;
382 state->combine_pos = state->pos;
383 }
384
385 if(state->pos.col + width >= THISROWWIDTH(state)) {
386 if(state->mode.autowrap)
387 state->at_phantom = 1;
388 }
389 else {
390 state->pos.col += width;
391 }
392 }
393
394 updatecursor(state, &oldpos, 0);
395
396 #ifdef DEBUG
397 if(state->pos.row < 0 || state->pos.row >= state->rows ||
398 state->pos.col < 0 || state->pos.col >= state->cols) {
399 fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
400 state->pos.row, state->pos.col);
401 abort();
402 }
403 #endif
404
405 return eaten;
406 }
407
on_control(unsigned char control,void * user)408 static int on_control(unsigned char control, void *user)
409 {
410 VTermState *state = user;
411
412 VTermPos oldpos = state->pos;
413
414 switch(control) {
415 case 0x07: // BEL - ECMA-48 8.3.3
416 if(state->callbacks && state->callbacks->bell)
417 (*state->callbacks->bell)(state->cbdata);
418 break;
419
420 case 0x08: // BS - ECMA-48 8.3.5
421 if(state->pos.col > 0)
422 state->pos.col--;
423 break;
424
425 case 0x09: // HT - ECMA-48 8.3.60
426 tab(state, 1, +1);
427 break;
428
429 case 0x0a: // LF - ECMA-48 8.3.74
430 case 0x0b: // VT
431 case 0x0c: // FF
432 linefeed(state);
433 if(state->mode.newline)
434 state->pos.col = 0;
435 break;
436
437 case 0x0d: // CR - ECMA-48 8.3.15
438 state->pos.col = 0;
439 break;
440
441 case 0x0e: // LS1 - ECMA-48 8.3.76
442 state->gl_set = 1;
443 break;
444
445 case 0x0f: // LS0 - ECMA-48 8.3.75
446 state->gl_set = 0;
447 break;
448
449 case 0x84: // IND - DEPRECATED but implemented for completeness
450 linefeed(state);
451 break;
452
453 case 0x85: // NEL - ECMA-48 8.3.86
454 linefeed(state);
455 state->pos.col = 0;
456 break;
457
458 case 0x88: // HTS - ECMA-48 8.3.62
459 set_col_tabstop(state, state->pos.col);
460 break;
461
462 case 0x8d: // RI - ECMA-48 8.3.104
463 if(state->pos.row == state->scrollregion_top) {
464 VTermRect rect = {
465 .start_row = state->scrollregion_top,
466 .end_row = SCROLLREGION_BOTTOM(state),
467 .start_col = SCROLLREGION_LEFT(state),
468 .end_col = SCROLLREGION_RIGHT(state),
469 };
470
471 scroll(state, rect, -1, 0);
472 }
473 else if(state->pos.row > 0)
474 state->pos.row--;
475 break;
476
477 case 0x8e: // SS2 - ECMA-48 8.3.141
478 state->gsingle_set = 2;
479 break;
480
481 case 0x8f: // SS3 - ECMA-48 8.3.142
482 state->gsingle_set = 3;
483 break;
484
485 default:
486 if(state->fallbacks && state->fallbacks->control)
487 if((*state->fallbacks->control)(control, state->fbdata))
488 return 1;
489
490 return 0;
491 }
492
493 updatecursor(state, &oldpos, 1);
494
495 #ifdef DEBUG
496 if(state->pos.row < 0 || state->pos.row >= state->rows ||
497 state->pos.col < 0 || state->pos.col >= state->cols) {
498 fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
499 control, state->pos.row, state->pos.col);
500 abort();
501 }
502 #endif
503
504 return 1;
505 }
506
settermprop_bool(VTermState * state,VTermProp prop,int v)507 static int settermprop_bool(VTermState *state, VTermProp prop, int v)
508 {
509 VTermValue val = { .boolean = v };
510 return vterm_state_set_termprop(state, prop, &val);
511 }
512
settermprop_int(VTermState * state,VTermProp prop,int v)513 static int settermprop_int(VTermState *state, VTermProp prop, int v)
514 {
515 VTermValue val = { .number = v };
516 return vterm_state_set_termprop(state, prop, &val);
517 }
518
settermprop_string(VTermState * state,VTermProp prop,const char * str,size_t len)519 static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len)
520 {
521 char strvalue[len+1];
522 strncpy(strvalue, str, len);
523 strvalue[len] = 0;
524
525 VTermValue val = { .string = strvalue };
526 return vterm_state_set_termprop(state, prop, &val);
527 }
528
savecursor(VTermState * state,int save)529 static void savecursor(VTermState *state, int save)
530 {
531 if(save) {
532 state->saved.pos = state->pos;
533 state->saved.mode.cursor_visible = state->mode.cursor_visible;
534 state->saved.mode.cursor_blink = state->mode.cursor_blink;
535 state->saved.mode.cursor_shape = state->mode.cursor_shape;
536
537 vterm_state_savepen(state, 1);
538 }
539 else {
540 VTermPos oldpos = state->pos;
541
542 state->pos = state->saved.pos;
543
544 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
545 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink);
546 settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape);
547
548 vterm_state_savepen(state, 0);
549
550 updatecursor(state, &oldpos, 1);
551 }
552 }
553
on_escape(const char * bytes,size_t len,void * user)554 static int on_escape(const char *bytes, size_t len, void *user)
555 {
556 VTermState *state = user;
557
558 /* Easier to decode this from the first byte, even though the final
559 * byte terminates it
560 */
561 switch(bytes[0]) {
562 case ' ':
563 if(len != 2)
564 return 0;
565
566 switch(bytes[1]) {
567 case 'F': // S7C1T
568 state->vt->mode.ctrl8bit = 0;
569 break;
570
571 case 'G': // S8C1T
572 state->vt->mode.ctrl8bit = 1;
573 break;
574
575 default:
576 return 0;
577 }
578 return 2;
579
580 case '#':
581 if(len != 2)
582 return 0;
583
584 switch(bytes[1]) {
585 case '3': // DECDHL top
586 if(state->mode.leftrightmargin)
587 break;
588 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
589 break;
590
591 case '4': // DECDHL bottom
592 if(state->mode.leftrightmargin)
593 break;
594 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
595 break;
596
597 case '5': // DECSWL
598 if(state->mode.leftrightmargin)
599 break;
600 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
601 break;
602
603 case '6': // DECDWL
604 if(state->mode.leftrightmargin)
605 break;
606 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
607 break;
608
609 case '8': // DECALN
610 {
611 VTermPos pos;
612 uint32_t E[] = { 'E', 0 };
613 for(pos.row = 0; pos.row < state->rows; pos.row++)
614 for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
615 putglyph(state, E, 1, pos);
616 break;
617 }
618
619 default:
620 return 0;
621 }
622 return 2;
623
624 case '(': case ')': case '*': case '+': // SCS
625 if(len != 2)
626 return 0;
627
628 {
629 int setnum = bytes[0] - 0x28;
630 VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
631
632 if(newenc) {
633 state->encoding[setnum].enc = newenc;
634
635 if(newenc->init)
636 (*newenc->init)(newenc, state->encoding[setnum].data);
637 }
638 }
639
640 return 2;
641
642 case '7': // DECSC
643 savecursor(state, 1);
644 return 1;
645
646 case '8': // DECRC
647 savecursor(state, 0);
648 return 1;
649
650 case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
651 return 1;
652
653 case '=': // DECKPAM
654 state->mode.keypad = 1;
655 return 1;
656
657 case '>': // DECKPNM
658 state->mode.keypad = 0;
659 return 1;
660
661 case 'c': // RIS - ECMA-48 8.3.105
662 {
663 VTermPos oldpos = state->pos;
664 vterm_state_reset(state, 1);
665 if(state->callbacks && state->callbacks->movecursor)
666 (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
667 return 1;
668 }
669
670 case 'n': // LS2 - ECMA-48 8.3.78
671 state->gl_set = 2;
672 return 1;
673
674 case 'o': // LS3 - ECMA-48 8.3.80
675 state->gl_set = 3;
676 return 1;
677
678 case '~': // LS1R - ECMA-48 8.3.77
679 state->gr_set = 1;
680 return 1;
681
682 case '}': // LS2R - ECMA-48 8.3.79
683 state->gr_set = 2;
684 return 1;
685
686 case '|': // LS3R - ECMA-48 8.3.81
687 state->gr_set = 3;
688 return 1;
689
690 default:
691 return 0;
692 }
693 }
694
set_mode(VTermState * state,int num,int val)695 static void set_mode(VTermState *state, int num, int val)
696 {
697 switch(num) {
698 case 4: // IRM - ECMA-48 7.2.10
699 state->mode.insert = val;
700 break;
701
702 case 20: // LNM - ANSI X3.4-1977
703 state->mode.newline = val;
704 break;
705
706 default:
707 DEBUG_LOG("libvterm: Unknown mode %d\n", num);
708 return;
709 }
710 }
711
set_dec_mode(VTermState * state,int num,int val)712 static void set_dec_mode(VTermState *state, int num, int val)
713 {
714 switch(num) {
715 case 1:
716 state->mode.cursor = val;
717 break;
718
719 case 5: // DECSCNM - screen mode
720 settermprop_bool(state, VTERM_PROP_REVERSE, val);
721 break;
722
723 case 6: // DECOM - origin mode
724 {
725 VTermPos oldpos = state->pos;
726 state->mode.origin = val;
727 state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
728 state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
729 updatecursor(state, &oldpos, 1);
730 }
731 break;
732
733 case 7:
734 state->mode.autowrap = val;
735 break;
736
737 case 12:
738 settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
739 break;
740
741 case 25:
742 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
743 break;
744
745 case 69: // DECVSSM - vertical split screen mode
746 // DECLRMM - left/right margin mode
747 state->mode.leftrightmargin = val;
748 if(val) {
749 // Setting DECVSSM must clear doublewidth/doubleheight state of every line
750 for(int row = 0; row < state->rows; row++)
751 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
752 }
753
754 break;
755
756 case 1000:
757 case 1002:
758 case 1003:
759 settermprop_int(state, VTERM_PROP_MOUSE,
760 !val ? VTERM_PROP_MOUSE_NONE :
761 (num == 1000) ? VTERM_PROP_MOUSE_CLICK :
762 (num == 1002) ? VTERM_PROP_MOUSE_DRAG :
763 VTERM_PROP_MOUSE_MOVE);
764 break;
765
766 case 1004:
767 state->mode.report_focus = val;
768 break;
769
770 case 1005:
771 state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
772 break;
773
774 case 1006:
775 state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
776 break;
777
778 case 1015:
779 state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
780 break;
781
782 case 1047:
783 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
784 break;
785
786 case 1048:
787 savecursor(state, val);
788 break;
789
790 case 1049:
791 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
792 savecursor(state, val);
793 break;
794
795 case 2004:
796 state->mode.bracketpaste = val;
797 break;
798
799 default:
800 DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num);
801 return;
802 }
803 }
804
request_dec_mode(VTermState * state,int num)805 static void request_dec_mode(VTermState *state, int num)
806 {
807 int reply;
808
809 switch(num) {
810 case 1:
811 reply = state->mode.cursor;
812 break;
813
814 case 5:
815 reply = state->mode.screen;
816 break;
817
818 case 6:
819 reply = state->mode.origin;
820 break;
821
822 case 7:
823 reply = state->mode.autowrap;
824 break;
825
826 case 12:
827 reply = state->mode.cursor_blink;
828 break;
829
830 case 25:
831 reply = state->mode.cursor_visible;
832 break;
833
834 case 69:
835 reply = state->mode.leftrightmargin;
836 break;
837
838 case 1000:
839 reply = state->mouse_flags == MOUSE_WANT_CLICK;
840 break;
841
842 case 1002:
843 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
844 break;
845
846 case 1003:
847 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
848 break;
849
850 case 1004:
851 reply = state->mode.report_focus;
852 break;
853
854 case 1005:
855 reply = state->mouse_protocol == MOUSE_UTF8;
856 break;
857
858 case 1006:
859 reply = state->mouse_protocol == MOUSE_SGR;
860 break;
861
862 case 1015:
863 reply = state->mouse_protocol == MOUSE_RXVT;
864 break;
865
866 case 1047:
867 reply = state->mode.alt_screen;
868 break;
869
870 case 2004:
871 reply = state->mode.bracketpaste;
872 break;
873
874 default:
875 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
876 return;
877 }
878
879 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
880 }
881
on_csi(const char * leader,const long args[],int argcount,const char * intermed,char command,void * user)882 static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
883 {
884 VTermState *state = user;
885 int leader_byte = 0;
886 int intermed_byte = 0;
887 int cancel_phantom = 1;
888
889 if(leader && leader[0]) {
890 if(leader[1]) // longer than 1 char
891 return 0;
892
893 switch(leader[0]) {
894 case '?':
895 case '>':
896 leader_byte = leader[0];
897 break;
898 default:
899 return 0;
900 }
901 }
902
903 if(intermed && intermed[0]) {
904 if(intermed[1]) // longer than 1 char
905 return 0;
906
907 switch(intermed[0]) {
908 case ' ':
909 case '"':
910 case '$':
911 case '\'':
912 intermed_byte = intermed[0];
913 break;
914 default:
915 return 0;
916 }
917 }
918
919 VTermPos oldpos = state->pos;
920
921 // Some temporaries for later code
922 int count, val;
923 int row, col;
924 VTermRect rect;
925 int selective;
926
927 #define LBOUND(v,min) if((v) < (min)) (v) = (min)
928 #define UBOUND(v,max) if((v) > (max)) (v) = (max)
929
930 #define LEADER(l,b) ((l << 8) | b)
931 #define INTERMED(i,b) ((i << 16) | b)
932
933 switch(intermed_byte << 16 | leader_byte << 8 | command) {
934 case 0x40: // ICH - ECMA-48 8.3.64
935 count = CSI_ARG_COUNT(args[0]);
936
937 if(!is_cursor_in_scrollregion(state))
938 break;
939
940 rect.start_row = state->pos.row;
941 rect.end_row = state->pos.row + 1;
942 rect.start_col = state->pos.col;
943 if(state->mode.leftrightmargin)
944 rect.end_col = SCROLLREGION_RIGHT(state);
945 else
946 rect.end_col = THISROWWIDTH(state);
947
948 scroll(state, rect, 0, -count);
949
950 break;
951
952 case 0x41: // CUU - ECMA-48 8.3.22
953 count = CSI_ARG_COUNT(args[0]);
954 state->pos.row -= count;
955 state->at_phantom = 0;
956 break;
957
958 case 0x42: // CUD - ECMA-48 8.3.19
959 count = CSI_ARG_COUNT(args[0]);
960 state->pos.row += count;
961 state->at_phantom = 0;
962 break;
963
964 case 0x43: // CUF - ECMA-48 8.3.20
965 count = CSI_ARG_COUNT(args[0]);
966 state->pos.col += count;
967 state->at_phantom = 0;
968 break;
969
970 case 0x44: // CUB - ECMA-48 8.3.18
971 count = CSI_ARG_COUNT(args[0]);
972 state->pos.col -= count;
973 state->at_phantom = 0;
974 break;
975
976 case 0x45: // CNL - ECMA-48 8.3.12
977 count = CSI_ARG_COUNT(args[0]);
978 state->pos.col = 0;
979 state->pos.row += count;
980 state->at_phantom = 0;
981 break;
982
983 case 0x46: // CPL - ECMA-48 8.3.13
984 count = CSI_ARG_COUNT(args[0]);
985 state->pos.col = 0;
986 state->pos.row -= count;
987 state->at_phantom = 0;
988 break;
989
990 case 0x47: // CHA - ECMA-48 8.3.9
991 val = CSI_ARG_OR(args[0], 1);
992 state->pos.col = val-1;
993 state->at_phantom = 0;
994 break;
995
996 case 0x48: // CUP - ECMA-48 8.3.21
997 row = CSI_ARG_OR(args[0], 1);
998 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
999 // zero-based
1000 state->pos.row = row-1;
1001 state->pos.col = col-1;
1002 if(state->mode.origin) {
1003 state->pos.row += state->scrollregion_top;
1004 state->pos.col += SCROLLREGION_LEFT(state);
1005 }
1006 state->at_phantom = 0;
1007 break;
1008
1009 case 0x49: // CHT - ECMA-48 8.3.10
1010 count = CSI_ARG_COUNT(args[0]);
1011 tab(state, count, +1);
1012 break;
1013
1014 case 0x4a: // ED - ECMA-48 8.3.39
1015 case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
1016 selective = (leader_byte == '?');
1017 switch(CSI_ARG(args[0])) {
1018 case CSI_ARG_MISSING:
1019 case 0:
1020 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
1021 rect.start_col = state->pos.col; rect.end_col = state->cols;
1022 if(rect.end_col > rect.start_col)
1023 erase(state, rect, selective);
1024
1025 rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
1026 rect.start_col = 0;
1027 for(int row = rect.start_row; row < rect.end_row; row++)
1028 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1029 if(rect.end_row > rect.start_row)
1030 erase(state, rect, selective);
1031 break;
1032
1033 case 1:
1034 rect.start_row = 0; rect.end_row = state->pos.row;
1035 rect.start_col = 0; rect.end_col = state->cols;
1036 for(int row = rect.start_row; row < rect.end_row; row++)
1037 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1038 if(rect.end_col > rect.start_col)
1039 erase(state, rect, selective);
1040
1041 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
1042 rect.end_col = state->pos.col + 1;
1043 if(rect.end_row > rect.start_row)
1044 erase(state, rect, selective);
1045 break;
1046
1047 case 2:
1048 rect.start_row = 0; rect.end_row = state->rows;
1049 rect.start_col = 0; rect.end_col = state->cols;
1050 for(int row = rect.start_row; row < rect.end_row; row++)
1051 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1052 erase(state, rect, selective);
1053 break;
1054 }
1055 break;
1056
1057 case 0x4b: // EL - ECMA-48 8.3.41
1058 case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
1059 selective = (leader_byte == '?');
1060 rect.start_row = state->pos.row;
1061 rect.end_row = state->pos.row + 1;
1062
1063 switch(CSI_ARG(args[0])) {
1064 case CSI_ARG_MISSING:
1065 case 0:
1066 rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
1067 case 1:
1068 rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
1069 case 2:
1070 rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
1071 default:
1072 return 0;
1073 }
1074
1075 if(rect.end_col > rect.start_col)
1076 erase(state, rect, selective);
1077
1078 break;
1079
1080 case 0x4c: // IL - ECMA-48 8.3.67
1081 count = CSI_ARG_COUNT(args[0]);
1082
1083 if(!is_cursor_in_scrollregion(state))
1084 break;
1085
1086 rect.start_row = state->pos.row;
1087 rect.end_row = SCROLLREGION_BOTTOM(state);
1088 rect.start_col = SCROLLREGION_LEFT(state);
1089 rect.end_col = SCROLLREGION_RIGHT(state);
1090
1091 scroll(state, rect, -count, 0);
1092
1093 break;
1094
1095 case 0x4d: // DL - ECMA-48 8.3.32
1096 count = CSI_ARG_COUNT(args[0]);
1097
1098 if(!is_cursor_in_scrollregion(state))
1099 break;
1100
1101 rect.start_row = state->pos.row;
1102 rect.end_row = SCROLLREGION_BOTTOM(state);
1103 rect.start_col = SCROLLREGION_LEFT(state);
1104 rect.end_col = SCROLLREGION_RIGHT(state);
1105
1106 scroll(state, rect, count, 0);
1107
1108 break;
1109
1110 case 0x50: // DCH - ECMA-48 8.3.26
1111 count = CSI_ARG_COUNT(args[0]);
1112
1113 if(!is_cursor_in_scrollregion(state))
1114 break;
1115
1116 rect.start_row = state->pos.row;
1117 rect.end_row = state->pos.row + 1;
1118 rect.start_col = state->pos.col;
1119 if(state->mode.leftrightmargin)
1120 rect.end_col = SCROLLREGION_RIGHT(state);
1121 else
1122 rect.end_col = THISROWWIDTH(state);
1123
1124 scroll(state, rect, 0, count);
1125
1126 break;
1127
1128 case 0x53: // SU - ECMA-48 8.3.147
1129 count = CSI_ARG_COUNT(args[0]);
1130
1131 rect.start_row = state->scrollregion_top;
1132 rect.end_row = SCROLLREGION_BOTTOM(state);
1133 rect.start_col = SCROLLREGION_LEFT(state);
1134 rect.end_col = SCROLLREGION_RIGHT(state);
1135
1136 scroll(state, rect, count, 0);
1137
1138 break;
1139
1140 case 0x54: // SD - ECMA-48 8.3.113
1141 count = CSI_ARG_COUNT(args[0]);
1142
1143 rect.start_row = state->scrollregion_top;
1144 rect.end_row = SCROLLREGION_BOTTOM(state);
1145 rect.start_col = SCROLLREGION_LEFT(state);
1146 rect.end_col = SCROLLREGION_RIGHT(state);
1147
1148 scroll(state, rect, -count, 0);
1149
1150 break;
1151
1152 case 0x58: // ECH - ECMA-48 8.3.38
1153 count = CSI_ARG_COUNT(args[0]);
1154
1155 rect.start_row = state->pos.row;
1156 rect.end_row = state->pos.row + 1;
1157 rect.start_col = state->pos.col;
1158 rect.end_col = state->pos.col + count;
1159 UBOUND(rect.end_col, THISROWWIDTH(state));
1160
1161 erase(state, rect, 0);
1162 break;
1163
1164 case 0x5a: // CBT - ECMA-48 8.3.7
1165 count = CSI_ARG_COUNT(args[0]);
1166 tab(state, count, -1);
1167 break;
1168
1169 case 0x60: // HPA - ECMA-48 8.3.57
1170 col = CSI_ARG_OR(args[0], 1);
1171 state->pos.col = col-1;
1172 state->at_phantom = 0;
1173 break;
1174
1175 case 0x61: // HPR - ECMA-48 8.3.59
1176 count = CSI_ARG_COUNT(args[0]);
1177 state->pos.col += count;
1178 state->at_phantom = 0;
1179 break;
1180
1181 case 0x62: { // REP - ECMA-48 8.3.103
1182 const int row_width = THISROWWIDTH(state);
1183 count = CSI_ARG_COUNT(args[0]);
1184 col = state->pos.col + count;
1185 UBOUND(col, row_width);
1186 while (state->pos.col < col) {
1187 putglyph(state, state->combine_chars, state->combine_width, state->pos);
1188 state->pos.col += state->combine_width;
1189 }
1190 if (state->pos.col + state->combine_width >= row_width) {
1191 if (state->mode.autowrap) {
1192 state->at_phantom = 1;
1193 cancel_phantom = 0;
1194 }
1195 }
1196 break;
1197 }
1198
1199 case 0x63: // DA - ECMA-48 8.3.24
1200 val = CSI_ARG_OR(args[0], 0);
1201 if(val == 0)
1202 // DEC VT100 response
1203 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c");
1204 break;
1205
1206 case LEADER('>', 0x63): // DEC secondary Device Attributes
1207 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
1208 break;
1209
1210 case 0x64: // VPA - ECMA-48 8.3.158
1211 row = CSI_ARG_OR(args[0], 1);
1212 state->pos.row = row-1;
1213 if(state->mode.origin)
1214 state->pos.row += state->scrollregion_top;
1215 state->at_phantom = 0;
1216 break;
1217
1218 case 0x65: // VPR - ECMA-48 8.3.160
1219 count = CSI_ARG_COUNT(args[0]);
1220 state->pos.row += count;
1221 state->at_phantom = 0;
1222 break;
1223
1224 case 0x66: // HVP - ECMA-48 8.3.63
1225 row = CSI_ARG_OR(args[0], 1);
1226 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
1227 // zero-based
1228 state->pos.row = row-1;
1229 state->pos.col = col-1;
1230 if(state->mode.origin) {
1231 state->pos.row += state->scrollregion_top;
1232 state->pos.col += SCROLLREGION_LEFT(state);
1233 }
1234 state->at_phantom = 0;
1235 break;
1236
1237 case 0x67: // TBC - ECMA-48 8.3.154
1238 val = CSI_ARG_OR(args[0], 0);
1239
1240 switch(val) {
1241 case 0:
1242 clear_col_tabstop(state, state->pos.col);
1243 break;
1244 case 3:
1245 case 5:
1246 for(col = 0; col < state->cols; col++)
1247 clear_col_tabstop(state, col);
1248 break;
1249 case 1:
1250 case 2:
1251 case 4:
1252 break;
1253 /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
1254 default:
1255 return 0;
1256 }
1257 break;
1258
1259 case 0x68: // SM - ECMA-48 8.3.125
1260 if(!CSI_ARG_IS_MISSING(args[0]))
1261 set_mode(state, CSI_ARG(args[0]), 1);
1262 break;
1263
1264 case LEADER('?', 0x68): // DEC private mode set
1265 if(!CSI_ARG_IS_MISSING(args[0]))
1266 set_dec_mode(state, CSI_ARG(args[0]), 1);
1267 break;
1268
1269 case 0x6a: // HPB - ECMA-48 8.3.58
1270 count = CSI_ARG_COUNT(args[0]);
1271 state->pos.col -= count;
1272 state->at_phantom = 0;
1273 break;
1274
1275 case 0x6b: // VPB - ECMA-48 8.3.159
1276 count = CSI_ARG_COUNT(args[0]);
1277 state->pos.row -= count;
1278 state->at_phantom = 0;
1279 break;
1280
1281 case 0x6c: // RM - ECMA-48 8.3.106
1282 if(!CSI_ARG_IS_MISSING(args[0]))
1283 set_mode(state, CSI_ARG(args[0]), 0);
1284 break;
1285
1286 case LEADER('?', 0x6c): // DEC private mode reset
1287 if(!CSI_ARG_IS_MISSING(args[0]))
1288 set_dec_mode(state, CSI_ARG(args[0]), 0);
1289 break;
1290
1291 case 0x6d: // SGR - ECMA-48 8.3.117
1292 vterm_state_setpen(state, args, argcount);
1293 break;
1294
1295 case 0x6e: // DSR - ECMA-48 8.3.35
1296 case LEADER('?', 0x6e): // DECDSR
1297 val = CSI_ARG_OR(args[0], 0);
1298
1299 {
1300 char *qmark = (leader_byte == '?') ? "?" : "";
1301
1302 switch(val) {
1303 case 0: case 1: case 2: case 3: case 4:
1304 // ignore - these are replies
1305 break;
1306 case 5:
1307 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
1308 break;
1309 case 6: // CPR - cursor position report
1310 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1);
1311 break;
1312 }
1313 }
1314 break;
1315
1316
1317 case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
1318 vterm_state_reset(state, 0);
1319 break;
1320
1321 case LEADER('?', INTERMED('$', 0x70)):
1322 request_dec_mode(state, CSI_ARG(args[0]));
1323 break;
1324
1325 case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
1326 val = CSI_ARG_OR(args[0], 1);
1327
1328 switch(val) {
1329 case 0: case 1:
1330 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1331 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1332 break;
1333 case 2:
1334 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1335 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1336 break;
1337 case 3:
1338 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1339 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
1340 break;
1341 case 4:
1342 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1343 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
1344 break;
1345 case 5:
1346 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1347 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
1348 break;
1349 case 6:
1350 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1351 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
1352 break;
1353 }
1354
1355 break;
1356
1357 case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
1358 val = CSI_ARG_OR(args[0], 0);
1359
1360 switch(val) {
1361 case 0: case 2:
1362 state->protected_cell = 0;
1363 break;
1364 case 1:
1365 state->protected_cell = 1;
1366 break;
1367 }
1368
1369 break;
1370
1371 case 0x72: // DECSTBM - DEC custom
1372 state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
1373 state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
1374 LBOUND(state->scrollregion_top, 0);
1375 UBOUND(state->scrollregion_top, state->rows);
1376 LBOUND(state->scrollregion_bottom, -1);
1377 if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
1378 state->scrollregion_bottom = -1;
1379 else
1380 UBOUND(state->scrollregion_bottom, state->rows);
1381
1382 if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
1383 // Invalid
1384 state->scrollregion_top = 0;
1385 state->scrollregion_bottom = -1;
1386 }
1387
1388 break;
1389
1390 case 0x73: // DECSLRM - DEC custom
1391 // Always allow setting these margins, just they won't take effect without DECVSSM
1392 state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
1393 state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
1394 LBOUND(state->scrollregion_left, 0);
1395 UBOUND(state->scrollregion_left, state->cols);
1396 LBOUND(state->scrollregion_right, -1);
1397 if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
1398 state->scrollregion_right = -1;
1399 else
1400 UBOUND(state->scrollregion_right, state->cols);
1401
1402 if(state->scrollregion_right > -1 &&
1403 state->scrollregion_right <= state->scrollregion_left) {
1404 // Invalid
1405 state->scrollregion_left = 0;
1406 state->scrollregion_right = -1;
1407 }
1408
1409 break;
1410
1411 case INTERMED('\'', 0x7D): // DECIC
1412 count = CSI_ARG_COUNT(args[0]);
1413
1414 if(!is_cursor_in_scrollregion(state))
1415 break;
1416
1417 rect.start_row = state->scrollregion_top;
1418 rect.end_row = SCROLLREGION_BOTTOM(state);
1419 rect.start_col = state->pos.col;
1420 rect.end_col = SCROLLREGION_RIGHT(state);
1421
1422 scroll(state, rect, 0, -count);
1423
1424 break;
1425
1426 case INTERMED('\'', 0x7E): // DECDC
1427 count = CSI_ARG_COUNT(args[0]);
1428
1429 if(!is_cursor_in_scrollregion(state))
1430 break;
1431
1432 rect.start_row = state->scrollregion_top;
1433 rect.end_row = SCROLLREGION_BOTTOM(state);
1434 rect.start_col = state->pos.col;
1435 rect.end_col = SCROLLREGION_RIGHT(state);
1436
1437 scroll(state, rect, 0, count);
1438
1439 break;
1440
1441 default:
1442 if(state->fallbacks && state->fallbacks->csi)
1443 if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata))
1444 return 1;
1445
1446 return 0;
1447 }
1448
1449 if(state->mode.origin) {
1450 LBOUND(state->pos.row, state->scrollregion_top);
1451 UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1);
1452 LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
1453 UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);
1454 }
1455 else {
1456 LBOUND(state->pos.row, 0);
1457 UBOUND(state->pos.row, state->rows-1);
1458 LBOUND(state->pos.col, 0);
1459 UBOUND(state->pos.col, THISROWWIDTH(state)-1);
1460 }
1461
1462 updatecursor(state, &oldpos, cancel_phantom);
1463
1464 #ifdef DEBUG
1465 if(state->pos.row < 0 || state->pos.row >= state->rows ||
1466 state->pos.col < 0 || state->pos.col >= state->cols) {
1467 fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
1468 command, state->pos.row, state->pos.col);
1469 abort();
1470 }
1471
1472 if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
1473 fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
1474 command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
1475 abort();
1476 }
1477
1478 if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
1479 fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
1480 command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
1481 abort();
1482 }
1483 #endif
1484
1485 return 1;
1486 }
1487
on_osc(const char * command,size_t cmdlen,void * user)1488 static int on_osc(const char *command, size_t cmdlen, void *user)
1489 {
1490 VTermState *state = user;
1491
1492 if(cmdlen < 2)
1493 return 0;
1494
1495 if(strneq(command, "0;", 2)) {
1496 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
1497 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
1498 return 1;
1499 }
1500 else if(strneq(command, "1;", 2)) {
1501 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
1502 return 1;
1503 }
1504 else if(strneq(command, "2;", 2)) {
1505 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
1506 return 1;
1507 }
1508 else if(state->fallbacks && state->fallbacks->osc)
1509 if((*state->fallbacks->osc)(command, cmdlen, state->fbdata))
1510 return 1;
1511
1512 return 0;
1513 }
1514
request_status_string(VTermState * state,const char * command,size_t cmdlen)1515 static void request_status_string(VTermState *state, const char *command, size_t cmdlen)
1516 {
1517 if(cmdlen == 1)
1518 switch(command[0]) {
1519 case 'm': // Query SGR
1520 {
1521 long args[20];
1522 int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
1523 vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r");
1524 for(int argi = 0; argi < argc; argi++)
1525 vterm_push_output_sprintf(state->vt,
1526 argi == argc - 1 ? "%d" :
1527 CSI_ARG_HAS_MORE(args[argi]) ? "%d:" :
1528 "%d;",
1529 CSI_ARG(args[argi]));
1530 vterm_push_output_sprintf(state->vt, "m");
1531 vterm_push_output_sprintf_ctrl(state->vt, C1_ST, "");
1532 }
1533 return;
1534 case 'r': // Query DECSTBM
1535 vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
1536 return;
1537 case 's': // Query DECSLRM
1538 vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
1539 return;
1540 }
1541
1542 if(cmdlen == 2) {
1543 if(strneq(command, " q", 2)) {
1544 int reply;
1545 switch(state->mode.cursor_shape) {
1546 case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break;
1547 case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
1548 case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break;
1549 }
1550 if(state->mode.cursor_blink)
1551 reply--;
1552 vterm_push_output_sprintf_dcs(state->vt, "1$r%d q", reply);
1553 return;
1554 }
1555 else if(strneq(command, "\"q", 2)) {
1556 vterm_push_output_sprintf_dcs(state->vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
1557 return;
1558 }
1559 }
1560
1561 vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command);
1562 }
1563
on_dcs(const char * command,size_t cmdlen,void * user)1564 static int on_dcs(const char *command, size_t cmdlen, void *user)
1565 {
1566 VTermState *state = user;
1567
1568 if(cmdlen >= 2 && strneq(command, "$q", 2)) {
1569 request_status_string(state, command+2, cmdlen-2);
1570 return 1;
1571 }
1572 else if(state->fallbacks && state->fallbacks->dcs)
1573 if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata))
1574 return 1;
1575
1576 return 0;
1577 }
1578
on_resize(int rows,int cols,void * user)1579 static int on_resize(int rows, int cols, void *user)
1580 {
1581 VTermState *state = user;
1582 VTermPos oldpos = state->pos;
1583
1584 if(cols != state->cols) {
1585 unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
1586
1587 /* TODO: This can all be done much more efficiently bytewise */
1588 int col;
1589 for(col = 0; col < state->cols && col < cols; col++) {
1590 unsigned char mask = 1 << (col & 7);
1591 if(state->tabstops[col >> 3] & mask)
1592 newtabstops[col >> 3] |= mask;
1593 else
1594 newtabstops[col >> 3] &= ~mask;
1595 }
1596
1597 for( ; col < cols; col++) {
1598 unsigned char mask = 1 << (col & 7);
1599 if(col % 8 == 0)
1600 newtabstops[col >> 3] |= mask;
1601 else
1602 newtabstops[col >> 3] &= ~mask;
1603 }
1604
1605 vterm_allocator_free(state->vt, state->tabstops);
1606 state->tabstops = newtabstops;
1607 }
1608
1609 if(rows != state->rows) {
1610 VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
1611
1612 int row;
1613 for(row = 0; row < state->rows && row < rows; row++) {
1614 newlineinfo[row] = state->lineinfo[row];
1615 }
1616
1617 for( ; row < rows; row++) {
1618 newlineinfo[row] = (VTermLineInfo){
1619 .doublewidth = 0,
1620 };
1621 }
1622
1623 vterm_allocator_free(state->vt, state->lineinfo);
1624 state->lineinfo = newlineinfo;
1625 }
1626
1627 state->rows = rows;
1628 state->cols = cols;
1629
1630 if(state->scrollregion_bottom > -1)
1631 UBOUND(state->scrollregion_bottom, state->rows);
1632 if(state->scrollregion_right > -1)
1633 UBOUND(state->scrollregion_right, state->cols);
1634
1635 VTermPos delta = { 0, 0 };
1636
1637 if(state->callbacks && state->callbacks->resize)
1638 (*state->callbacks->resize)(rows, cols, &delta, state->cbdata);
1639
1640 if(state->at_phantom && state->pos.col < cols-1) {
1641 state->at_phantom = 0;
1642 state->pos.col++;
1643 }
1644
1645 state->pos.row += delta.row;
1646 state->pos.col += delta.col;
1647
1648 if(state->pos.row >= rows)
1649 state->pos.row = rows - 1;
1650 if(state->pos.col >= cols)
1651 state->pos.col = cols - 1;
1652
1653 updatecursor(state, &oldpos, 1);
1654
1655 return 1;
1656 }
1657
1658 static const VTermParserCallbacks parser_callbacks = {
1659 .text = on_text,
1660 .control = on_control,
1661 .escape = on_escape,
1662 .csi = on_csi,
1663 .osc = on_osc,
1664 .dcs = on_dcs,
1665 .resize = on_resize,
1666 };
1667
vterm_obtain_state(VTerm * vt)1668 VTermState *vterm_obtain_state(VTerm *vt)
1669 {
1670 if(vt->state)
1671 return vt->state;
1672
1673 VTermState *state = vterm_state_new(vt);
1674 vt->state = state;
1675
1676 state->combine_chars_size = 16;
1677 state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
1678
1679 state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
1680
1681 state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
1682
1683 state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
1684 if(*state->encoding_utf8.enc->init)
1685 (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
1686
1687 vterm_parser_set_callbacks(vt, &parser_callbacks, state);
1688
1689 return state;
1690 }
1691
vterm_state_reset(VTermState * state,int hard)1692 void vterm_state_reset(VTermState *state, int hard)
1693 {
1694 state->scrollregion_top = 0;
1695 state->scrollregion_bottom = -1;
1696 state->scrollregion_left = 0;
1697 state->scrollregion_right = -1;
1698
1699 state->mode.keypad = 0;
1700 state->mode.cursor = 0;
1701 state->mode.autowrap = 1;
1702 state->mode.insert = 0;
1703 state->mode.newline = 0;
1704 state->mode.alt_screen = 0;
1705 state->mode.origin = 0;
1706 state->mode.leftrightmargin = 0;
1707 state->mode.bracketpaste = 0;
1708 state->mode.report_focus = 0;
1709
1710 state->vt->mode.ctrl8bit = 0;
1711
1712 for(int col = 0; col < state->cols; col++)
1713 if(col % 8 == 0)
1714 set_col_tabstop(state, col);
1715 else
1716 clear_col_tabstop(state, col);
1717
1718 for(int row = 0; row < state->rows; row++)
1719 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1720
1721 if(state->callbacks && state->callbacks->initpen)
1722 (*state->callbacks->initpen)(state->cbdata);
1723
1724 vterm_state_resetpen(state);
1725
1726 VTermEncoding *default_enc = state->vt->mode.utf8 ?
1727 vterm_lookup_encoding(ENC_UTF8, 'u') :
1728 vterm_lookup_encoding(ENC_SINGLE_94, 'B');
1729
1730 for(int i = 0; i < 4; i++) {
1731 state->encoding[i].enc = default_enc;
1732 if(default_enc->init)
1733 (*default_enc->init)(default_enc, state->encoding[i].data);
1734 }
1735
1736 state->gl_set = 0;
1737 state->gr_set = 1;
1738 state->gsingle_set = 0;
1739
1740 state->protected_cell = 0;
1741
1742 // Initialise the props
1743 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
1744 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1745 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1746
1747 if(hard) {
1748 state->pos.row = 0;
1749 state->pos.col = 0;
1750 state->at_phantom = 0;
1751
1752 VTermRect rect = { 0, state->rows, 0, state->cols };
1753 erase(state, rect, 0);
1754 }
1755 }
1756
vterm_state_get_cursorpos(const VTermState * state,VTermPos * cursorpos)1757 void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
1758 {
1759 *cursorpos = state->pos;
1760 }
1761
vterm_state_set_callbacks(VTermState * state,const VTermStateCallbacks * callbacks,void * user)1762 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
1763 {
1764 if(callbacks) {
1765 state->callbacks = callbacks;
1766 state->cbdata = user;
1767
1768 if(state->callbacks && state->callbacks->initpen)
1769 (*state->callbacks->initpen)(state->cbdata);
1770 }
1771 else {
1772 state->callbacks = NULL;
1773 state->cbdata = NULL;
1774 }
1775 }
1776
vterm_state_get_cbdata(VTermState * state)1777 void *vterm_state_get_cbdata(VTermState *state)
1778 {
1779 return state->cbdata;
1780 }
1781
vterm_state_set_unrecognised_fallbacks(VTermState * state,const VTermParserCallbacks * fallbacks,void * user)1782 void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user)
1783 {
1784 if(fallbacks) {
1785 state->fallbacks = fallbacks;
1786 state->fbdata = user;
1787 }
1788 else {
1789 state->fallbacks = NULL;
1790 state->fbdata = NULL;
1791 }
1792 }
1793
vterm_state_get_unrecognised_fbdata(VTermState * state)1794 void *vterm_state_get_unrecognised_fbdata(VTermState *state)
1795 {
1796 return state->fbdata;
1797 }
1798
vterm_state_set_termprop(VTermState * state,VTermProp prop,VTermValue * val)1799 int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
1800 {
1801 /* Only store the new value of the property if usercode said it was happy.
1802 * This is especially important for altscreen switching */
1803 if(state->callbacks && state->callbacks->settermprop)
1804 if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))
1805 return 0;
1806
1807 switch(prop) {
1808 case VTERM_PROP_TITLE:
1809 case VTERM_PROP_ICONNAME:
1810 // we don't store these, just transparently pass through
1811 return 1;
1812 case VTERM_PROP_CURSORVISIBLE:
1813 state->mode.cursor_visible = val->boolean;
1814 return 1;
1815 case VTERM_PROP_CURSORBLINK:
1816 state->mode.cursor_blink = val->boolean;
1817 return 1;
1818 case VTERM_PROP_CURSORSHAPE:
1819 state->mode.cursor_shape = val->number;
1820 return 1;
1821 case VTERM_PROP_REVERSE:
1822 state->mode.screen = val->boolean;
1823 return 1;
1824 case VTERM_PROP_ALTSCREEN:
1825 state->mode.alt_screen = val->boolean;
1826 if(state->mode.alt_screen) {
1827 VTermRect rect = {
1828 .start_row = 0,
1829 .start_col = 0,
1830 .end_row = state->rows,
1831 .end_col = state->cols,
1832 };
1833 erase(state, rect, 0);
1834 }
1835 return 1;
1836 case VTERM_PROP_MOUSE:
1837 state->mouse_flags = 0;
1838 if(val->number)
1839 state->mouse_flags |= MOUSE_WANT_CLICK;
1840 if(val->number == VTERM_PROP_MOUSE_DRAG)
1841 state->mouse_flags |= MOUSE_WANT_DRAG;
1842 if(val->number == VTERM_PROP_MOUSE_MOVE)
1843 state->mouse_flags |= MOUSE_WANT_MOVE;
1844 return 1;
1845
1846 case VTERM_N_PROPS:
1847 return 0;
1848 }
1849
1850 return 0;
1851 }
1852
vterm_state_focus_in(VTermState * state)1853 void vterm_state_focus_in(VTermState *state)
1854 {
1855 if(state->mode.report_focus)
1856 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
1857 }
1858
vterm_state_focus_out(VTermState * state)1859 void vterm_state_focus_out(VTermState *state)
1860 {
1861 if(state->mode.report_focus)
1862 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
1863 }
1864
vterm_state_get_lineinfo(const VTermState * state,int row)1865 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
1866 {
1867 return state->lineinfo + row;
1868 }
1869