A better solution from an ecosystem perspective is to just work with the original protocol. I have modified kitty's escape parser to special case OSC 52 handling without changing its max escape code size. Basically, it works by splitting up OSC 52 escape codes longer than the max size into a series of partial OSC 52 escape codes. These get dispatched to the UI layer where it accumulates them upto the 8MB limit and then sends to clipboard when the partial sequence ends. See https://github.com/ranger/ranger/issues/1861
1573 lines
55 KiB
C
1573 lines
55 KiB
C
/*
|
|
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
|
|
*
|
|
* Distributed under terms of the GPL3 license.
|
|
*/
|
|
|
|
// Need _POSIX_C_SOURCE for strtok_r
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "data-types.h"
|
|
#include "control-codes.h"
|
|
#include "screen.h"
|
|
#include "graphics.h"
|
|
#include "charsets.h"
|
|
#include "monotonic.h"
|
|
#include <time.h>
|
|
|
|
extern PyTypeObject Screen_Type;
|
|
#define EXTENDED_OSC_SENTINEL 0x1bu
|
|
|
|
// utils {{{
|
|
static const uint64_t pow_10_array[] = {
|
|
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000
|
|
};
|
|
|
|
static inline int64_t
|
|
utoi(const uint32_t *buf, unsigned int sz) {
|
|
int64_t ans = 0;
|
|
const uint32_t *p = buf;
|
|
int mult = 1;
|
|
if (sz && *p == '-') {
|
|
mult = -1; p++; sz--;
|
|
}
|
|
// Ignore leading zeros
|
|
while(sz > 0) {
|
|
if (*p == '0') { p++; sz--; }
|
|
else break;
|
|
}
|
|
if (sz < sizeof(pow_10_array)/sizeof(pow_10_array[0])) {
|
|
for (int i = sz-1, j=0; i >= 0; i--, j++) {
|
|
ans += (p[i] - '0') * pow_10_array[j];
|
|
}
|
|
}
|
|
return ans * mult;
|
|
}
|
|
|
|
|
|
static inline const char*
|
|
utf8(char_type codepoint) {
|
|
if (!codepoint) return "";
|
|
static char buf[8];
|
|
int n = encode_utf8(codepoint, buf);
|
|
buf[n] = 0;
|
|
return buf;
|
|
}
|
|
|
|
// }}}
|
|
|
|
// Macros {{{
|
|
#define IS_DIGIT \
|
|
case '0': \
|
|
case '1': \
|
|
case '2': \
|
|
case '3': \
|
|
case '4': \
|
|
case '5': \
|
|
case '6': \
|
|
case '7': \
|
|
case '8': \
|
|
case '9':
|
|
|
|
|
|
#ifdef DUMP_COMMANDS
|
|
static void
|
|
_report_error(PyObject *dump_callback, const char *fmt, ...) {
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
PyObject *temp = PyUnicode_FromFormatV(fmt, argptr);
|
|
va_end(argptr);
|
|
if (temp != NULL) {
|
|
Py_XDECREF(PyObject_CallFunctionObjArgs(dump_callback, temp, NULL)); PyErr_Clear();
|
|
Py_CLEAR(temp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_report_params(PyObject *dump_callback, const char *name, int *params, unsigned int count, Region *r) {
|
|
static char buf[MAX_PARAMS*3] = {0};
|
|
unsigned int i, p=0;
|
|
if (r) p += snprintf(buf + p, sizeof(buf) - 2, "%u %u %u %u ", r->top, r->left, r->bottom, r->right);
|
|
for(i = 0; i < count && p < MAX_PARAMS*3-20; i++) {
|
|
int n = snprintf(buf + p, MAX_PARAMS*3 - p, "%i ", params[i]);
|
|
if (n < 0) break;
|
|
p += n;
|
|
}
|
|
buf[p] = 0;
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "ss", name, buf)); PyErr_Clear();
|
|
}
|
|
|
|
#define DUMP_UNUSED
|
|
|
|
#define REPORT_ERROR(...) _report_error(dump_callback, __VA_ARGS__);
|
|
|
|
#define REPORT_COMMAND1(name) \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "s", #name)); PyErr_Clear();
|
|
|
|
#define REPORT_COMMAND2(name, x) \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "si", #name, (int)x)); PyErr_Clear();
|
|
|
|
#define REPORT_COMMAND3(name, x, y) \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "sii", #name, (int)x, (int)y)); PyErr_Clear();
|
|
|
|
#define GET_MACRO(_1,_2,_3,NAME,...) NAME
|
|
#define REPORT_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__)
|
|
#define REPORT_VA_COMMAND(...) Py_XDECREF(PyObject_CallFunction(dump_callback, __VA_ARGS__)); PyErr_Clear();
|
|
|
|
#define REPORT_DRAW(ch) \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", "draw", ch)); PyErr_Clear();
|
|
|
|
#define REPORT_PARAMS(name, params, num, region) _report_params(dump_callback, name, params, num_params, region)
|
|
|
|
#define FLUSH_DRAW \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", "draw", Py_None)); PyErr_Clear();
|
|
|
|
#define REPORT_OSC(name, string) \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", #name, string)); PyErr_Clear();
|
|
|
|
#define REPORT_OSC2(name, code, string) \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "siO", #name, code, string)); PyErr_Clear();
|
|
|
|
#define REPORT_HYPERLINK(id, url) \
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "szz", "set_active_hyperlink", id, url)); PyErr_Clear();
|
|
|
|
#else
|
|
|
|
#define DUMP_UNUSED UNUSED
|
|
|
|
#define REPORT_ERROR(...) log_error(ERROR_PREFIX " " __VA_ARGS__);
|
|
|
|
#define REPORT_COMMAND(...)
|
|
#define REPORT_VA_COMMAND(...)
|
|
#define REPORT_DRAW(ch)
|
|
#define REPORT_PARAMS(...)
|
|
#define FLUSH_DRAW
|
|
#define REPORT_OSC(name, string)
|
|
#define REPORT_OSC2(name, code, string)
|
|
#define REPORT_HYPERLINK(id, url)
|
|
|
|
#endif
|
|
|
|
#define SET_STATE(state) screen->parser_state = state; screen->parser_buf_pos = 0;
|
|
// }}}
|
|
|
|
// Normal mode {{{
|
|
static void
|
|
screen_nel(Screen *screen) { screen_carriage_return(screen); screen_linefeed(screen); }
|
|
|
|
static void
|
|
dispatch_normal_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
|
|
#define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(name); name(screen); break;
|
|
switch(ch) {
|
|
case BEL:
|
|
CALL_SCREEN_HANDLER(screen_bell);
|
|
case BS:
|
|
CALL_SCREEN_HANDLER(screen_backspace);
|
|
case HT:
|
|
CALL_SCREEN_HANDLER(screen_tab);
|
|
case NEL:
|
|
CALL_SCREEN_HANDLER(screen_nel);
|
|
case LF:
|
|
case VT:
|
|
case FF:
|
|
CALL_SCREEN_HANDLER(screen_linefeed);
|
|
case CR:
|
|
CALL_SCREEN_HANDLER(screen_carriage_return);
|
|
case SI:
|
|
REPORT_COMMAND(screen_change_charset, 0);
|
|
screen_change_charset(screen, 0); break;
|
|
case SO:
|
|
REPORT_COMMAND(screen_change_charset, 1);
|
|
screen_change_charset(screen, 1); break;
|
|
case IND:
|
|
CALL_SCREEN_HANDLER(screen_index);
|
|
case RI:
|
|
CALL_SCREEN_HANDLER(screen_reverse_index);
|
|
case HTS:
|
|
CALL_SCREEN_HANDLER(screen_set_tab_stop);
|
|
case ESC:
|
|
case CSI:
|
|
case OSC:
|
|
case DCS:
|
|
case APC:
|
|
case PM:
|
|
SET_STATE(ch); break;
|
|
case NUL:
|
|
case DEL:
|
|
break; // no-op
|
|
default:
|
|
REPORT_DRAW(ch);
|
|
screen_draw(screen, ch, true);
|
|
break;
|
|
}
|
|
#undef CALL_SCREEN_HANDLER
|
|
} // }}}
|
|
|
|
// Esc mode {{{
|
|
static void
|
|
dispatch_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
|
|
#define CALL_ED(name) REPORT_COMMAND(name); name(screen); SET_STATE(0);
|
|
#define CALL_ED1(name, ch) REPORT_COMMAND(name, ch); name(screen, ch); SET_STATE(0);
|
|
#define CALL_ED2(name, a, b) REPORT_COMMAND(name, a, b); name(screen, a, b); SET_STATE(0);
|
|
switch(screen->parser_buf_pos) {
|
|
case 0:
|
|
switch (ch) {
|
|
case ESC_DCS:
|
|
SET_STATE(DCS); break;
|
|
case ESC_OSC:
|
|
SET_STATE(OSC); break;
|
|
case ESC_CSI:
|
|
SET_STATE(CSI); break;
|
|
case ESC_APC:
|
|
SET_STATE(APC); break;
|
|
case ESC_PM:
|
|
SET_STATE(PM); break;
|
|
case ESC_RIS:
|
|
CALL_ED(screen_reset); break;
|
|
case ESC_IND:
|
|
CALL_ED(screen_index); break;
|
|
case ESC_NEL:
|
|
CALL_ED(screen_nel); break;
|
|
case ESC_RI:
|
|
CALL_ED(screen_reverse_index); break;
|
|
case ESC_HTS:
|
|
CALL_ED(screen_set_tab_stop); break;
|
|
case ESC_DECSC:
|
|
CALL_ED(screen_save_cursor); break;
|
|
case ESC_DECRC:
|
|
CALL_ED(screen_restore_cursor); break;
|
|
case ESC_DECKPNM:
|
|
CALL_ED(screen_normal_keypad_mode); break;
|
|
case ESC_DECKPAM:
|
|
CALL_ED(screen_alternate_keypad_mode); break;
|
|
case '%':
|
|
case '(':
|
|
case ')':
|
|
case '*':
|
|
case '+':
|
|
case '-':
|
|
case '.':
|
|
case '/':
|
|
case ' ':
|
|
case '#':
|
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
|
break;
|
|
default:
|
|
REPORT_ERROR("%s0x%x", "Unknown char after ESC: ", ch);
|
|
SET_STATE(0); break;
|
|
}
|
|
break;
|
|
default:
|
|
switch(screen->parser_buf[0]) {
|
|
case '%':
|
|
switch(ch) {
|
|
case '@':
|
|
REPORT_COMMAND(screen_use_latin1, 1);
|
|
screen_use_latin1(screen, true);
|
|
break;
|
|
case 'G':
|
|
REPORT_COMMAND(screen_use_latin1, 0);
|
|
screen_use_latin1(screen, false);
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Unhandled Esc %% code: 0x%x", ch); break;
|
|
}
|
|
break;
|
|
case '#':
|
|
if (ch == '8') { CALL_ED(screen_align); }
|
|
else { REPORT_ERROR("Unhandled Esc # code: 0x%x", ch); }
|
|
break;
|
|
case '(':
|
|
case ')':
|
|
switch(ch) {
|
|
case 'A':
|
|
case 'B':
|
|
case '0':
|
|
case 'U':
|
|
case 'V':
|
|
CALL_ED2(screen_designate_charset, screen->parser_buf[0] - '(', ch); break;
|
|
default:
|
|
REPORT_ERROR("Unknown charset: 0x%x", ch); break;
|
|
}
|
|
break;
|
|
case ' ':
|
|
switch(ch) {
|
|
case 'F':
|
|
case 'G':
|
|
REPORT_COMMAND(screen_set_8bit_controls, ch == 'G');
|
|
screen_set_8bit_controls(screen, ch == 'G');
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Unhandled ESC SP escape code: 0x%x", ch); break;
|
|
}
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Unhandled charset related escape code: 0x%x 0x%x", screen->parser_buf[0], ch); break;
|
|
}
|
|
SET_STATE(0);
|
|
break;
|
|
}
|
|
#undef CALL_ED
|
|
#undef CALL_ED1
|
|
} // }}}
|
|
|
|
// OSC mode {{{
|
|
|
|
static inline bool
|
|
parse_osc_8(char *buf, char **id, char **url) {
|
|
char *boundary = strstr(buf, ";");
|
|
if (boundary == NULL) return false;
|
|
*boundary = 0;
|
|
if (*(boundary + 1)) *url = boundary + 1;
|
|
char *save = NULL, *token = strtok_r(buf, ":", &save);
|
|
while (token != NULL) {
|
|
size_t len = strlen(token);
|
|
if (len > 3 && token[0] == 'i' && token[1] == 'd' && token[2] == '=' && token[3]) {
|
|
*id = token + 3;
|
|
break;
|
|
}
|
|
token = strtok_r(NULL, ":", &save);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline void
|
|
dispatch_hyperlink(Screen *screen, size_t pos, size_t size, PyObject DUMP_UNUSED *dump_callback) {
|
|
// since the spec says only ASCII printable chars are allowed in OSC 8, we
|
|
// can just convert to char* directly
|
|
if (!size) return; // ignore empty OSC 8 since it must have two semi-colons to be valid, which means one semi-colon here
|
|
char *id = NULL, *url = NULL;
|
|
char *data = malloc(size + 1);
|
|
if (!data) fatal("Out of memory");
|
|
for (size_t i = 0; i < size; i++) {
|
|
data[i] = screen->parser_buf[i + pos] & 0x7f;
|
|
if (data[i] < 32 || data[i] > 126) data[i] = '_';
|
|
}
|
|
data[size] = 0;
|
|
|
|
if (parse_osc_8(data, &id, &url)) {
|
|
REPORT_HYPERLINK(id, url);
|
|
set_active_hyperlink(screen, id, url);
|
|
} else {
|
|
REPORT_ERROR("Ignoring malformed OSC 8 code");
|
|
}
|
|
|
|
free(data);
|
|
}
|
|
|
|
static void
|
|
continue_osc_52(Screen *screen) {
|
|
screen->parser_buf[0] = '5'; screen->parser_buf[1] = '2'; screen->parser_buf[2] = ';';
|
|
screen->parser_buf[3] = ';'; screen->parser_buf_pos = 4;
|
|
}
|
|
|
|
static bool
|
|
is_extended_osc(const Screen *screen) {
|
|
return screen->parser_buf_pos > 2 && screen->parser_buf[0] == EXTENDED_OSC_SENTINEL && screen->parser_buf[1] == 1 && screen->parser_buf[2] == ';';
|
|
}
|
|
|
|
static void
|
|
dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|
#define DISPATCH_OSC_WITH_CODE(name) REPORT_OSC2(name, code, string); name(screen, code, string);
|
|
#define DISPATCH_OSC(name) REPORT_OSC(name, string); name(screen, string);
|
|
#define START_DISPATCH {\
|
|
PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + i, limit - i); \
|
|
if (string) {
|
|
#define END_DISPATCH Py_CLEAR(string); } PyErr_Clear(); break; }
|
|
|
|
const unsigned int limit = screen->parser_buf_pos;
|
|
int code=0;
|
|
unsigned int i;
|
|
for (i = 0; i < MIN(limit, 5u); i++) {
|
|
if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break;
|
|
}
|
|
if (i > 0) {
|
|
code = utoi(screen->parser_buf, i);
|
|
if (i < limit && screen->parser_buf[i] == ';') i++;
|
|
} else {
|
|
if (is_extended_osc(screen)) {
|
|
// partial OSC 52
|
|
i = 3;
|
|
code = -52;
|
|
}
|
|
}
|
|
switch(code) {
|
|
case 0:
|
|
START_DISPATCH
|
|
DISPATCH_OSC(set_title);
|
|
DISPATCH_OSC(set_icon);
|
|
END_DISPATCH
|
|
case 1:
|
|
START_DISPATCH
|
|
DISPATCH_OSC(set_icon);
|
|
END_DISPATCH
|
|
case 2:
|
|
START_DISPATCH
|
|
DISPATCH_OSC(set_title);
|
|
END_DISPATCH
|
|
case 4:
|
|
case 104:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(set_color_table_color);
|
|
END_DISPATCH
|
|
case 7:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(process_cwd_notification);
|
|
END_DISPATCH
|
|
case 8:
|
|
dispatch_hyperlink(screen, i, limit-i, dump_callback);
|
|
break;
|
|
case 9:
|
|
case 99:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(desktop_notify)
|
|
END_DISPATCH
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
case 17:
|
|
case 19:
|
|
case 110:
|
|
case 111:
|
|
case 112:
|
|
case 117:
|
|
case 119:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(set_dynamic_color);
|
|
END_DISPATCH
|
|
case 52:
|
|
case -52:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(clipboard_control);
|
|
if (code == -52) continue_osc_52(screen);
|
|
END_DISPATCH
|
|
case 30001:
|
|
REPORT_COMMAND(screen_push_dynamic_colors);
|
|
screen_push_colors(screen, 0);
|
|
break;
|
|
case 30101:
|
|
REPORT_COMMAND(screen_pop_dynamic_colors);
|
|
screen_pop_colors(screen, 0);
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Unknown OSC code: %u", code);
|
|
break;
|
|
}
|
|
#undef DISPATCH_OSC
|
|
#undef DISPATCH_OSC_WITH_CODE
|
|
#undef START_DISPATCH
|
|
#undef END_DISPATCH
|
|
}
|
|
// }}}
|
|
|
|
// CSI mode {{{
|
|
// As per ECMA 48 section 5.4 secondary byte is column 02 of the 7-bit ascii table
|
|
#define CSI_SECONDARY \
|
|
case ' ': \
|
|
case '!': \
|
|
case '"': \
|
|
case '#': \
|
|
case '$': \
|
|
case '%': \
|
|
case '&': \
|
|
case '\'': \
|
|
case '(': \
|
|
case ')': \
|
|
case '*': \
|
|
case '+': \
|
|
case ',': \
|
|
case '-': \
|
|
case '.': \
|
|
case '/':
|
|
|
|
|
|
static inline void
|
|
screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, false, -1); }
|
|
static inline void
|
|
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
|
|
static inline void
|
|
screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1u, count); i++) screen_tab(s); }
|
|
|
|
static inline const char*
|
|
repr_csi_params(int *params, unsigned int num_params) {
|
|
if (!num_params) return "";
|
|
static char buf[256];
|
|
unsigned int pos = 0, i = 0;
|
|
while (pos < 200 && i++ < num_params && sizeof(buf) > pos + 1) {
|
|
const char *fmt = i < num_params ? "%i, " : "%i";
|
|
int ret = snprintf(buf + pos, sizeof(buf) - pos - 1, fmt, params[i-1]);
|
|
if (ret < 0) return "An error occurred formatting the params array";
|
|
pos += ret;
|
|
}
|
|
buf[pos] = 0;
|
|
return buf;
|
|
}
|
|
|
|
#ifdef DUMP_COMMANDS
|
|
static
|
|
#endif
|
|
void
|
|
parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, int *params, PyObject DUMP_UNUSED *dump_callback, const char *report_name DUMP_UNUSED, Region *region) {
|
|
enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 };
|
|
enum State state = START;
|
|
unsigned int num_params, num_start, i;
|
|
|
|
#define READ_PARAM { params[num_params++] = utoi(buf + num_start, i - num_start); }
|
|
#define SEND_SGR { REPORT_PARAMS(report_name, params, num_params, region); select_graphic_rendition(screen, params, num_params, region); state = START; num_params = 0; }
|
|
|
|
for (i=0, num_start=0, num_params=0; i < num && num_params < MAX_PARAMS; i++) {
|
|
switch(buf[i]) {
|
|
IS_DIGIT
|
|
switch(state) {
|
|
case START:
|
|
num_start = i;
|
|
state = NORMAL;
|
|
num_params = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ';':
|
|
switch(state) {
|
|
case START:
|
|
params[num_params++] = 0;
|
|
SEND_SGR;
|
|
break;
|
|
case NORMAL:
|
|
READ_PARAM;
|
|
switch(params[0]) {
|
|
case 38:
|
|
case 48:
|
|
case 58:
|
|
state = COLOR;
|
|
num_start = i + 1;
|
|
break;
|
|
default:
|
|
SEND_SGR;
|
|
break;
|
|
}
|
|
break;
|
|
case MULTIPLE:
|
|
READ_PARAM;
|
|
SEND_SGR;
|
|
break;
|
|
case COLOR:
|
|
READ_PARAM;
|
|
switch(params[1]) {
|
|
case 2:
|
|
state = COLOR3;
|
|
break;
|
|
case 5:
|
|
state = COLOR1;
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Invalid SGR color code with unknown color type: %u", params[1]);
|
|
return;
|
|
}
|
|
num_start = i + 1;
|
|
break;
|
|
case COLOR1:
|
|
READ_PARAM;
|
|
SEND_SGR;
|
|
break;
|
|
case COLOR3:
|
|
READ_PARAM;
|
|
if (num_params == 5) { SEND_SGR; }
|
|
else num_start = i + 1;
|
|
break;
|
|
}
|
|
break;
|
|
case ':':
|
|
switch(state) {
|
|
case START:
|
|
REPORT_ERROR("Invalid SGR code containing ':' at an invalid location: %u", i);
|
|
return;
|
|
case NORMAL:
|
|
READ_PARAM;
|
|
state = MULTIPLE;
|
|
num_start = i + 1;
|
|
break;
|
|
case MULTIPLE:
|
|
READ_PARAM;
|
|
num_start = i + 1;
|
|
break;
|
|
case COLOR:
|
|
case COLOR1:
|
|
case COLOR3:
|
|
REPORT_ERROR("Invalid SGR code containing disallowed character: %s", utf8(buf[i]));
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Invalid SGR code containing disallowed character: %s", utf8(buf[i]));
|
|
return;
|
|
}
|
|
}
|
|
switch(state) {
|
|
case START:
|
|
if (num_params < MAX_PARAMS) params[num_params++] = 0;
|
|
SEND_SGR;
|
|
break;
|
|
case COLOR1:
|
|
case NORMAL:
|
|
case MULTIPLE:
|
|
if (i > num_start && num_params < MAX_PARAMS) { READ_PARAM; }
|
|
if (num_params) { SEND_SGR; }
|
|
else { REPORT_ERROR("Incomplete SGR code"); }
|
|
break;
|
|
case COLOR:
|
|
REPORT_ERROR("Invalid SGR code containing incomplete semi-colon separated color sequence");
|
|
break;
|
|
case COLOR3:
|
|
if (i > num_start && num_params < MAX_PARAMS) READ_PARAM;
|
|
if (num_params != 5) {
|
|
REPORT_ERROR("Invalid SGR code containing incomplete semi-colon separated color sequence");
|
|
break;
|
|
}
|
|
if (num_params) { SEND_SGR; }
|
|
else { REPORT_ERROR("Incomplete SGR code"); }
|
|
break;
|
|
}
|
|
#undef READ_PARAM
|
|
#undef SEND_SGR
|
|
}
|
|
|
|
static inline unsigned int
|
|
parse_region(Region *r, uint32_t *buf, unsigned int num) {
|
|
unsigned int i, start, num_params = 0;
|
|
int params[8] = {0};
|
|
for (i=0, start=0; i < num && num_params < 4; i++) {
|
|
switch(buf[i]) {
|
|
IS_DIGIT
|
|
break;
|
|
default:
|
|
if (i > start) params[num_params++] = utoi(buf + start, i - start);
|
|
else if (i == start && buf[i] == ';') params[num_params++] = 0;
|
|
start = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch(num_params) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
r->top = params[0];
|
|
break;
|
|
case 2:
|
|
r->top = params[0]; r->left = params[1];
|
|
break;
|
|
case 3:
|
|
r->top = params[0]; r->left = params[1]; r->bottom = params[2];
|
|
break;
|
|
default:
|
|
r->top = params[0]; r->left = params[1]; r->bottom = params[2]; r->right = params[3];
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static inline const char*
|
|
csi_letter(unsigned code) {
|
|
static char buf[8];
|
|
if (33 <= code && code <= 126) snprintf(buf, sizeof(buf), "%c", code);
|
|
else snprintf(buf, sizeof(buf), "0x%x", code);
|
|
return buf;
|
|
}
|
|
|
|
static inline void
|
|
dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|
#define AT_MOST_ONE_PARAMETER { \
|
|
if (num_params > 1) { \
|
|
REPORT_ERROR("CSI code %s has %u > 1 parameters", csi_letter(code), num_params); \
|
|
break; \
|
|
} \
|
|
}
|
|
#define NON_NEGATIVE_PARAM(x) { \
|
|
if (x < 0) { \
|
|
REPORT_ERROR("CSI code %s is not allowed to have negative parameter (%d)", csi_letter(code), x); \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
#define CALL_CSI_HANDLER1(name, defval) \
|
|
AT_MOST_ONE_PARAMETER; \
|
|
p1 = num_params > 0 ? params[0] : defval; \
|
|
NON_NEGATIVE_PARAM(p1); \
|
|
REPORT_COMMAND(name, p1); \
|
|
name(screen, p1); \
|
|
break;
|
|
|
|
#define CALL_CSI_HANDLER1P(name, defval, qch) \
|
|
AT_MOST_ONE_PARAMETER; \
|
|
p1 = num_params > 0 ? params[0] : defval; \
|
|
NON_NEGATIVE_PARAM(p1); \
|
|
private = start_modifier == qch; \
|
|
REPORT_COMMAND(name, p1, private); \
|
|
name(screen, p1, private); \
|
|
break;
|
|
|
|
#define CALL_CSI_HANDLER1S(name, defval) \
|
|
AT_MOST_ONE_PARAMETER; \
|
|
p1 = num_params > 0 ? params[0] : defval; \
|
|
NON_NEGATIVE_PARAM(p1); \
|
|
REPORT_COMMAND(name, p1, start_modifier); \
|
|
name(screen, p1, start_modifier); \
|
|
break;
|
|
|
|
#define CALL_CSI_HANDLER1M(name, defval) \
|
|
AT_MOST_ONE_PARAMETER; \
|
|
p1 = num_params > 0 ? params[0] : defval; \
|
|
NON_NEGATIVE_PARAM(p1); \
|
|
REPORT_COMMAND(name, p1, end_modifier); \
|
|
name(screen, p1, end_modifier); \
|
|
break;
|
|
|
|
#define CALL_CSI_HANDLER2(name, defval1, defval2) \
|
|
if (num_params > 2) { \
|
|
REPORT_ERROR("CSI code %s has %u > 2 parameters", csi_letter(code), num_params); \
|
|
break; \
|
|
} \
|
|
p1 = num_params > 0 ? params[0] : defval1; \
|
|
p2 = num_params > 1 ? params[1] : defval2; \
|
|
NON_NEGATIVE_PARAM(p1); \
|
|
NON_NEGATIVE_PARAM(p2); \
|
|
REPORT_COMMAND(name, p1, p2); \
|
|
name(screen, p1, p2); \
|
|
break;
|
|
|
|
#define SET_MODE(func) \
|
|
p1 = start_modifier == '?' ? 5 : 0; \
|
|
for (i = 0; i < num_params; i++) { \
|
|
NON_NEGATIVE_PARAM(params[i]); \
|
|
REPORT_COMMAND(func, params[i], start_modifier == '?'); \
|
|
func(screen, params[i] << p1); \
|
|
} \
|
|
break;
|
|
|
|
#define NO_MODIFIERS(modifier, special, special_msg) { \
|
|
if (start_modifier || end_modifier) { \
|
|
if (special && modifier == special) { REPORT_ERROR(special_msg); } \
|
|
else { REPORT_ERROR("CSI code %s has unsupported start modifier: %s or end modifier: %s", csi_letter(code), csi_letter(start_modifier), csi_letter(end_modifier));} \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
char start_modifier = 0, end_modifier = 0;
|
|
uint32_t *buf = screen->parser_buf, code = screen->parser_buf[screen->parser_buf_pos];
|
|
unsigned int num = screen->parser_buf_pos, start, i, num_params=0;
|
|
static int params[MAX_PARAMS] = {0}, p1, p2;
|
|
bool private;
|
|
if (buf[0] == '>' || buf[0] == '<' || buf[0] == '?' || buf[0] == '!' || buf[0] == '=') {
|
|
start_modifier = (char)screen->parser_buf[0];
|
|
buf++; num--;
|
|
}
|
|
if (num > 0) {
|
|
switch(buf[num-1]) {
|
|
CSI_SECONDARY
|
|
end_modifier = (char)buf[--num];
|
|
break;
|
|
}
|
|
}
|
|
if (code == SGR && !start_modifier && !end_modifier) {
|
|
parse_sgr(screen, buf, num, params, dump_callback, "select_graphic_rendition", NULL);
|
|
return;
|
|
}
|
|
if (code == 'r' && !start_modifier && end_modifier == '$') {
|
|
// DECCARA
|
|
Region r = {0};
|
|
unsigned int consumed = parse_region(&r, buf, num);
|
|
num -= consumed; buf += consumed;
|
|
parse_sgr(screen, buf, num, params, dump_callback, "deccara", &r);
|
|
return;
|
|
}
|
|
|
|
for (i=0, start=0; i < num; i++) {
|
|
switch(buf[i]) {
|
|
IS_DIGIT
|
|
break;
|
|
case '-':
|
|
if (i > start) {
|
|
REPORT_ERROR("CSI code can contain hyphens only at the start of numbers");
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
if (i > start) params[num_params++] = utoi(buf + start, i - start);
|
|
else if (i == start && buf[i] == ';') params[num_params++] = 0;
|
|
if (num_params >= MAX_PARAMS) { i = num; start = num + 1; }
|
|
else { start = i + 1; break; }
|
|
}
|
|
}
|
|
if (i > start) params[num_params++] = utoi(buf + start, i - start);
|
|
switch(code) {
|
|
case ICH:
|
|
NO_MODIFIERS(end_modifier, ' ', "Shift left escape code not implemented");
|
|
CALL_CSI_HANDLER1(screen_insert_characters, 1);
|
|
case REP:
|
|
CALL_CSI_HANDLER1(screen_repeat_character, 1);
|
|
case CUU:
|
|
NO_MODIFIERS(end_modifier, ' ', "Shift right escape code not implemented");
|
|
CALL_CSI_HANDLER1(screen_cursor_up2, 1);
|
|
case CUD:
|
|
case VPR:
|
|
CALL_CSI_HANDLER1(screen_cursor_down, 1);
|
|
case CUF:
|
|
case HPR:
|
|
CALL_CSI_HANDLER1(screen_cursor_forward, 1);
|
|
case CUB:
|
|
CALL_CSI_HANDLER1(screen_cursor_back1, 1);
|
|
case CNL:
|
|
CALL_CSI_HANDLER1(screen_cursor_down1, 1);
|
|
case CPL:
|
|
CALL_CSI_HANDLER1(screen_cursor_up1, 1);
|
|
case CHA:
|
|
case HPA:
|
|
CALL_CSI_HANDLER1(screen_cursor_to_column, 1);
|
|
case VPA:
|
|
CALL_CSI_HANDLER1(screen_cursor_to_line, 1);
|
|
case CBT:
|
|
CALL_CSI_HANDLER1(screen_backtab, 1);
|
|
case CHT:
|
|
CALL_CSI_HANDLER1(screen_tabn, 1);
|
|
case CUP:
|
|
case HVP:
|
|
CALL_CSI_HANDLER2(screen_cursor_position, 1, 1);
|
|
case ED:
|
|
CALL_CSI_HANDLER1P(screen_erase_in_display, 0, '?');
|
|
case EL:
|
|
CALL_CSI_HANDLER1P(screen_erase_in_line, 0, '?');
|
|
case IL:
|
|
CALL_CSI_HANDLER1(screen_insert_lines, 1);
|
|
case DL:
|
|
CALL_CSI_HANDLER1(screen_delete_lines, 1);
|
|
case DCH:
|
|
if (end_modifier == '#' && !start_modifier) {
|
|
CALL_CSI_HANDLER1(screen_push_colors, 0);
|
|
} else {
|
|
CALL_CSI_HANDLER1(screen_delete_characters, 1);
|
|
}
|
|
case 'Q':
|
|
if (end_modifier == '#' && !start_modifier) { CALL_CSI_HANDLER1(screen_pop_colors, 0); }
|
|
REPORT_ERROR("Unknown CSI Q sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
|
break;
|
|
case 'R':
|
|
if (end_modifier == '#' && !start_modifier) {
|
|
REPORT_COMMAND(screen_report_color_stack);
|
|
screen_report_color_stack(screen);
|
|
break;
|
|
}
|
|
REPORT_ERROR("Unknown CSI R sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
|
break;
|
|
case ECH:
|
|
CALL_CSI_HANDLER1(screen_erase_characters, 1);
|
|
case DA:
|
|
CALL_CSI_HANDLER1S(report_device_attributes, 0);
|
|
case TBC:
|
|
CALL_CSI_HANDLER1(screen_clear_tab_stop, 0);
|
|
case SM:
|
|
SET_MODE(screen_set_mode);
|
|
case RM:
|
|
SET_MODE(screen_reset_mode);
|
|
case DSR:
|
|
CALL_CSI_HANDLER1P(report_device_status, 0, '?');
|
|
case 's':
|
|
if (!start_modifier && !end_modifier && !num_params) {
|
|
REPORT_COMMAND(screen_save_cursor);
|
|
screen_save_cursor(screen);
|
|
break;
|
|
} else if (start_modifier == '?' && !end_modifier && !num_params) {
|
|
REPORT_COMMAND(screen_save_modes);
|
|
screen_save_modes(screen);
|
|
break;
|
|
}
|
|
REPORT_ERROR("Unknown CSI s sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
|
break;
|
|
case 't':
|
|
if (!num_params) {
|
|
REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c' and no parameters", start_modifier, end_modifier);
|
|
break;
|
|
}
|
|
if (start_modifier || end_modifier) {
|
|
REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c', %u parameters and first parameter: %d", start_modifier, end_modifier, num_params, params[0]);
|
|
break;
|
|
}
|
|
switch(params[0]) {
|
|
case 4:
|
|
case 8:
|
|
log_error("Escape codes to resize text area are not supported");
|
|
break;
|
|
case 14:
|
|
case 16:
|
|
case 18:
|
|
CALL_CSI_HANDLER1(screen_report_size, 0);
|
|
break;
|
|
case 22:
|
|
case 23:
|
|
CALL_CSI_HANDLER2(screen_manipulate_title_stack, 22, 0);
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Unknown CSI t window manipulation sequence with %u parameters and first parameter: %d", num_params, params[0]);
|
|
break;
|
|
}
|
|
break;
|
|
case 'u':
|
|
if (!start_modifier && !end_modifier && !num_params) {
|
|
REPORT_COMMAND(screen_restore_cursor);
|
|
screen_restore_cursor(screen);
|
|
break;
|
|
}
|
|
if (!end_modifier && start_modifier == '?') {
|
|
REPORT_COMMAND(screen_report_key_encoding_flags);
|
|
screen_report_key_encoding_flags(screen);
|
|
break;
|
|
}
|
|
if (!end_modifier && start_modifier == '=') {
|
|
CALL_CSI_HANDLER2(screen_set_key_encoding_flags, 0, 1);
|
|
break;
|
|
}
|
|
if (!end_modifier && start_modifier == '>') {
|
|
CALL_CSI_HANDLER1(screen_push_key_encoding_flags, 0);
|
|
break;
|
|
}
|
|
if (!end_modifier && start_modifier == '<') {
|
|
CALL_CSI_HANDLER1(screen_pop_key_encoding_flags, 1);
|
|
break;
|
|
}
|
|
REPORT_ERROR("Unknown CSI u sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
|
break;
|
|
case 'r':
|
|
if (!start_modifier && !end_modifier) {
|
|
// DECSTBM
|
|
CALL_CSI_HANDLER2(screen_set_margins, 0, 0);
|
|
} else if (start_modifier == '?' && !end_modifier && !num_params) {
|
|
REPORT_COMMAND(screen_restore_modes);
|
|
screen_restore_modes(screen);
|
|
break;
|
|
}
|
|
REPORT_ERROR("Unknown CSI r sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
|
break;
|
|
case 'x':
|
|
if (!start_modifier && end_modifier == '*') {
|
|
CALL_CSI_HANDLER1(screen_decsace, 0);
|
|
}
|
|
REPORT_ERROR("Unknown CSI x sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
|
|
break;
|
|
case DECSCUSR:
|
|
if (!start_modifier && end_modifier == ' ') {
|
|
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
|
|
}
|
|
if (start_modifier == '>' && !end_modifier) {
|
|
CALL_CSI_HANDLER1(screen_xtversion, 0);
|
|
}
|
|
REPORT_ERROR("Unknown CSI q sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
|
|
break;
|
|
case SU:
|
|
NO_MODIFIERS(end_modifier, ' ', "Select presentation directions escape code not implemented");
|
|
CALL_CSI_HANDLER1(screen_scroll, 1);
|
|
case SD:
|
|
if (!start_modifier && end_modifier == '+') {
|
|
CALL_CSI_HANDLER1(screen_reverse_scroll_and_fill_from_scrollback, 1);
|
|
} else {
|
|
NO_MODIFIERS(start_modifier, 0, "");
|
|
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);
|
|
}
|
|
break;
|
|
case DECSTR:
|
|
if (end_modifier == '$') {
|
|
// DECRQM
|
|
CALL_CSI_HANDLER1P(report_mode_status, 0, '?');
|
|
} else {
|
|
REPORT_ERROR("Unknown DECSTR CSI sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
|
|
}
|
|
break;
|
|
case 'm':
|
|
if (start_modifier == '>' && !end_modifier) {
|
|
CALL_CSI_HANDLER2(screen_xtmodkeys, 0, 0);
|
|
break;
|
|
}
|
|
/* fallthrough */
|
|
default:
|
|
REPORT_ERROR("Unknown CSI code: '%s' with start_modifier: '%c' and end_modifier: '%c' and parameters: '%s'", utf8(code), start_modifier, end_modifier, repr_csi_params(params, num_params));
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
// DCS mode {{{
|
|
|
|
static inline bool
|
|
startswith(const uint32_t *string, size_t sz, const char *prefix) {
|
|
size_t l = strlen(prefix);
|
|
if (sz < l) return false;
|
|
for (size_t i = 0; i < l; i++) {
|
|
if (string[i] != (unsigned char)prefix[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define PENDING_MODE_CHAR '='
|
|
|
|
static inline void
|
|
dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|
if (screen->parser_buf_pos < 2) return;
|
|
switch(screen->parser_buf[0]) {
|
|
case '+':
|
|
case '$':
|
|
if (screen->parser_buf[1] == 'q') {
|
|
PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 2, screen->parser_buf_pos - 2);
|
|
if (string != NULL) {
|
|
REPORT_OSC2(screen_request_capabilities, (char)screen->parser_buf[0], string);
|
|
screen_request_capabilities(screen, (char)screen->parser_buf[0], string);
|
|
Py_DECREF(string);
|
|
} else PyErr_Clear();
|
|
} else {
|
|
REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]);
|
|
}
|
|
break;
|
|
case PENDING_MODE_CHAR:
|
|
if (screen->parser_buf_pos > 2 && (screen->parser_buf[1] == '1' || screen->parser_buf[1] == '2') && screen->parser_buf[2] == 's') {
|
|
if (screen->parser_buf[1] == '1') {
|
|
screen->pending_mode.activated_at = monotonic();
|
|
REPORT_COMMAND(screen_start_pending_mode);
|
|
} else {
|
|
// ignore stop without matching start, see queue_pending_bytes()
|
|
// for how stop is detected while in pending mode.
|
|
REPORT_ERROR("Pending mode stop command issued while not in pending mode, this can"
|
|
" be either a bug in the terminal application or caused by a timeout with no data"
|
|
" received for too long or by too much data in pending mode");
|
|
REPORT_COMMAND(screen_stop_pending_mode);
|
|
}
|
|
} else {
|
|
REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]);
|
|
}
|
|
break;
|
|
case '@':
|
|
#define CMD_PREFIX "kitty-cmd{"
|
|
if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 2, CMD_PREFIX)) {
|
|
PyObject *cmd = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 10, screen->parser_buf_pos - 10);
|
|
if (cmd != NULL) {
|
|
REPORT_OSC2(screen_handle_cmd, (char)screen->parser_buf[0], cmd);
|
|
screen_handle_cmd(screen, cmd);
|
|
Py_DECREF(cmd);
|
|
} else PyErr_Clear();
|
|
#undef CMD_PREFIX
|
|
#define PRINT_PREFIX "kitty-print|"
|
|
} else if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 1, PRINT_PREFIX)) {
|
|
const size_t pp_size = sizeof(PRINT_PREFIX);
|
|
PyObject *msg = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + pp_size, screen->parser_buf_pos - pp_size);
|
|
if (msg != NULL) {
|
|
REPORT_OSC2(screen_handle_print, (char)screen->parser_buf[0], msg);
|
|
screen_handle_print(screen, msg);
|
|
Py_DECREF(msg);
|
|
} else PyErr_Clear();
|
|
} else {
|
|
REPORT_ERROR("Unrecognized DCS @ code: 0x%x", screen->parser_buf[1]);
|
|
}
|
|
break;
|
|
#undef PRINT_PREFIX
|
|
default:
|
|
REPORT_ERROR("Unrecognized DCS code: 0x%x", screen->parser_buf[0]);
|
|
break;
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
// APC mode {{{
|
|
|
|
#include "parse-graphics-command.h"
|
|
|
|
static inline void
|
|
dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|
if (screen->parser_buf_pos < 2) return;
|
|
switch(screen->parser_buf[0]) {
|
|
case 'G':
|
|
parse_graphics_code(screen, dump_callback);
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Unrecognized APC code: 0x%x", screen->parser_buf[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
|
|
// PM mode {{{
|
|
static inline void
|
|
dispatch_pm(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|
if (screen->parser_buf_pos < 2) return;
|
|
switch(screen->parser_buf[0]) {
|
|
default:
|
|
REPORT_ERROR("Unrecognized PM code: 0x%x", screen->parser_buf[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// }}}
|
|
|
|
// Parse loop {{{
|
|
|
|
static bool
|
|
handle_extended_osc_code(Screen *screen) {
|
|
// Handle extra long OSC 52 codes
|
|
if (screen->parser_buf[0] != '5' || screen->parser_buf[1] != '2' || screen->parser_buf[2] != ';') return false;
|
|
screen->parser_buf[0] = EXTENDED_OSC_SENTINEL; screen->parser_buf[1] = 1;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback, bool *extended_osc_code) {
|
|
switch(ch) {
|
|
case ST:
|
|
return true;
|
|
case BEL:
|
|
return true;
|
|
case NUL:
|
|
case DEL:
|
|
break;
|
|
case ESC_ST:
|
|
if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos - 1] == ESC) {
|
|
screen->parser_buf_pos--;
|
|
return true;
|
|
}
|
|
/* fallthrough */
|
|
default:
|
|
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
|
if (handle_extended_osc_code(screen)) *extended_osc_code = true;
|
|
else REPORT_ERROR("OSC sequence too long, truncating.");
|
|
return true;
|
|
}
|
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline bool
|
|
accumulate_dcs(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
|
|
switch(ch) {
|
|
case ST:
|
|
return true;
|
|
case NUL:
|
|
case DEL:
|
|
break;
|
|
case ESC:
|
|
START_ALLOW_CASE_RANGE
|
|
case 32 ... 126:
|
|
END_ALLOW_CASE_RANGE
|
|
if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos-1] == ESC) {
|
|
if (ch == '\\') { screen->parser_buf_pos--; return true; }
|
|
REPORT_ERROR("DCS sequence contained ESC without trailing \\ ignoring the sequence");
|
|
SET_STATE(ESC); return false;
|
|
}
|
|
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
|
REPORT_ERROR("DCS sequence too long, truncating.");
|
|
return true;
|
|
}
|
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
|
break;
|
|
default:
|
|
REPORT_ERROR("DCS sequence contained non-printable character: 0x%x ignoring the sequence", ch);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static inline bool
|
|
accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
|
|
switch(ch) {
|
|
case ST:
|
|
return true;
|
|
case DEL:
|
|
case NUL:
|
|
break;
|
|
case ESC_ST:
|
|
if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos - 1] == ESC) {
|
|
screen->parser_buf_pos--;
|
|
return true;
|
|
}
|
|
/* fallthrough */
|
|
default:
|
|
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
|
REPORT_ERROR("OTH sequence too long, truncating.");
|
|
return true;
|
|
}
|
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static inline bool
|
|
accumulate_csi(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
|
|
#define ENSURE_SPACE \
|
|
if (screen->parser_buf_pos > PARSER_BUF_SZ - 1) { \
|
|
REPORT_ERROR("CSI sequence too long, ignoring"); \
|
|
SET_STATE(0); \
|
|
return false; \
|
|
}
|
|
|
|
switch(ch) {
|
|
IS_DIGIT
|
|
CSI_SECONDARY
|
|
case ':':
|
|
case ';':
|
|
ENSURE_SPACE;
|
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
|
break;
|
|
case '?':
|
|
case '>':
|
|
case '<':
|
|
case '=':
|
|
if (screen->parser_buf_pos != 0) {
|
|
REPORT_ERROR("Invalid character in CSI: 0x%x, ignoring the sequence", ch);
|
|
SET_STATE(0);
|
|
return false;
|
|
}
|
|
ENSURE_SPACE;
|
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
|
break;
|
|
START_ALLOW_CASE_RANGE
|
|
case 'a' ... 'z':
|
|
case 'A' ... 'Z':
|
|
END_ALLOW_CASE_RANGE
|
|
case '@':
|
|
case '`':
|
|
case '{':
|
|
case '|':
|
|
case '}':
|
|
case '~':
|
|
screen->parser_buf[screen->parser_buf_pos] = ch;
|
|
return true;
|
|
case BEL:
|
|
case BS:
|
|
case HT:
|
|
case LF:
|
|
case VT:
|
|
case FF:
|
|
case NEL:
|
|
case CR:
|
|
case SO:
|
|
case SI:
|
|
case IND:
|
|
case RI:
|
|
case HTS:
|
|
dispatch_normal_mode_char(screen, ch, dump_callback);
|
|
break;
|
|
case NUL:
|
|
case DEL:
|
|
SET_STATE(0);
|
|
break; // no-op
|
|
default:
|
|
REPORT_ERROR("Invalid character in CSI: 0x%x, ignoring the sequence", ch);
|
|
SET_STATE(0);
|
|
return false;
|
|
|
|
}
|
|
return false;
|
|
#undef ENSURE_SPACE
|
|
}
|
|
|
|
#define dispatch_unicode_char(codepoint, dispatch, watch_for_pending) { \
|
|
switch(screen->parser_state) { \
|
|
case ESC: \
|
|
dispatch##_esc_mode_char(screen, codepoint, dump_callback); \
|
|
break; \
|
|
case CSI: \
|
|
if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch##_csi(screen, dump_callback); SET_STATE(0); watch_for_pending; } \
|
|
break; \
|
|
case OSC: \
|
|
{ \
|
|
bool extended_osc_code = false; \
|
|
if (accumulate_osc(screen, codepoint, dump_callback, &extended_osc_code)) { \
|
|
dispatch##_osc(screen, dump_callback); \
|
|
if (extended_osc_code) { \
|
|
if (accumulate_osc(screen, codepoint, dump_callback, &extended_osc_code)) { dispatch##_osc(screen, dump_callback); SET_STATE(0); } \
|
|
} else { SET_STATE(0); } \
|
|
} \
|
|
} \
|
|
break; \
|
|
case APC: \
|
|
if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch##_apc(screen, dump_callback); SET_STATE(0); } \
|
|
break; \
|
|
case PM: \
|
|
if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch##_pm(screen, dump_callback); SET_STATE(0); } \
|
|
break; \
|
|
case DCS: \
|
|
if (accumulate_dcs(screen, codepoint, dump_callback)) { dispatch##_dcs(screen, dump_callback); SET_STATE(0); watch_for_pending; } \
|
|
if (screen->parser_state == ESC) { dispatch##_esc_mode_char(screen, codepoint, dump_callback); } \
|
|
break; \
|
|
default: \
|
|
dispatch##_normal_mode_char(screen, codepoint, dump_callback); \
|
|
break; \
|
|
} \
|
|
} \
|
|
|
|
extern uint32_t *latin1_charset;
|
|
|
|
#define decode_loop(dispatch, watch_for_pending) { \
|
|
i = 0; \
|
|
uint32_t prev = screen->utf8_state; \
|
|
while(i < (size_t)len) { \
|
|
uint8_t ch = buf[i++]; \
|
|
if (screen->use_latin1) { \
|
|
dispatch_unicode_char(latin1_charset[ch], dispatch, watch_for_pending); \
|
|
} else { \
|
|
switch (decode_utf8(&screen->utf8_state, &screen->utf8_codepoint, ch)) { \
|
|
case UTF8_ACCEPT: \
|
|
dispatch_unicode_char(screen->utf8_codepoint, dispatch, watch_for_pending); \
|
|
break; \
|
|
case UTF8_REJECT: \
|
|
screen->utf8_state = UTF8_ACCEPT; \
|
|
if (prev != UTF8_ACCEPT && i > 0) i--; \
|
|
break; \
|
|
} \
|
|
prev = screen->utf8_state; \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
static inline void
|
|
_parse_bytes(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
|
|
unsigned int i;
|
|
decode_loop(dispatch, ;);
|
|
FLUSH_DRAW;
|
|
}
|
|
|
|
static inline size_t
|
|
_parse_bytes_watching_for_pending(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
|
|
unsigned int i;
|
|
decode_loop(dispatch, if (screen->pending_mode.activated_at) goto end);
|
|
end:
|
|
FLUSH_DRAW;
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
write_pending_char(Screen *screen, uint32_t ch) {
|
|
screen->pending_mode.used += encode_utf8(ch, (char*)screen->pending_mode.buf + screen->pending_mode.used);
|
|
}
|
|
|
|
static void
|
|
pending_normal_mode_char(Screen *screen, uint32_t ch, PyObject *dump_callback UNUSED) {
|
|
switch(ch) {
|
|
case ESC: case CSI: case OSC: case DCS: case APC: case PM:
|
|
SET_STATE(ch); break;
|
|
default:
|
|
write_pending_char(screen, ch); break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pending_esc_mode_char(Screen *screen, uint32_t ch, PyObject *dump_callback UNUSED) {
|
|
if (screen->parser_buf_pos > 0) {
|
|
write_pending_char(screen, ESC);
|
|
write_pending_char(screen, screen->parser_buf[screen->parser_buf_pos - 1]);
|
|
write_pending_char(screen, ch);
|
|
SET_STATE(0);
|
|
return;
|
|
}
|
|
switch (ch) {
|
|
case ESC_DCS:
|
|
SET_STATE(DCS); break;
|
|
case ESC_OSC:
|
|
SET_STATE(OSC); break;
|
|
case ESC_CSI:
|
|
SET_STATE(CSI); break;
|
|
case ESC_APC:
|
|
SET_STATE(APC); break;
|
|
case ESC_PM:
|
|
SET_STATE(PM); break;
|
|
case '%':
|
|
case '(':
|
|
case ')':
|
|
case '*':
|
|
case '+':
|
|
case '-':
|
|
case '.':
|
|
case '/':
|
|
case ' ':
|
|
case '#':
|
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
|
break;
|
|
default:
|
|
write_pending_char(screen, ESC); write_pending_char(screen, ch);
|
|
SET_STATE(0); break;
|
|
}
|
|
}
|
|
|
|
#define pb(i) screen->parser_buf[i]
|
|
static void
|
|
pending_escape_code(Screen *screen, char_type start_ch, char_type end_ch) {
|
|
write_pending_char(screen, start_ch);
|
|
for (unsigned i = 0; i < screen->parser_buf_pos; i++) write_pending_char(screen, pb(i));
|
|
write_pending_char(screen, end_ch);
|
|
}
|
|
|
|
static void pending_pm(Screen *screen, PyObject *dump_callback UNUSED) { pending_escape_code(screen, PM, ST); }
|
|
static void pending_apc(Screen *screen, PyObject *dump_callback UNUSED) { pending_escape_code(screen, APC, ST); }
|
|
|
|
static void
|
|
pending_osc(Screen *screen, PyObject *dump_callback UNUSED) {
|
|
const bool extended = is_extended_osc(screen);
|
|
pending_escape_code(screen, OSC, ST);
|
|
if (extended) continue_osc_52(screen);
|
|
}
|
|
|
|
|
|
static void
|
|
pending_dcs(Screen *screen, PyObject *dump_callback DUMP_UNUSED) {
|
|
if (screen->parser_buf_pos >= 3 && pb(0) == '=' && (pb(1) == '1' || pb(1) == '2') && pb(2) == 's') {
|
|
screen->pending_mode.activated_at = pb(1) == '1' ? monotonic() : 0;
|
|
if (pb(1) == '1') {
|
|
REPORT_COMMAND(screen_start_pending_mode);
|
|
screen->pending_mode.activated_at = monotonic();
|
|
} else {
|
|
screen->pending_mode.stop_escape_code_type = DCS;
|
|
screen->pending_mode.activated_at = 0;
|
|
}
|
|
} else pending_escape_code(screen, DCS, ST);
|
|
}
|
|
|
|
static void
|
|
pending_csi(Screen *screen, PyObject *dump_callback DUMP_UNUSED) {
|
|
if (screen->parser_buf_pos == 5 && pb(0) == '?' && pb(1) == '2' && pb(2) == '0' && pb(3) == '2' && pb(4) == '6' && (pb(5) == 'h' || pb(5) == 'l')) {
|
|
if (pb(5) == 'h') {
|
|
REPORT_COMMAND(screen_set_mode, 2026, 1);
|
|
screen->pending_mode.activated_at = monotonic();
|
|
} else {
|
|
screen->pending_mode.activated_at = 0;
|
|
screen->pending_mode.stop_escape_code_type = CSI;
|
|
}
|
|
} else pending_escape_code(screen, CSI, pb(screen->parser_buf_pos));
|
|
}
|
|
|
|
#undef pb
|
|
|
|
static size_t
|
|
queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) {
|
|
unsigned int i;
|
|
decode_loop(pending, if (!screen->pending_mode.activated_at) goto end);
|
|
end:
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
create_pending_space(Screen *screen, size_t needed_space) {
|
|
screen->pending_mode.capacity = MAX(screen->pending_mode.capacity * 2, screen->pending_mode.used + needed_space);
|
|
if (screen->pending_mode.capacity > READ_BUF_SZ) screen->pending_mode.capacity = screen->pending_mode.used + needed_space;
|
|
screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity);
|
|
if (!screen->pending_mode.buf) fatal("Out of memory");
|
|
}
|
|
|
|
static void
|
|
dump_partial_escape_code_to_pending(Screen *screen) {
|
|
if (screen->parser_buf_pos) {
|
|
const size_t needed_space = 4 * screen->parser_buf_pos + 8;
|
|
if (screen->pending_mode.used + needed_space >= screen->pending_mode.capacity) create_pending_space(screen, needed_space);
|
|
write_pending_char(screen, screen->parser_state);
|
|
for (unsigned i = 0; i < screen->parser_buf_pos; i++) write_pending_char(screen, screen->parser_buf[i]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, monotonic_t now, PyObject *dump_callback DUMP_UNUSED) {
|
|
enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING};
|
|
enum STATE state = START;
|
|
size_t read_buf_pos = 0;
|
|
unsigned int parser_state_at_start_of_pending = 0;
|
|
|
|
do {
|
|
switch(state) {
|
|
case START:
|
|
if (screen->pending_mode.activated_at) {
|
|
if (screen->pending_mode.activated_at + screen->pending_mode.wait_time < now) {
|
|
dump_partial_escape_code_to_pending(screen);
|
|
screen->pending_mode.activated_at = 0;
|
|
state = START;
|
|
} else state = QUEUE_PENDING;
|
|
} else {
|
|
state = screen->pending_mode.used ? PARSE_PENDING : PARSE_READ_BUF;
|
|
}
|
|
break;
|
|
|
|
case PARSE_PENDING:
|
|
screen->parser_state = parser_state_at_start_of_pending;
|
|
parser_state_at_start_of_pending = 0;
|
|
_parse_bytes(screen, screen->pending_mode.buf, screen->pending_mode.used, dump_callback);
|
|
screen->pending_mode.used = 0;
|
|
screen->pending_mode.activated_at = 0; // ignore any pending starts in the pending bytes
|
|
if (screen->pending_mode.stop_escape_code_type) {
|
|
if (screen->pending_mode.stop_escape_code_type == DCS) { REPORT_COMMAND(screen_stop_pending_mode); }
|
|
else if (screen->pending_mode.stop_escape_code_type == CSI) { REPORT_COMMAND(screen_reset_mode, 2026, 1); }
|
|
screen->pending_mode.stop_escape_code_type = 0;
|
|
}
|
|
state = START;
|
|
break;
|
|
|
|
case PARSE_READ_BUF:
|
|
screen->pending_mode.activated_at = 0;
|
|
read_buf_pos += _parse_bytes_watching_for_pending(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
|
|
state = START;
|
|
break;
|
|
|
|
case QUEUE_PENDING: {
|
|
const size_t needed_space = read_buf_sz * 2;
|
|
screen->pending_mode.stop_escape_code_type = 0;
|
|
if (screen->pending_mode.capacity - screen->pending_mode.used < needed_space) {
|
|
if (screen->pending_mode.capacity >= READ_BUF_SZ) {
|
|
dump_partial_escape_code_to_pending(screen);
|
|
screen->pending_mode.activated_at = 0;
|
|
state = START;
|
|
break;
|
|
}
|
|
create_pending_space(screen, needed_space);
|
|
}
|
|
if (!screen->pending_mode.used) parser_state_at_start_of_pending = screen->parser_state;
|
|
read_buf_pos += queue_pending_bytes(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
|
|
state = START;
|
|
} break;
|
|
}
|
|
} while(read_buf_pos < read_buf_sz || (!screen->pending_mode.activated_at && screen->pending_mode.used));
|
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// Entry points {{{
|
|
#ifdef DUMP_COMMANDS
|
|
#define FNAME(x) x##_dump
|
|
#else
|
|
#define FNAME(x) x
|
|
#endif
|
|
|
|
PyObject*
|
|
FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) {
|
|
PyObject *dump_callback = NULL;
|
|
Py_buffer pybuf;
|
|
Screen *screen;
|
|
#ifdef DUMP_COMMANDS
|
|
if (!PyArg_ParseTuple(args, "OO!y*", &dump_callback, &Screen_Type, &screen, &pybuf)) return NULL;
|
|
#else
|
|
if (!PyArg_ParseTuple(args, "O!y*", &Screen_Type, &screen, &pybuf)) return NULL;
|
|
#endif
|
|
do_parse_bytes(screen, pybuf.buf, pybuf.len, monotonic(), dump_callback);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
void
|
|
FNAME(parse_worker)(Screen *screen, PyObject *dump_callback, monotonic_t now) {
|
|
#ifdef DUMP_COMMANDS
|
|
if (screen->read_buf_sz) {
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, screen->read_buf_sz)); PyErr_Clear();
|
|
}
|
|
#endif
|
|
do_parse_bytes(screen, screen->read_buf, screen->read_buf_sz, now, dump_callback);
|
|
screen->read_buf_sz = 0;
|
|
}
|
|
#undef FNAME
|
|
// }}}
|