Mercurial > vim
view src/libvterm/src/state.c @ 31192:dcde141f2d1e v9.0.0930
patch 9.0.0930: cannot debug the Kitty keyboard protocol with TermDebug
Commit: https://github.com/vim/vim/commit/63a2e360cca2c70ab0a85d14771d3259d4b3aafa
Author: Bram Moolenaar <Bram@vim.org>
Date: Wed Nov 23 20:20:18 2022 +0000
patch 9.0.0930: cannot debug the Kitty keyboard protocol with TermDebug
Problem: Cannot debug the Kitty keyboard protocol with TermDebug.
Solution: Add Kitty keyboard protocol support to the libvterm fork.
Recognize the escape sequences that the protocol generates. Add
the 'keyprotocol' option to allow the user to specify for which
terminal what protocol is to be used, instead of hard-coding this.
Add recognizing the kitty keyboard protocol status.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Wed, 23 Nov 2022 21:30:04 +0100 |
parents | 82336c3b679d |
children | 984ebd1f6605 |
line wrap: on
line source
#include "vterm_internal.h" #include <stdio.h> #include <string.h> #define strneq(a,b,n) (strncmp(a,b,n)==0) #if defined(DEBUG) && DEBUG > 1 # define DEBUG_GLYPH_COMBINE #endif static int on_resize(int rows, int cols, void *user); /* Some convenient wrappers to make callback functions easier */ static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) { VTermGlyphInfo info; info.chars = chars; info.width = width; info.protected_cell = state->protected_cell; info.dwl = state->lineinfo[pos.row].doublewidth; info.dhl = state->lineinfo[pos.row].doubleheight; if(state->callbacks && state->callbacks->putglyph) if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) return; DEBUG_LOG3("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(rect.end_col == state->cols) { int row; /* If we're erasing the final cells of any lines, cancel the continuation * marker on the subsequent line */ for(row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) state->lineinfo[row].continuation = 0; } 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)); if (state == NULL) return NULL; state->vt = vt; state->rows = vt->rows; state->cols = vt->cols; state->mouse_col = 0; state->mouse_row = 0; state->mouse_buttons = 0; state->mouse_protocol = MOUSE_X10; state->callbacks = NULL; state->cbdata = NULL; state->selection.callbacks = NULL; state->selection.user = NULL; state->selection.buffer = NULL; vterm_state_newpen(state); state->bold_is_highbright = 0; 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->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); /* TODO: Make an 'enable' function */ state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; 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); return state; } INTERNAL void vterm_state_free(VTermState *state) { vterm_allocator_free(state->vt, state->tabstops); vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); if(state->lineinfos[BUFIDX_ALTSCREEN]) vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); 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) { int rows; int cols; if(!downward && !rightward) return; rows = rect.end_row - rect.start_row; if(downward > rows) downward = rows; else if(downward < -rows) downward = -rows; cols = rect.end_col - rect.start_col; if(rightward > cols) rightward = cols; else if(rightward < -cols) rightward = -cols; // 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); int row; VTermLineInfo zeroLineInfo = {0x0}; if(downward > 0) { memmove(state->lineinfo + rect.start_row, state->lineinfo + rect.start_row + downward, height * sizeof(state->lineinfo[0])); for(row = rect.end_row - downward; row < rect.end_row; row++) state->lineinfo[row] = zeroLineInfo; } else { memmove(state->lineinfo + rect.start_row - downward, state->lineinfo + rect.start_row, height * sizeof(state->lineinfo[0])); for(row = rect.start_row; row < rect.start_row - downward; row++) state->lineinfo[row] = zeroLineInfo; } } 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; 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, 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 int is_cursor_in_scrollregion(const VTermState *state) { if(state->pos.row < state->scrollregion_top || state->pos.row >= SCROLLREGION_BOTTOM(state)) return 0; if(state->pos.col < SCROLLREGION_LEFT(state) || state->pos.col >= SCROLLREGION_RIGHT(state)) return 0; return 1; } static void tab(VTermState *state, int count, int direction) { while(count > 0) { if(direction > 0) { if(state->pos.col >= THISROWWIDTH(state)-1) return; state->pos.col++; } else if(direction < 0) { if(state->pos.col < 1) return; state->pos.col--; } if(is_col_tabstop(state, state->pos.col)) count--; } } #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; int npoints = 0; size_t eaten = 0; VTermEncodingInstance *encoding; int i = 0; VTermPos oldpos = state->pos; // We'll have at most len codepoints, plus one from a previous incomplete // sequence. uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); 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 : (int)maxpoints, bytes, &eaten, len); /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet * for even a single codepoint */ if(!npoints) { return (int)eaten; } if(state->gsingle_set && npoints) state->gsingle_set = 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 >= (int)state->combine_chars_size) grow_combine_buffer(state); state->combine_chars[saved_i++] = codepoints[i++]; } if(saved_i >= (int)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 { DEBUG_LOG("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; int width = 0; for(glyph_ends = i + 1; (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL); glyph_ends++) if(!vterm_unicode_is_combining(codepoints[glyph_ends])) break; uint32_t *chars = vterm_allocator_malloc(state->vt, (VTERM_MAX_CHARS_PER_CELL + 1) * sizeof(uint32_t)); if (chars == NULL) break; for( ; i < glyph_ends; i++) { int this_width; if(vterm_get_special_pty_type() == 2) { state->vt->in_backspace -= (state->vt->in_backspace > 0) ? 1 : 0; if(state->vt->in_backspace == 1) codepoints[i] = 0; // codepoints under this condition must be 0 } chars[i - glyph_starts] = codepoints[i]; this_width = vterm_unicode_width(codepoints[i]); #ifdef DEBUG if(this_width < 0) { fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]); abort(); } #endif if (i == glyph_starts || this_width > width) width = this_width; // TODO: should be += ? } while(i < npoints && vterm_unicode_is_combining(codepoints[i])) 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; state->lineinfo[state->pos.row].continuation = 1; } 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; rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.start_col = state->pos.col; rect.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 >= (int)state->combine_chars_size) grow_combine_buffer(state); state->combine_chars[save_i] = chars[save_i]; } if(save_i >= (int)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; } vterm_allocator_free(state->vt, chars); } updatecursor(state, &oldpos, 0); #ifdef DEBUG if(state->pos.row < 0 || state->pos.row >= state->rows || state->pos.col < 0 || state->pos.col >= state->cols) { fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", state->pos.row, state->pos.col); abort(); } #endif return (int)eaten; } static int on_control(unsigned char control, void *user) { VTermState *state = user; VTermPos oldpos = state->pos; VTermScreenCell cell; // Preparing to see the leading byte VTermPos leadpos = state->pos; leadpos.col -= (leadpos.col >= 2 ? 2 : 0); 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--; if(vterm_get_special_pty_type() == 2) { // In 2 cell letters, go back 2 cells vterm_screen_get_cell(state->vt->screen, leadpos, &cell); if(vterm_unicode_width(cell.chars[0]) == 2) 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; 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, -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: if(state->fallbacks && state->fallbacks->control) if((*state->fallbacks->control)(control, state->fbdata)) return 1; return 0; } updatecursor(state, &oldpos, 1); #ifdef DEBUG if(state->pos.row < 0 || state->pos.row >= state->rows || state->pos.col < 0 || state->pos.col >= state->cols) { fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", control, state->pos.row, state->pos.col); abort(); } #endif return 1; } static int settermprop_bool(VTermState *state, VTermProp prop, int v) { VTermValue val; val.boolean = v; return vterm_state_set_termprop(state, prop, &val); } static int settermprop_int(VTermState *state, VTermProp prop, int v) { VTermValue val; val.number = v; return vterm_state_set_termprop(state, prop, &val); } static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) { VTermValue val; val.string = frag; 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: DEBUG_LOG1("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) { int row; // Setting DECVSSM must clear doublewidth/doubleheight state of every line for(row = 0; row < state->rows; row++) set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); } break; case 1000: case 1002: case 1003: settermprop_int(state, VTERM_PROP_MOUSE, !val ? VTERM_PROP_MOUSE_NONE : (num == 1000) ? VTERM_PROP_MOUSE_CLICK : (num == 1002) ? VTERM_PROP_MOUSE_DRAG : VTERM_PROP_MOUSE_MOVE); break; case 1004: state->mode.report_focus = val; 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; case 2004: state->mode.bracketpaste = val; break; default: DEBUG_LOG1("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 1004: reply = state->mode.report_focus; 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; case 2004: reply = state->mode.bracketpaste; 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 void request_version_string(VTermState *state) { vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, ">|libvterm(%d.%d)", VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); } 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; int cancel_phantom = 1; VTermPos oldpos = state->pos; int handled = 1; // Some temporaries for later code int count, val; int row, col; VTermRect rect; int selective; 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; } } oldpos = state->pos; #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]); if(!is_cursor_in_scrollregion(state)) break; 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 if(vterm_get_special_pty_type() == 2) { // Fix a sequence that is not correct right now if(state->pos.row == row - 1) { int cnt, ptr = 0; for(cnt = 0; cnt < col - 1; ++cnt) { VTermPos p; VTermScreenCell c0, c1; p.row = row - 1; p.col = ptr; vterm_screen_get_cell(state->vt->screen, p, &c0); p.col++; vterm_screen_get_cell(state->vt->screen, p, &c1); ptr += (c1.chars[0] == (uint32_t)-1) // double cell? ? (vterm_unicode_is_ambiguous(c0.chars[0])) // is ambiguous? ? vterm_unicode_width(0x00a1) : 1 // &ambiwidth : 1; // not ambiguous } col = ptr + 1; } } 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(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(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(row = rect.start_row; row < rect.end_row; row++) set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); erase(state, rect, selective); break; case 3: if(state->callbacks && state->callbacks->sb_clear) if((*state->callbacks->sb_clear)(state->cbdata)) return 1; 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]); if(!is_cursor_in_scrollregion(state)) break; 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]); if(!is_cursor_in_scrollregion(state)) break; 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]); if(!is_cursor_in_scrollregion(state)) break; 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 0x62: { // REP - ECMA-48 8.3.103 const int row_width = THISROWWIDTH(state); count = CSI_ARG_COUNT(args[0]); col = state->pos.col + count; UBOUND(col, row_width); while (state->pos.col < col) { putglyph(state, state->combine_chars, state->combine_width, state->pos); state->pos.col += state->combine_width; } if (state->pos.col + state->combine_width >= row_width) { if (state->mode.autowrap) { state->at_phantom = 1; cancel_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 // This returns xterm version number 100. 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 LEADER('?', 0x6d): // DECSGR /* No actual DEC terminal recognised these, but some printers did. These * are alternative ways to request subscript/superscript/off */ for(int argi = 0; argi < argcount; argi++) { long arg; switch(arg = CSI_ARG(args[argi])) { case 4: // Superscript on arg = 73; vterm_state_setpen(state, &arg, 1); break; case 5: // Subscript on arg = 74; vterm_state_setpen(state, &arg, 1); break; case 24: // Super+subscript off arg = 75; vterm_state_setpen(state, &arg, 1); break; } } break; case LEADER('>', 0x6d): // CSI > 4 ; Pv m xterm resource modifyOtherKeys if (argcount == 2 && args[0] == 4) { // can't have both modify_other_keys and kitty_keyboard state->mode.kitty_keyboard = 0; state->mode.modify_other_keys = args[1] == 2; } break; case LEADER('>', 0x75): // CSI > 1 u enable kitty keyboard protocol if (argcount == 1 && args[0] == 1) { // can't have both modify_other_keys and kitty_keyboard state->mode.modify_other_keys = 0; state->mode.kitty_keyboard = 1; } break; case LEADER('<', 0x75): // CSI < u disable kitty keyboard protocol if (argcount <= 1) state->mode.kitty_keyboard = 0; break; case LEADER('?', 0x75): // CSI ? u request kitty keyboard protocol state if (argcount <= 1) // TODO: this only uses the values zero and one. The protocol specifies // more values, the progressive enhancement flags. vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du", state->mode.kitty_keyboard); 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 LEADER('>', 0x71): // XTVERSION - xterm query version string request_version_string(state); 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, 0); 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); if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { // Invalid state->scrollregion_top = 0; state->scrollregion_bottom = -1; } // Setting the scrolling region restores the cursor to the home position state->pos.row = 0; state->pos.col = 0; if(state->mode.origin) { state->pos.row += state->scrollregion_top; state->pos.col += SCROLLREGION_LEFT(state); } 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, 0); 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); if(state->scrollregion_right > -1 && state->scrollregion_right <= state->scrollregion_left) { // Invalid state->scrollregion_left = 0; state->scrollregion_right = -1; } // Setting the scrolling region restores the cursor to the home position state->pos.row = 0; state->pos.col = 0; if(state->mode.origin) { state->pos.row += state->scrollregion_top; state->pos.col += SCROLLREGION_LEFT(state); } break; case 0x74: switch(CSI_ARG(args[0])) { case 8: // CSI 8 ; rows ; cols t set size if (argcount == 3) on_resize(CSI_ARG(args[1]), CSI_ARG(args[2]), state); break; default: handled = 0; break; } break; case INTERMED('\'', 0x7D): // DECIC count = CSI_ARG_COUNT(args[0]); if(!is_cursor_in_scrollregion(state)) break; 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]); if(!is_cursor_in_scrollregion(state)) break; 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: handled = 0; break; } if (!handled) { if(state->fallbacks && state->fallbacks->csi) if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) return 1; return 0; } if(state->mode.origin) { LBOUND(state->pos.row, state->scrollregion_top); UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-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, cancel_phantom); #ifdef DEBUG if(state->pos.row < 0 || state->pos.row >= state->rows || state->pos.col < 0 || state->pos.col >= state->cols) { fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", command, state->pos.row, state->pos.col); abort(); } if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); abort(); } if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); abort(); } #endif return 1; } static char base64_one(uint8_t b) { if(b < 26) return 'A' + b; else if(b < 52) return 'a' + b - 26; else if(b < 62) return '0' + b - 52; else if(b == 62) return '+'; else if(b == 63) return '/'; return 0; } static uint8_t unbase64one(char c) { if(c >= 'A' && c <= 'Z') return c - 'A'; else if(c >= 'a' && c <= 'z') return c - 'a' + 26; else if(c >= '0' && c <= '9') return c - '0' + 52; else if(c == '+') return 62; else if(c == '/') return 63; return 0xFF; } static void osc_selection(VTermState *state, VTermStringFragment frag) { if(frag.initial) { state->tmp.selection.mask = 0; state->tmp.selection.state = SELECTION_INITIAL; } while(!state->tmp.selection.state && frag.len) { /* Parse selection parameter */ switch(frag.str[0]) { case 'c': state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; break; case 'p': state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; break; case 'q': state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; break; case 's': state->tmp.selection.mask |= VTERM_SELECTION_SELECT; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); break; case ';': state->tmp.selection.state = SELECTION_SELECTED; if(!state->tmp.selection.mask) state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; break; } frag.str++; frag.len--; } if(!frag.len) return; if(state->tmp.selection.state == SELECTION_SELECTED) { if(frag.str[0] == '?') { state->tmp.selection.state = SELECTION_QUERY; } else { state->tmp.selection.state = SELECTION_SET_INITIAL; state->tmp.selection.recvpartial = 0; } } if(state->tmp.selection.state == SELECTION_QUERY) { if(state->selection.callbacks->query) (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); return; } if(state->selection.callbacks->set) { size_t bufcur = 0; char *buffer = state->selection.buffer; uint32_t x = 0; /* Current decoding value */ int n = 0; /* Number of sextets consumed */ if(state->tmp.selection.recvpartial) { n = state->tmp.selection.recvpartial >> 24; x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */ state->tmp.selection.recvpartial = 0; } while((state->selection.buflen - bufcur) >= 3 && frag.len) { if(frag.str[0] == '=') { if(n == 2) { buffer[0] = (x >> 4) & 0xFF; buffer += 1, bufcur += 1; } if(n == 3) { buffer[0] = (x >> 10) & 0xFF; buffer[1] = (x >> 2) & 0xFF; buffer += 2, bufcur += 2; } while(frag.len && frag.str[0] == '=') frag.str++, frag.len--; n = 0; } else { uint8_t b = unbase64one(frag.str[0]); if(b == 0xFF) { DEBUG_LOG1("base64decode bad input %02X\n", (uint8_t)frag.str[0]); } else { x = (x << 6) | b; n++; } frag.str++, frag.len--; if(n == 4) { buffer[0] = (x >> 16) & 0xFF; buffer[1] = (x >> 8) & 0xFF; buffer[2] = (x >> 0) & 0xFF; buffer += 3, bufcur += 3; x = 0; n = 0; } } if(!frag.len || (state->selection.buflen - bufcur) < 3) { if(bufcur) { VTermStringFragment setfrag = { state->selection.buffer, // str bufcur, // len state->tmp.selection.state == SELECTION_SET_INITIAL, // initial frag.final // final }; (*state->selection.callbacks->set)(state->tmp.selection.mask, setfrag, state->selection.user); state->tmp.selection.state = SELECTION_SET; } buffer = state->selection.buffer; bufcur = 0; } } if(n) state->tmp.selection.recvpartial = (n << 24) | x; } } static int on_osc(int command, VTermStringFragment frag, void *user) { VTermState *state = user; switch(command) { case 0: settermprop_string(state, VTERM_PROP_ICONNAME, frag); settermprop_string(state, VTERM_PROP_TITLE, frag); return 1; case 1: settermprop_string(state, VTERM_PROP_ICONNAME, frag); return 1; case 2: settermprop_string(state, VTERM_PROP_TITLE, frag); return 1; case 10: { // request foreground color: <Esc>]10;?<0x07> int red = state->default_fg.red; int blue = state->default_fg.blue; int green = state->default_fg.green; vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "10;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue); return 1; } case 11: { // request background color: <Esc>]11;?<0x07> int red = state->default_bg.red; int blue = state->default_bg.blue; int green = state->default_bg.green; vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "11;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue); return 1; } case 12: settermprop_string(state, VTERM_PROP_CURSORCOLOR, frag); return 1; case 52: if(state->selection.callbacks) osc_selection(state, frag); return 1; default: if(state->fallbacks && state->fallbacks->osc) if((*state->fallbacks->osc)(command, frag, state->fbdata)) return 1; } return 0; } static void request_status_string(VTermState *state, VTermStringFragment frag) { VTerm *vt = state->vt; char *tmp = state->tmp.decrqss; if(frag.initial) tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; size_t i = 0; while(i < sizeof(state->tmp.decrqss)-1 && tmp[i]) i++; while(i < sizeof(state->tmp.decrqss)-1 && frag.len--) tmp[i++] = (frag.str++)[0]; tmp[i] = 0; if(!frag.final) return; switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) { case 'm': { // Query SGR long args[20]; int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); size_t cur = 0; cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... if(cur >= vt->tmpbuffer_len) return; for(int argi = 0; argi < argc; argi++) { cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, argi == argc - 1 ? "%ld" : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : "%ld;", CSI_ARG(args[argi])); if(cur >= vt->tmpbuffer_len) return; } cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST if(cur >= vt->tmpbuffer_len) return; vterm_push_output_bytes(vt, vt->tmpbuffer, cur); return; } case 'r': // Query DECSTBM vterm_push_output_sprintf_str(vt, C1_DCS, TRUE, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); return; case 's': // Query DECSLRM vterm_push_output_sprintf_str(vt, C1_DCS, TRUE, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); return; case ' '|('q'<<8): { // Query DECSCUSR int reply; switch(state->mode.cursor_shape) { case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; default: /* VTERM_PROP_CURSORSHAPE_BAR_LEFT */ reply = 6; break; } if(state->mode.cursor_blink) reply--; vterm_push_output_sprintf_str(vt, C1_DCS, TRUE, "1$r%d q", reply); return; } case '\"'|('q'<<8): // Query DECSCA vterm_push_output_sprintf_str(vt, C1_DCS, TRUE, "1$r%d\"q", state->protected_cell ? 1 : 2); return; } vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, "0$r%s", tmp); } static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) { VTermState *state = user; if(commandlen == 2 && strneq(command, "$q", 2)) { request_status_string(state, frag); return 1; } else if(state->fallbacks && state->fallbacks->dcs) if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) return 1; DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); return 0; } static int on_apc(VTermStringFragment frag, void *user) { VTermState *state = user; if(state->fallbacks && state->fallbacks->apc) if((*state->fallbacks->apc)(frag, state->fbdata)) return 1; /* No DEBUG_LOG because all APCs are unhandled */ return 0; } static int on_pm(VTermStringFragment frag, void *user) { VTermState *state = user; if(state->fallbacks && state->fallbacks->pm) if((*state->fallbacks->pm)(frag, state->fbdata)) return 1; /* No DEBUG_LOG because all PMs are unhandled */ return 0; } static int on_sos(VTermStringFragment frag, void *user) { VTermState *state = user; if(state->fallbacks && state->fallbacks->sos) if((*state->fallbacks->sos)(frag, state->fbdata)) return 1; /* No DEBUG_LOG because all SOSs are unhandled */ 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); if (newtabstops == NULL) return 0; /* 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; } state->rows = rows; state->cols = cols; if(state->scrollregion_bottom > -1) UBOUND(state->scrollregion_bottom, state->rows); if(state->scrollregion_right > -1) UBOUND(state->scrollregion_right, state->cols); VTermStateFields fields; fields.pos = state->pos; fields.lineinfos[0] = state->lineinfos[0]; fields.lineinfos[1] = state->lineinfos[1]; if(state->callbacks && state->callbacks->resize) { (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); state->pos = fields.pos; state->lineinfos[0] = fields.lineinfos[0]; state->lineinfos[1] = fields.lineinfos[1]; } else { if(rows != state->rows) { for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; if(!oldlineinfo) continue; VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); int row; for(row = 0; row < state->rows && row < rows; row++) { newlineinfo[row] = oldlineinfo[row]; } for( ; row < rows; row++) { newlineinfo[row] = (VTermLineInfo){ .doublewidth = 0, }; } vterm_allocator_free(state->vt, state->lineinfos[bufidx]); state->lineinfos[bufidx] = newlineinfo; } } } state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; if(state->at_phantom && state->pos.col < cols-1) { state->at_phantom = 0; state->pos.col++; } if(state->pos.row < 0) state->pos.row = 0; if(state->pos.row >= rows) state->pos.row = rows - 1; if(state->pos.col < 0) state->pos.col = 0; if(state->pos.col >= cols) state->pos.col = cols - 1; updatecursor(state, &oldpos, 1); return 1; } static const VTermParserCallbacks parser_callbacks = { on_text, // text on_control, // control on_escape, // escape on_csi, // csi on_osc, // osc on_dcs, // dcs on_apc, // apc on_pm, // pm on_sos, // sos on_resize // resize }; /* * Return the existing state or create a new one. * Returns NULL when out of memory. */ VTermState *vterm_obtain_state(VTerm *vt) { if(vt->state) return vt->state; VTermState *state = vterm_state_new(vt); if (state == NULL) return NULL; vt->state = state; vterm_parser_set_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->mode.bracketpaste = 0; state->mode.report_focus = 0; state->mouse_flags = 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, 0, 0, 0 }; rect.end_row = state->rows; rect.end_col = state->cols; erase(state, rect, 0); } } void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) { *cursorpos = state->pos; } void vterm_state_get_mousestate(const VTermState *state, VTermMouseState *mousestate) { mousestate->pos.col = state->mouse_col; mousestate->pos.row = state->mouse_row; mousestate->buttons = state->mouse_buttons; mousestate->flags = state->mouse_flags; } 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; } } void *vterm_state_get_cbdata(VTermState *state) { return state->cbdata; } void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user) { if(fallbacks) { state->fallbacks = fallbacks; state->fbdata = user; } else { state->fallbacks = NULL; state->fbdata = NULL; } } void *vterm_state_get_unrecognised_fbdata(VTermState *state) { return state->fbdata; } 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: case VTERM_PROP_CURSORCOLOR: // 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; state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; if(state->mode.alt_screen) { VTermRect rect = {0, 0, 0, 0}; rect.end_row = state->rows; rect.end_col = state->cols; erase(state, rect, 0); } return 1; case VTERM_PROP_MOUSE: state->mouse_flags = 0; if(val->number) state->mouse_flags |= MOUSE_WANT_CLICK; if(val->number == VTERM_PROP_MOUSE_DRAG) state->mouse_flags |= MOUSE_WANT_DRAG; if(val->number == VTERM_PROP_MOUSE_MOVE) state->mouse_flags |= MOUSE_WANT_MOVE; return 1; case VTERM_N_PROPS: return 0; } return 0; } void vterm_state_focus_in(VTermState *state) { if(state->mode.report_focus) vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); } void vterm_state_focus_out(VTermState *state) { if(state->mode.report_focus) vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); } const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) { return state->lineinfo + row; } void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, char *buffer, size_t buflen) { if(buflen && !buffer) buffer = vterm_allocator_malloc(state->vt, buflen); state->selection.callbacks = callbacks; state->selection.user = user; state->selection.buffer = buffer; state->selection.buflen = buflen; } void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag) { VTerm *vt = state->vt; if(frag.initial) { /* TODO: support sending more than one mask bit */ static char selection_chars[] = "cpqs"; int idx; for(idx = 0; idx < 4; idx++) if(mask & (1 << idx)) break; vterm_push_output_sprintf_str(vt, C1_OSC, FALSE, "52;%c;", selection_chars[idx]); state->tmp.selection.sendpartial = 0; } if(frag.len) { size_t bufcur = 0; char *buffer = state->selection.buffer; uint32_t x = 0; int n = 0; if(state->tmp.selection.sendpartial) { n = state->tmp.selection.sendpartial >> 24; x = state->tmp.selection.sendpartial & 0xFFFFFF; state->tmp.selection.sendpartial = 0; } while((state->selection.buflen - bufcur) >= 4 && frag.len) { x = (x << 8) | frag.str[0]; n++; frag.str++, frag.len--; if(n == 3) { buffer[0] = base64_one((x >> 18) & 0x3F); buffer[1] = base64_one((x >> 12) & 0x3F); buffer[2] = base64_one((x >> 6) & 0x3F); buffer[3] = base64_one((x >> 0) & 0x3F); buffer += 4, bufcur += 4; x = 0; n = 0; } if(!frag.len || (state->selection.buflen - bufcur) < 4) { if(bufcur) vterm_push_output_bytes(vt, state->selection.buffer, bufcur); buffer = state->selection.buffer; bufcur = 0; } } if(n) state->tmp.selection.sendpartial = (n << 24) | x; } if(frag.final) { if(state->tmp.selection.sendpartial) { int n = state->tmp.selection.sendpartial >> 24; uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF; char *buffer = state->selection.buffer; /* n is either 1 or 2 now */ x <<= (n == 1) ? 16 : 8; buffer[0] = base64_one((x >> 18) & 0x3F); buffer[1] = base64_one((x >> 12) & 0x3F); buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F); buffer[3] = '='; vterm_push_output_sprintf_str(vt, 0, TRUE, "%.*s", 4, buffer); } else vterm_push_output_sprintf_str(vt, 0, TRUE, ""); } }