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