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