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