mirror of
https://github.com/vim/vim.git
synced 2025-07-26 11:04:33 -04:00
2408 lines
62 KiB
C
2408 lines
62 KiB
C
#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;
|
|
uint32_t *codepoints;
|
|
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.
|
|
codepoints = vterm_allocator_malloc(state->vt, (len + 1) * sizeof(uint32_t));
|
|
if (codepoints == NULL)
|
|
return 0;
|
|
|
|
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)len,
|
|
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)
|
|
{
|
|
vterm_allocator_free(state->vt, codepoints);
|
|
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;
|
|
uint32_t *chars;
|
|
|
|
for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
|
|
if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
|
|
break;
|
|
|
|
chars = vterm_allocator_malloc(state->vt, (glyph_ends - glyph_starts + 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;
|
|
}
|
|
|
|
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
|
|
|
|
vterm_allocator_free(state->vt, codepoints);
|
|
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 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;
|
|
}
|
|
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): // xterm resource modifyOtherKeys
|
|
if (argcount == 2 && args[0] == 4)
|
|
state->mode.modify_other_keys = args[1] == 2;
|
|
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, 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;
|
|
size_t i = 0;
|
|
|
|
if(frag.initial)
|
|
tmp[0] = tmp[1] = tmp[2] = tmp[3] = 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;
|
|
int argi;
|
|
|
|
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(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;
|
|
VTermStateFields fields;
|
|
|
|
if(cols != state->cols) {
|
|
int col;
|
|
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 */
|
|
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) {
|
|
int bufidx;
|
|
for(bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
|
|
int row;
|
|
VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
|
|
VTermLineInfo *newlineinfo;
|
|
if(!oldlineinfo)
|
|
continue;
|
|
|
|
newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
|
|
|
|
for(row = 0; row < state->rows && row < rows; row++) {
|
|
newlineinfo[row] = oldlineinfo[row];
|
|
}
|
|
|
|
for( ; row < rows; row++) {
|
|
VTermLineInfo lineInfo = {0x0};
|
|
newlineinfo[row] = lineInfo;
|
|
}
|
|
|
|
vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
|
|
state->lineinfos[bufidx] = newlineinfo;
|
|
}
|
|
|
|
state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
|
|
}
|
|
|
|
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);
|
|
|
|
fields.pos = state->pos;
|
|
|
|
if(state->callbacks && state->callbacks->resize)
|
|
(*state->callbacks->resize)(rows, cols, &fields, state->cbdata);
|
|
|
|
state->pos = fields.pos;
|
|
|
|
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)
|
|
{
|
|
VTermState *state;
|
|
if(vt->state)
|
|
return vt->state;
|
|
|
|
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)
|
|
{
|
|
VTermEncoding *default_enc;
|
|
|
|
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;
|
|
|
|
{
|
|
int col;
|
|
for(col = 0; col < state->cols; col++)
|
|
if(col % 8 == 0)
|
|
set_col_tabstop(state, col);
|
|
else
|
|
clear_col_tabstop(state, col);
|
|
}
|
|
|
|
{
|
|
int row;
|
|
for(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);
|
|
|
|
default_enc = state->vt->mode.utf8 ?
|
|
vterm_lookup_encoding(ENC_UTF8, 'u') :
|
|
vterm_lookup_encoding(ENC_SINGLE_94, 'B');
|
|
|
|
{
|
|
int i;
|
|
for(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) {
|
|
VTermRect rect = { 0, 0, 0, 0 };
|
|
|
|
state->pos.row = 0;
|
|
state->pos.col = 0;
|
|
state->at_phantom = 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, "");
|
|
}
|
|
}
|