| #include "vterm_internal.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #define strneq(a,b,n) (strncmp(a,b,n)==0) |
| |
| #include "utf8.h" |
| |
| #if defined(DEBUG) && DEBUG > 1 |
| # define DEBUG_GLYPH_COMBINE |
| #endif |
| |
| #define MOUSE_WANT_CLICK 0x01 |
| #define MOUSE_WANT_DRAG 0x02 |
| #define MOUSE_WANT_MOVE 0x04 |
| |
| /* Some convenient wrappers to make callback functions easier */ |
| |
| static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) |
| { |
| VTermGlyphInfo info = { |
| .chars = chars, |
| .width = width, |
| .protected_cell = state->protected_cell, |
| .dwl = state->lineinfo[pos.row].doublewidth, |
| .dhl = state->lineinfo[pos.row].doubleheight, |
| }; |
| |
| if(state->callbacks && state->callbacks->putglyph) |
| if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) |
| return; |
| |
| fprintf(stderr, "libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); |
| } |
| |
| static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) |
| { |
| if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) |
| return; |
| |
| if(cancel_phantom) |
| state->at_phantom = 0; |
| |
| if(state->callbacks && state->callbacks->movecursor) |
| if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) |
| return; |
| } |
| |
| static void erase(VTermState *state, VTermRect rect, int selective) |
| { |
| if(state->callbacks && state->callbacks->erase) |
| if((*state->callbacks->erase)(rect, selective, state->cbdata)) |
| return; |
| } |
| |
| static VTermState *vterm_state_new(VTerm *vt) |
| { |
| VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); |
| |
| state->vt = vt; |
| |
| state->rows = vt->rows; |
| state->cols = vt->cols; |
| |
| vterm_state_newpen(state); |
| |
| state->bold_is_highbright = 0; |
| |
| return state; |
| } |
| |
| INTERNAL void vterm_state_free(VTermState *state) |
| { |
| vterm_allocator_free(state->vt, state->tabstops); |
| vterm_allocator_free(state->vt, state->lineinfo); |
| vterm_allocator_free(state->vt, state->combine_chars); |
| vterm_allocator_free(state->vt, state); |
| } |
| |
| static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) |
| { |
| if(!downward && !rightward) |
| return; |
| |
| // Update lineinfo if full line |
| if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { |
| int height = rect.end_row - rect.start_row - abs(downward); |
| |
| if(downward > 0) |
| memmove(state->lineinfo + rect.start_row, |
| state->lineinfo + rect.start_row + downward, |
| height * sizeof(state->lineinfo[0])); |
| else |
| memmove(state->lineinfo + rect.start_row - downward, |
| state->lineinfo + rect.start_row, |
| height * sizeof(state->lineinfo[0])); |
| } |
| |
| if(state->callbacks && state->callbacks->scrollrect) |
| if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) |
| return; |
| |
| if(state->callbacks) |
| vterm_scroll_rect(rect, downward, rightward, |
| state->callbacks->moverect, state->callbacks->erase, state->cbdata); |
| } |
| |
| static void linefeed(VTermState *state) |
| { |
| if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { |
| VTermRect rect = { |
| .start_row = state->scrollregion_top, |
| .end_row = SCROLLREGION_BOTTOM(state), |
| .start_col = SCROLLREGION_LEFT(state), |
| .end_col = SCROLLREGION_RIGHT(state), |
| }; |
| |
| scroll(state, rect, 1, 0); |
| } |
| else if(state->pos.row < state->rows-1) |
| state->pos.row++; |
| } |
| |
| static void grow_combine_buffer(VTermState *state) |
| { |
| size_t new_size = state->combine_chars_size * 2; |
| uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); |
| |
| memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); |
| |
| vterm_allocator_free(state->vt, state->combine_chars); |
| |
| state->combine_chars = new_chars; |
| state->combine_chars_size = new_size; |
| } |
| |
| static void set_col_tabstop(VTermState *state, int col) |
| { |
| unsigned char mask = 1 << (col & 7); |
| state->tabstops[col >> 3] |= mask; |
| } |
| |
| static void clear_col_tabstop(VTermState *state, int col) |
| { |
| unsigned char mask = 1 << (col & 7); |
| state->tabstops[col >> 3] &= ~mask; |
| } |
| |
| static int is_col_tabstop(VTermState *state, int col) |
| { |
| unsigned char mask = 1 << (col & 7); |
| return state->tabstops[col >> 3] & mask; |
| } |
| |
| static void tab(VTermState *state, int count, int direction) |
| { |
| while(count--) |
| while(state->pos.col >= 0 && state->pos.col < THISROWWIDTH(state)-1) { |
| state->pos.col += direction; |
| |
| if(is_col_tabstop(state, state->pos.col)) |
| break; |
| } |
| } |
| |
| #define NO_FORCE 0 |
| #define FORCE 1 |
| |
| #define DWL_OFF 0 |
| #define DWL_ON 1 |
| |
| #define DHL_OFF 0 |
| #define DHL_TOP 1 |
| #define DHL_BOTTOM 2 |
| |
| static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) |
| { |
| VTermLineInfo info = state->lineinfo[row]; |
| |
| if(dwl == DWL_OFF) |
| info.doublewidth = DWL_OFF; |
| else if(dwl == DWL_ON) |
| info.doublewidth = DWL_ON; |
| // else -1 to ignore |
| |
| if(dhl == DHL_OFF) |
| info.doubleheight = DHL_OFF; |
| else if(dhl == DHL_TOP) |
| info.doubleheight = DHL_TOP; |
| else if(dhl == DHL_BOTTOM) |
| info.doubleheight = DHL_BOTTOM; |
| |
| if((state->callbacks && |
| state->callbacks->setlineinfo && |
| (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) |
| || force) |
| state->lineinfo[row] = info; |
| } |
| |
| static int on_text(const char bytes[], size_t len, void *user) |
| { |
| VTermState *state = user; |
| |
| VTermPos oldpos = state->pos; |
| |
| // We'll have at most len codepoints |
| uint32_t codepoints[len]; |
| int npoints = 0; |
| size_t eaten = 0; |
| |
| VTermEncodingInstance *encoding = |
| state->gsingle_set ? &state->encoding[state->gsingle_set] : |
| !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : |
| state->vt->mode.utf8 ? &state->encoding_utf8 : |
| &state->encoding[state->gr_set]; |
| |
| (*encoding->enc->decode)(encoding->enc, encoding->data, |
| codepoints, &npoints, state->gsingle_set ? 1 : len, |
| bytes, &eaten, len); |
| |
| if(state->gsingle_set && npoints) |
| state->gsingle_set = 0; |
| |
| int i = 0; |
| |
| /* This is a combining char. that needs to be merged with the previous |
| * glyph output */ |
| if(vterm_unicode_is_combining(codepoints[i])) { |
| /* See if the cursor has moved since */ |
| if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { |
| #ifdef DEBUG_GLYPH_COMBINE |
| int printpos; |
| printf("DEBUG: COMBINING SPLIT GLYPH of chars {"); |
| for(printpos = 0; state->combine_chars[printpos]; printpos++) |
| printf("U+%04x ", state->combine_chars[printpos]); |
| printf("} + {"); |
| #endif |
| |
| /* Find where we need to append these combining chars */ |
| int saved_i = 0; |
| while(state->combine_chars[saved_i]) |
| saved_i++; |
| |
| /* Add extra ones */ |
| while(i < npoints && vterm_unicode_is_combining(codepoints[i])) { |
| if(saved_i >= state->combine_chars_size) |
| grow_combine_buffer(state); |
| state->combine_chars[saved_i++] = codepoints[i++]; |
| } |
| if(saved_i >= state->combine_chars_size) |
| grow_combine_buffer(state); |
| state->combine_chars[saved_i] = 0; |
| |
| #ifdef DEBUG_GLYPH_COMBINE |
| for(; state->combine_chars[printpos]; printpos++) |
| printf("U+%04x ", state->combine_chars[printpos]); |
| printf("}\n"); |
| #endif |
| |
| /* Now render it */ |
| putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); |
| } |
| else { |
| fprintf(stderr, "libvterm: TODO: Skip over split char+combining\n"); |
| } |
| } |
| |
| for(; i < npoints; i++) { |
| // Try to find combining characters following this |
| int glyph_starts = i; |
| int glyph_ends; |
| for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++) |
| if(!vterm_unicode_is_combining(codepoints[glyph_ends])) |
| break; |
| |
| int width = 0; |
| |
| uint32_t chars[glyph_ends - glyph_starts + 1]; |
| |
| for( ; i < glyph_ends; i++) { |
| chars[i - glyph_starts] = codepoints[i]; |
| width += vterm_unicode_width(codepoints[i]); |
| } |
| |
| chars[glyph_ends - glyph_starts] = 0; |
| i--; |
| |
| #ifdef DEBUG_GLYPH_COMBINE |
| int printpos; |
| printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts); |
| for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++) |
| printf("U+%04x ", chars[printpos]); |
| printf("}, onscreen width %d\n", width); |
| #endif |
| |
| if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { |
| linefeed(state); |
| state->pos.col = 0; |
| state->at_phantom = 0; |
| } |
| |
| if(state->mode.insert) { |
| /* TODO: This will be a little inefficient for large bodies of text, as |
| * it'll have to 'ICH' effectively before every glyph. We should scan |
| * ahead and ICH as many times as required |
| */ |
| VTermRect rect = { |
| .start_row = state->pos.row, |
| .end_row = state->pos.row + 1, |
| .start_col = state->pos.col, |
| .end_col = THISROWWIDTH(state), |
| }; |
| scroll(state, rect, 0, -1); |
| } |
| |
| putglyph(state, chars, width, state->pos); |
| |
| if(i == npoints - 1) { |
| /* End of the buffer. Save the chars in case we have to combine with |
| * more on the next call */ |
| int save_i; |
| for(save_i = 0; chars[save_i]; save_i++) { |
| if(save_i >= state->combine_chars_size) |
| grow_combine_buffer(state); |
| state->combine_chars[save_i] = chars[save_i]; |
| } |
| if(save_i >= state->combine_chars_size) |
| grow_combine_buffer(state); |
| state->combine_chars[save_i] = 0; |
| state->combine_width = width; |
| state->combine_pos = state->pos; |
| } |
| |
| if(state->pos.col + width >= THISROWWIDTH(state)) { |
| if(state->mode.autowrap) |
| state->at_phantom = 1; |
| } |
| else { |
| state->pos.col += width; |
| } |
| } |
| |
| updatecursor(state, &oldpos, 0); |
| |
| return eaten; |
| } |
| |
| static int on_control(unsigned char control, void *user) |
| { |
| VTermState *state = user; |
| |
| VTermPos oldpos = state->pos; |
| |
| switch(control) { |
| case 0x07: // BEL - ECMA-48 8.3.3 |
| if(state->callbacks && state->callbacks->bell) |
| (*state->callbacks->bell)(state->cbdata); |
| break; |
| |
| case 0x08: // BS - ECMA-48 8.3.5 |
| if(state->pos.col > 0) |
| state->pos.col--; |
| break; |
| |
| case 0x09: // HT - ECMA-48 8.3.60 |
| tab(state, 1, +1); |
| break; |
| |
| case 0x0a: // LF - ECMA-48 8.3.74 |
| case 0x0b: // VT |
| case 0x0c: // FF |
| linefeed(state); |
| if(state->mode.newline) |
| state->pos.col = 0; |
| break; |
| |
| case 0x0d: // CR - ECMA-48 8.3.15 |
| state->pos.col = 0; |
| break; |
| |
| case 0x0e: // LS1 - ECMA-48 8.3.76 |
| state->gl_set = 1; |
| break; |
| |
| case 0x0f: // LS0 - ECMA-48 8.3.75 |
| state->gl_set = 0; |
| break; |
| |
| case 0x84: // IND - DEPRECATED but implemented for completeness |
| linefeed(state); |
| break; |
| |
| case 0x85: // NEL - ECMA-48 8.3.86 |
| linefeed(state); |
| state->pos.col = 0; |
| break; |
| |
| case 0x88: // HTS - ECMA-48 8.3.62 |
| set_col_tabstop(state, state->pos.col); |
| break; |
| |
| case 0x8d: // RI - ECMA-48 8.3.104 |
| if(state->pos.row == state->scrollregion_top) { |
| VTermRect rect = { |
| .start_row = state->scrollregion_top, |
| .end_row = SCROLLREGION_BOTTOM(state), |
| .start_col = SCROLLREGION_LEFT(state), |
| .end_col = SCROLLREGION_RIGHT(state), |
| }; |
| |
| scroll(state, rect, -1, 0); |
| } |
| else if(state->pos.row > 0) |
| state->pos.row--; |
| break; |
| |
| case 0x8e: // SS2 - ECMA-48 8.3.141 |
| state->gsingle_set = 2; |
| break; |
| |
| case 0x8f: // SS3 - ECMA-48 8.3.142 |
| state->gsingle_set = 3; |
| break; |
| |
| default: |
| return 0; |
| } |
| |
| updatecursor(state, &oldpos, 1); |
| |
| return 1; |
| } |
| |
| static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) |
| { |
| modifiers <<= 2; |
| |
| switch(state->mouse_protocol) { |
| case MOUSE_X10: |
| if(col + 0x21 > 0xff) |
| col = 0xff - 0x21; |
| if(row + 0x21 > 0xff) |
| row = 0xff - 0x21; |
| |
| if(!pressed) |
| code = 3; |
| |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", |
| (code | modifiers) + 0x20, col + 0x21, row + 0x21); |
| break; |
| |
| case MOUSE_UTF8: |
| { |
| char utf8[18]; size_t len = 0; |
| |
| if(!pressed) |
| code = 3; |
| |
| len += fill_utf8((code | modifiers) + 0x20, utf8 + len); |
| len += fill_utf8(col + 0x21, utf8 + len); |
| len += fill_utf8(row + 0x21, utf8 + len); |
| utf8[len] = 0; |
| |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); |
| } |
| break; |
| |
| case MOUSE_SGR: |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", |
| code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); |
| break; |
| |
| case MOUSE_RXVT: |
| if(!pressed) |
| code = 3; |
| |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", |
| code | modifiers, col + 1, row + 1); |
| break; |
| } |
| } |
| |
| static void mousefunc(int col, int row, int button, int pressed, int modifiers, void *data) |
| { |
| VTermState *state = data; |
| |
| int old_col = state->mouse_col; |
| int old_row = state->mouse_row; |
| int old_buttons = state->mouse_buttons; |
| |
| state->mouse_col = col; |
| state->mouse_row = row; |
| |
| if(button > 0 && button <= 3) { |
| if(pressed) |
| state->mouse_buttons |= (1 << (button-1)); |
| else |
| state->mouse_buttons &= ~(1 << (button-1)); |
| } |
| |
| modifiers &= 0x7; |
| |
| |
| /* Most of the time we don't get button releases from 4/5 */ |
| if(state->mouse_buttons != old_buttons || button >= 4) { |
| if(button < 4) { |
| output_mouse(state, button-1, pressed, modifiers, col, row); |
| } |
| else if(button < 6) { |
| output_mouse(state, button-4 + 0x40, pressed, modifiers, col, row); |
| } |
| } |
| else if(col != old_col || row != old_row) { |
| if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || |
| (state->mouse_flags & MOUSE_WANT_MOVE)) { |
| int button = state->mouse_buttons & 0x01 ? 1 : |
| state->mouse_buttons & 0x02 ? 2 : |
| state->mouse_buttons & 0x04 ? 3 : 4; |
| output_mouse(state, button-1 + 0x20, 1, modifiers, col, row); |
| } |
| } |
| } |
| |
| static int settermprop_bool(VTermState *state, VTermProp prop, int v) |
| { |
| VTermValue val = { .boolean = v }; |
| return vterm_state_set_termprop(state, prop, &val); |
| } |
| |
| static int settermprop_int(VTermState *state, VTermProp prop, int v) |
| { |
| VTermValue val = { .number = v }; |
| return vterm_state_set_termprop(state, prop, &val); |
| } |
| |
| static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) |
| { |
| char strvalue[len+1]; |
| strncpy(strvalue, str, len); |
| strvalue[len] = 0; |
| |
| VTermValue val = { .string = strvalue }; |
| return vterm_state_set_termprop(state, prop, &val); |
| } |
| |
| static void savecursor(VTermState *state, int save) |
| { |
| if(save) { |
| state->saved.pos = state->pos; |
| state->saved.mode.cursor_visible = state->mode.cursor_visible; |
| state->saved.mode.cursor_blink = state->mode.cursor_blink; |
| state->saved.mode.cursor_shape = state->mode.cursor_shape; |
| |
| vterm_state_savepen(state, 1); |
| } |
| else { |
| VTermPos oldpos = state->pos; |
| |
| state->pos = state->saved.pos; |
| |
| settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); |
| |
| vterm_state_savepen(state, 0); |
| |
| updatecursor(state, &oldpos, 1); |
| } |
| } |
| |
| static int on_escape(const char *bytes, size_t len, void *user) |
| { |
| VTermState *state = user; |
| |
| /* Easier to decode this from the first byte, even though the final |
| * byte terminates it |
| */ |
| switch(bytes[0]) { |
| case ' ': |
| if(len != 2) |
| return 0; |
| |
| switch(bytes[1]) { |
| case 'F': // S7C1T |
| state->vt->mode.ctrl8bit = 0; |
| break; |
| |
| case 'G': // S8C1T |
| state->vt->mode.ctrl8bit = 1; |
| break; |
| |
| default: |
| return 0; |
| } |
| return 2; |
| |
| case '#': |
| if(len != 2) |
| return 0; |
| |
| switch(bytes[1]) { |
| case '3': // DECDHL top |
| if(state->mode.leftrightmargin) |
| break; |
| set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); |
| break; |
| |
| case '4': // DECDHL bottom |
| if(state->mode.leftrightmargin) |
| break; |
| set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); |
| break; |
| |
| case '5': // DECSWL |
| if(state->mode.leftrightmargin) |
| break; |
| set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); |
| break; |
| |
| case '6': // DECDWL |
| if(state->mode.leftrightmargin) |
| break; |
| set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); |
| break; |
| |
| case '8': // DECALN |
| { |
| VTermPos pos; |
| uint32_t E[] = { 'E', 0 }; |
| for(pos.row = 0; pos.row < state->rows; pos.row++) |
| for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) |
| putglyph(state, E, 1, pos); |
| break; |
| } |
| |
| default: |
| return 0; |
| } |
| return 2; |
| |
| case '(': case ')': case '*': case '+': // SCS |
| if(len != 2) |
| return 0; |
| |
| { |
| int setnum = bytes[0] - 0x28; |
| VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); |
| |
| if(newenc) { |
| state->encoding[setnum].enc = newenc; |
| |
| if(newenc->init) |
| (*newenc->init)(newenc, state->encoding[setnum].data); |
| } |
| } |
| |
| return 2; |
| |
| case '7': // DECSC |
| savecursor(state, 1); |
| return 1; |
| |
| case '8': // DECRC |
| savecursor(state, 0); |
| return 1; |
| |
| case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 |
| return 1; |
| |
| case '=': // DECKPAM |
| state->mode.keypad = 1; |
| return 1; |
| |
| case '>': // DECKPNM |
| state->mode.keypad = 0; |
| return 1; |
| |
| case 'c': // RIS - ECMA-48 8.3.105 |
| { |
| VTermPos oldpos = state->pos; |
| vterm_state_reset(state, 1); |
| if(state->callbacks && state->callbacks->movecursor) |
| (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); |
| return 1; |
| } |
| |
| case 'n': // LS2 - ECMA-48 8.3.78 |
| state->gl_set = 2; |
| return 1; |
| |
| case 'o': // LS3 - ECMA-48 8.3.80 |
| state->gl_set = 3; |
| return 1; |
| |
| case '~': // LS1R - ECMA-48 8.3.77 |
| state->gr_set = 1; |
| return 1; |
| |
| case '}': // LS2R - ECMA-48 8.3.79 |
| state->gr_set = 2; |
| return 1; |
| |
| case '|': // LS3R - ECMA-48 8.3.81 |
| state->gr_set = 3; |
| return 1; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| static void set_mode(VTermState *state, int num, int val) |
| { |
| switch(num) { |
| case 4: // IRM - ECMA-48 7.2.10 |
| state->mode.insert = val; |
| break; |
| |
| case 20: // LNM - ANSI X3.4-1977 |
| state->mode.newline = val; |
| break; |
| |
| default: |
| fprintf(stderr, "libvterm: Unknown mode %d\n", num); |
| return; |
| } |
| } |
| |
| static void set_dec_mode(VTermState *state, int num, int val) |
| { |
| switch(num) { |
| case 1: |
| state->mode.cursor = val; |
| break; |
| |
| case 5: // DECSCNM - screen mode |
| settermprop_bool(state, VTERM_PROP_REVERSE, val); |
| break; |
| |
| case 6: // DECOM - origin mode |
| { |
| VTermPos oldpos = state->pos; |
| state->mode.origin = val; |
| state->pos.row = state->mode.origin ? state->scrollregion_top : 0; |
| state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; |
| updatecursor(state, &oldpos, 1); |
| } |
| break; |
| |
| case 7: |
| state->mode.autowrap = val; |
| break; |
| |
| case 12: |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); |
| break; |
| |
| case 25: |
| settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); |
| break; |
| |
| case 69: // DECVSSM - vertical split screen mode |
| // DECLRMM - left/right margin mode |
| state->mode.leftrightmargin = val; |
| if(val) { |
| // Setting DECVSSM must clear doublewidth/doubleheight state of every line |
| for(int row = 0; row < state->rows; row++) |
| set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); |
| } |
| |
| break; |
| |
| case 1000: |
| case 1002: |
| case 1003: |
| if(val) { |
| state->mouse_col = 0; |
| state->mouse_row = 0; |
| state->mouse_buttons = 0; |
| |
| state->mouse_flags = MOUSE_WANT_CLICK; |
| state->mouse_protocol = MOUSE_X10; |
| |
| if(num == 1002) |
| state->mouse_flags |= MOUSE_WANT_DRAG; |
| if(num == 1003) |
| state->mouse_flags |= MOUSE_WANT_MOVE; |
| } |
| else { |
| state->mouse_flags = 0; |
| } |
| |
| if(state->callbacks && state->callbacks->setmousefunc) |
| (*state->callbacks->setmousefunc)(val ? mousefunc : NULL, state, state->cbdata); |
| |
| break; |
| |
| case 1005: |
| state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; |
| break; |
| |
| case 1006: |
| state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; |
| break; |
| |
| case 1015: |
| state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; |
| break; |
| |
| case 1047: |
| settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); |
| break; |
| |
| case 1048: |
| savecursor(state, val); |
| break; |
| |
| case 1049: |
| settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); |
| savecursor(state, val); |
| break; |
| |
| default: |
| fprintf(stderr, "libvterm: Unknown DEC mode %d\n", num); |
| return; |
| } |
| } |
| |
| static void request_dec_mode(VTermState *state, int num) |
| { |
| int reply; |
| |
| switch(num) { |
| case 1: |
| reply = state->mode.cursor; |
| break; |
| |
| case 5: |
| reply = state->mode.screen; |
| break; |
| |
| case 6: |
| reply = state->mode.origin; |
| break; |
| |
| case 7: |
| reply = state->mode.autowrap; |
| break; |
| |
| case 12: |
| reply = state->mode.cursor_blink; |
| break; |
| |
| case 25: |
| reply = state->mode.cursor_visible; |
| break; |
| |
| case 69: |
| reply = state->mode.leftrightmargin; |
| break; |
| |
| case 1000: |
| reply = state->mouse_flags == MOUSE_WANT_CLICK; |
| break; |
| |
| case 1002: |
| reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); |
| break; |
| |
| case 1003: |
| reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); |
| break; |
| |
| case 1005: |
| reply = state->mouse_protocol == MOUSE_UTF8; |
| break; |
| |
| case 1006: |
| reply = state->mouse_protocol == MOUSE_SGR; |
| break; |
| |
| case 1015: |
| reply = state->mouse_protocol == MOUSE_RXVT; |
| break; |
| |
| case 1047: |
| reply = state->mode.alt_screen; |
| break; |
| |
| default: |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); |
| return; |
| } |
| |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); |
| } |
| |
| static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) |
| { |
| VTermState *state = user; |
| int leader_byte = 0; |
| int intermed_byte = 0; |
| |
| if(leader && leader[0]) { |
| if(leader[1]) // longer than 1 char |
| return 0; |
| |
| switch(leader[0]) { |
| case '?': |
| case '>': |
| leader_byte = leader[0]; |
| break; |
| default: |
| return 0; |
| } |
| } |
| |
| if(intermed && intermed[0]) { |
| if(intermed[1]) // longer than 1 char |
| return 0; |
| |
| switch(intermed[0]) { |
| case ' ': |
| case '"': |
| case '$': |
| case '\'': |
| intermed_byte = intermed[0]; |
| break; |
| default: |
| return 0; |
| } |
| } |
| |
| VTermPos oldpos = state->pos; |
| |
| // Some temporaries for later code |
| int count, val; |
| int row, col; |
| VTermRect rect; |
| int selective; |
| |
| #define LBOUND(v,min) if((v) < (min)) (v) = (min) |
| #define UBOUND(v,max) if((v) > (max)) (v) = (max) |
| |
| #define LEADER(l,b) ((l << 8) | b) |
| #define INTERMED(i,b) ((i << 16) | b) |
| |
| switch(intermed_byte << 16 | leader_byte << 8 | command) { |
| case 0x40: // ICH - ECMA-48 8.3.64 |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->pos.row; |
| rect.end_row = state->pos.row + 1; |
| rect.start_col = state->pos.col; |
| if(state->mode.leftrightmargin) |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| else |
| rect.end_col = THISROWWIDTH(state); |
| |
| scroll(state, rect, 0, -count); |
| |
| break; |
| |
| case 0x41: // CUU - ECMA-48 8.3.22 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.row -= count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x42: // CUD - ECMA-48 8.3.19 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.row += count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x43: // CUF - ECMA-48 8.3.20 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.col += count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x44: // CUB - ECMA-48 8.3.18 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.col -= count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x45: // CNL - ECMA-48 8.3.12 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.col = 0; |
| state->pos.row += count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x46: // CPL - ECMA-48 8.3.13 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.col = 0; |
| state->pos.row -= count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x47: // CHA - ECMA-48 8.3.9 |
| val = CSI_ARG_OR(args[0], 1); |
| state->pos.col = val-1; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x48: // CUP - ECMA-48 8.3.21 |
| row = CSI_ARG_OR(args[0], 1); |
| col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); |
| // zero-based |
| state->pos.row = row-1; |
| state->pos.col = col-1; |
| if(state->mode.origin) { |
| state->pos.row += state->scrollregion_top; |
| state->pos.col += SCROLLREGION_LEFT(state); |
| } |
| state->at_phantom = 0; |
| break; |
| |
| case 0x49: // CHT - ECMA-48 8.3.10 |
| count = CSI_ARG_COUNT(args[0]); |
| tab(state, count, +1); |
| break; |
| |
| case 0x4a: // ED - ECMA-48 8.3.39 |
| case LEADER('?', 0x4a): // DECSED - Selective Erase in Display |
| selective = (leader_byte == '?'); |
| switch(CSI_ARG(args[0])) { |
| case CSI_ARG_MISSING: |
| case 0: |
| rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; |
| rect.start_col = state->pos.col; rect.end_col = state->cols; |
| if(rect.end_col > rect.start_col) |
| erase(state, rect, selective); |
| |
| rect.start_row = state->pos.row + 1; rect.end_row = state->rows; |
| rect.start_col = 0; |
| for(int row = rect.start_row; row < rect.end_row; row++) |
| set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); |
| if(rect.end_row > rect.start_row) |
| erase(state, rect, selective); |
| break; |
| |
| case 1: |
| rect.start_row = 0; rect.end_row = state->pos.row; |
| rect.start_col = 0; rect.end_col = state->cols; |
| for(int row = rect.start_row; row < rect.end_row; row++) |
| set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); |
| if(rect.end_col > rect.start_col) |
| erase(state, rect, selective); |
| |
| rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; |
| rect.end_col = state->pos.col + 1; |
| if(rect.end_row > rect.start_row) |
| erase(state, rect, selective); |
| break; |
| |
| case 2: |
| rect.start_row = 0; rect.end_row = state->rows; |
| rect.start_col = 0; rect.end_col = state->cols; |
| for(int row = rect.start_row; row < rect.end_row; row++) |
| set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); |
| erase(state, rect, selective); |
| break; |
| } |
| break; |
| |
| case 0x4b: // EL - ECMA-48 8.3.41 |
| case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line |
| selective = (leader_byte == '?'); |
| rect.start_row = state->pos.row; |
| rect.end_row = state->pos.row + 1; |
| |
| switch(CSI_ARG(args[0])) { |
| case CSI_ARG_MISSING: |
| case 0: |
| rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; |
| case 1: |
| rect.start_col = 0; rect.end_col = state->pos.col + 1; break; |
| case 2: |
| rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; |
| default: |
| return 0; |
| } |
| |
| if(rect.end_col > rect.start_col) |
| erase(state, rect, selective); |
| |
| break; |
| |
| case 0x4c: // IL - ECMA-48 8.3.67 |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->pos.row; |
| rect.end_row = SCROLLREGION_BOTTOM(state); |
| rect.start_col = SCROLLREGION_LEFT(state); |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| |
| scroll(state, rect, -count, 0); |
| |
| break; |
| |
| case 0x4d: // DL - ECMA-48 8.3.32 |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->pos.row; |
| rect.end_row = SCROLLREGION_BOTTOM(state); |
| rect.start_col = SCROLLREGION_LEFT(state); |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| |
| scroll(state, rect, count, 0); |
| |
| break; |
| |
| case 0x50: // DCH - ECMA-48 8.3.26 |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->pos.row; |
| rect.end_row = state->pos.row + 1; |
| rect.start_col = state->pos.col; |
| if(state->mode.leftrightmargin) |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| else |
| rect.end_col = THISROWWIDTH(state); |
| |
| scroll(state, rect, 0, count); |
| |
| break; |
| |
| case 0x53: // SU - ECMA-48 8.3.147 |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->scrollregion_top; |
| rect.end_row = SCROLLREGION_BOTTOM(state); |
| rect.start_col = SCROLLREGION_LEFT(state); |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| |
| scroll(state, rect, count, 0); |
| |
| break; |
| |
| case 0x54: // SD - ECMA-48 8.3.113 |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->scrollregion_top; |
| rect.end_row = SCROLLREGION_BOTTOM(state); |
| rect.start_col = SCROLLREGION_LEFT(state); |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| |
| scroll(state, rect, -count, 0); |
| |
| break; |
| |
| case 0x58: // ECH - ECMA-48 8.3.38 |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->pos.row; |
| rect.end_row = state->pos.row + 1; |
| rect.start_col = state->pos.col; |
| rect.end_col = state->pos.col + count; |
| UBOUND(rect.end_col, THISROWWIDTH(state)); |
| |
| erase(state, rect, 0); |
| break; |
| |
| case 0x5a: // CBT - ECMA-48 8.3.7 |
| count = CSI_ARG_COUNT(args[0]); |
| tab(state, count, -1); |
| break; |
| |
| case 0x60: // HPA - ECMA-48 8.3.57 |
| col = CSI_ARG_OR(args[0], 1); |
| state->pos.col = col-1; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x61: // HPR - ECMA-48 8.3.59 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.col += count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x63: // DA - ECMA-48 8.3.24 |
| val = CSI_ARG_OR(args[0], 0); |
| if(val == 0) |
| // DEC VT100 response |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); |
| break; |
| |
| case LEADER('>', 0x63): // DEC secondary Device Attributes |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); |
| break; |
| |
| case 0x64: // VPA - ECMA-48 8.3.158 |
| row = CSI_ARG_OR(args[0], 1); |
| state->pos.row = row-1; |
| if(state->mode.origin) |
| state->pos.row += state->scrollregion_top; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x65: // VPR - ECMA-48 8.3.160 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.row += count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x66: // HVP - ECMA-48 8.3.63 |
| row = CSI_ARG_OR(args[0], 1); |
| col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); |
| // zero-based |
| state->pos.row = row-1; |
| state->pos.col = col-1; |
| if(state->mode.origin) { |
| state->pos.row += state->scrollregion_top; |
| state->pos.col += SCROLLREGION_LEFT(state); |
| } |
| state->at_phantom = 0; |
| break; |
| |
| case 0x67: // TBC - ECMA-48 8.3.154 |
| val = CSI_ARG_OR(args[0], 0); |
| |
| switch(val) { |
| case 0: |
| clear_col_tabstop(state, state->pos.col); |
| break; |
| case 3: |
| case 5: |
| for(col = 0; col < state->cols; col++) |
| clear_col_tabstop(state, col); |
| break; |
| case 1: |
| case 2: |
| case 4: |
| break; |
| /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ |
| default: |
| return 0; |
| } |
| break; |
| |
| case 0x68: // SM - ECMA-48 8.3.125 |
| if(!CSI_ARG_IS_MISSING(args[0])) |
| set_mode(state, CSI_ARG(args[0]), 1); |
| break; |
| |
| case LEADER('?', 0x68): // DEC private mode set |
| if(!CSI_ARG_IS_MISSING(args[0])) |
| set_dec_mode(state, CSI_ARG(args[0]), 1); |
| break; |
| |
| case 0x6a: // HPB - ECMA-48 8.3.58 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.col -= count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x6b: // VPB - ECMA-48 8.3.159 |
| count = CSI_ARG_COUNT(args[0]); |
| state->pos.row -= count; |
| state->at_phantom = 0; |
| break; |
| |
| case 0x6c: // RM - ECMA-48 8.3.106 |
| if(!CSI_ARG_IS_MISSING(args[0])) |
| set_mode(state, CSI_ARG(args[0]), 0); |
| break; |
| |
| case LEADER('?', 0x6c): // DEC private mode reset |
| if(!CSI_ARG_IS_MISSING(args[0])) |
| set_dec_mode(state, CSI_ARG(args[0]), 0); |
| break; |
| |
| case 0x6d: // SGR - ECMA-48 8.3.117 |
| vterm_state_setpen(state, args, argcount); |
| break; |
| |
| case 0x6e: // DSR - ECMA-48 8.3.35 |
| case LEADER('?', 0x6e): // DECDSR |
| val = CSI_ARG_OR(args[0], 0); |
| |
| { |
| char *qmark = (leader_byte == '?') ? "?" : ""; |
| |
| switch(val) { |
| case 0: case 1: case 2: case 3: case 4: |
| // ignore - these are replies |
| break; |
| case 5: |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); |
| break; |
| case 6: // CPR - cursor position report |
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); |
| break; |
| } |
| } |
| break; |
| |
| |
| case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset |
| vterm_state_reset(state, 0); |
| break; |
| |
| case LEADER('?', INTERMED('$', 0x70)): |
| request_dec_mode(state, CSI_ARG(args[0])); |
| break; |
| |
| case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape |
| val = CSI_ARG_OR(args[0], 1); |
| |
| switch(val) { |
| case 0: case 1: |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); |
| break; |
| case 2: |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); |
| break; |
| case 3: |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); |
| break; |
| case 4: |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); |
| break; |
| case 5: |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); |
| break; |
| case 6: |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); |
| break; |
| } |
| |
| break; |
| |
| case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute |
| val = CSI_ARG_OR(args[0], 0); |
| |
| switch(val) { |
| case 0: case 2: |
| state->protected_cell = 0; |
| break; |
| case 1: |
| state->protected_cell = 1; |
| break; |
| } |
| |
| break; |
| |
| case 0x72: // DECSTBM - DEC custom |
| state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; |
| state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); |
| LBOUND(state->scrollregion_top, -1); |
| UBOUND(state->scrollregion_top, state->rows); |
| LBOUND(state->scrollregion_bottom, -1); |
| if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) |
| state->scrollregion_bottom = -1; |
| else |
| UBOUND(state->scrollregion_bottom, state->rows); |
| |
| break; |
| |
| case 0x73: // DECSLRM - DEC custom |
| // Always allow setting these margins, just they won't take effect without DECVSSM |
| state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; |
| state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); |
| LBOUND(state->scrollregion_left, -1); |
| UBOUND(state->scrollregion_left, state->cols); |
| LBOUND(state->scrollregion_right, -1); |
| if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) |
| state->scrollregion_right = -1; |
| else |
| UBOUND(state->scrollregion_right, state->cols); |
| |
| break; |
| |
| case INTERMED('\'', 0x7D): // DECIC |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->scrollregion_top; |
| rect.end_row = SCROLLREGION_BOTTOM(state); |
| rect.start_col = state->pos.col; |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| |
| scroll(state, rect, 0, -count); |
| |
| break; |
| |
| case INTERMED('\'', 0x7E): // DECDC |
| count = CSI_ARG_COUNT(args[0]); |
| |
| rect.start_row = state->scrollregion_top; |
| rect.end_row = SCROLLREGION_BOTTOM(state); |
| rect.start_col = state->pos.col; |
| rect.end_col = SCROLLREGION_RIGHT(state); |
| |
| scroll(state, rect, 0, count); |
| |
| break; |
| |
| default: |
| return 0; |
| } |
| |
| if(state->mode.origin) { |
| LBOUND(state->pos.row, state->scrollregion_top); |
| UBOUND(state->pos.row, state->scrollregion_bottom-1); |
| LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); |
| UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); |
| } |
| else { |
| LBOUND(state->pos.row, 0); |
| UBOUND(state->pos.row, state->rows-1); |
| LBOUND(state->pos.col, 0); |
| UBOUND(state->pos.col, THISROWWIDTH(state)-1); |
| } |
| |
| updatecursor(state, &oldpos, 1); |
| |
| return 1; |
| } |
| |
| static int on_osc(const char *command, size_t cmdlen, void *user) |
| { |
| VTermState *state = user; |
| |
| if(cmdlen < 2) |
| return 0; |
| |
| if(strneq(command, "0;", 2)) { |
| settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); |
| settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); |
| return 1; |
| } |
| else if(strneq(command, "1;", 2)) { |
| settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); |
| return 1; |
| } |
| else if(strneq(command, "2;", 2)) { |
| settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void request_status_string(VTermState *state, const char *command, size_t cmdlen) |
| { |
| if(cmdlen == 1) |
| switch(command[0]) { |
| case 'm': // Query SGR |
| { |
| long args[20]; |
| int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); |
| vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r"); |
| for(int argi = 0; argi < argc; argi++) |
| vterm_push_output_sprintf(state->vt, |
| argi == argc - 1 ? "%d" : |
| CSI_ARG_HAS_MORE(args[argi]) ? "%d:" : |
| "%d;", |
| CSI_ARG(args[argi])); |
| vterm_push_output_sprintf(state->vt, "m"); |
| vterm_push_output_sprintf_ctrl(state->vt, C1_ST, ""); |
| } |
| return; |
| case 'r': // Query DECSTBM |
| vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); |
| return; |
| case 's': // Query DECSLRM |
| vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); |
| return; |
| } |
| |
| if(cmdlen == 2) { |
| if(strneq(command, " q", 2)) { |
| int reply; |
| switch(state->mode.cursor_shape) { |
| case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; |
| case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; |
| case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; |
| } |
| if(state->mode.cursor_blink) |
| reply--; |
| vterm_push_output_sprintf_dcs(state->vt, "1$r%d q", reply); |
| return; |
| } |
| else if(strneq(command, "\"q", 2)) { |
| vterm_push_output_sprintf_dcs(state->vt, "1$r%d\"q", state->protected_cell ? 1 : 2); |
| return; |
| } |
| } |
| |
| vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command); |
| } |
| |
| static int on_dcs(const char *command, size_t cmdlen, void *user) |
| { |
| VTermState *state = user; |
| |
| if(cmdlen >= 2 && strneq(command, "$q", 2)) { |
| request_status_string(state, command+2, cmdlen-2); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int on_resize(int rows, int cols, void *user) |
| { |
| VTermState *state = user; |
| VTermPos oldpos = state->pos; |
| |
| if(cols != state->cols) { |
| unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); |
| |
| /* TODO: This can all be done much more efficiently bytewise */ |
| int col; |
| for(col = 0; col < state->cols && col < cols; col++) { |
| unsigned char mask = 1 << (col & 7); |
| if(state->tabstops[col >> 3] & mask) |
| newtabstops[col >> 3] |= mask; |
| else |
| newtabstops[col >> 3] &= ~mask; |
| } |
| |
| for( ; col < cols; col++) { |
| unsigned char mask = 1 << (col & 7); |
| if(col % 8 == 0) |
| newtabstops[col >> 3] |= mask; |
| else |
| newtabstops[col >> 3] &= ~mask; |
| } |
| |
| vterm_allocator_free(state->vt, state->tabstops); |
| state->tabstops = newtabstops; |
| } |
| |
| if(rows != state->rows) { |
| VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); |
| |
| int row; |
| for(row = 0; row < state->rows && row < rows; row++) { |
| newlineinfo[row] = state->lineinfo[row]; |
| } |
| |
| for( ; row < rows; row++) { |
| newlineinfo[row] = (VTermLineInfo){ |
| .doublewidth = 0, |
| }; |
| } |
| |
| vterm_allocator_free(state->vt, state->lineinfo); |
| state->lineinfo = newlineinfo; |
| } |
| |
| state->rows = rows; |
| state->cols = cols; |
| |
| VTermPos delta = { 0, 0 }; |
| |
| if(state->callbacks && state->callbacks->resize) |
| (*state->callbacks->resize)(rows, cols, &delta, state->cbdata); |
| |
| if(state->at_phantom && state->pos.col < cols-1) { |
| state->at_phantom = 0; |
| state->pos.col++; |
| } |
| |
| state->pos.row += delta.row; |
| state->pos.col += delta.col; |
| |
| if(state->pos.row >= rows) |
| state->pos.row = rows - 1; |
| if(state->pos.col >= cols) |
| state->pos.col = cols - 1; |
| |
| updatecursor(state, &oldpos, 1); |
| |
| return 1; |
| } |
| |
| static const VTermParserCallbacks parser_callbacks = { |
| .text = on_text, |
| .control = on_control, |
| .escape = on_escape, |
| .csi = on_csi, |
| .osc = on_osc, |
| .dcs = on_dcs, |
| .resize = on_resize, |
| }; |
| |
| VTermState *vterm_obtain_state(VTerm *vt) |
| { |
| if(vt->state) |
| return vt->state; |
| |
| VTermState *state = vterm_state_new(vt); |
| vt->state = state; |
| |
| state->combine_chars_size = 16; |
| state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); |
| |
| state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); |
| |
| state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); |
| |
| state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); |
| if(*state->encoding_utf8.enc->init) |
| (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); |
| |
| vterm_set_parser_callbacks(vt, &parser_callbacks, state); |
| |
| return state; |
| } |
| |
| void vterm_state_reset(VTermState *state, int hard) |
| { |
| state->scrollregion_top = 0; |
| state->scrollregion_bottom = -1; |
| state->scrollregion_left = 0; |
| state->scrollregion_right = -1; |
| |
| state->mode.keypad = 0; |
| state->mode.cursor = 0; |
| state->mode.autowrap = 1; |
| state->mode.insert = 0; |
| state->mode.newline = 0; |
| state->mode.alt_screen = 0; |
| state->mode.origin = 0; |
| state->mode.leftrightmargin = 0; |
| |
| state->vt->mode.ctrl8bit = 0; |
| |
| for(int col = 0; col < state->cols; col++) |
| if(col % 8 == 0) |
| set_col_tabstop(state, col); |
| else |
| clear_col_tabstop(state, col); |
| |
| for(int row = 0; row < state->rows; row++) |
| set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); |
| |
| if(state->callbacks && state->callbacks->initpen) |
| (*state->callbacks->initpen)(state->cbdata); |
| |
| vterm_state_resetpen(state); |
| |
| VTermEncoding *default_enc = state->vt->mode.utf8 ? |
| vterm_lookup_encoding(ENC_UTF8, 'u') : |
| vterm_lookup_encoding(ENC_SINGLE_94, 'B'); |
| |
| for(int i = 0; i < 4; i++) { |
| state->encoding[i].enc = default_enc; |
| if(default_enc->init) |
| (*default_enc->init)(default_enc, state->encoding[i].data); |
| } |
| |
| state->gl_set = 0; |
| state->gr_set = 1; |
| state->gsingle_set = 0; |
| |
| state->protected_cell = 0; |
| |
| // Initialise the props |
| settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); |
| settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); |
| settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); |
| |
| if(hard) { |
| state->pos.row = 0; |
| state->pos.col = 0; |
| state->at_phantom = 0; |
| |
| VTermRect rect = { 0, state->rows, 0, state->cols }; |
| erase(state, rect, 0); |
| } |
| } |
| |
| void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) |
| { |
| *cursorpos = state->pos; |
| } |
| |
| void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) |
| { |
| if(callbacks) { |
| state->callbacks = callbacks; |
| state->cbdata = user; |
| |
| if(state->callbacks && state->callbacks->initpen) |
| (*state->callbacks->initpen)(state->cbdata); |
| } |
| else { |
| state->callbacks = NULL; |
| state->cbdata = NULL; |
| } |
| } |
| |
| int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) |
| { |
| /* Only store the new value of the property if usercode said it was happy. |
| * This is especially important for altscreen switching */ |
| if(state->callbacks && state->callbacks->settermprop) |
| if(!(*state->callbacks->settermprop)(prop, val, state->cbdata)) |
| return 0; |
| |
| switch(prop) { |
| case VTERM_PROP_TITLE: |
| case VTERM_PROP_ICONNAME: |
| // we don't store these, just transparently pass through |
| return 1; |
| case VTERM_PROP_CURSORVISIBLE: |
| state->mode.cursor_visible = val->boolean; |
| return 1; |
| case VTERM_PROP_CURSORBLINK: |
| state->mode.cursor_blink = val->boolean; |
| return 1; |
| case VTERM_PROP_CURSORSHAPE: |
| state->mode.cursor_shape = val->number; |
| return 1; |
| case VTERM_PROP_REVERSE: |
| state->mode.screen = val->boolean; |
| return 1; |
| case VTERM_PROP_ALTSCREEN: |
| state->mode.alt_screen = val->boolean; |
| if(state->mode.alt_screen) { |
| VTermRect rect = { |
| .start_row = 0, |
| .start_col = 0, |
| .end_row = state->rows, |
| .end_col = state->cols, |
| }; |
| erase(state, rect, 0); |
| } |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) |
| { |
| return state->lineinfo + row; |
| } |