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