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