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