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