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