/* * vt-parser.c * Copyright (C) 2023 Kovid Goyal * * 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 #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(); } #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_params, is_group, region) #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_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: REPORT_ERROR("Ignoring OSC 3008, used by systemd for OSC-context"); break; 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 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_back(s, count, -1); } 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_HANDLER1(screen_report_size, 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 (!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) { _parse_sgr(self, &self->csi); break; } if (start_modifier == '>' && !end_modifier) { CALL_CSI_HANDLER1(screen_modify_other_keys, 0); 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 // }}}