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