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