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