mirror of
https://github.com/kovidgoyal/kitty.git
synced 2025-12-13 20:36:22 +01:00
1681 lines
58 KiB
C
1681 lines
58 KiB
C
/*
|
|
* vt-parser.c
|
|
* Copyright (C) 2023 Kovid Goyal <kovid at kovidgoyal.net>
|
|
*
|
|
* Distributed under terms of the GPL3 license.
|
|
*/
|
|
|
|
// TODO: Test clipboard kitten with 52 and 5522
|
|
// TODO: Test screen_request_capabilities
|
|
|
|
#include "vt-parser.h"
|
|
#include "screen.h"
|
|
#include "control-codes.h"
|
|
#include "state.h"
|
|
#include "simd-string.h"
|
|
#include <stdalign.h>
|
|
|
|
#define BUF_SZ (1024u*1024u)
|
|
// The extra bytes are so loads of large integers such as for AVX 512 dont read past the end of the buffer
|
|
#define BUF_EXTRA (512u/8u)
|
|
#define MAX_ESCAPE_CODE_LENGTH (BUF_SZ / 4u)
|
|
#define MAX_CSI_PARAMS 256u
|
|
|
|
|
|
// Macros {{{
|
|
|
|
#define SET_STATE(x) \
|
|
self->vte_state = VTE_##x;
|
|
|
|
#define DIGIT '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9'
|
|
|
|
static void
|
|
_report_unknown_escape_code(PyObject *dump_callback, id_type window_id, const char *name, const uint8_t *payload) {
|
|
char buf[1024];
|
|
if (strlen((const char*)payload) < 64) snprintf(buf, sizeof(buf), "Unknown %s escape code: %.64s", name, payload);
|
|
else snprintf(buf, sizeof(buf), "Unknown %s escape code: %.64s...", name, payload);
|
|
if (dump_callback) {
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "Kss", window_id, "error", buf)); PyErr_Clear();
|
|
} else log_error(ERROR_PREFIX " " "%s", buf);
|
|
}
|
|
|
|
#define REPORT_UKNOWN_ESCAPE_CODE(name, data) _report_unknown_escape_code(self->dump_callback, self->window_id, name, data);
|
|
|
|
#ifdef DUMP_COMMANDS
|
|
|
|
static void
|
|
_report_error(PyObject *dump_callback, id_type window_id, const char *fmt, ...) {
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
RAII_PyObject(temp, PyUnicode_FromFormatV(fmt, argptr));
|
|
va_end(argptr);
|
|
if (temp != NULL) {
|
|
RAII_PyObject(wid, PyLong_FromUnsignedLongLong(window_id));
|
|
RAII_PyObject(err, PyUnicode_FromString("error"));
|
|
if (wid && err) Py_XDECREF(PyObject_CallFunctionObjArgs(dump_callback, wid, err, temp, NULL));
|
|
}
|
|
PyErr_Clear();
|
|
}
|
|
|
|
static void
|
|
_report_params(PyObject *dump_callback, id_type window_id, const char *name, int *params, unsigned int count, bool is_group, Region *r) {
|
|
static char buf[MAX_CSI_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);
|
|
const char *fmt = is_group ? "%i:" : "%i;";
|
|
for(i = 0; i < count && p < arraysz(buf)-20; i++) {
|
|
int n = snprintf(buf + p, arraysz(buf) - p, fmt, params[i]);
|
|
if (n < 0) break;
|
|
p += n;
|
|
}
|
|
buf[count ? p-1 : p] = 0;
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "Kss", window_id, name, buf)); PyErr_Clear();
|
|
}
|
|
|
|
static void
|
|
_report_params_with_first(PyObject *dump_callback, id_type window_id, const char *name, int first_param, int *params, unsigned count) {
|
|
static char buf[MAX_CSI_PARAMS*3] = {0};
|
|
unsigned int i, p=0;
|
|
p += snprintf(buf + p, sizeof(buf) - 2, "%d;", first_param);
|
|
for(i = 0; i < count && p < arraysz(buf)-20; i++) {
|
|
int n = snprintf(buf + p, arraysz(buf) - p, "%i:", params[i]);
|
|
if (n < 0) break;
|
|
p += n;
|
|
}
|
|
buf[count ? p-1 : p] = 0;
|
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "Kss", window_id, name, buf)); PyErr_Clear();
|
|
}
|
|
|
|
|
|
#define DUMP_UNUSED
|
|
|
|
#define REPORT_ERROR(...) _report_error(self->dump_callback, self->window_id, __VA_ARGS__);
|
|
|
|
#define REPORT_COMMAND1(name) \
|
|
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "Ks", self->window_id, #name)); PyErr_Clear();
|
|
|
|
#define REPORT_COMMAND2(name, x) \
|
|
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "Ksi", self->window_id, #name, (int)x)); PyErr_Clear();
|
|
|
|
#define REPORT_COMMAND3(name, x, y) \
|
|
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "Ksii", self->window_id, #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(self->dump_callback, __VA_ARGS__)); PyErr_Clear();
|
|
|
|
#define REPORT_DRAW(chars, num) { \
|
|
for (unsigned i = 0; i < (num); i++) { \
|
|
uint32_t rd_ch = (chars)[i]; \
|
|
switch(rd_ch) { \
|
|
case BEL: REPORT_COMMAND(screen_bell); break; \
|
|
case BS: REPORT_COMMAND(screen_backspace); break; \
|
|
case HT: REPORT_COMMAND(screen_tab); break; \
|
|
case SI: REPORT_COMMAND(screen_change_charset, 0); break; \
|
|
case SO: REPORT_COMMAND(screen_change_charset, 1); break; \
|
|
case LF: case VT: case FF: REPORT_COMMAND(screen_linefeed); break; \
|
|
case CR: REPORT_COMMAND(screen_carriage_return); break; \
|
|
default: \
|
|
if (rd_ch >= ' ') { \
|
|
RAII_PyObject(t, PyObject_CallFunction(self->dump_callback, "KsC", self->window_id, "draw", rd_ch)); \
|
|
if (t == NULL) PyErr_Clear(); \
|
|
} \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
|
|
#define REPORT_PARAMS(name, params, num, is_group, region) _report_params(self->dump_callback, self->window_id, name, params, num, is_group, region)
|
|
|
|
#define REPORT_PARAMS_WITH_FIRST(name, first, params, num) _report_params_with_first(self->dump_callback, self->window_id, name, first, params, num)
|
|
|
|
#define REPORT_OSC(name, string) \
|
|
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "KsO", self->window_id, #name, string)); PyErr_Clear();
|
|
|
|
#define REPORT_OSC2(name, code, string) \
|
|
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "KsiO", self->window_id, #name, code, string)); PyErr_Clear();
|
|
|
|
#define REPORT_HYPERLINK(id, url) \
|
|
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "Kszz", self->window_id, "set_active_hyperlink", id, url)); PyErr_Clear();
|
|
|
|
#else
|
|
#define REPORT_ERROR(...) log_error(ERROR_PREFIX " " __VA_ARGS__);
|
|
#define REPORT_COMMAND(...)
|
|
#define REPORT_VA_COMMAND(...)
|
|
#define REPORT_DRAW(...)
|
|
#define REPORT_PARAMS(...)
|
|
#define REPORT_PARAMS_WITH_FIRST(...)
|
|
#define REPORT_OSC(name, string)
|
|
#define REPORT_OSC2(name, code, string)
|
|
#define REPORT_HYPERLINK(id, url)
|
|
|
|
#endif
|
|
// }}}
|
|
|
|
// Utils {{{
|
|
static const int64_t digit_multipliers[] = {
|
|
10000000000000000l,
|
|
1000000000000000l,
|
|
100000000000000l,
|
|
10000000000000l,
|
|
1000000000000l,
|
|
100000000000l,
|
|
10000000000l,
|
|
1000000000l,
|
|
100000000l,
|
|
10000000l,
|
|
1000000l,
|
|
100000l,
|
|
10000l,
|
|
1000l,
|
|
100l,
|
|
1l
|
|
};
|
|
|
|
// }}}
|
|
|
|
// Data structures {{{
|
|
typedef enum VTEState {
|
|
VTE_NORMAL, VTE_ESC = ESC, VTE_CSI = ESC_CSI, VTE_OSC = ESC_OSC, VTE_DCS = ESC_DCS, VTE_APC = ESC_APC, VTE_PM = ESC_PM, VTE_SOS = ESC_SOS
|
|
} VTEState;
|
|
|
|
static inline const char*
|
|
vte_state_name(VTEState s) {
|
|
switch(s) {
|
|
case VTE_NORMAL: return "VTE_NORMAL";
|
|
case VTE_ESC: return "VTE_ESC";
|
|
case VTE_CSI: return "VTE_CSI";
|
|
case VTE_OSC: return "VTE_OSC";
|
|
case VTE_DCS: return "VTE_DCS";
|
|
case VTE_APC: return "VTE_APC";
|
|
case VTE_PM: return "VTE_PM";
|
|
case VTE_SOS: return "VTE_SOS";
|
|
}
|
|
static char buf[16];
|
|
snprintf(buf, sizeof(buf), "VTE_0x%x", s);
|
|
return buf;
|
|
}
|
|
|
|
typedef enum { CSI_START, CSI_BODY, CSI_POST_SECONDARY } CSIState;
|
|
|
|
typedef struct ParsedCSI {
|
|
char primary, secondary, trailer;
|
|
CSIState state;
|
|
unsigned num_params, num_digits;
|
|
bool is_valid;
|
|
uint64_t accumulator; int mult;
|
|
int params[MAX_CSI_PARAMS];
|
|
uint8_t is_sub_param[MAX_CSI_PARAMS];
|
|
} ParsedCSI;
|
|
|
|
typedef struct PS {
|
|
alignas(BUF_EXTRA) uint8_t buf[BUF_SZ + BUF_EXTRA];
|
|
UTF8Decoder utf8_decoder;
|
|
|
|
id_type window_id;
|
|
|
|
VTEState vte_state;
|
|
ParsedCSI csi;
|
|
|
|
// these are temporary variables set only for duration of a parse call
|
|
PyObject *dump_callback;
|
|
Screen *screen;
|
|
monotonic_t now, new_input_at;
|
|
pthread_mutex_t lock;
|
|
|
|
// The buffer
|
|
struct { size_t consumed, pos, sz; } read;
|
|
struct { size_t offset, sz, pending; } write;
|
|
} PS;
|
|
|
|
static void
|
|
reset_csi(ParsedCSI *csi) {
|
|
csi->num_params = 0; csi->primary = 0; csi->secondary = 0;
|
|
csi->trailer = 0; csi->state = CSI_START; csi->num_digits = 0;
|
|
csi->is_valid = false; csi->accumulator = 0; csi->mult = 1;
|
|
}
|
|
// }}}
|
|
|
|
// Normal mode {{{
|
|
|
|
static void
|
|
dispatch_single_byte_control(PS *self, uint32_t ch) {
|
|
REPORT_DRAW(&ch, 1);
|
|
screen_draw_text(self->screen, &ch, 1);
|
|
}
|
|
|
|
static void
|
|
consume_normal(PS *self) {
|
|
do {
|
|
const bool sentinel_found = utf8_decode_to_esc(&self->utf8_decoder, self->buf + self->read.pos, self->read.sz - self->read.pos);
|
|
self->read.pos += self->utf8_decoder.num_consumed;
|
|
if (self->utf8_decoder.output.pos) {
|
|
REPORT_DRAW(self->utf8_decoder.output.storage, self->utf8_decoder.output.pos);
|
|
screen_draw_text(self->screen, self->utf8_decoder.output.storage, self->utf8_decoder.output.pos);
|
|
}
|
|
if (sentinel_found) { SET_STATE(ESC); break; }
|
|
} while (self->read.pos < self->read.sz);
|
|
}
|
|
// }}}
|
|
|
|
// Esc mode {{{
|
|
#define IS_ESCAPED_CHAR \
|
|
case '%': \
|
|
case '(': \
|
|
case ')': \
|
|
case '*': \
|
|
case '+': \
|
|
case '-': \
|
|
case '.': \
|
|
case '/': \
|
|
case ' ': \
|
|
case '#'
|
|
|
|
|
|
static void
|
|
screen_nel(Screen *screen) { screen_carriage_return(screen); screen_linefeed(screen); }
|
|
|
|
static bool
|
|
consume_esc(PS *self) {
|
|
#define CALL_ED(name) REPORT_COMMAND(name); name(self->screen); SET_STATE(NORMAL);
|
|
#define CALL_ED1(name, ch) REPORT_COMMAND(name, ch); name(self->screen, ch); SET_STATE(NORMAL);
|
|
#define CALL_ED2(name, a, b) REPORT_COMMAND(name, a, b); name(self->screen, a, b); SET_STATE(NORMAL);
|
|
const uint8_t ch = self->buf[self->read.pos++];
|
|
const bool is_first_char = self->read.pos - self->read.consumed == 1;
|
|
if (is_first_char) {
|
|
switch(ch) {
|
|
case ESC_DCS: SET_STATE(DCS); break;
|
|
case ESC_OSC: SET_STATE(OSC); break;
|
|
case ESC_CSI: SET_STATE(CSI); reset_csi(&self->csi); break;
|
|
case ESC_APC: SET_STATE(APC); break;
|
|
case ESC_SOS: SET_STATE(SOS); break;
|
|
case ESC_PM: SET_STATE(PM); break;
|
|
IS_ESCAPED_CHAR:
|
|
return false;
|
|
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;
|
|
default:
|
|
REPORT_ERROR("%s0x%x", "Unknown char after ESC: ", ch);
|
|
SET_STATE(NORMAL); break;
|
|
}
|
|
return true;
|
|
} else {
|
|
const uint8_t prev_ch = self->buf[self->read.pos-2];
|
|
SET_STATE(NORMAL);
|
|
switch(prev_ch) {
|
|
case '%':
|
|
switch(ch) {
|
|
case '@':
|
|
REPORT_ERROR("Ignoring attempt to switch to non-utf8 encoding");
|
|
break;
|
|
case 'G':
|
|
REPORT_ERROR("Ignoring attempt to switch to utf8 encoding as we are always utf-8");
|
|
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, prev_ch - '(', ch); break;
|
|
default:
|
|
REPORT_ERROR("Unknown charset: 0x%x", ch); break;
|
|
}
|
|
break;
|
|
case ' ':
|
|
switch(ch) {
|
|
case 'F':
|
|
case 'G':
|
|
REPORT_ERROR("Ignoring attempt to turn on/off C1 controls as we only support C0 controls"); 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", prev_ch, ch); break;
|
|
}
|
|
return true;
|
|
}
|
|
#undef CALL_ED
|
|
#undef CALL_ED1
|
|
} // }}}
|
|
|
|
// ST terminator {{{
|
|
static bool
|
|
find_st_terminator(PS *self, size_t *end_pos) {
|
|
const size_t sz = self->read.sz - self->read.pos;
|
|
const uint8_t *q = find_either_of_two_bytes(self->buf + self->read.pos, sz, BEL, ESC_ST);
|
|
if (q == NULL) {
|
|
self->read.pos += sz;
|
|
return false;
|
|
}
|
|
switch(*q) {
|
|
case ESC_ST:
|
|
if (q > self->buf && *(q-1) == ESC) {
|
|
*end_pos = q - 1 - self->buf;
|
|
self->read.pos = *end_pos + 2;
|
|
return true;
|
|
}
|
|
self->read.pos = (q - self->buf) + 1;
|
|
break;
|
|
case BEL:
|
|
*end_pos = q - self->buf;
|
|
self->read.pos = *end_pos + 1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
// }}}
|
|
|
|
// OSC {{{
|
|
|
|
#include "parse-multicell-command.h"
|
|
|
|
static bool
|
|
is_osc_52(PS *self) {
|
|
return memcmp(self->buf + self->read.consumed, "52;", 3) == 0;
|
|
}
|
|
|
|
static void
|
|
continue_osc_52(PS *self) {
|
|
self->read.pos -= 4;
|
|
self->read.consumed = self->read.pos;
|
|
self->buf[self->read.pos++] = '5'; self->buf[self->read.pos++] = '2';
|
|
self->buf[self->read.pos++] = ';'; self->buf[self->read.pos++] = ';';
|
|
}
|
|
|
|
|
|
static bool
|
|
accumulate_st_terminated_esc_code(PS *self, void(dispatch)(PS*, uint8_t*, size_t, bool)) {
|
|
size_t pos;
|
|
if (find_st_terminator(self, &pos)) {
|
|
// technically we should check MAX_ESCAPE_CODE_LENGTH here but lets be generous in what we accept since we
|
|
// have a full escape code
|
|
uint8_t *buf = self->buf + self->read.consumed;
|
|
size_t sz = pos - self->read.consumed;
|
|
buf[sz] = 0; // ensure null termination, this is anyway an ST termination char
|
|
dispatch(self, buf, sz, false);
|
|
return true;
|
|
}
|
|
if (UNLIKELY((pos=self->read.pos - self->read.consumed) > MAX_ESCAPE_CODE_LENGTH)) {
|
|
if (self->vte_state == VTE_OSC && is_osc_52(self)) {
|
|
// null terminate
|
|
self->read.pos--;
|
|
uint8_t before = self->buf[self->read.pos];
|
|
self->buf[self->read.pos] = 0;
|
|
// send partial OSC 52
|
|
dispatch(self, self->buf + self->read.consumed, self->read.pos - self->read.consumed, true);
|
|
// continue OSC 52
|
|
self->buf[self->read.pos] = before;
|
|
continue_osc_52(self);
|
|
return accumulate_st_terminated_esc_code(self, dispatch);
|
|
}
|
|
REPORT_ERROR("%s escape code too long (%zu bytes), ignoring it", vte_state_name(self->vte_state), pos);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
parse_osc_8(char *buf, char **id, char **url) {
|
|
// the spec says only ASCII printable chars are allowed in OSC 8
|
|
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 void
|
|
dispatch_hyperlink(PS *self, char *buf) {
|
|
char *id = NULL, *url = NULL;
|
|
if (parse_osc_8(buf, &id, &url)) {
|
|
REPORT_HYPERLINK(id, url);
|
|
set_active_hyperlink(self->screen, id, url);
|
|
} else {
|
|
REPORT_ERROR("Ignoring malformed OSC 8 code");
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
dispatch_osc(PS *self, uint8_t *buf, size_t limit, bool is_extended_osc) {
|
|
#define DISPATCH_OSC_WITH_CODE(name) REPORT_OSC2(name, code, mv); name(self->screen, code, mv);
|
|
#define DISPATCH_OSC(name) REPORT_OSC(name, mv); name(self->screen, mv);
|
|
#define START_DISPATCH {\
|
|
RAII_PyObject(mv, PyMemoryView_FromMemory((char*)buf + i, limit - i, PyBUF_READ)); \
|
|
if (mv) {
|
|
#define END_DISPATCH_WITHOUT_BREAK }; PyErr_Clear(); }
|
|
#define END_DISPATCH }; PyErr_Clear(); break; }
|
|
|
|
int64_t accumulator = 0;
|
|
int code=0;
|
|
unsigned int i;
|
|
for (i = 0; i < MIN(limit, 5u); i++) {
|
|
int64_t num = buf[i] - '0';
|
|
if (num < 0 || num > 9) break;
|
|
accumulator += num * digit_multipliers[i];
|
|
}
|
|
if (i > 0) {
|
|
code = accumulator / digit_multipliers[i - 1];
|
|
if (i < limit && buf[i] == ';') i++;
|
|
}
|
|
|
|
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 5: case 105: REPORT_ERROR("Ignoring OSC 5/105, used by XTerm to change special colors used for rendering bold/italic/underline"); break;
|
|
case 6: case 106: { // report only once as this is used by benchmark kitten causing log spam
|
|
static bool reported = false;
|
|
if (!reported) {
|
|
reported = true;
|
|
REPORT_ERROR("Ignoring OSC 6/106, used by XTerm to enable/disable special colors used for rendering bold/italic/underline");
|
|
}
|
|
} break;
|
|
case 4:
|
|
case 104:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(set_color_table_color);
|
|
END_DISPATCH
|
|
case 7:
|
|
#ifdef DUMP_COMMANDS
|
|
START_DISPATCH
|
|
REPORT_OSC2(process_cwd_notification, code, mv);
|
|
END_DISPATCH_WITHOUT_BREAK
|
|
#endif
|
|
process_cwd_notification(self->screen, code, (char*)buf + i, limit-i);
|
|
break;
|
|
case 8:
|
|
dispatch_hyperlink(self, (char*)buf + i);
|
|
break;
|
|
case 9:
|
|
case 99:
|
|
case 777:
|
|
case 1337:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(desktop_notify)
|
|
END_DISPATCH
|
|
case 13: case 14: case 15: case 16: case 18:
|
|
REPORT_ERROR("Ignoring OSC 13,14,15,16 and 18 used for pointer and Textronic colors by XTerm"); break;
|
|
break;
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
case 17:
|
|
case 19:
|
|
case 22:
|
|
case 110:
|
|
case 111:
|
|
case 112:
|
|
case 117:
|
|
case 119:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(set_dynamic_color);
|
|
END_DISPATCH
|
|
case 21:
|
|
START_DISPATCH
|
|
DISPATCH_OSC_WITH_CODE(color_control);
|
|
END_DISPATCH
|
|
case 52: case 5522:
|
|
START_DISPATCH
|
|
if (is_extended_osc && code == 52) code = -52;
|
|
DISPATCH_OSC_WITH_CODE(clipboard_control);
|
|
END_DISPATCH
|
|
case 46: REPORT_ERROR("Ignoring OSC 46 used for file logging in XTerm"); break;
|
|
case 50: REPORT_ERROR("Ignoring OSC 50 used for font changing in XTerm"); break;
|
|
case 51: REPORT_ERROR("Ignoring OSC 51 used by emacs shell"); break;
|
|
case 60: case 61: REPORT_ERROR("Ignoring OSC 60/61 used for query control in XTerm"); break;
|
|
case 66:
|
|
parse_multicell_code(self, buf + i, limit - i);
|
|
break;
|
|
case 133:
|
|
#ifdef DUMP_COMMANDS
|
|
START_DISPATCH
|
|
REPORT_OSC2(shell_prompt_marking, code, mv);
|
|
END_DISPATCH_WITHOUT_BREAK
|
|
#endif
|
|
if (limit > i) {
|
|
buf[limit] = 0; // safe to do as we have 8 extra bytes after PARSER_BUF_SZ
|
|
shell_prompt_marking(self->screen, (char*)buf + i);
|
|
}
|
|
break;
|
|
case FILE_TRANSFER_CODE:
|
|
START_DISPATCH
|
|
DISPATCH_OSC(file_transmission);
|
|
END_DISPATCH
|
|
case 30001:
|
|
REPORT_COMMAND(screen_push_dynamic_colors);
|
|
screen_push_colors(self->screen, 0);
|
|
break;
|
|
case 30101:
|
|
REPORT_COMMAND(screen_pop_dynamic_colors);
|
|
screen_pop_colors(self->screen, 0);
|
|
break;
|
|
case 440: REPORT_ERROR("Ignoring OSC 440 used for audio by mintty"); break;
|
|
case 633: REPORT_ERROR("Ignoring OSC 633, use by Windows Terminal for VSCode actions"); break;
|
|
case 666: REPORT_ERROR("Ignoring OSC 666, typically used by VTE terminals for shell integration"); break;
|
|
case 697: REPORT_ERROR("Ignoring OSC 697, typically used by Fig for shell integration"); break;
|
|
case 701: REPORT_ERROR("Ignoring OSC 701, used by mintty for locale"); break;
|
|
case 3008:
|
|
START_DISPATCH
|
|
DISPATCH_OSC(osc_context);
|
|
END_DISPATCH
|
|
case 7704: REPORT_ERROR("Ignoring OSC 7704, used by mintty for ANSI colors"); break;
|
|
case 7750: REPORT_ERROR("Ignoring OSC 7750, used by mintty for Emoji style"); break;
|
|
case 7770: REPORT_ERROR("Ignoring OSC 7770, used by mintty for font size"); break;
|
|
case 7721: REPORT_ERROR("Ignoring OSC 7721, used by mintty for copy window title"); break;
|
|
case 7771: REPORT_ERROR("Ignoring OSC 7771, used by mintty for glyph coverage"); break;
|
|
case 7777: REPORT_ERROR("Ignoring OSC 7777, used by mintty for window size"); break;
|
|
case 77119: REPORT_ERROR("Ignoring OSC 7777, used by mintty for wide chars"); break;
|
|
case 9001: REPORT_ERROR("Ignoring OSC 9001, used by windows terminal"); break;
|
|
default:
|
|
REPORT_UKNOWN_ESCAPE_CODE("OSC", buf);
|
|
break;
|
|
}
|
|
#undef DISPATCH_OSC
|
|
#undef DISPATCH_OSC_WITH_CODE
|
|
#undef START_DISPATCH
|
|
#undef END_DISPATCH
|
|
}
|
|
|
|
// }}}
|
|
|
|
// DCS {{{
|
|
|
|
static bool
|
|
startswith(const uint8_t *string, ssize_t sz, const char *prefix, ssize_t l) {
|
|
if (sz < l) return false;
|
|
for (ssize_t i = 0; i < l; i++) {
|
|
if (string[i] != (unsigned char)prefix[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_kitty_dcs(PS *self, uint8_t *buf, size_t bufsz) {
|
|
#define starts_with(x) startswith(buf, bufsz, x, literal_strlen(x))
|
|
#define inc(x) buf += literal_strlen(x); bufsz -= literal_strlen(x)
|
|
#define dispatch(prefix, func, delta) {\
|
|
if (starts_with(prefix)) {\
|
|
inc(prefix); buf -= delta; bufsz += delta; \
|
|
PyObject *cmd = PyMemoryView_FromMemory((char*)buf, bufsz, PyBUF_READ); \
|
|
if (cmd) { \
|
|
REPORT_OSC(func, cmd); \
|
|
screen_handle_kitty_dcs(self->screen, #func, cmd); \
|
|
Py_DECREF(cmd); \
|
|
} else PyErr_Clear(); \
|
|
return true; \
|
|
}}
|
|
if (!starts_with("kitty-")) return false;
|
|
inc("kitty-");
|
|
|
|
dispatch("cmd{", handle_remote_cmd, 1);
|
|
dispatch("overlay-ready|", handle_overlay_ready, 0)
|
|
dispatch("kitten-result|", handle_kitten_result, 0)
|
|
dispatch("print|", handle_remote_print, 0)
|
|
dispatch("echo|", handle_remote_echo, 0)
|
|
dispatch("ssh|", handle_remote_ssh, 0)
|
|
dispatch("ask|", handle_remote_askpass, 0)
|
|
dispatch("clone|", handle_remote_clone, 0)
|
|
dispatch("edit|", handle_remote_edit, 0)
|
|
dispatch("restore-cursor-appearance|", handle_restore_cursor_appearance, 0)
|
|
|
|
return false;
|
|
#undef dispatch
|
|
#undef starts_with
|
|
#undef inc
|
|
}
|
|
|
|
static void
|
|
dispatch_dcs(PS *self, uint8_t *buf, size_t bufsz, bool is_extended UNUSED) {
|
|
if (bufsz < 2) return;
|
|
switch (buf[0]) {
|
|
case '+':
|
|
case '$':
|
|
if (buf[1] == 'q') {
|
|
PyObject *mv = PyMemoryView_FromMemory((char*)buf + 2, bufsz-2, PyBUF_READ);
|
|
if (mv) {
|
|
REPORT_OSC2(screen_request_capabilities, (char)buf[0], mv);
|
|
Py_DECREF(mv);
|
|
} else PyErr_Clear();
|
|
screen_request_capabilities(self->screen, (char)buf[0], (char*)buf + 2);
|
|
} else {
|
|
REPORT_UKNOWN_ESCAPE_CODE("DCS", buf);
|
|
}
|
|
break;
|
|
case '=':
|
|
if (bufsz > 2 && (buf[1] == '1' || buf[1] == '2') && buf[2] == 's') {
|
|
if (buf[1] == '1') {
|
|
REPORT_COMMAND(screen_start_pending_mode)
|
|
if (!screen_pause_rendering(self->screen, true, 0)) {
|
|
REPORT_ERROR("Pending mode start requested while already in pending mode. This is most likely an application error.");
|
|
}
|
|
} else {
|
|
REPORT_COMMAND(screen_stop_pending_mode);
|
|
if (!screen_pause_rendering(self->screen, false, 0)) {
|
|
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");
|
|
}
|
|
}
|
|
} else {
|
|
REPORT_UKNOWN_ESCAPE_CODE("DCS", buf);
|
|
} break;
|
|
case '@':
|
|
if (!parse_kitty_dcs(self, buf + 1, bufsz-1)) REPORT_UKNOWN_ESCAPE_CODE("DCS", buf);
|
|
break;
|
|
default:
|
|
REPORT_UKNOWN_ESCAPE_CODE("DCS", buf);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
|
|
// CSI {{{
|
|
|
|
#define CSI_SECONDARY \
|
|
' ': \
|
|
case '!': \
|
|
case '"': \
|
|
case '#': \
|
|
case '$': \
|
|
case '%': \
|
|
case '&': \
|
|
case '\'': \
|
|
case '(': \
|
|
case ')': \
|
|
case '*': \
|
|
case '+': \
|
|
case ',': \
|
|
case '-': \
|
|
case '.': \
|
|
case '/'
|
|
|
|
#define CSI_TRAILER \
|
|
'@': \
|
|
START_ALLOW_CASE_RANGE \
|
|
case 'a' ... 'z': \
|
|
case 'A' ... 'Z': \
|
|
END_ALLOW_CASE_RANGE \
|
|
case '`': \
|
|
case '{': \
|
|
case '|': \
|
|
case '}': \
|
|
case '~'
|
|
|
|
#define CSI_NORMAL_MODE_EMBEDDINGS \
|
|
BEL: \
|
|
case BS: \
|
|
case HT: \
|
|
case LF: \
|
|
case VT: \
|
|
case FF: \
|
|
case CR: \
|
|
case SO: \
|
|
case SI
|
|
|
|
static 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 bool
|
|
commit_csi_param(PS *self UNUSED, ParsedCSI *csi) {
|
|
if (!csi->num_digits) return true;
|
|
if (csi->num_params >= MAX_CSI_PARAMS) {
|
|
REPORT_ERROR("CSI escape code has too many parameters, ignoring it");
|
|
return false;
|
|
}
|
|
csi->params[csi->num_params++] = csi->mult * (csi->accumulator / digit_multipliers[csi->num_digits - 1]);
|
|
csi->num_digits = 0; csi->mult = 1; csi->accumulator = 0;
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
csi_add_digit(ParsedCSI *csi, uint8_t ch) {
|
|
if (UNLIKELY(csi->num_digits >= arraysz(digit_multipliers))) return;
|
|
csi->accumulator += (ch - '0') * digit_multipliers[csi->num_digits++];
|
|
}
|
|
|
|
static bool
|
|
csi_parse_loop(PS *self, ParsedCSI *csi, const uint8_t *buf, size_t *pos, const size_t sz, const size_t start) {
|
|
while (*pos < sz) {
|
|
const uint8_t ch = buf[*pos]; *pos += 1;
|
|
switch(csi->state) {
|
|
case CSI_START:
|
|
switch (ch) {
|
|
case CSI_NORMAL_MODE_EMBEDDINGS:
|
|
dispatch_single_byte_control(self, ch); break;
|
|
case ';':
|
|
csi->params[csi->num_params++] = 0;
|
|
csi->state = CSI_BODY;
|
|
break;
|
|
case DIGIT:
|
|
csi_add_digit(csi, ch);
|
|
csi->state = CSI_BODY;
|
|
break;
|
|
case '?':
|
|
case '>':
|
|
case '<':
|
|
case '=':
|
|
csi->state = CSI_BODY;
|
|
csi->primary = ch;
|
|
break;
|
|
case CSI_SECONDARY:
|
|
if (ch == '-') {
|
|
csi->mult = -1;
|
|
csi->num_digits++;
|
|
csi->state = CSI_BODY;
|
|
} else {
|
|
csi->secondary = ch;
|
|
csi->state = CSI_POST_SECONDARY;
|
|
}
|
|
break;
|
|
case CSI_TRAILER:
|
|
csi->is_valid = true;
|
|
csi->trailer = ch;
|
|
return true;
|
|
default:
|
|
REPORT_ERROR("Invalid character in CSI: %s (0x%x), ignoring the sequence", csi_letter(ch), ch);
|
|
return true;
|
|
}
|
|
break;
|
|
case CSI_POST_SECONDARY:
|
|
switch (ch) {
|
|
case CSI_NORMAL_MODE_EMBEDDINGS:
|
|
dispatch_single_byte_control(self, ch); break;
|
|
case CSI_TRAILER:
|
|
csi->is_valid = true;
|
|
csi->trailer = ch;
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Invalid character in CSI: %s (0x%x), ignoring the sequence", csi_letter(ch), ch);
|
|
break;
|
|
}
|
|
return true;
|
|
case CSI_BODY:
|
|
switch(ch) {
|
|
case CSI_NORMAL_MODE_EMBEDDINGS:
|
|
dispatch_single_byte_control(self, ch); break;
|
|
case CSI_SECONDARY:
|
|
if (ch == '-' && csi->num_digits == 0) {
|
|
csi->mult = -1; csi->num_digits = 1;
|
|
} else {
|
|
if (!commit_csi_param(self, csi)) return true;
|
|
csi->secondary = ch;
|
|
csi->state = CSI_POST_SECONDARY;
|
|
}
|
|
break;
|
|
case CSI_TRAILER:
|
|
if (csi->num_digits == 1 && csi->secondary == 0 && csi->mult == -1) {
|
|
csi->num_digits = 0; csi->secondary = '-';
|
|
}
|
|
if (!commit_csi_param(self, csi)) return true;
|
|
csi->is_valid = true;
|
|
csi->trailer = ch;
|
|
return true;
|
|
case ':':
|
|
if (!commit_csi_param(self, csi)) return true;
|
|
csi->is_sub_param[csi->num_params] = true;
|
|
break;
|
|
case ';':
|
|
if (!csi->num_digits) csi->num_digits++; // Empty means zero
|
|
if (!commit_csi_param(self, csi)) return true;
|
|
csi->is_sub_param[csi->num_params] = false;
|
|
break;
|
|
case DIGIT:
|
|
csi_add_digit(csi, ch);
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Invalid character in CSI: %s (0x%x), ignoring the sequence", csi_letter(ch), ch);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (UNLIKELY(*pos - start > MAX_ESCAPE_CODE_LENGTH)) {
|
|
REPORT_ERROR("CSI escape too long ignoring and truncating");
|
|
return true;
|
|
}
|
|
return false;
|
|
#undef COMMIT_PARAM
|
|
}
|
|
|
|
static bool
|
|
consume_csi(PS *self) {
|
|
return csi_parse_loop(self, &self->csi, self->buf, &self->read.pos, self->read.sz, self->read.consumed);
|
|
}
|
|
|
|
static void
|
|
_parse_multi_cursors(PS *self, ParsedCSI *csi) {
|
|
switch(csi->num_params) {
|
|
case 0:
|
|
REPORT_COMMAND("screen_multi_cursor");
|
|
screen_multi_cursor(self->screen, 0, NULL, 0);
|
|
break;
|
|
case 1:
|
|
REPORT_PARAMS_WITH_FIRST("screen_multi_cursor", csi->params[0], csi->params, 0);
|
|
screen_multi_cursor(self->screen, csi->params[0], csi->params, 0);
|
|
break;
|
|
default: {
|
|
unsigned pos = 1, first_param = pos;
|
|
for (; pos < csi->num_params; pos++) {
|
|
if (pos > first_param) {
|
|
if (!csi->is_sub_param[pos]) {
|
|
REPORT_PARAMS_WITH_FIRST("screen_multi_cursor", csi->params[0], csi->params + first_param, pos - first_param);
|
|
screen_multi_cursor(self->screen, csi->params[0], csi->params + first_param, pos - first_param);
|
|
first_param = pos;
|
|
}
|
|
}
|
|
}
|
|
if (pos > first_param) {
|
|
REPORT_PARAMS_WITH_FIRST("screen_multi_cursor", csi->params[0], csi->params + first_param, pos - first_param);
|
|
screen_multi_cursor(self->screen, csi->params[0], csi->params + first_param, pos - first_param);
|
|
}}}
|
|
}
|
|
|
|
|
|
static unsigned int
|
|
parse_region(const ParsedCSI *csi, Region *r) {
|
|
switch(csi->num_params) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
r->top = csi->params[0];
|
|
return 1;
|
|
case 2:
|
|
r->top = csi->params[0]; r->left = csi->params[1];
|
|
return 2;
|
|
case 3:
|
|
r->top = csi->params[0]; r->left = csi->params[1]; r->bottom = csi->params[2];
|
|
return 3;
|
|
default:
|
|
r->top = csi->params[0]; r->left = csi->params[1]; r->bottom = csi->params[2]; r->right = csi->params[3];
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
_parse_sgr(PS *self, ParsedCSI *csi) {
|
|
#define SEND_SGR if (num_params) { \
|
|
REPORT_PARAMS(report_name, csi->params + first_param, num_params, state != NORMAL, region); \
|
|
select_graphic_rendition(screen, csi->params + first_param, num_params, state != NORMAL, region); \
|
|
state = NORMAL; first_param += num_params; num_params = 0; \
|
|
}
|
|
Screen *screen = self->screen;
|
|
size_t pos = 0, first_param, num_params = 0;
|
|
Region r = {0}, *region = NULL;
|
|
const char *report_name = "select_graphic_rendition";
|
|
if (csi->trailer == 'r') { // DECCARA
|
|
region = &r;
|
|
if (csi->num_params == 0) {
|
|
for (; csi->num_params < 5; csi->num_params++) csi->params[csi->num_params] = 0;
|
|
}
|
|
pos = parse_region(csi, region);
|
|
report_name = "deccara";
|
|
(void)report_name;
|
|
} else if (csi->num_params == 0) {
|
|
csi->params[0] = 0;
|
|
csi->num_params++;
|
|
}
|
|
enum State { NORMAL, SUB_PARAMS, COLOR, COLOR1, COLOR3 };
|
|
enum State state = NORMAL;
|
|
|
|
for (first_param = pos; pos < csi->num_params; pos++) {
|
|
switch (state) {
|
|
case NORMAL:
|
|
if (csi->is_sub_param[pos]) {
|
|
if (num_params == 0 || pos == 0) {
|
|
REPORT_ERROR("SGR escape code has an unexpected sub-parameter ignoring the full code");
|
|
return false;
|
|
}
|
|
num_params--;
|
|
SEND_SGR;
|
|
state = SUB_PARAMS;
|
|
first_param = pos - 1;
|
|
num_params = 1;
|
|
}
|
|
switch(csi->params[pos]) {
|
|
case 38: case 48: case DECORATION_FG_CODE:
|
|
SEND_SGR;
|
|
state = COLOR;
|
|
first_param = pos;
|
|
num_params = 1;
|
|
break;
|
|
default:
|
|
num_params++;
|
|
break;
|
|
} break;
|
|
case SUB_PARAMS:
|
|
switch(csi->is_sub_param[pos]) {
|
|
case true:
|
|
num_params++; break;
|
|
case false:
|
|
SEND_SGR;
|
|
pos--;
|
|
break;
|
|
} break;
|
|
case COLOR:
|
|
switch(csi->params[pos]) {
|
|
case 2:
|
|
state = csi->is_sub_param[pos] ? SUB_PARAMS : COLOR3;
|
|
num_params++;
|
|
break;
|
|
case 5:
|
|
state = csi->is_sub_param[pos] ? SUB_PARAMS : COLOR1;
|
|
num_params++;
|
|
break;
|
|
default:
|
|
REPORT_ERROR("SGR escape code has unknown color type: %d ignoring the full code", csi->params[pos]);
|
|
return false;
|
|
} break;
|
|
case COLOR1:
|
|
num_params++;
|
|
SEND_SGR;
|
|
break;
|
|
case COLOR3:
|
|
num_params++;
|
|
if (num_params >= 5) { SEND_SGR; }
|
|
break;
|
|
}
|
|
}
|
|
SEND_SGR;
|
|
return true;
|
|
#undef SEND_SGR
|
|
}
|
|
|
|
#ifndef DUMP_COMMANDS
|
|
bool
|
|
parse_sgr(Screen *screen, const uint8_t *buf, unsigned int num, const char *report_name UNUSED, bool is_deccara) {
|
|
ParsedCSI csi = {.mult=1};
|
|
size_t pos = 0;
|
|
RAII_ALLOC(uint8_t, _buf, malloc(num + 3));
|
|
if (!_buf) return false;
|
|
memcpy(_buf, buf, num);
|
|
if (is_deccara) {
|
|
_buf[num++] = '$'; _buf[num++] = 'r';
|
|
} else {
|
|
_buf[num++] = 'm';
|
|
}
|
|
_buf[num] = 0;
|
|
PS *state = (PS*)screen->vt_parser->state;
|
|
state->screen = screen;
|
|
if (!csi_parse_loop(state, &csi, _buf, &pos, num, 0)) return false;
|
|
return _parse_sgr(state, &csi);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, false, -1); }
|
|
static void
|
|
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_move(s, count, -1, false); }
|
|
static void
|
|
screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1u, count); i++) screen_tab(s); }
|
|
|
|
static 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;
|
|
}
|
|
|
|
static void
|
|
handle_mode(PS *self) {
|
|
bool is_shifted = self->csi.primary == '?';
|
|
int shift = is_shifted ? 5 : 0;
|
|
for (unsigned i = 0; i < self->csi.num_params; i++) {
|
|
int p = self->csi.params[i];
|
|
if (p >= 0) {
|
|
unsigned int sp = p << shift;
|
|
switch (self->csi.trailer) {
|
|
case SM:
|
|
screen_set_mode(self->screen, sp);
|
|
REPORT_COMMAND(screen_set_mode, p, is_shifted);
|
|
break;
|
|
case RM:
|
|
screen_reset_mode(self->screen, sp);
|
|
REPORT_COMMAND(screen_reset_mode, p, is_shifted);
|
|
break;
|
|
case 's':
|
|
screen_save_mode(self->screen, sp);
|
|
REPORT_COMMAND(screen_save_mode, p, is_shifted);
|
|
break;
|
|
case 'r':
|
|
screen_restore_mode(self->screen, sp);
|
|
REPORT_COMMAND(screen_restore_mode, p, is_shifted);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispatch_csi(PS *self) {
|
|
#define num_params self->csi.num_params
|
|
#define code self->csi.trailer
|
|
#define params self->csi.params
|
|
#define start_modifier self->csi.primary
|
|
#define end_modifier self->csi.secondary
|
|
|
|
#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(self->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(self->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(self->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(self->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(self->screen, p1, p2); \
|
|
break;
|
|
|
|
#define NO_MODIFIERS(modifier, special, special_msg) { \
|
|
if (self->csi.primary || self->csi.secondary) { \
|
|
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(self->csi.trailer), csi_letter(self->csi.primary), csi_letter(self->csi.secondary));} \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
int p1, p2; bool private;
|
|
|
|
switch(self->csi.trailer) {
|
|
case ICH:
|
|
NO_MODIFIERS(self->csi.secondary, ' ', "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(self->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:
|
|
handle_mode(self); break;
|
|
case RM:
|
|
handle_mode(self); break;
|
|
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(self->screen);
|
|
break;
|
|
} else if (start_modifier == '?' && !end_modifier) {
|
|
if (!num_params) {
|
|
REPORT_COMMAND(screen_save_modes);
|
|
screen_save_modes(self->screen);
|
|
} else handle_mode(self);
|
|
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:
|
|
REPORT_ERROR("Escape codes to resize text area are not supported");
|
|
break;
|
|
case 14:
|
|
case 16:
|
|
case 18:
|
|
CALL_CSI_HANDLER2(screen_report_size, 0, 0);
|
|
break;
|
|
case 22:
|
|
case 23:
|
|
if (num_params == 3 && !params[2]) num_params = 2; // ignore extra 0, generated by weechat or ncurses
|
|
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(self->screen);
|
|
break;
|
|
}
|
|
if (!end_modifier && start_modifier == '?') {
|
|
REPORT_COMMAND(screen_report_key_encoding_flags);
|
|
screen_report_key_encoding_flags(self->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) {
|
|
if (!num_params) {
|
|
REPORT_COMMAND(screen_restore_modes);
|
|
screen_restore_modes(self->screen);
|
|
} else handle_mode(self);
|
|
break;
|
|
} else if (!start_modifier && end_modifier == '$') {
|
|
_parse_sgr(self, &self->csi);
|
|
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 (end_modifier == ' ') {
|
|
if (!start_modifier) { CALL_CSI_HANDLER1M(screen_set_cursor, 1); }
|
|
if (start_modifier == '>') {
|
|
_parse_multi_cursors(self, &self->csi);
|
|
break;
|
|
}
|
|
} else if (end_modifier == 0 && start_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) {
|
|
_parse_sgr(self, &self->csi);
|
|
break;
|
|
}
|
|
if (start_modifier == '>' && !end_modifier) {
|
|
CALL_CSI_HANDLER2(screen_modify_other_keys, 0, INT_MAX);
|
|
break;
|
|
}
|
|
/* fallthrough */
|
|
default:
|
|
REPORT_ERROR("Unknown CSI code: '%c' with start_modifier: '%c' and end_modifier: '%c' and parameters: '%s'", code, start_modifier, end_modifier, repr_csi_params(params, num_params));
|
|
}
|
|
#undef num_params
|
|
#undef code
|
|
#undef params
|
|
#undef start_modifier
|
|
#undef end_modifier
|
|
}
|
|
|
|
// }}}
|
|
|
|
// APC mode {{{
|
|
|
|
#include "parse-graphics-command.h"
|
|
|
|
static void
|
|
dispatch_apc(PS *self, uint8_t *buf, size_t bufsz, bool is_extended UNUSED) {
|
|
if (bufsz < 2) return;
|
|
switch(buf[0]) {
|
|
case 'G':
|
|
parse_graphics_code(self, buf, bufsz);
|
|
break;
|
|
default:
|
|
REPORT_ERROR("Unrecognized APC code: 0x%x", buf[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
|
|
// PM mode {{{
|
|
static void
|
|
dispatch_pm(PS *self UNUSED, uint8_t *buf, size_t bufsz, bool is_extended UNUSED) {
|
|
if (bufsz < 2) return;
|
|
switch(buf[0]) {
|
|
default:
|
|
REPORT_ERROR("Unrecognized PM code: 0x%x", buf[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// }}}
|
|
|
|
// SOS mode {{{
|
|
static void
|
|
dispatch_sos(PS *self UNUSED, uint8_t *buf, size_t bufsz, bool is_extended UNUSED) {
|
|
if (bufsz < 2) return;
|
|
switch(buf[0]) {
|
|
default:
|
|
REPORT_ERROR("Unrecognized SOS code: 0x%x", buf[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// }}}
|
|
|
|
// Parse loop {{{
|
|
static void
|
|
consume_input(PS *self, PyObject *dump_callback UNUSED, id_type window_id UNUSED) {
|
|
#define consume(x) if (accumulate_st_terminated_esc_code(self, dispatch_##x)) { self->read.consumed = self->read.pos; SET_STATE(NORMAL); } break;
|
|
|
|
#ifdef DUMP_COMMANDS
|
|
PyObject *dumped_bytes = PyBytes_FromStringAndSize((const char*)self->buf + self->read.pos, self->read.sz - self->read.pos);
|
|
size_t pre_consume_pos = self->read.pos;
|
|
#endif
|
|
|
|
switch (self->vte_state) {
|
|
case VTE_NORMAL:
|
|
consume_normal(self); self->read.consumed = self->read.pos; break;
|
|
case VTE_ESC:
|
|
if (consume_esc(self)) { self->read.consumed = self->read.pos; }
|
|
break;
|
|
case VTE_CSI:
|
|
if (consume_csi(self)) { self->read.consumed = self->read.pos; if (self->csi.is_valid) dispatch_csi(self); SET_STATE(NORMAL); }
|
|
break;
|
|
case VTE_OSC:
|
|
consume(osc);
|
|
case VTE_APC:
|
|
consume(apc);
|
|
case VTE_PM:
|
|
consume(pm);
|
|
case VTE_DCS:
|
|
consume(dcs);
|
|
case VTE_SOS:
|
|
consume(sos);
|
|
}
|
|
|
|
#ifdef DUMP_COMMANDS
|
|
if (dumped_bytes && dump_callback && self->read.pos > pre_consume_pos) {
|
|
if (_PyBytes_Resize(&dumped_bytes, self->read.pos - pre_consume_pos) == 0) {
|
|
PyObject *ret = PyObject_CallFunction(dump_callback, "KsO", window_id, "bytes", dumped_bytes);
|
|
Py_DECREF(dumped_bytes);
|
|
if (ret) { Py_DECREF(ret); } else { PyErr_Clear(); }
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#undef consume
|
|
}
|
|
|
|
// }}}
|
|
|
|
// API {{{
|
|
|
|
#define with_lock pthread_mutex_lock(&self->lock);
|
|
#define end_with_lock pthread_mutex_unlock(&self->lock);
|
|
|
|
static void
|
|
run_worker(void *p, ParseData *pd, bool flush) {
|
|
Screen *screen = (Screen*)p;
|
|
PS *self = (PS*)screen->vt_parser->state;
|
|
screen->parsing_at = pd->now;
|
|
with_lock {
|
|
self->read.sz += self->write.pending; self->write.pending = 0;
|
|
pd->has_pending_input = self->read.pos < self->read.sz;
|
|
if (pd->has_pending_input) {
|
|
pd->time_since_new_input = pd->now - self->new_input_at;
|
|
if (flush || pd->time_since_new_input >= OPT(input_delay) || self->read.sz + 16 * 1024 > BUF_SZ) {
|
|
pd->input_read = true;
|
|
self->dump_callback = pd->dump_callback; self->now = pd->now;
|
|
self->screen = screen;
|
|
self->read.consumed = 0;
|
|
do {
|
|
end_with_lock; {
|
|
consume_input(self, pd->dump_callback, screen->window_id);
|
|
} with_lock;
|
|
self->read.sz += self->write.pending; self->write.pending = 0;
|
|
} while (self->read.pos < self->read.sz);
|
|
self->new_input_at = 0;
|
|
if (self->read.consumed) {
|
|
pd->write_space_created = self->read.sz >= BUF_SZ;
|
|
self->read.pos -= MIN(self->read.pos, self->read.consumed);
|
|
self->read.sz -= MIN(self->read.sz, self->read.consumed);
|
|
if (self->read.sz) memmove(self->buf, self->buf + self->read.consumed, self->read.sz);
|
|
}
|
|
}
|
|
}
|
|
} end_with_lock;
|
|
}
|
|
|
|
#ifndef DUMP_COMMANDS
|
|
|
|
uint8_t*
|
|
vt_parser_create_write_buffer(Parser *p, size_t *sz) {
|
|
PS *self = (PS*)p->state;
|
|
uint8_t *ans;
|
|
with_lock {
|
|
if (self->write.sz) fatal("vt_parser_create_write_buffer() called with an already existing write buffer");
|
|
self->write.offset = self->read.sz + self->write.pending;
|
|
*sz = BUF_SZ - self->write.offset;
|
|
self->write.sz = *sz;
|
|
ans = self->buf + self->write.offset;
|
|
} end_with_lock;
|
|
return ans;
|
|
}
|
|
|
|
void
|
|
vt_parser_commit_write(Parser *p, size_t sz) {
|
|
PS *self = (PS*)p->state;
|
|
with_lock {
|
|
size_t off = self->read.sz + self->write.pending;
|
|
if (self->new_input_at == 0) self->new_input_at = monotonic();
|
|
if (self->write.offset > off) memmove(self->buf + off, self->buf + self->write.offset, sz);
|
|
self->write.pending += sz;
|
|
self->write.sz = 0;
|
|
} end_with_lock;
|
|
}
|
|
|
|
bool
|
|
vt_parser_has_space_for_input(const Parser *p) {
|
|
PS *self = (PS*)p->state;
|
|
bool ans;
|
|
with_lock {
|
|
ans = self->read.sz + self->write.pending < BUF_SZ;
|
|
} end_with_lock;
|
|
return ans;
|
|
}
|
|
#endif
|
|
|
|
// }}}
|
|
|
|
// Boilerplate {{{
|
|
|
|
#ifdef DUMP_COMMANDS
|
|
void
|
|
parse_worker_dump(void *p, ParseData *pd, bool flush) { run_worker(p, pd, flush); }
|
|
#else
|
|
void
|
|
parse_worker(void *p, ParseData *pd, bool flush) { run_worker(p, pd, flush); }
|
|
#endif
|
|
|
|
#ifndef DUMP_COMMANDS
|
|
static PyObject*
|
|
new_vtparser_object(PyTypeObject *type UNUSED, PyObject *args, PyObject UNUSED *kwds) {
|
|
id_type window_id=0;
|
|
if (!PyArg_ParseTuple(args, "|K", &window_id)) return NULL;
|
|
return (PyObject*) alloc_vt_parser(window_id);
|
|
}
|
|
|
|
void
|
|
free_vt_parser(Parser* self) {
|
|
if (self->state) {
|
|
PS *s = (PS*)self->state;
|
|
utf8_decoder_free(&s->utf8_decoder);
|
|
pthread_mutex_destroy(&s->lock);
|
|
free(self->state); self->state = NULL;
|
|
}
|
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
}
|
|
|
|
static void
|
|
reset(PS *self) {
|
|
SET_STATE(NORMAL);
|
|
reset_csi(&self->csi);
|
|
utf8_decoder_reset(&self->utf8_decoder);
|
|
}
|
|
|
|
void
|
|
reset_vt_parser(Parser *self) {
|
|
reset((PS*)self->state);
|
|
}
|
|
|
|
extern PyTypeObject Screen_Type;
|
|
|
|
static PyObject*
|
|
current_state(Parser *self, PyObject *closure UNUSED) {
|
|
PS *state = (PS*)self->state;
|
|
return PyUnicode_FromString(vte_state_name(state->vte_state));
|
|
}
|
|
|
|
static PyGetSetDef getsetters[] = {
|
|
{"vte_state", (getter)current_state, NULL, "The VTE parser state", NULL},
|
|
{NULL} /* Sentinel */
|
|
};
|
|
|
|
|
|
static PyMethodDef methods[] = {
|
|
{NULL},
|
|
};
|
|
|
|
PyTypeObject Parser_Type = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
.tp_name = "fast_data_types.Parser",
|
|
.tp_basicsize = sizeof(Parser),
|
|
.tp_dealloc = (destructor)free_vt_parser,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
|
.tp_doc = "VT Escape code parser",
|
|
.tp_methods = methods,
|
|
.tp_getset = getsetters,
|
|
.tp_new = new_vtparser_object,
|
|
};
|
|
|
|
Parser*
|
|
alloc_vt_parser(id_type window_id) {
|
|
Parser *self = (Parser*)Parser_Type.tp_alloc(&Parser_Type, 1);
|
|
if (self != NULL) {
|
|
int ret;
|
|
if ((ret = posix_memalign((void**)&self->state, BUF_EXTRA, sizeof(PS))) != 0) {
|
|
Py_CLEAR(self);
|
|
PyErr_Format(PyExc_RuntimeError, "Failed to call posix_memalign: %s", strerror(ret));
|
|
return NULL;
|
|
}
|
|
memset(self->state, 0, sizeof(PS));
|
|
PS *state = (PS*)self->state;
|
|
if ((intptr_t)state->buf % BUF_EXTRA != 0) {
|
|
Py_CLEAR(self); PyErr_SetString(PyExc_TypeError, "PS->buf is not aligned");
|
|
return NULL;
|
|
}
|
|
if ((ret = pthread_mutex_init(&state->lock, NULL)) != 0) {
|
|
Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Parser lock mutex: %s", strerror(ret));
|
|
return NULL;
|
|
}
|
|
state->window_id = window_id;
|
|
utf8_decoder_reset(&state->utf8_decoder);
|
|
reset_csi(&state->csi);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#undef EXTRA_INIT
|
|
#define EXTRA_INIT \
|
|
if (0 != PyModule_AddIntConstant(module, "VT_PARSER_BUFFER_SIZE", BUF_SZ)) return 0; \
|
|
if (0 != PyModule_AddIntConstant(module, "VT_PARSER_MAX_ESCAPE_CODE_SIZE", MAX_ESCAPE_CODE_LENGTH)) return 0; \
|
|
if (!init_simd(module)) return 0; \
|
|
|
|
INIT_TYPE(Parser)
|
|
|
|
#endif
|
|
// }}}
|