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