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   VTerm *vt = state->vt;
1518 
1519   if(cmdlen == 1)
1520     switch(command[0]) {
1521       case 'm': // Query SGR
1522         {
1523           long args[20];
1524           int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
1525           size_t cur = 0;
1526 
1527           cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
1528               vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ...
1529           if(cur >= vt->tmpbuffer_len)
1530             return;
1531 
1532           for(int argi = 0; argi < argc; argi++) {
1533             cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
1534                 argi == argc - 1             ? "%ld" :
1535                 CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" :
1536                                                "%ld;",
1537                 CSI_ARG(args[argi]));
1538             if(cur >= vt->tmpbuffer_len)
1539               return;
1540           }
1541 
1542           cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
1543               vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST
1544           if(cur >= vt->tmpbuffer_len)
1545             return;
1546 
1547           vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
1548         }
1549         return;
1550       case 'r': // Query DECSTBM
1551         vterm_push_output_sprintf_dcs(vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
1552         return;
1553       case 's': // Query DECSLRM
1554         vterm_push_output_sprintf_dcs(vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
1555         return;
1556     }
1557 
1558   if(cmdlen == 2) {
1559     if(strneq(command, " q", 2)) {
1560       int reply;
1561       switch(state->mode.cursor_shape) {
1562         case VTERM_PROP_CURSORSHAPE_BLOCK:     reply = 2; break;
1563         case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
1564         case VTERM_PROP_CURSORSHAPE_BAR_LEFT:  reply = 6; break;
1565       }
1566       if(state->mode.cursor_blink)
1567         reply--;
1568       vterm_push_output_sprintf_dcs(vt, "1$r%d q", reply);
1569       return;
1570     }
1571     else if(strneq(command, "\"q", 2)) {
1572       vterm_push_output_sprintf_dcs(vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
1573       return;
1574     }
1575   }
1576 
1577   vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command);
1578 }
1579 
on_dcs(const char * command,size_t cmdlen,void * user)1580 static int on_dcs(const char *command, size_t cmdlen, void *user)
1581 {
1582   VTermState *state = user;
1583 
1584   if(cmdlen >= 2 && strneq(command, "$q", 2)) {
1585     request_status_string(state, command+2, cmdlen-2);
1586     return 1;
1587   }
1588   else if(state->fallbacks && state->fallbacks->dcs)
1589     if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata))
1590       return 1;
1591 
1592   return 0;
1593 }
1594 
on_resize(int rows,int cols,void * user)1595 static int on_resize(int rows, int cols, void *user)
1596 {
1597   VTermState *state = user;
1598   VTermPos oldpos = state->pos;
1599 
1600   if(cols != state->cols) {
1601     unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
1602 
1603     /* TODO: This can all be done much more efficiently bytewise */
1604     int col;
1605     for(col = 0; col < state->cols && col < cols; col++) {
1606       unsigned char mask = 1 << (col & 7);
1607       if(state->tabstops[col >> 3] & mask)
1608         newtabstops[col >> 3] |= mask;
1609       else
1610         newtabstops[col >> 3] &= ~mask;
1611       }
1612 
1613     for( ; col < cols; col++) {
1614       unsigned char mask = 1 << (col & 7);
1615       if(col % 8 == 0)
1616         newtabstops[col >> 3] |= mask;
1617       else
1618         newtabstops[col >> 3] &= ~mask;
1619     }
1620 
1621     vterm_allocator_free(state->vt, state->tabstops);
1622     state->tabstops = newtabstops;
1623   }
1624 
1625   if(rows != state->rows) {
1626     VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
1627 
1628     int row;
1629     for(row = 0; row < state->rows && row < rows; row++) {
1630       newlineinfo[row] = state->lineinfo[row];
1631     }
1632 
1633     for( ; row < rows; row++) {
1634       newlineinfo[row] = (VTermLineInfo){
1635         .doublewidth = 0,
1636       };
1637     }
1638 
1639     vterm_allocator_free(state->vt, state->lineinfo);
1640     state->lineinfo = newlineinfo;
1641   }
1642 
1643   state->rows = rows;
1644   state->cols = cols;
1645 
1646   if(state->scrollregion_bottom > -1)
1647     UBOUND(state->scrollregion_bottom, state->rows);
1648   if(state->scrollregion_right > -1)
1649     UBOUND(state->scrollregion_right, state->cols);
1650 
1651   VTermPos delta = { 0, 0 };
1652 
1653   if(state->callbacks && state->callbacks->resize)
1654     (*state->callbacks->resize)(rows, cols, &delta, state->cbdata);
1655 
1656   if(state->at_phantom && state->pos.col < cols-1) {
1657     state->at_phantom = 0;
1658     state->pos.col++;
1659   }
1660 
1661   state->pos.row += delta.row;
1662   state->pos.col += delta.col;
1663 
1664   if(state->pos.row >= rows)
1665     state->pos.row = rows - 1;
1666   if(state->pos.col >= cols)
1667     state->pos.col = cols - 1;
1668 
1669   updatecursor(state, &oldpos, 1);
1670 
1671   return 1;
1672 }
1673 
1674 static const VTermParserCallbacks parser_callbacks = {
1675   .text    = on_text,
1676   .control = on_control,
1677   .escape  = on_escape,
1678   .csi     = on_csi,
1679   .osc     = on_osc,
1680   .dcs     = on_dcs,
1681   .resize  = on_resize,
1682 };
1683 
vterm_obtain_state(VTerm * vt)1684 VTermState *vterm_obtain_state(VTerm *vt)
1685 {
1686   if(vt->state)
1687     return vt->state;
1688 
1689   VTermState *state = vterm_state_new(vt);
1690   vt->state = state;
1691 
1692   state->combine_chars_size = 16;
1693   state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
1694 
1695   state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
1696 
1697   state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
1698 
1699   state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
1700   if(*state->encoding_utf8.enc->init)
1701     (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
1702 
1703   vterm_parser_set_callbacks(vt, &parser_callbacks, state);
1704 
1705   return state;
1706 }
1707 
vterm_state_reset(VTermState * state,int hard)1708 void vterm_state_reset(VTermState *state, int hard)
1709 {
1710   state->scrollregion_top = 0;
1711   state->scrollregion_bottom = -1;
1712   state->scrollregion_left = 0;
1713   state->scrollregion_right = -1;
1714 
1715   state->mode.keypad          = 0;
1716   state->mode.cursor          = 0;
1717   state->mode.autowrap        = 1;
1718   state->mode.insert          = 0;
1719   state->mode.newline         = 0;
1720   state->mode.alt_screen      = 0;
1721   state->mode.origin          = 0;
1722   state->mode.leftrightmargin = 0;
1723   state->mode.bracketpaste    = 0;
1724   state->mode.report_focus    = 0;
1725 
1726   state->vt->mode.ctrl8bit   = 0;
1727 
1728   for(int col = 0; col < state->cols; col++)
1729     if(col % 8 == 0)
1730       set_col_tabstop(state, col);
1731     else
1732       clear_col_tabstop(state, col);
1733 
1734   for(int row = 0; row < state->rows; row++)
1735     set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1736 
1737   if(state->callbacks && state->callbacks->initpen)
1738     (*state->callbacks->initpen)(state->cbdata);
1739 
1740   vterm_state_resetpen(state);
1741 
1742   VTermEncoding *default_enc = state->vt->mode.utf8 ?
1743       vterm_lookup_encoding(ENC_UTF8,      'u') :
1744       vterm_lookup_encoding(ENC_SINGLE_94, 'B');
1745 
1746   for(int i = 0; i < 4; i++) {
1747     state->encoding[i].enc = default_enc;
1748     if(default_enc->init)
1749       (*default_enc->init)(default_enc, state->encoding[i].data);
1750   }
1751 
1752   state->gl_set = 0;
1753   state->gr_set = 1;
1754   state->gsingle_set = 0;
1755 
1756   state->protected_cell = 0;
1757 
1758   // Initialise the props
1759   settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
1760   settermprop_bool(state, VTERM_PROP_CURSORBLINK,   1);
1761   settermprop_int (state, VTERM_PROP_CURSORSHAPE,   VTERM_PROP_CURSORSHAPE_BLOCK);
1762 
1763   if(hard) {
1764     state->pos.row = 0;
1765     state->pos.col = 0;
1766     state->at_phantom = 0;
1767 
1768     VTermRect rect = { 0, state->rows, 0, state->cols };
1769     erase(state, rect, 0);
1770   }
1771 }
1772 
vterm_state_get_cursorpos(const VTermState * state,VTermPos * cursorpos)1773 void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
1774 {
1775   *cursorpos = state->pos;
1776 }
1777 
vterm_state_set_callbacks(VTermState * state,const VTermStateCallbacks * callbacks,void * user)1778 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
1779 {
1780   if(callbacks) {
1781     state->callbacks = callbacks;
1782     state->cbdata = user;
1783 
1784     if(state->callbacks && state->callbacks->initpen)
1785       (*state->callbacks->initpen)(state->cbdata);
1786   }
1787   else {
1788     state->callbacks = NULL;
1789     state->cbdata = NULL;
1790   }
1791 }
1792 
vterm_state_get_cbdata(VTermState * state)1793 void *vterm_state_get_cbdata(VTermState *state)
1794 {
1795   return state->cbdata;
1796 }
1797 
vterm_state_set_unrecognised_fallbacks(VTermState * state,const VTermParserCallbacks * fallbacks,void * user)1798 void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user)
1799 {
1800   if(fallbacks) {
1801     state->fallbacks = fallbacks;
1802     state->fbdata = user;
1803   }
1804   else {
1805     state->fallbacks = NULL;
1806     state->fbdata = NULL;
1807   }
1808 }
1809 
vterm_state_get_unrecognised_fbdata(VTermState * state)1810 void *vterm_state_get_unrecognised_fbdata(VTermState *state)
1811 {
1812   return state->fbdata;
1813 }
1814 
vterm_state_set_termprop(VTermState * state,VTermProp prop,VTermValue * val)1815 int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
1816 {
1817   /* Only store the new value of the property if usercode said it was happy.
1818    * This is especially important for altscreen switching */
1819   if(state->callbacks && state->callbacks->settermprop)
1820     if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))
1821       return 0;
1822 
1823   switch(prop) {
1824   case VTERM_PROP_TITLE:
1825   case VTERM_PROP_ICONNAME:
1826     // we don't store these, just transparently pass through
1827     return 1;
1828   case VTERM_PROP_CURSORVISIBLE:
1829     state->mode.cursor_visible = val->boolean;
1830     return 1;
1831   case VTERM_PROP_CURSORBLINK:
1832     state->mode.cursor_blink = val->boolean;
1833     return 1;
1834   case VTERM_PROP_CURSORSHAPE:
1835     state->mode.cursor_shape = val->number;
1836     return 1;
1837   case VTERM_PROP_REVERSE:
1838     state->mode.screen = val->boolean;
1839     return 1;
1840   case VTERM_PROP_ALTSCREEN:
1841     state->mode.alt_screen = val->boolean;
1842     if(state->mode.alt_screen) {
1843       VTermRect rect = {
1844         .start_row = 0,
1845         .start_col = 0,
1846         .end_row = state->rows,
1847         .end_col = state->cols,
1848       };
1849       erase(state, rect, 0);
1850     }
1851     return 1;
1852   case VTERM_PROP_MOUSE:
1853     state->mouse_flags = 0;
1854     if(val->number)
1855       state->mouse_flags |= MOUSE_WANT_CLICK;
1856     if(val->number == VTERM_PROP_MOUSE_DRAG)
1857       state->mouse_flags |= MOUSE_WANT_DRAG;
1858     if(val->number == VTERM_PROP_MOUSE_MOVE)
1859       state->mouse_flags |= MOUSE_WANT_MOVE;
1860     return 1;
1861 
1862   case VTERM_N_PROPS:
1863     return 0;
1864   }
1865 
1866   return 0;
1867 }
1868 
vterm_state_focus_in(VTermState * state)1869 void vterm_state_focus_in(VTermState *state)
1870 {
1871   if(state->mode.report_focus)
1872     vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
1873 }
1874 
vterm_state_focus_out(VTermState * state)1875 void vterm_state_focus_out(VTermState *state)
1876 {
1877   if(state->mode.report_focus)
1878     vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
1879 }
1880 
vterm_state_get_lineinfo(const VTermState * state,int row)1881 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
1882 {
1883   return state->lineinfo + row;
1884 }
1885