diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index 24f56e114..37b68a0e5 100755 --- a/gen/apc_parsers.py +++ b/gen/apc_parsers.py @@ -52,7 +52,7 @@ def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str) q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values)) lines.append(f''' case {attr}: {{ - g.{attr} = screen->parser_buf[pos++] & 0xff; + g.{attr} = parser_buf[pos++]; if ({q}) {{ REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr}); return; @@ -116,22 +116,23 @@ def generate( parr = 'static uint8_t payload[4096];' payload_case = f''' case PAYLOAD: {{ - sz = screen->parser_buf_pos - pos; + sz = parser_buf_pos - pos; g.payload_sz = sizeof(payload); - if (!base64_decode32(screen->parser_buf + pos, sz, payload, &g.payload_sz)) {{ + if (!base64_decode8(parser_buf + pos, sz, payload, &g.payload_sz)) {{ REPORT_ERROR("Failed to parse {command_class} command payload with error: payload size (%zu) too large", sz); return; }} - pos = screen->parser_buf_pos; + pos = parser_buf_pos; }} break; ''' - callback = f'{callback_name}(screen, &g, payload)' + callback = f'{callback_name}(self->screen, &g, payload)' else: payload_after_value = payload = parr = payload_case = '' - callback = f'{callback_name}(screen, &g)' + callback = f'{callback_name}(self->screen, &g)' return f''' + #include "base64.h" static inline void -{function_name}(Screen *screen, PyObject UNUSED *dump_callback) {{ +{function_name}(PS *self, const uint8_t *parser_buf, const size_t parser_buf_pos) {{ unsigned int pos = 1; enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }}; enum PARSER_STATES state = KEY, value_state = FLAG; @@ -144,12 +145,12 @@ static inline void {parr} {keys_enum} enum KEYS key = '{initial_key}'; - if (screen->parser_buf[pos] == ';') state = AFTER_VALUE; + if (parser_buf[pos] == ';') state = AFTER_VALUE; - while (pos < screen->parser_buf_pos) {{ + while (pos < parser_buf_pos) {{ switch(state) {{ case KEY: - key = screen->parser_buf[pos++]; + key = parser_buf[pos++]; state = EQUAL; switch(key) {{ {handle_key} @@ -160,8 +161,8 @@ static inline void break; case EQUAL: - if (screen->parser_buf[pos++] != '=') {{ - REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", screen->parser_buf[pos-1]); + if (parser_buf[pos++] != '=') {{ + REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", parser_buf[pos-1]); return; }} state = value_state; @@ -178,16 +179,16 @@ static inline void case INT: #define READ_UINT \\ - for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) {{ \\ - if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; \\ + for (i = pos; i < MIN(parser_buf_pos, pos + 10); i++) {{ \\ + if (parser_buf[i] < '0' || parser_buf[i] > '9') break; \\ }} \\ if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\ - lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \\ + lcode = utoi(parser_buf + pos, i - pos); pos = i; \\ if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\ code = lcode; is_negative = false; - if(screen->parser_buf[pos] == '-') {{ is_negative = true; pos++; }} + if(parser_buf[pos] == '-') {{ is_negative = true; pos++; }} #define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break READ_UINT; switch(key) {{ @@ -210,10 +211,10 @@ static inline void #undef READ_UINT case AFTER_VALUE: - switch (screen->parser_buf[pos++]) {{ + switch (parser_buf[pos++]) {{ default: REPORT_ERROR("Malformed {command_class} control block, expecting a comma or semi-colon after a value, found: 0x%x", - screen->parser_buf[pos - 1]); + parser_buf[pos - 1]); return; case ',': state = KEY; diff --git a/kittens/ssh/utils.py b/kittens/ssh/utils.py index b700d29a6..0664b7d25 100644 --- a/kittens/ssh/utils.py +++ b/kittens/ssh/utils.py @@ -112,11 +112,11 @@ def read_data_from_shared_memory(shm_name: str) -> Any: return json.loads(shm.read_data_with_size()) -def get_ssh_data(msg: str, request_id: str) -> Iterator[bytes]: +def get_ssh_data(msgb: memoryview, request_id: str) -> Iterator[bytes]: from base64 import standard_b64decode yield b'\nKITTY_DATA_START\n' # to discard leading data try: - msg = standard_b64decode(msg).decode('utf-8') + msg = standard_b64decode(msgb).decode('utf-8') md = dict(x.split('=', 1) for x in msg.split(':')) pw = md['pw'] pwfilename = md['pwfile'] diff --git a/kitty/boss.py b/kitty/boss.py index 08fc5baf6..032932423 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -584,7 +584,7 @@ class Boss: self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.window_id_map[window.id] = window - def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> RCResponse: + def _handle_remote_command(self, cmd: memoryview, window: Optional[Window] = None, peer_id: int = 0) -> RCResponse: from .remote_control import is_cmd_allowed, parse_cmd, remote_control_allowed response = None window = window or None @@ -778,7 +778,7 @@ class Boss: cmd_prefix = b'\x1bP@kitty-cmd' terminator = b'\x1b\\' if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator): - cmd = msg_bytes[len(cmd_prefix):-len(terminator)].decode('utf-8') + cmd = memoryview(msg_bytes[len(cmd_prefix):-len(terminator)]) response = self._handle_remote_command(cmd, peer_id=peer_id) if response is None: return None @@ -834,7 +834,7 @@ class Boss: log_error('Unknown message received over single instance socket, ignoring') return None - def handle_remote_cmd(self, cmd: str, window: Optional[Window] = None) -> None: + def handle_remote_cmd(self, cmd: memoryview, window: Optional[Window] = None) -> None: response = self._handle_remote_command(cmd, window) if response is not None and not isinstance(response, AsyncResponse) and window is not None: window.send_cmd_response(response) diff --git a/kitty/charsets.c b/kitty/charsets.c index 9dbf664e0..0715af029 100644 --- a/kitty/charsets.c +++ b/kitty/charsets.c @@ -10,205 +10,6 @@ #include "data-types.h" -static uint32_t charset_translations[5][256] = { - /* 8-bit Latin-1 mapped to Unicode -- trivial mapping */ - { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, - 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, - 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, - 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, - 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, - 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, - 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, - 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, - 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, - 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, - 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, - 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, - 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, - 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, - 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, - 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, - 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, - 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, - 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, - 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff - }, - /* VT100 graphics mapped to Unicode */ - { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, - 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f, - 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0, - 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, - 0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, - 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, - 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f, - 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, - 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, - 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, - 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, - 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, - 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, - 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, - 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, - 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, - 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, - 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, - 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, - 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, - 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, - 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, - 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff - }, - /* IBM Codepage 437 mapped to Unicode */ - { - 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, - 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, - 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, - 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, - 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, - 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, - 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, - 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, - 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, - 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, - 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, - 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, - 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, - 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, - 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, - 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, - 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, - 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, - 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, - 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, - 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, - 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 - }, - // VAX 42 map - { - 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, - 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, - 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, - 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, - 0x0020, 0x043b, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x0435, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, - 0x0060, 0x0441, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0435, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x043a, - 0x0070, 0x0071, 0x0442, 0x0073, 0x043b, 0x0435, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, - 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, - 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, - 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, - 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, - 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, - 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, - 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, - 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, - 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, - 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, - 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, - 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, - 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, - 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, - 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, - 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 - }, - /* UK mapping, same as 8-bit Latin1 except the pound sign replaces # */ - { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, - 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, - 0x0020, 0x0021, 0x0022, 0x00a3, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, - 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, - 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, - 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, - 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, - 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, - 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, - 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, - 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, - 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, - 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, - 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, - 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, - 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, - 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, - 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, - 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, - 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, - 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff - }, - -}; - -uint32_t* -translation_table(uint32_t which) { - switch(which){ - case 'B': - return charset_translations[0]; - case '0': - return charset_translations[1]; - case 'U': - return charset_translations[2]; - case 'V': - return charset_translations[3]; - case 'A': - return charset_translations[4]; - default: - return charset_translations[0]; - } -} - -uint32_t *latin1_charset = charset_translations[0]; - // UTF-8 decode taken from: https://bjoern.hoehrmann.de/utf-8/decoder/dfa/ static const uint8_t utf8_data[] = { diff --git a/kitty/clipboard.py b/kitty/clipboard.py index 0f178d26e..353a251c6 100644 --- a/kitty/clipboard.py +++ b/kitty/clipboard.py @@ -11,9 +11,10 @@ from typing import IO, Callable, Dict, List, Mapping, NamedTuple, Optional, Tupl from .conf.utils import uniq from .constants import supports_primary_selection from .fast_data_types import ( + ESC_OSC, GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION, - OSC, + find_in_memoryview, get_boss, get_clipboard_mime, get_options, @@ -335,11 +336,17 @@ class ClipboardRequestManager: self.currently_asking_permission_for: Optional[ReadRequest] = None self.in_flight_write_request: Optional[WriteRequest] = None - def parse_osc_5522(self, data: str) -> None: + def parse_osc_5522(self, data: memoryview) -> None: import base64 from .notify import sanitize_id - metadata, _, epayload = data.partition(';') + idx = find_in_memoryview(data, ord(b';')) + if idx > -1: + metadata = str(data[:idx], "utf-8", "replace") + epayload = data[idx+1:] + else: + metadata = str(data, "utf-8", "replace") + epayload = data[len(data):] m: Dict[str, str] = {} for record in metadata.split(':'): try: @@ -381,12 +388,12 @@ class ClipboardRequestManager: wr.add_base64_data(epayload, mime) except OSError: if w is not None: - w.screen.send_escape_code_to_child(OSC, wr.encode_response(status='EIO')) + w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EIO')) self.in_flight_write_request = None raise except Exception: if w is not None: - w.screen.send_escape_code_to_child(OSC, wr.encode_response(status='EINVAL')) + w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EINVAL')) self.in_flight_write_request = None raise else: @@ -394,18 +401,24 @@ class ClipboardRequestManager: wr.commit() self.in_flight_write_request = None if w is not None: - w.screen.send_escape_code_to_child(OSC, wr.encode_response(status='DONE')) + w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='DONE')) - def parse_osc_52(self, data: str, is_partial: bool = False) -> None: - where, text = data.partition(';')[::2] - if text == '?': + def parse_osc_52(self, data: memoryview, is_partial: bool = False) -> None: + idx = find_in_memoryview(data, ord(b';')) + if idx > -1: + where = str(data[idx:], "utf-8", 'replace') + data = data[idx+1:] + else: + where = str(data, "utf-8", 'replace') + data = data[len(data):] + if len(data) == 1 and data.tobytes() == b'?': rr = ReadRequest(is_primary_selection=ClipboardType.from_osc52_where_field(where) is ClipboardType.primary_selection) self.handle_read_request(rr) else: wr = self.in_flight_write_request if wr is None: wr = self.in_flight_write_request = WriteRequest(ClipboardType.from_osc52_where_field(where) is ClipboardType.primary_selection) - wr.add_base64_data(text) + wr.add_base64_data(data) if is_partial: return self.in_flight_write_request = None @@ -426,7 +439,7 @@ class ClipboardRequestManager: if not allowed or not cp.enabled: self.in_flight_write_request = None if w is not None: - w.screen.send_escape_code_to_child(OSC, wr.encode_response(status='EPERM' if not allowed else 'ENOSYS')) + w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EPERM' if not allowed else 'ENOSYS')) def fulfill_legacy_write_request(self, wr: WriteRequest, allowed: bool = True) -> None: cp = get_boss().primary_selection if wr.is_primary_selection else get_boss().clipboard @@ -455,12 +468,12 @@ class ClipboardRequestManager: return cp = get_boss().primary_selection if rr.is_primary_selection else get_boss().clipboard if not cp.enabled: - w.screen.send_escape_code_to_child(OSC, rr.encode_response(status='ENOSYS')) + w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='ENOSYS')) return if not allowed: - w.screen.send_escape_code_to_child(OSC, rr.encode_response(status='EPERM')) + w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='EPERM')) return - w.screen.send_escape_code_to_child(OSC, rr.encode_response(status='OK')) + w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='OK')) current_mime = '' @@ -468,7 +481,7 @@ class ClipboardRequestManager: assert w is not None mv = memoryview(data) while mv: - w.screen.send_escape_code_to_child(OSC, rr.encode_response(payload=mv[:4096], mime=current_mime)) + w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(payload=mv[:4096], mime=current_mime)) mv = mv[4096:] for mime in rr.mime_types: @@ -477,20 +490,20 @@ class ClipboardRequestManager: payload = ' '.join(cp.get_available_mime_types_for_paste()).encode('utf-8') if payload: payload += b'\n' - w.screen.send_escape_code_to_child(OSC, rr.encode_response(payload=payload, mime=current_mime)) + w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(payload=payload, mime=current_mime)) continue try: cp.get_mime(mime, write_chunks) except Exception as e: log_error(f'Failed to read requested mime type {mime} with error: {e}') - w.screen.send_escape_code_to_child(OSC, rr.encode_response(status='DONE')) + w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='DONE')) def reject_read_request(self, rr: ReadRequest) -> None: if rr.protocol_type is ProtocolType.osc_52: return self.fulfill_legacy_read_request(rr, False) w = get_boss().window_id_map.get(self.window_id) if w is not None: - w.screen.send_escape_code_to_child(OSC, rr.encode_response(status='EPERM')) + w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='EPERM')) def fulfill_legacy_read_request(self, rr: ReadRequest, allowed: bool = True) -> None: cp = get_boss().primary_selection if rr.is_primary_selection else get_boss().clipboard @@ -500,7 +513,7 @@ class ClipboardRequestManager: if cp.enabled and allowed: text = cp.get_text() loc = 'p' if rr.is_primary_selection else 'c' - w.screen.send_escape_code_to_child(OSC, encode_osc52(loc, text)) + w.screen.send_escape_code_to_child(ESC_OSC, encode_osc52(loc, text)) def ask_to_read_clipboard(self, rr: ReadRequest) -> None: if rr.mime_types == (TARGETS_MIME,): diff --git a/kitty/control-codes.h b/kitty/control-codes.h index 5c869c712..af090ce94 100644 --- a/kitty/control-codes.h +++ b/kitty/control-codes.h @@ -55,19 +55,6 @@ // *Delete*: Is ignored. #define DEL 0x7f -#ifndef NO_C1_CONTROLS -#define IND 0x84 -#define NEL 0x85 -#define HTS 0x88 -#define RI 0x8d -#define DCS 0x90 -#define CSI 0x9b -#define ST 0x9c -#define OSC 0x9d -#define PM 0x9e -#define APC 0x9f -#endif - // Sharp control codes // ------------------- diff --git a/kitty/data-types.c b/kitty/data-types.c index 75912100c..20ab0a2b4 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -320,6 +320,17 @@ expand_ansi_c_escapes(PyObject *self UNUSED, PyObject *src) { return ans; } +static PyObject* +find_in_memoryview(PyObject *self UNUSED, PyObject *args) { + const char *buf; Py_ssize_t sz; + unsigned char q; + if (!PyArg_ParseTuple(args, "y#b", &buf, &sz, &q)) return NULL; + const char *p = memchr(buf, q, sz); + Py_ssize_t ans = -1; + if (p) ans = p - buf; + return PyLong_FromSsize_t(ans); +} + static PyMethodDef module_methods[] = { {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"expand_ansi_c_escapes", (PyCFunction)expand_ansi_c_escapes, METH_O, ""}, @@ -335,13 +346,12 @@ static PyMethodDef module_methods[] = { {"base64_encode", (PyCFunction)pybase64_encode, METH_VARARGS, ""}, {"base64_decode", (PyCFunction)pybase64_decode, METH_VARARGS, ""}, {"thread_write", (PyCFunction)cm_thread_write, METH_VARARGS, ""}, - {"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""}, - {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""}, {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""}, {"locale_is_valid", (PyCFunction)locale_is_valid, METH_VARARGS, ""}, {"shm_open", (PyCFunction)py_shm_open, METH_VARARGS, ""}, {"shm_unlink", (PyCFunction)py_shm_unlink, METH_VARARGS, ""}, {"wrapped_kitten_names", (PyCFunction)wrapped_kittens, METH_NOARGS, ""}, + {"find_in_memoryview", (PyCFunction)find_in_memoryview, METH_VARARGS, ""}, #ifdef __APPLE__ METHODB(user_cache_dir, METH_NOARGS), METHODB(process_group_map, METH_NOARGS), @@ -481,11 +491,12 @@ PyInit_fast_data_types(void) { PyModule_AddIntMacro(m, DECCOLM); PyModule_AddIntMacro(m, DECOM); PyModule_AddIntMacro(m, IRM); - PyModule_AddIntMacro(m, CSI); - PyModule_AddIntMacro(m, DCS); - PyModule_AddIntMacro(m, APC); - PyModule_AddIntMacro(m, OSC); PyModule_AddIntMacro(m, FILE_TRANSFER_CODE); + PyModule_AddIntMacro(m, ESC_CSI); + PyModule_AddIntMacro(m, ESC_OSC); + PyModule_AddIntMacro(m, ESC_APC); + PyModule_AddIntMacro(m, ESC_DCS); + PyModule_AddIntMacro(m, ESC_PM); #ifdef __APPLE__ // Apple says its SHM_NAME_MAX but SHM_NAME_MAX is not actually declared in typical CrApple style. // This value is based on experimentation and from qsharedmemory.cpp in Qt diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 36148ae81..bf6bd32d8 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -263,8 +263,6 @@ CELL_BG_PROGRAM: int CELL_FG_PROGRAM: int CELL_PROGRAM: int CELL_SPECIAL_PROGRAM: int -CSI: int -DCS: int DECORATION: int DIM: int GRAPHICS_ALPHA_MASK_PROGRAM: int @@ -274,8 +272,12 @@ MARK: int MARK_MASK: int DECORATION_MASK: int NUM_UNDERLINE_STYLES: int -OSC: int FILE_TRANSFER_CODE: int +ESC_CSI: int +ESC_OSC: int +ESC_DCS: int +ESC_APC: int +ESC_PM: int REVERSE: int SCROLL_FULL: int SCROLL_LINE: int @@ -1564,3 +1566,4 @@ def cocoa_recreate_global_menu() -> None: ... def cocoa_clear_global_shortcuts() -> None: ... def update_pointer_shape(os_window_id: int) -> None: ... def os_window_focus_counters() -> Dict[int, int]: ... +def find_in_memoryview(buf: Union[bytes, memoryview, bytearray], chr: int) -> int: ... diff --git a/kitty/file_transmission.py b/kitty/file_transmission.py index fd610a8ec..dbfc112ca 100644 --- a/kitty/file_transmission.py +++ b/kitty/file_transmission.py @@ -20,7 +20,7 @@ from time import monotonic, time_ns from typing import IO, Any, Callable, DefaultDict, Deque, Dict, Iterable, Iterator, List, Optional, Tuple, Union from kittens.transfer.utils import IdentityCompressor, ZlibCompressor, abspath, expand_home, home_path -from kitty.fast_data_types import FILE_TRANSFER_CODE, OSC, AES256GCMDecrypt, add_timer, base64_decode, base64_encode, get_boss, get_options +from kitty.fast_data_types import ESC_OSC, FILE_TRANSFER_CODE, AES256GCMDecrypt, add_timer, base64_decode, base64_encode, get_boss, get_options from kitty.types import run_once from .utils import log_error @@ -855,7 +855,7 @@ class FileTransmission: if self.active_sends[a].is_expired: self.drop_send(a) - def handle_serialized_command(self, data: str) -> None: + def handle_serialized_command(self, data: memoryview) -> None: try: cmd = FileTransmissionCommand.deserialize(data) except Exception as e: @@ -1147,7 +1147,7 @@ class FileTransmission: window = boss.window_id_map.get(self.window_id) if window is not None: data = tuple(payload.get_serialized_fields(prefix_with_osc_code=True)) - queued = window.screen.send_escape_code_to_child(OSC, data) + queued = window.screen.send_escape_code_to_child(ESC_OSC, data) if not queued: if use_pending: if appendleft: diff --git a/kitty/mouse.c b/kitty/mouse.c index 8a7d5a1fc..39ef1876b 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -394,7 +394,7 @@ HANDLER(handle_move_event) { } else { if (!mouse_cell_changed && screen->modes.mouse_tracking_protocol != SGR_PIXEL_PROTOCOL) return; int sz = encode_mouse_button(w, button, button >=0 ? DRAG : MOVE, modifiers); - if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } + if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } @@ -566,7 +566,7 @@ HANDLER(handle_button_event) { if (!dispatch_mouse_event(w, button, is_release ? -1 : 1, modifiers, screen->modes.mouse_tracking_mode != 0)) { if (screen->modes.mouse_tracking_mode != 0) { int sz = encode_mouse_button(w, button, is_release ? RELEASE : PRESS, modifiers); - if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } + if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } // the windows array might have been re-alloced in dispatch_mouse_event @@ -957,7 +957,7 @@ scroll_event(double xoffset, double yoffset, int flags, int modifiers) { if (sz > 0) { mouse_event_buf[sz] = 0; for (s = abs(s); s > 0; s--) { - write_escape_code_to_child(screen, CSI, mouse_event_buf); + write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } else { @@ -974,7 +974,7 @@ scroll_event(double xoffset, double yoffset, int flags, int modifiers) { if (sz > 0) { mouse_event_buf[sz] = 0; for (s = abs(s); s > 0; s--) { - write_escape_code_to_child(screen, CSI, mouse_event_buf); + write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } @@ -999,7 +999,7 @@ send_mouse_event(PyObject *self UNUSED, PyObject *args, PyObject *kw) { int sz = encode_mouse_event_impl(&mpos, screen->modes.mouse_tracking_protocol, button, action, mods); if (sz > 0) { mouse_event_buf[sz] = 0; - write_escape_code_to_child(screen, CSI, mouse_event_buf); + write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); Py_RETURN_TRUE; } } diff --git a/kitty/parse-graphics-command.h b/kitty/parse-graphics-command.h index cb402f88e..57e94eae4 100644 --- a/kitty/parse-graphics-command.h +++ b/kitty/parse-graphics-command.h @@ -2,8 +2,9 @@ #pragma once -static inline void parse_graphics_code(Screen *screen, - PyObject UNUSED *dump_callback) { +#include "base64.h" +static inline void parse_graphics_code(PS *self, const uint8_t *parser_buf, + const size_t parser_buf_pos) { unsigned int pos = 1; enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD }; enum PARSER_STATES state = KEY, value_state = FLAG; @@ -48,13 +49,13 @@ static inline void parse_graphics_code(Screen *screen, }; enum KEYS key = 'a'; - if (screen->parser_buf[pos] == ';') + if (parser_buf[pos] == ';') state = AFTER_VALUE; - while (pos < screen->parser_buf_pos) { + while (pos < parser_buf_pos) { switch (state) { case KEY: - key = screen->parser_buf[pos++]; + key = parser_buf[pos++]; state = EQUAL; switch (key) { case action: @@ -153,10 +154,10 @@ static inline void parse_graphics_code(Screen *screen, break; case EQUAL: - if (screen->parser_buf[pos++] != '=') { + if (parser_buf[pos++] != '=') { REPORT_ERROR("Malformed GraphicsCommand control block, no = after key, " "found: 0x%x instead", - screen->parser_buf[pos - 1]); + parser_buf[pos - 1]); return; } state = value_state; @@ -166,7 +167,7 @@ static inline void parse_graphics_code(Screen *screen, switch (key) { case action: { - g.action = screen->parser_buf[pos++] & 0xff; + g.action = parser_buf[pos++]; if (g.action != 'T' && g.action != 'a' && g.action != 'c' && g.action != 'd' && g.action != 'f' && g.action != 'p' && g.action != 'q' && g.action != 't') { @@ -178,7 +179,7 @@ static inline void parse_graphics_code(Screen *screen, } break; case delete_action: { - g.delete_action = screen->parser_buf[pos++] & 0xff; + g.delete_action = parser_buf[pos++]; if (g.delete_action != 'A' && g.delete_action != 'C' && g.delete_action != 'F' && g.delete_action != 'I' && g.delete_action != 'N' && g.delete_action != 'P' && @@ -197,7 +198,7 @@ static inline void parse_graphics_code(Screen *screen, } break; case transmission_type: { - g.transmission_type = screen->parser_buf[pos++] & 0xff; + g.transmission_type = parser_buf[pos++]; if (g.transmission_type != 'd' && g.transmission_type != 'f' && g.transmission_type != 's' && g.transmission_type != 't') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " @@ -208,7 +209,7 @@ static inline void parse_graphics_code(Screen *screen, } break; case compressed: { - g.compressed = screen->parser_buf[pos++] & 0xff; + g.compressed = parser_buf[pos++]; if (g.compressed != 'z') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for compressed: 0x%x", @@ -225,8 +226,8 @@ static inline void parse_graphics_code(Screen *screen, case INT: #define READ_UINT \ - for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) { \ - if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') \ + for (i = pos; i < MIN(parser_buf_pos, pos + 10); i++) { \ + if (parser_buf[i] < '0' || parser_buf[i] > '9') \ break; \ } \ if (i == pos) { \ @@ -235,7 +236,7 @@ static inline void parse_graphics_code(Screen *screen, key & 0xFF); \ return; \ } \ - lcode = utoi(screen->parser_buf + pos, i - pos); \ + lcode = utoi(parser_buf + pos, i - pos); \ pos = i; \ if (lcode > UINT32_MAX) { \ REPORT_ERROR( \ @@ -245,7 +246,7 @@ static inline void parse_graphics_code(Screen *screen, code = lcode; is_negative = false; - if (screen->parser_buf[pos] == '-') { + if (parser_buf[pos] == '-') { is_negative = true; pos++; } @@ -302,11 +303,11 @@ static inline void parse_graphics_code(Screen *screen, #undef READ_UINT case AFTER_VALUE: - switch (screen->parser_buf[pos++]) { + switch (parser_buf[pos++]) { default: REPORT_ERROR("Malformed GraphicsCommand control block, expecting a " "comma or semi-colon after a value, found: 0x%x", - screen->parser_buf[pos - 1]); + parser_buf[pos - 1]); return; case ',': state = KEY; @@ -318,16 +319,15 @@ static inline void parse_graphics_code(Screen *screen, break; case PAYLOAD: { - sz = screen->parser_buf_pos - pos; + sz = parser_buf_pos - pos; g.payload_sz = sizeof(payload); - if (!base64_decode32(screen->parser_buf + pos, sz, payload, - &g.payload_sz)) { + if (!base64_decode8(parser_buf + pos, sz, payload, &g.payload_sz)) { REPORT_ERROR("Failed to parse GraphicsCommand command payload with " "error: payload size (%zu) too large", sz); return; } - pos = screen->parser_buf_pos; + pos = parser_buf_pos; } break; } // end switch @@ -373,5 +373,5 @@ static inline void parse_graphics_code(Screen *screen, "offset_from_parent_y", (int)g.offset_from_parent_y, "payload_sz", g.payload_sz, payload, g.payload_sz); - screen_handle_graphics_command(screen, &g, payload); + screen_handle_graphics_command(self->screen, &g, payload); } diff --git a/kitty/parser.c b/kitty/parser.c deleted file mode 100644 index 11c287d9a..000000000 --- a/kitty/parser.c +++ /dev/null @@ -1,1608 +0,0 @@ -/* - * Copyright (C) 2016 Kovid Goyal - * - * Distributed under terms of the GPL3 license. - */ - -// Need _POSIX_C_SOURCE for strtok_r -#define _POSIX_C_SOURCE 200809L - -#include "data-types.h" -#define B64_INPUT_BITSIZE 32 -#include "base64.h" -#include "control-codes.h" -#include "screen.h" -#include "charsets.h" -#include "monotonic.h" -#include - -extern PyTypeObject Screen_Type; -#define EXTENDED_OSC_SENTINEL 0x1bu -#define PARSER_BUF_SZ (8u * 1024u) -#define PENDING_BUF_INCREMENT (16u * 1024u) - -// utils {{{ -static const uint64_t pow_10_array[] = { - 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000 -}; - -static int64_t -utoi(const uint32_t *buf, unsigned int sz) { - int64_t ans = 0; - const uint32_t *p = buf; - int mult = 1; - if (sz && *p == '-') { - mult = -1; p++; sz--; - } - // Ignore leading zeros - while(sz > 0) { - if (*p == '0') { p++; sz--; } - else break; - } - if (sz < sizeof(pow_10_array)/sizeof(pow_10_array[0])) { - for (int i = sz-1, j=0; i >= 0; i--, j++) { - ans += (p[i] - '0') * pow_10_array[j]; - } - } - return ans * mult; -} - - -static const char* -utf8(char_type codepoint) { - if (!codepoint) return ""; - static char buf[8]; - int n = encode_utf8(codepoint, buf); - buf[n] = 0; - return buf; -} - -// }}} - -// Macros {{{ -#define IS_DIGIT \ - case '0': \ - case '1': \ - case '2': \ - case '3': \ - case '4': \ - case '5': \ - case '6': \ - case '7': \ - case '8': \ - case '9': - - -#ifdef DUMP_COMMANDS -static void -_report_error(PyObject *dump_callback, const char *fmt, ...) { - va_list argptr; - va_start(argptr, fmt); - PyObject *temp = PyUnicode_FromFormatV(fmt, argptr); - va_end(argptr); - if (temp != NULL) { - Py_XDECREF(PyObject_CallFunctionObjArgs(dump_callback, temp, NULL)); PyErr_Clear(); - Py_CLEAR(temp); - } -} - -static void -_report_params(PyObject *dump_callback, const char *name, int *params, unsigned int count, Region *r) { - static char buf[MAX_PARAMS*3] = {0}; - unsigned int i, p=0; - if (r) p += snprintf(buf + p, sizeof(buf) - 2, "%u %u %u %u ", r->top, r->left, r->bottom, r->right); - for(i = 0; i < count && p < MAX_PARAMS*3-20; i++) { - int n = snprintf(buf + p, MAX_PARAMS*3 - p, "%i ", params[i]); - if (n < 0) break; - p += n; - } - buf[p] = 0; - Py_XDECREF(PyObject_CallFunction(dump_callback, "ss", name, buf)); PyErr_Clear(); -} - -#define DUMP_UNUSED - -#define REPORT_ERROR(...) _report_error(dump_callback, __VA_ARGS__); - -#define REPORT_COMMAND1(name) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "s", #name)); PyErr_Clear(); - -#define REPORT_COMMAND2(name, x) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "si", #name, (int)x)); PyErr_Clear(); - -#define REPORT_COMMAND3(name, x, y) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "sii", #name, (int)x, (int)y)); PyErr_Clear(); - -#define GET_MACRO(_1,_2,_3,NAME,...) NAME -#define REPORT_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__) -#define REPORT_VA_COMMAND(...) Py_XDECREF(PyObject_CallFunction(dump_callback, __VA_ARGS__)); PyErr_Clear(); - -#define REPORT_DRAW(ch) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", "draw", ch)); PyErr_Clear(); - -#define REPORT_PARAMS(name, params, num, region) _report_params(dump_callback, name, params, num_params, region) - -#define FLUSH_DRAW \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", "draw", Py_None)); PyErr_Clear(); - -#define REPORT_OSC(name, string) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", #name, string)); PyErr_Clear(); - -#define REPORT_OSC2(name, code, string) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "siO", #name, code, string)); PyErr_Clear(); - -#define REPORT_HYPERLINK(id, url) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "szz", "set_active_hyperlink", id, url)); PyErr_Clear(); - -#else - -#define DUMP_UNUSED UNUSED - -#define REPORT_ERROR(...) log_error(ERROR_PREFIX " " __VA_ARGS__); - -#define REPORT_COMMAND(...) -#define REPORT_VA_COMMAND(...) -#define REPORT_DRAW(ch) -#define REPORT_PARAMS(...) -#define FLUSH_DRAW -#define REPORT_OSC(name, string) -#define REPORT_OSC2(name, code, string) -#define REPORT_HYPERLINK(id, url) - -#endif - -#define SET_STATE(state) screen->parser_state = state; screen->parser_buf_pos = 0; -// }}} - -// Normal mode {{{ -static void -screen_nel(Screen *screen) { screen_carriage_return(screen); screen_linefeed(screen); } - -static void -dispatch_normal_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { -#define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(name); name(screen); break; - switch(ch) { - case BEL: - CALL_SCREEN_HANDLER(screen_bell); - case BS: - CALL_SCREEN_HANDLER(screen_backspace); - case HT: - CALL_SCREEN_HANDLER(screen_tab); - case NEL: - CALL_SCREEN_HANDLER(screen_nel); - case LF: - case VT: - case FF: - CALL_SCREEN_HANDLER(screen_linefeed); - case CR: - CALL_SCREEN_HANDLER(screen_carriage_return); - case SI: - REPORT_COMMAND(screen_change_charset, 0); - screen_change_charset(screen, 0); break; - case SO: - REPORT_COMMAND(screen_change_charset, 1); - screen_change_charset(screen, 1); break; - case IND: - CALL_SCREEN_HANDLER(screen_index); - case RI: - CALL_SCREEN_HANDLER(screen_reverse_index); - case HTS: - CALL_SCREEN_HANDLER(screen_set_tab_stop); - case ESC: - case CSI: - case OSC: - case DCS: - case APC: - case PM: - SET_STATE(ch); break; - case NUL: - case DEL: - break; // no-op - default: - REPORT_DRAW(ch); - screen_draw(screen, ch, true); - break; - } -#undef CALL_SCREEN_HANDLER -} // }}} - -// Esc mode {{{ -static void -dispatch_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { -#define CALL_ED(name) REPORT_COMMAND(name); name(screen); SET_STATE(0); -#define CALL_ED1(name, ch) REPORT_COMMAND(name, ch); name(screen, ch); SET_STATE(0); -#define CALL_ED2(name, a, b) REPORT_COMMAND(name, a, b); name(screen, a, b); SET_STATE(0); - switch(screen->parser_buf_pos) { - case 0: - switch (ch) { - case ESC_DCS: - SET_STATE(DCS); break; - case ESC_OSC: - SET_STATE(OSC); break; - case ESC_CSI: - SET_STATE(CSI); break; - case ESC_APC: - SET_STATE(APC); break; - case ESC_PM: - SET_STATE(PM); break; - case ESC_RIS: - CALL_ED(screen_reset); break; - case ESC_IND: - CALL_ED(screen_index); break; - case ESC_NEL: - CALL_ED(screen_nel); break; - case ESC_RI: - CALL_ED(screen_reverse_index); break; - case ESC_HTS: - CALL_ED(screen_set_tab_stop); break; - case ESC_DECSC: - CALL_ED(screen_save_cursor); break; - case ESC_DECRC: - CALL_ED(screen_restore_cursor); break; - case ESC_DECKPNM: - CALL_ED(screen_normal_keypad_mode); break; - case ESC_DECKPAM: - CALL_ED(screen_alternate_keypad_mode); break; - case '%': - case '(': - case ')': - case '*': - case '+': - case '-': - case '.': - case '/': - case ' ': - case '#': - screen->parser_buf[screen->parser_buf_pos++] = ch; - break; - default: - REPORT_ERROR("%s0x%x", "Unknown char after ESC: ", ch); - SET_STATE(0); break; - } - break; - default: - switch(screen->parser_buf[0]) { - case '%': - switch(ch) { - case '@': - REPORT_COMMAND(screen_use_latin1, 1); - screen_use_latin1(screen, true); - break; - case 'G': - REPORT_COMMAND(screen_use_latin1, 0); - screen_use_latin1(screen, false); - break; - default: - REPORT_ERROR("Unhandled Esc %% code: 0x%x", ch); break; - } - break; - case '#': - if (ch == '8') { CALL_ED(screen_align); } - else { REPORT_ERROR("Unhandled Esc # code: 0x%x", ch); } - break; - case '(': - case ')': - switch(ch) { - case 'A': - case 'B': - case '0': - case 'U': - case 'V': - CALL_ED2(screen_designate_charset, screen->parser_buf[0] - '(', ch); break; - default: - REPORT_ERROR("Unknown charset: 0x%x", ch); break; - } - break; - case ' ': - switch(ch) { - case 'F': - case 'G': - REPORT_COMMAND(screen_set_8bit_controls, ch == 'G'); - screen_set_8bit_controls(screen, ch == 'G'); - break; - default: - REPORT_ERROR("Unhandled ESC SP escape code: 0x%x", ch); break; - } - break; - default: - REPORT_ERROR("Unhandled charset related escape code: 0x%x 0x%x", screen->parser_buf[0], ch); break; - } - SET_STATE(0); - break; - } -#undef CALL_ED -#undef CALL_ED1 -} // }}} - -// OSC mode {{{ - -static bool -parse_osc_8(char *buf, char **id, char **url) { - char *boundary = strstr(buf, ";"); - if (boundary == NULL) return false; - *boundary = 0; - if (*(boundary + 1)) *url = boundary + 1; - char *save = NULL, *token = strtok_r(buf, ":", &save); - while (token != NULL) { - size_t len = strlen(token); - if (len > 3 && token[0] == 'i' && token[1] == 'd' && token[2] == '=' && token[3]) { - *id = token + 3; - break; - } - token = strtok_r(NULL, ":", &save); - } - return true; -} - -static void -dispatch_hyperlink(Screen *screen, size_t pos, size_t size, PyObject DUMP_UNUSED *dump_callback) { - // since the spec says only ASCII printable chars are allowed in OSC 8, we - // can just convert to char* directly - if (!size) return; // ignore empty OSC 8 since it must have two semi-colons to be valid, which means one semi-colon here - char *id = NULL, *url = NULL; - char *data = malloc(size + 1); - if (!data) fatal("Out of memory"); - for (size_t i = 0; i < size; i++) { - data[i] = screen->parser_buf[i + pos] & 0x7f; - if (data[i] < 32 || data[i] > 126) data[i] = '_'; - } - data[size] = 0; - - if (parse_osc_8(data, &id, &url)) { - REPORT_HYPERLINK(id, url); - set_active_hyperlink(screen, id, url); - } else { - REPORT_ERROR("Ignoring malformed OSC 8 code"); - } - - free(data); -} - -static void -continue_osc_52(Screen *screen) { - screen->parser_buf[0] = '5'; screen->parser_buf[1] = '2'; screen->parser_buf[2] = ';'; - screen->parser_buf[3] = ';'; screen->parser_buf_pos = 4; -} - -static bool -is_extended_osc(const Screen *screen) { - return screen->parser_buf_pos > 2 && screen->parser_buf[0] == EXTENDED_OSC_SENTINEL && screen->parser_buf[1] == 1 && screen->parser_buf[2] == ';'; -} - -static void -dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { -#define DISPATCH_OSC_WITH_CODE(name) REPORT_OSC2(name, code, string); name(screen, code, string); -#define DISPATCH_OSC(name) REPORT_OSC(name, string); name(screen, string); -#define START_DISPATCH {\ - PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + i, limit - i); \ - if (string) { -#define END_DISPATCH Py_CLEAR(string); } PyErr_Clear(); break; } - - const unsigned int limit = screen->parser_buf_pos; - int code=0; - unsigned int i; - for (i = 0; i < MIN(limit, 5u); i++) { - if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; - } - if (i > 0) { - code = utoi(screen->parser_buf, i); - if (i < limit && screen->parser_buf[i] == ';') i++; - } else { - if (is_extended_osc(screen)) { - // partial OSC 52 - i = 3; - code = -52; - } - } - switch(code) { - case 0: - START_DISPATCH - DISPATCH_OSC(set_title); - DISPATCH_OSC(set_icon); - END_DISPATCH - case 1: - START_DISPATCH - DISPATCH_OSC(set_icon); - END_DISPATCH - case 2: - START_DISPATCH - DISPATCH_OSC(set_title); - END_DISPATCH - case 4: - case 104: - START_DISPATCH - DISPATCH_OSC_WITH_CODE(set_color_table_color); - END_DISPATCH - case 6: - case 7: - START_DISPATCH - DISPATCH_OSC_WITH_CODE(process_cwd_notification); - END_DISPATCH - case 8: - dispatch_hyperlink(screen, i, limit-i, dump_callback); - break; - case 9: - case 99: - case 777: - case 1337: - START_DISPATCH - DISPATCH_OSC_WITH_CODE(desktop_notify) - END_DISPATCH - 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 52: case -52: case 5522: - START_DISPATCH - DISPATCH_OSC_WITH_CODE(clipboard_control); - if (code == -52) continue_osc_52(screen); - END_DISPATCH - case 133: - START_DISPATCH - DISPATCH_OSC(shell_prompt_marking); - END_DISPATCH - case FILE_TRANSFER_CODE: - START_DISPATCH - DISPATCH_OSC(file_transmission); - END_DISPATCH - case 30001: - REPORT_COMMAND(screen_push_dynamic_colors); - screen_push_colors(screen, 0); - break; - case 30101: - REPORT_COMMAND(screen_pop_dynamic_colors); - screen_pop_colors(screen, 0); - break; - case 697: - REPORT_ERROR("Ignoring OSC 697, typically used by Fig for shell integration"); - break; - default: - REPORT_ERROR("Unknown OSC code: %u", code); - break; - } -#undef DISPATCH_OSC -#undef DISPATCH_OSC_WITH_CODE -#undef START_DISPATCH -#undef END_DISPATCH -} -// }}} - -// CSI mode {{{ -// As per ECMA 48 section 5.4 secondary byte is column 02 of the 7-bit ascii table -#define CSI_SECONDARY \ - case ' ': \ - case '!': \ - case '"': \ - case '#': \ - case '$': \ - case '%': \ - case '&': \ - case '\'': \ - case '(': \ - case ')': \ - case '*': \ - case '+': \ - case ',': \ - case '-': \ - case '.': \ - case '/': - - -static 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; -} - -#ifdef DUMP_COMMANDS -static -#endif -void -parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, int *params, PyObject DUMP_UNUSED *dump_callback, const char *report_name DUMP_UNUSED, Region *region) { - enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 }; - enum State state = START; - unsigned int num_params, num_start, i; - -#define READ_PARAM { params[num_params++] = utoi(buf + num_start, i - num_start); } -#define SEND_SGR { REPORT_PARAMS(report_name, params, num_params, region); select_graphic_rendition(screen, params, num_params, region); state = START; num_params = 0; } - - for (i=0, num_start=0, num_params=0; i < num && num_params < MAX_PARAMS; i++) { - switch(buf[i]) { - IS_DIGIT - switch(state) { - case START: - num_start = i; - state = NORMAL; - num_params = 0; - break; - default: - break; - } - break; - case ';': - switch(state) { - case START: - params[num_params++] = 0; - SEND_SGR; - break; - case NORMAL: - READ_PARAM; - switch(params[0]) { - case 38: - case 48: - case 58: - state = COLOR; - num_start = i + 1; - break; - default: - SEND_SGR; - break; - } - break; - case MULTIPLE: - READ_PARAM; - SEND_SGR; - break; - case COLOR: - READ_PARAM; - switch(params[1]) { - case 2: - state = COLOR3; - break; - case 5: - state = COLOR1; - break; - default: - REPORT_ERROR("Invalid SGR color code with unknown color type: %u", params[1]); - return; - } - num_start = i + 1; - break; - case COLOR1: - READ_PARAM; - SEND_SGR; - break; - case COLOR3: - READ_PARAM; - if (num_params == 5) { SEND_SGR; } - else num_start = i + 1; - break; - } - break; - case ':': - switch(state) { - case START: - REPORT_ERROR("Invalid SGR code containing ':' at an invalid location: %u", i); - return; - case NORMAL: - READ_PARAM; - state = MULTIPLE; - num_start = i + 1; - break; - case MULTIPLE: - READ_PARAM; - num_start = i + 1; - break; - case COLOR: - case COLOR1: - case COLOR3: - REPORT_ERROR("Invalid SGR code containing disallowed character: %s (U+%x)", utf8(buf[i]), buf[i]); - return; - } - break; - default: - REPORT_ERROR("Invalid SGR code containing disallowed character: %s (U+%x)", utf8(buf[i]), buf[i]); - return; - } - } - switch(state) { - case START: - if (num_params < MAX_PARAMS) params[num_params++] = 0; - SEND_SGR; - break; - case COLOR1: - case NORMAL: - case MULTIPLE: - if (i > num_start && num_params < MAX_PARAMS) { READ_PARAM; } - if (num_params) { SEND_SGR; } - else { REPORT_ERROR("Incomplete SGR code"); } - break; - case COLOR: - REPORT_ERROR("Invalid SGR code containing incomplete semi-colon separated color sequence"); - break; - case COLOR3: - if (i > num_start && num_params < MAX_PARAMS) READ_PARAM; - if (num_params != 5) { - REPORT_ERROR("Invalid SGR code containing incomplete semi-colon separated color sequence"); - break; - } - if (num_params) { SEND_SGR; } - else { REPORT_ERROR("Incomplete SGR code"); } - break; - } -#undef READ_PARAM -#undef SEND_SGR -} - -static unsigned int -parse_region(Region *r, uint32_t *buf, unsigned int num) { - unsigned int i, start, num_params = 0; - int params[8] = {0}; - for (i=0, start=0; i < num && num_params < 4; i++) { - switch(buf[i]) { - IS_DIGIT - break; - default: - if (i > start) params[num_params++] = utoi(buf + start, i - start); - else if (i == start && buf[i] == ';') params[num_params++] = 0; - start = i + 1; - break; - } - } - - switch(num_params) { - case 0: - break; - case 1: - r->top = params[0]; - break; - case 2: - r->top = params[0]; r->left = params[1]; - break; - case 3: - r->top = params[0]; r->left = params[1]; r->bottom = params[2]; - break; - default: - r->top = params[0]; r->left = params[1]; r->bottom = params[2]; r->right = params[3]; - break; - } - return i; -} - -static 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 void -dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { -#define AT_MOST_ONE_PARAMETER { \ - if (num_params > 1) { \ - REPORT_ERROR("CSI code %s has %u > 1 parameters", csi_letter(code), num_params); \ - break; \ - } \ -} -#define NON_NEGATIVE_PARAM(x) { \ - if (x < 0) { \ - REPORT_ERROR("CSI code %s is not allowed to have negative parameter (%d)", csi_letter(code), x); \ - break; \ - } \ -} - -#define CALL_CSI_HANDLER1(name, defval) \ - AT_MOST_ONE_PARAMETER; \ - p1 = num_params > 0 ? params[0] : defval; \ - NON_NEGATIVE_PARAM(p1); \ - REPORT_COMMAND(name, p1); \ - name(screen, p1); \ - break; - -#define CALL_CSI_HANDLER1P(name, defval, qch) \ - AT_MOST_ONE_PARAMETER; \ - p1 = num_params > 0 ? params[0] : defval; \ - NON_NEGATIVE_PARAM(p1); \ - private = start_modifier == qch; \ - REPORT_COMMAND(name, p1, private); \ - name(screen, p1, private); \ - break; - -#define CALL_CSI_HANDLER1S(name, defval) \ - AT_MOST_ONE_PARAMETER; \ - p1 = num_params > 0 ? params[0] : defval; \ - NON_NEGATIVE_PARAM(p1); \ - REPORT_COMMAND(name, p1, start_modifier); \ - name(screen, p1, start_modifier); \ - break; - -#define CALL_CSI_HANDLER1M(name, defval) \ - AT_MOST_ONE_PARAMETER; \ - p1 = num_params > 0 ? params[0] : defval; \ - NON_NEGATIVE_PARAM(p1); \ - REPORT_COMMAND(name, p1, end_modifier); \ - name(screen, p1, end_modifier); \ - break; - -#define CALL_CSI_HANDLER2(name, defval1, defval2) \ - if (num_params > 2) { \ - REPORT_ERROR("CSI code %s has %u > 2 parameters", csi_letter(code), num_params); \ - break; \ - } \ - p1 = num_params > 0 ? params[0] : defval1; \ - p2 = num_params > 1 ? params[1] : defval2; \ - NON_NEGATIVE_PARAM(p1); \ - NON_NEGATIVE_PARAM(p2); \ - REPORT_COMMAND(name, p1, p2); \ - name(screen, p1, p2); \ - break; - -#define SET_MODE(func) \ - p1 = start_modifier == '?' ? 5 : 0; \ - for (i = 0; i < num_params; i++) { \ - NON_NEGATIVE_PARAM(params[i]); \ - REPORT_COMMAND(func, params[i], start_modifier == '?'); \ - func(screen, params[i] << p1); \ - } \ - break; - -#define NO_MODIFIERS(modifier, special, special_msg) { \ - if (start_modifier || end_modifier) { \ - if (special && modifier == special) { REPORT_ERROR(special_msg); } \ - else { REPORT_ERROR("CSI code %s has unsupported start modifier: %s or end modifier: %s", csi_letter(code), csi_letter(start_modifier), csi_letter(end_modifier));} \ - break; \ - } \ -} - - char start_modifier = 0, end_modifier = 0; - uint32_t *buf = screen->parser_buf, code = screen->parser_buf[screen->parser_buf_pos]; - unsigned int num = screen->parser_buf_pos, start, i, num_params=0; - static int params[MAX_PARAMS] = {0}, p1, p2; - bool private; - if (buf[0] == '>' || buf[0] == '<' || buf[0] == '?' || buf[0] == '!' || buf[0] == '=') { - start_modifier = (char)screen->parser_buf[0]; - buf++; num--; - } - if (num > 0) { - switch(buf[num-1]) { - CSI_SECONDARY - end_modifier = (char)buf[--num]; - break; - } - } - if (code == SGR && !start_modifier && !end_modifier) { - parse_sgr(screen, buf, num, params, dump_callback, "select_graphic_rendition", NULL); - return; - } - if (code == 'r' && !start_modifier && end_modifier == '$') { - // DECCARA - Region r = {0}; - unsigned int consumed = parse_region(&r, buf, num); - num -= consumed; buf += consumed; - parse_sgr(screen, buf, num, params, dump_callback, "deccara", &r); - return; - } - - for (i=0, start=0; i < num; i++) { - switch(buf[i]) { - IS_DIGIT - break; - case '-': - if (i > start) { - REPORT_ERROR("CSI code can contain hyphens only at the start of numbers"); - return; - } - break; - default: - if (i > start) params[num_params++] = utoi(buf + start, i - start); - else if (i == start && buf[i] == ';') params[num_params++] = 0; - if (num_params >= MAX_PARAMS) { i = num; start = num + 1; } - else { start = i + 1; break; } - } - } - if (i > start) params[num_params++] = utoi(buf + start, i - start); - switch(code) { - case ICH: - NO_MODIFIERS(end_modifier, ' ', "Shift left escape code not implemented"); - CALL_CSI_HANDLER1(screen_insert_characters, 1); - case REP: - CALL_CSI_HANDLER1(screen_repeat_character, 1); - case CUU: - NO_MODIFIERS(end_modifier, ' ', "Shift right escape code not implemented"); - CALL_CSI_HANDLER1(screen_cursor_up2, 1); - case CUD: - case VPR: - CALL_CSI_HANDLER1(screen_cursor_down, 1); - case CUF: - case HPR: - CALL_CSI_HANDLER1(screen_cursor_forward, 1); - case CUB: - CALL_CSI_HANDLER1(screen_cursor_back1, 1); - case CNL: - CALL_CSI_HANDLER1(screen_cursor_down1, 1); - case CPL: - CALL_CSI_HANDLER1(screen_cursor_up1, 1); - case CHA: - case HPA: - CALL_CSI_HANDLER1(screen_cursor_to_column, 1); - case VPA: - CALL_CSI_HANDLER1(screen_cursor_to_line, 1); - case CBT: - CALL_CSI_HANDLER1(screen_backtab, 1); - case CHT: - CALL_CSI_HANDLER1(screen_tabn, 1); - case CUP: - case HVP: - CALL_CSI_HANDLER2(screen_cursor_position, 1, 1); - case ED: - CALL_CSI_HANDLER1P(screen_erase_in_display, 0, '?'); - case EL: - CALL_CSI_HANDLER1P(screen_erase_in_line, 0, '?'); - case IL: - CALL_CSI_HANDLER1(screen_insert_lines, 1); - case DL: - CALL_CSI_HANDLER1(screen_delete_lines, 1); - case DCH: - if (end_modifier == '#' && !start_modifier) { - CALL_CSI_HANDLER1(screen_push_colors, 0); - } else { - CALL_CSI_HANDLER1(screen_delete_characters, 1); - } - case 'Q': - if (end_modifier == '#' && !start_modifier) { CALL_CSI_HANDLER1(screen_pop_colors, 0); } - REPORT_ERROR("Unknown CSI Q sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params); - break; - case 'R': - if (end_modifier == '#' && !start_modifier) { - REPORT_COMMAND(screen_report_color_stack); - screen_report_color_stack(screen); - break; - } - REPORT_ERROR("Unknown CSI R sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params); - break; - case ECH: - CALL_CSI_HANDLER1(screen_erase_characters, 1); - case DA: - CALL_CSI_HANDLER1S(report_device_attributes, 0); - case TBC: - CALL_CSI_HANDLER1(screen_clear_tab_stop, 0); - case SM: - SET_MODE(screen_set_mode); - case RM: - SET_MODE(screen_reset_mode); - case DSR: - CALL_CSI_HANDLER1P(report_device_status, 0, '?'); - case 's': - if (!start_modifier && !end_modifier && !num_params) { - REPORT_COMMAND(screen_save_cursor); - screen_save_cursor(screen); - break; - } else if (start_modifier == '?' && !end_modifier) { - if (!num_params) { - REPORT_COMMAND(screen_save_modes); - screen_save_modes(screen); - } else { SET_MODE(screen_save_mode); } - break; - } - REPORT_ERROR("Unknown CSI s sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params); - break; - case 't': - if (!num_params) { - REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c' and no parameters", start_modifier, end_modifier); - break; - } - if (start_modifier || end_modifier) { - REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c', %u parameters and first parameter: %d", start_modifier, end_modifier, num_params, params[0]); - break; - } - switch(params[0]) { - case 4: - case 8: - log_error("Escape codes to resize text area are not supported"); - break; - case 14: - case 16: - case 18: - CALL_CSI_HANDLER1(screen_report_size, 0); - break; - case 22: - case 23: - 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(screen); - break; - } - if (!end_modifier && start_modifier == '?') { - REPORT_COMMAND(screen_report_key_encoding_flags); - screen_report_key_encoding_flags(screen); - break; - } - if (!end_modifier && start_modifier == '=') { - CALL_CSI_HANDLER2(screen_set_key_encoding_flags, 0, 1); - break; - } - if (!end_modifier && start_modifier == '>') { - CALL_CSI_HANDLER1(screen_push_key_encoding_flags, 0); - break; - } - if (!end_modifier && start_modifier == '<') { - CALL_CSI_HANDLER1(screen_pop_key_encoding_flags, 1); - break; - } - REPORT_ERROR("Unknown CSI u sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params); - break; - case 'r': - if (!start_modifier && !end_modifier) { - // DECSTBM - CALL_CSI_HANDLER2(screen_set_margins, 0, 0); - } else if (start_modifier == '?' && !end_modifier) { - if (!num_params) { - REPORT_COMMAND(screen_restore_modes); - screen_restore_modes(screen); - } else { SET_MODE(screen_restore_mode); } - 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) { - REPORT_ERROR( - "The application is trying to use xterm's modifyOtherKeys." - " This is superseded by the kitty keyboard protocol: https://sw.kovidgoyal.net/kitty/keyboard-protocol/" - " the application should be updated to use that" - ); - break; - } - /* fallthrough */ - default: - REPORT_ERROR("Unknown CSI code: '%s' with start_modifier: '%c' and end_modifier: '%c' and parameters: '%s'", utf8(code), start_modifier, end_modifier, repr_csi_params(params, num_params)); - } -} -// }}} - -// DCS mode {{{ - -static bool -startswith(const uint32_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; -} - -#define PENDING_MODE_CHAR '=' - -static void -dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { - if (screen->parser_buf_pos < 2) return; - switch(screen->parser_buf[0]) { - case '+': - case '$': - if (screen->parser_buf[1] == 'q') { - PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 2, screen->parser_buf_pos - 2); - if (string != NULL) { - REPORT_OSC2(screen_request_capabilities, (char)screen->parser_buf[0], string); - screen_request_capabilities(screen, (char)screen->parser_buf[0], string); - Py_DECREF(string); - } else PyErr_Clear(); - } else { - REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]); - } - break; - case PENDING_MODE_CHAR: - if (screen->parser_buf_pos > 2 && (screen->parser_buf[1] == '1' || screen->parser_buf[1] == '2') && screen->parser_buf[2] == 's') { - if (screen->parser_buf[1] == '1') { - screen->pending_mode.activated_at = monotonic(); - REPORT_COMMAND(screen_start_pending_mode); - } else { - // ignore stop without matching start, see queue_pending_bytes() - // for how stop is detected while in pending mode. - REPORT_ERROR("Pending mode stop command issued while not in pending mode, this can" - " be either a bug in the terminal application or caused by a timeout with no data" - " received for too long or by too much data in pending mode"); - REPORT_COMMAND(screen_stop_pending_mode); - } - } else { - REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]); - } - break; - case '@': - if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 2, "kitty-", sizeof("kitty-") - 1)) { - if (startswith(screen->parser_buf + 7, screen->parser_buf_pos - 2, "cmd{", sizeof("cmd{") - 1)) { - PyObject *cmd = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 10, screen->parser_buf_pos - 10); - if (cmd != NULL) { - REPORT_OSC2(screen_handle_cmd, (char)screen->parser_buf[0], cmd); - screen_handle_cmd(screen, cmd); - Py_DECREF(cmd); - } else PyErr_Clear(); -#define IF_SIMPLE_PREFIX(prefix, func) \ - if (startswith(screen->parser_buf + 7, screen->parser_buf_pos - 1, prefix, sizeof(prefix) - 1)) { \ - const size_t pp_size = sizeof("kitty") + sizeof(prefix); \ - PyObject *msg = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + pp_size, screen->parser_buf_pos - pp_size); \ - if (msg != NULL) { \ - REPORT_OSC2(func, (char)screen->parser_buf[0], msg); \ - screen_handle_kitty_dcs(screen, #func, msg); \ - Py_DECREF(msg); \ - } else PyErr_Clear(); - - } else IF_SIMPLE_PREFIX("overlay-ready|", handle_overlay_ready) - } else IF_SIMPLE_PREFIX("kitten-result|", handle_kitten_result) - } else IF_SIMPLE_PREFIX("print|", handle_remote_print) - } else IF_SIMPLE_PREFIX("echo|", handle_remote_echo) - } else IF_SIMPLE_PREFIX("ssh|", handle_remote_ssh) - } else IF_SIMPLE_PREFIX("ask|", handle_remote_askpass) - } else IF_SIMPLE_PREFIX("clone|", handle_remote_clone) - } else IF_SIMPLE_PREFIX("edit|", handle_remote_edit) -#undef IF_SIMPLE_PREFIX - } else { - PyObject *tp = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf, screen->parser_buf_pos); - REPORT_ERROR("Unrecognized DCS @ code: %s", tp ? PyUnicode_AsUTF8(tp) : "could not read"); - Py_XDECREF(tp); - } - } - break; - default: - REPORT_ERROR("Unrecognized DCS code: 0x%x", screen->parser_buf[0]); - break; - } -} -// }}} - -// APC mode {{{ - -#include "parse-graphics-command.h" - -static void -dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { - if (screen->parser_buf_pos < 2) return; - switch(screen->parser_buf[0]) { - case 'G': - parse_graphics_code(screen, dump_callback); - break; - default: - REPORT_ERROR("Unrecognized APC code: 0x%x", screen->parser_buf[0]); - break; - } -} - -// }}} - -// PM mode {{{ -static void -dispatch_pm(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { - if (screen->parser_buf_pos < 2) return; - switch(screen->parser_buf[0]) { - default: - REPORT_ERROR("Unrecognized PM code: 0x%x", screen->parser_buf[0]); - break; - } -} - - -// }}} - -// Parse loop {{{ - -static bool -handle_extended_osc_code(Screen *screen) { - // Handle extra long OSC 52 codes - if (screen->parser_buf[0] != '5' || screen->parser_buf[1] != '2' || screen->parser_buf[2] != ';') return false; - screen->parser_buf[0] = EXTENDED_OSC_SENTINEL; screen->parser_buf[1] = 1; - return true; -} - -static bool -accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback, bool *extended_osc_code) { - switch(ch) { - case ST: - return true; - case BEL: - return true; - case NUL: - case DEL: - break; - case ESC_ST: - if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos - 1] == ESC) { - screen->parser_buf_pos--; - return true; - } - /* fallthrough */ - default: - if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { - if (handle_extended_osc_code(screen)) *extended_osc_code = true; - else REPORT_ERROR("OSC sequence too long (> %d bytes) truncating.", PARSER_BUF_SZ); - return true; - } - screen->parser_buf[screen->parser_buf_pos++] = ch; - break; - } - return false; -} - -static bool -accumulate_dcs(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { - switch(ch) { - case ST: - return true; - case NUL: - case DEL: - break; - case ESC: -START_ALLOW_CASE_RANGE - case 32 ... 126: -END_ALLOW_CASE_RANGE - if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos-1] == ESC) { - if (ch == '\\') { screen->parser_buf_pos--; return true; } - REPORT_ERROR("DCS sequence contained ESC without trailing \\ at pos: %u ignoring the sequence", screen->parser_buf_pos); - SET_STATE(ESC); return false; - } - if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { - REPORT_ERROR("DCS sequence too long, truncating."); - return true; - } - screen->parser_buf[screen->parser_buf_pos++] = ch; - break; - default: - REPORT_ERROR("DCS sequence contained non-printable character: 0x%x ignoring the sequence", ch); - } - return false; -} - - -static bool -accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { - switch(ch) { - case ST: - return true; - case BEL: - return true; - case DEL: - case NUL: - break; - case ESC_ST: - if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos - 1] == ESC) { - screen->parser_buf_pos--; - return true; - } - /* fallthrough */ - default: - if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { - REPORT_ERROR("OTH sequence too long, truncating."); - return true; - } - screen->parser_buf[screen->parser_buf_pos++] = ch; - break; - } - return false; -} - - -static bool -accumulate_csi(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { -#define ENSURE_SPACE \ - if (screen->parser_buf_pos > PARSER_BUF_SZ - 1) { \ - REPORT_ERROR("CSI sequence too long, ignoring"); \ - SET_STATE(0); \ - return false; \ - } - - switch(ch) { - IS_DIGIT - CSI_SECONDARY - case ':': - case ';': - ENSURE_SPACE; - screen->parser_buf[screen->parser_buf_pos++] = ch; - break; - case '?': - case '>': - case '<': - case '=': - if (screen->parser_buf_pos != 0) { - REPORT_ERROR("Invalid character in CSI: 0x%x, ignoring the sequence", ch); - SET_STATE(0); - return false; - } - ENSURE_SPACE; - screen->parser_buf[screen->parser_buf_pos++] = ch; - break; -START_ALLOW_CASE_RANGE - case 'a' ... 'z': - case 'A' ... 'Z': -END_ALLOW_CASE_RANGE - case '@': - case '`': - case '{': - case '|': - case '}': - case '~': - screen->parser_buf[screen->parser_buf_pos] = ch; - return true; - case BEL: - case BS: - case HT: - case LF: - case VT: - case FF: - case NEL: - case CR: - case SO: - case SI: - case IND: - case RI: - case HTS: - dispatch_normal_mode_char(screen, ch, dump_callback); - break; - case NUL: - case DEL: - SET_STATE(0); - break; // no-op - default: - REPORT_ERROR("Invalid character in CSI: 0x%x, ignoring the sequence", ch); - SET_STATE(0); - return false; - - } - return false; -#undef ENSURE_SPACE -} - -#define dispatch_unicode_char(codepoint, dispatch, watch_for_pending) { \ - switch(screen->parser_state) { \ - case ESC: \ - dispatch##_esc_mode_char(screen, codepoint, dump_callback); \ - break; \ - case CSI: \ - if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch##_csi(screen, dump_callback); SET_STATE(0); watch_for_pending; } \ - break; \ - case OSC: \ - { \ - bool extended_osc_code = false; \ - if (accumulate_osc(screen, codepoint, dump_callback, &extended_osc_code)) { \ - dispatch##_osc(screen, dump_callback); \ - if (extended_osc_code) { \ - if (accumulate_osc(screen, codepoint, dump_callback, &extended_osc_code)) { dispatch##_osc(screen, dump_callback); SET_STATE(0); } \ - } else { SET_STATE(0); } \ - } \ - } \ - break; \ - case APC: \ - if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch##_apc(screen, dump_callback); SET_STATE(0); } \ - break; \ - case PM: \ - if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch##_pm(screen, dump_callback); SET_STATE(0); } \ - break; \ - case DCS: \ - if (accumulate_dcs(screen, codepoint, dump_callback)) { dispatch##_dcs(screen, dump_callback); SET_STATE(0); watch_for_pending; } \ - if (screen->parser_state == ESC) { dispatch##_esc_mode_char(screen, codepoint, dump_callback); } \ - break; \ - default: \ - dispatch##_normal_mode_char(screen, codepoint, dump_callback); \ - break; \ - } \ -} \ - -extern uint32_t *latin1_charset; - -#define decode_loop(dispatch, watch_for_pending) { \ - i = 0; \ - uint32_t prev = screen->utf8_state; \ - while(i < (size_t)len) { \ - uint8_t ch = buf[i++]; \ - if (screen->use_latin1) { \ - dispatch_unicode_char(latin1_charset[ch], dispatch, watch_for_pending); \ - } else { \ - switch (decode_utf8(&screen->utf8_state, &screen->utf8_codepoint, ch)) { \ - case UTF8_ACCEPT: \ - dispatch_unicode_char(screen->utf8_codepoint, dispatch, watch_for_pending); \ - break; \ - case UTF8_REJECT: \ - screen->utf8_state = UTF8_ACCEPT; \ - if (prev != UTF8_ACCEPT && i > 0) i--; \ - break; \ - } \ - prev = screen->utf8_state; \ - } \ - } \ -} - -static void -_parse_bytes(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) { - unsigned int i; - decode_loop(dispatch, ;); -FLUSH_DRAW; -} - -static size_t -_parse_bytes_watching_for_pending(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) { - unsigned int i; - decode_loop(dispatch, if (screen->pending_mode.activated_at) goto end); -end: -FLUSH_DRAW; - return i; -} - -static void -write_pending_char(Screen *screen, uint32_t ch) { - if (screen->pending_mode.capacity < screen->pending_mode.used + 8) { - if (screen->pending_mode.capacity) { - screen->pending_mode.capacity += screen->pending_mode.capacity >= READ_BUF_SZ ? PENDING_BUF_INCREMENT : screen->pending_mode.capacity; - } else screen->pending_mode.capacity = PENDING_BUF_INCREMENT; - screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity); - if (!screen->pending_mode.buf) fatal("Out of memory"); - } - screen->pending_mode.used += encode_utf8(ch, (char*)screen->pending_mode.buf + screen->pending_mode.used); -} - -static void -pending_normal_mode_char(Screen *screen, uint32_t ch, PyObject *dump_callback UNUSED) { - switch(ch) { - case ESC: case CSI: case OSC: case DCS: case APC: case PM: - SET_STATE(ch); break; - default: - write_pending_char(screen, ch); break; - } -} - -static void -pending_esc_mode_char(Screen *screen, uint32_t ch, PyObject *dump_callback UNUSED) { - if (screen->parser_buf_pos > 0) { - write_pending_char(screen, ESC); - write_pending_char(screen, screen->parser_buf[screen->parser_buf_pos - 1]); - write_pending_char(screen, ch); - SET_STATE(0); - return; - } - switch (ch) { - case ESC_DCS: - SET_STATE(DCS); break; - case ESC_OSC: - SET_STATE(OSC); break; - case ESC_CSI: - SET_STATE(CSI); break; - case ESC_APC: - SET_STATE(APC); break; - case ESC_PM: - SET_STATE(PM); break; - case '%': - case '(': - case ')': - case '*': - case '+': - case '-': - case '.': - case '/': - case ' ': - case '#': - screen->parser_buf[screen->parser_buf_pos++] = ch; - break; - default: - write_pending_char(screen, ESC); write_pending_char(screen, ch); - SET_STATE(0); break; - } -} - -#define pb(i) screen->parser_buf[i] -static void -pending_escape_code(Screen *screen, char_type start_ch, char_type end_ch) { - write_pending_char(screen, start_ch); - for (unsigned i = 0; i < screen->parser_buf_pos; i++) write_pending_char(screen, pb(i)); - write_pending_char(screen, end_ch); -} - -static void pending_pm(Screen *screen, PyObject *dump_callback UNUSED) { pending_escape_code(screen, PM, ST); } -static void pending_apc(Screen *screen, PyObject *dump_callback UNUSED) { pending_escape_code(screen, APC, ST); } - -static void -pending_osc(Screen *screen, PyObject *dump_callback UNUSED) { - const bool extended = is_extended_osc(screen); - pending_escape_code(screen, OSC, ST); - if (extended) continue_osc_52(screen); -} - - -static void -pending_dcs(Screen *screen, PyObject *dump_callback DUMP_UNUSED) { - if (screen->parser_buf_pos >= 3 && pb(0) == '=' && (pb(1) == '1' || pb(1) == '2') && pb(2) == 's') { - screen->pending_mode.activated_at = pb(1) == '1' ? monotonic() : 0; - if (pb(1) == '1') { - REPORT_COMMAND(screen_start_pending_mode); - screen->pending_mode.activated_at = monotonic(); - } else { - screen->pending_mode.stop_escape_code_type = DCS; - screen->pending_mode.activated_at = 0; - } - } else pending_escape_code(screen, DCS, ST); -} - -static void -pending_csi(Screen *screen, PyObject *dump_callback DUMP_UNUSED) { - if (screen->parser_buf_pos == 5 && pb(0) == '?' && pb(1) == '2' && pb(2) == '0' && pb(3) == '2' && pb(4) == '6' && (pb(5) == 'h' || pb(5) == 'l')) { - if (pb(5) == 'h') { - REPORT_COMMAND(screen_set_mode, 2026, 1); - screen->pending_mode.activated_at = monotonic(); - } else { - screen->pending_mode.activated_at = 0; - screen->pending_mode.stop_escape_code_type = CSI; - } - } else pending_escape_code(screen, CSI, pb(screen->parser_buf_pos)); -} - -#undef pb - -static size_t -queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) { - unsigned int i; - decode_loop(pending, if (!screen->pending_mode.activated_at) goto end); -end: - return i; -} - -static void -dump_partial_escape_code_to_pending(Screen *screen) { - if (screen->parser_buf_pos) { - write_pending_char(screen, screen->parser_state); - for (unsigned i = 0; i < screen->parser_buf_pos; i++) write_pending_char(screen, screen->parser_buf[i]); - } -} - -static void -do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, monotonic_t now, PyObject *dump_callback DUMP_UNUSED) { - enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING}; - enum STATE state = START; - size_t read_buf_pos = 0; - unsigned int parser_state_at_start_of_pending = 0; - - do { - switch(state) { - case START: - if (screen->pending_mode.activated_at) { - if (screen->pending_mode.activated_at + screen->pending_mode.wait_time < now) { - dump_partial_escape_code_to_pending(screen); - screen->pending_mode.activated_at = 0; - state = START; - } else state = QUEUE_PENDING; - } else { - state = screen->pending_mode.used ? PARSE_PENDING : PARSE_READ_BUF; - } - break; - - case PARSE_PENDING: - screen->parser_state = parser_state_at_start_of_pending; - parser_state_at_start_of_pending = 0; - _parse_bytes(screen, screen->pending_mode.buf, screen->pending_mode.used, dump_callback); - screen->pending_mode.used = 0; - screen->pending_mode.activated_at = 0; // ignore any pending starts in the pending bytes - if (screen->pending_mode.capacity > READ_BUF_SZ + PENDING_BUF_INCREMENT) { - screen->pending_mode.capacity = READ_BUF_SZ; - screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity); - if (!screen->pending_mode.buf) fatal("Out of memory"); - } - if (screen->pending_mode.stop_escape_code_type) { - if (screen->pending_mode.stop_escape_code_type == DCS) { REPORT_COMMAND(screen_stop_pending_mode); } - else if (screen->pending_mode.stop_escape_code_type == CSI) { REPORT_COMMAND(screen_reset_mode, 2026, 1); } - screen->pending_mode.stop_escape_code_type = 0; - } - state = START; - break; - - case PARSE_READ_BUF: - screen->pending_mode.activated_at = 0; - read_buf_pos += _parse_bytes_watching_for_pending(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback); - state = START; - break; - - case QUEUE_PENDING: { - screen->pending_mode.stop_escape_code_type = 0; - if (screen->pending_mode.used >= READ_BUF_SZ) { - dump_partial_escape_code_to_pending(screen); - screen->pending_mode.activated_at = 0; - state = START; - break; - } - if (!screen->pending_mode.used) parser_state_at_start_of_pending = screen->parser_state; - read_buf_pos += queue_pending_bytes(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback); - state = START; - } break; - } - } while(read_buf_pos < read_buf_sz || (!screen->pending_mode.activated_at && screen->pending_mode.used)); - -} - -// }}} - -// Entry points {{{ -#ifdef DUMP_COMMANDS -#define FNAME(x) x##_dump -#else -#define FNAME(x) x -#endif - -PyObject* -FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) { - PyObject *dump_callback = NULL; - RAII_PY_BUFFER(pybuf); - Screen *screen; -#ifdef DUMP_COMMANDS - if (!PyArg_ParseTuple(args, "OO!y*", &dump_callback, &Screen_Type, &screen, &pybuf)) return NULL; -#else - if (!PyArg_ParseTuple(args, "O!y*", &Screen_Type, &screen, &pybuf)) return NULL; -#endif - do_parse_bytes(screen, pybuf.buf, pybuf.len, monotonic(), dump_callback); - Py_RETURN_NONE; -} - - -void -FNAME(parse_worker)(Screen *screen, PyObject *dump_callback, monotonic_t now) { -#ifdef DUMP_COMMANDS - if (screen->read_buf_sz) { - Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, screen->read_buf_sz)); PyErr_Clear(); - } -#endif - do_parse_bytes(screen, screen->read_buf, screen->read_buf_sz, now, dump_callback); - screen->read_buf_sz = 0; -} -#undef FNAME -// }}} diff --git a/kitty/remote_control.py b/kitty/remote_control.py index 846f53e36..1c168d5b4 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -52,7 +52,7 @@ def encode_response_for_peer(response: Any) -> bytes: return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\' -def parse_cmd(serialized_cmd: str, encryption_key: EllipticCurveKey) -> Dict[str, Any]: +def parse_cmd(serialized_cmd: memoryview, encryption_key: EllipticCurveKey) -> Dict[str, Any]: try: pcmd = json.loads(serialized_cmd) except Exception: diff --git a/kitty/screen.c b/kitty/screen.c index 67b8c2f5f..366b3fb20 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -10,6 +10,7 @@ if (PyModule_AddFunctions(module, module_methods) != 0) return false; \ } +#include "control-codes.h" #include "state.h" #include "iqsort.h" #include "fonts.h" @@ -24,8 +25,8 @@ #include "modes.h" #include "wcwidth-std.h" #include "wcswidth.h" -#include "control-codes.h" #include "keys.h" +#include "vt-parser.h" static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; @@ -81,14 +82,6 @@ static void update_overlay_position(Screen *self); static void render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data); static void update_overlay_line_data(Screen *self, uint8_t *data); -#define RESET_CHARSETS \ - self->g0_charset = translation_table(0); \ - self->g1_charset = self->g0_charset; \ - self->g_charset = self->g0_charset; \ - self->current_charset = 0; \ - self->utf8_state = 0; \ - self->utf8_codepoint = 0; \ - self->use_latin1 = false; #define CALLBACK(...) \ if (self->callbacks != Py_None) { \ PyObject *callback_ret = PyObject_CallMethod(self->callbacks, __VA_ARGS__); \ @@ -114,6 +107,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Screen write_buf_lock mutex: %s", strerror(ret)); return NULL; } + self->vt_parser = alloc_vt_parser(window_id); + if (self->vt_parser == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); } self->reload_all_gpu_data = true; self->cell_size.width = cell_width; self->cell_size.height = cell_height; self->columns = columns; self->lines = lines; @@ -127,7 +122,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->scroll_changed = false; self->margin_top = 0; self->margin_bottom = self->lines - 1; self->history_line_added_count = 0; - RESET_CHARSETS; + reset_vt_parser(self->vt_parser); self->callbacks = callbacks; Py_INCREF(callbacks); self->test_child = test_child; Py_INCREF(test_child); self->cursor = alloc_cursor(); @@ -194,7 +189,7 @@ screen_reset(Screen *self) { #define R(name) self->color_profile->overridden.name.val = 0 R(default_fg); R(default_bg); R(cursor_color); R(highlight_fg); R(highlight_bg); #undef R - RESET_CHARSETS; + reset_vt_parser(self->vt_parser); self->margin_top = 0; self->margin_bottom = self->lines - 1; screen_normal_keypad_mode(self); init_tabstops(self->main_tabstops, self->columns); @@ -207,10 +202,6 @@ screen_reset(Screen *self) { set_dynamic_color(self, 110, NULL); set_dynamic_color(self, 111, NULL); set_color_table_color(self, 104, NULL); - self->parser_state = 0; - self->parser_text_start = 0; - self->parser_buf_pos = 0; - self->parser_has_pending_text = false; } void @@ -466,6 +457,7 @@ static void dealloc(Screen* self) { pthread_mutex_destroy(&self->read_buf_lock); pthread_mutex_destroy(&self->write_buf_lock); + free_vt_parser(self->vt_parser); self->vt_parser = NULL; Py_CLEAR(self->main_grman); Py_CLEAR(self->alt_grman); Py_CLEAR(self->last_reported_cwd); @@ -495,34 +487,6 @@ dealloc(Screen* self) { // Draw text {{{ -void -screen_change_charset(Screen *self, uint32_t which) { - switch(which) { - case 0: - self->current_charset = 0; - self->g_charset = self->g0_charset; - break; - case 1: - self->current_charset = 1; - self->g_charset = self->g1_charset; - break; - } -} - -void -screen_designate_charset(Screen *self, uint32_t which, uint32_t as) { - switch(which) { - case 0: - self->g0_charset = translation_table(as); - if (self->current_charset == 0) self->g_charset = self->g0_charset; - break; - case 1: - self->g1_charset = translation_table(as); - if (self->current_charset == 1) self->g_charset = self->g1_charset; - break; - } -} - static void move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) { self->cursor->x = xpos; self->cursor->y = ypos; @@ -681,8 +645,8 @@ draw_combining_char(Screen *self, char_type ch) { } static void -draw_codepoint(Screen *self, char_type och, bool from_input_stream) { - if (is_ignored_char(och)) return; +draw_codepoint(Screen *self, char_type ch, bool from_input_stream) { + if (is_ignored_char(ch)) return; if (!self->has_activity_since_last_focus && !self->has_focus && self->callbacks != Py_None) { PyObject *ret = PyObject_CallMethod(self->callbacks, "on_activity_since_last_focus", NULL); if (ret == NULL) PyErr_Print(); @@ -691,7 +655,6 @@ draw_codepoint(Screen *self, char_type och, bool from_input_stream) { Py_DECREF(ret); } } - uint32_t ch = och < 256 ? self->g_charset[och] : och; if (UNLIKELY(is_combining_char(ch))) { if (UNLIKELY(is_flag_codepoint(ch))) { if (draw_second_flag_codepoint(self, ch)) return; @@ -816,23 +779,23 @@ write_to_child(Screen *self, const char *data, size_t sz) { } static void -get_prefix_and_suffix_for_escape_code(const Screen *self, unsigned char which, const char ** prefix, const char ** suffix) { - *suffix = self->modes.eight_bit_controls ? "\x9c" : "\033\\"; +get_prefix_and_suffix_for_escape_code(unsigned char which, const char ** prefix, const char ** suffix) { + *suffix = "\033\\"; switch(which) { - case DCS: - *prefix = self->modes.eight_bit_controls ? "\x90" : "\033P"; + case ESC_DCS: + *prefix = "\033P"; break; - case CSI: - *prefix = self->modes.eight_bit_controls ? "\x9b" : "\033["; *suffix = ""; + case ESC_CSI: + *prefix = "\033["; *suffix = ""; break; - case OSC: - *prefix = self->modes.eight_bit_controls ? "\x9d" : "\033]"; + case ESC_OSC: + *prefix = "\033]"; break; - case PM: - *prefix = self->modes.eight_bit_controls ? "\x9e" : "\033^"; + case ESC_PM: + *prefix = "\033^"; break; - case APC: - *prefix = self->modes.eight_bit_controls ? "\x9f" : "\033_"; + case ESC_APC: + *prefix = "\033_"; break; default: fatal("Unknown escape code to write: %u", which); @@ -843,7 +806,7 @@ bool write_escape_code_to_child(Screen *self, unsigned char which, const char *data) { bool written = false; const char *prefix, *suffix; - get_prefix_and_suffix_for_escape_code(self, which, &prefix, &suffix); + get_prefix_and_suffix_for_escape_code(which, &prefix, &suffix); if (self->window_id) { if (suffix[0]) { written = schedule_write_to_child(self->window_id, 3, prefix, strlen(prefix), data, strlen(data), suffix, strlen(suffix)); @@ -863,7 +826,7 @@ static bool write_escape_code_to_child_python(Screen *self, unsigned char which, PyObject *data) { bool written = false; const char *prefix, *suffix; - get_prefix_and_suffix_for_escape_code(self, which, &prefix, &suffix); + get_prefix_and_suffix_for_escape_code(which, &prefix, &suffix); if (self->window_id) written = schedule_write_to_child_python(self->window_id, prefix, data, suffix); if (self->test_child != Py_None) { write_to_test_child(self, prefix, strlen(prefix)); @@ -911,7 +874,7 @@ void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload) { unsigned int x = self->cursor->x, y = self->cursor->y; const char *response = grman_handle_command(self->grman, cmd, payload, self->cursor, &self->is_dirty, self->cell_size); - if (response != NULL) write_escape_code_to_child(self, APC, response); + if (response != NULL) write_escape_code_to_child(self, ESC_APC, response); if (x != self->cursor->x || y != self->cursor->y) { bool in_margins = cursor_within_margins(self); if (self->cursor->x >= self->columns) { self->cursor->x = 0; self->cursor->y++; } @@ -1070,11 +1033,6 @@ screen_reset_mode(Screen *self, unsigned int mode) { set_mode_from_const(self, mode, false); } -void -screen_set_8bit_controls(Screen *self, bool yes) { - self->modes.eight_bit_controls = yes; -} - uint8_t screen_current_key_encoding_flags(Screen *self) { for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) { @@ -1090,7 +1048,7 @@ screen_report_key_encoding_flags(Screen *self) { debug("\x1b[35mReporting key encoding flags: %u\x1b[39m\n", screen_current_key_encoding_flags(self)); } snprintf(buf, sizeof(buf), "?%uu", screen_current_key_encoding_flags(self)); - write_escape_code_to_child(self, CSI, buf); + write_escape_code_to_child(self, ESC_CSI, buf); } void @@ -1534,14 +1492,6 @@ screen_linefeed(Screen *self) { } \ } -#define COPY_CHARSETS(self, sp) \ - sp->utf8_state = self->utf8_state; \ - sp->utf8_codepoint = self->utf8_codepoint; \ - sp->g0_charset = self->g0_charset; \ - sp->g1_charset = self->g1_charset; \ - sp->current_charset = self->current_charset; \ - sp->use_latin1 = self->use_latin1; - void screen_save_cursor(Screen *self) { Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint; @@ -1549,7 +1499,6 @@ screen_save_cursor(Screen *self) { sp->mDECOM = self->modes.mDECOM; sp->mDECAWM = self->modes.mDECAWM; sp->mDECSCNM = self->modes.mDECSCNM; - COPY_CHARSETS(self, sp); sp->is_valid = true; } @@ -1626,11 +1575,8 @@ screen_restore_cursor(Screen *self) { if (!sp->is_valid) { screen_cursor_position(self, 1, 1); screen_reset_mode(self, DECOM); - RESET_CHARSETS; screen_reset_mode(self, DECSCNM); } else { - COPY_CHARSETS(sp, self); - self->g_charset = self->current_charset ? self->g1_charset : self->g0_charset; set_mode_from_const(self, DECOM, sp->mDECOM); set_mode_from_const(self, DECAWM, sp->mDECAWM); set_mode_from_const(self, DECSCNM, sp->mDECSCNM); @@ -1965,12 +1911,6 @@ screen_erase_characters(Screen *self, unsigned int count) { // Device control {{{ -void -screen_use_latin1(Screen *self, bool on) { - self->use_latin1 = on; self->utf8_state = 0; self->utf8_codepoint = 0; - CALLBACK("use_utf8", "O", on ? Py_False : Py_True); -} - bool screen_invert_colors(Screen *self) { bool inverted = false; @@ -1998,10 +1938,10 @@ report_device_attributes(Screen *self, unsigned int mode, char start_modifier) { if (mode == 0) { switch(start_modifier) { case 0: - write_escape_code_to_child(self, CSI, "?62;c"); + write_escape_code_to_child(self, ESC_CSI, "?62;c"); break; case '>': - write_escape_code_to_child(self, CSI, ">1;" xstr(PRIMARY_VERSION) ";" xstr(SECONDARY_VERSION) "c"); // VT-220 + primary version + secondary version + write_escape_code_to_child(self, ESC_CSI, ">1;" xstr(PRIMARY_VERSION) ";" xstr(SECONDARY_VERSION) "c"); // VT-220 + primary version + secondary version break; } } @@ -2010,7 +1950,7 @@ report_device_attributes(Screen *self, unsigned int mode, char start_modifier) { void screen_xtversion(Screen *self, unsigned int mode) { if (mode == 0) { - write_escape_code_to_child(self, DCS, ">|kitty(" XT_VERSION ")"); + write_escape_code_to_child(self, ESC_DCS, ">|kitty(" XT_VERSION ")"); } } @@ -2038,7 +1978,7 @@ screen_report_size(Screen *self, unsigned int which) { } if (code) { snprintf(buf, sizeof(buf), "%u;%u;%ut", code, height, width); - write_escape_code_to_child(self, CSI, buf); + write_escape_code_to_child(self, ESC_CSI, buf); } } @@ -2059,7 +1999,7 @@ report_device_status(Screen *self, unsigned int which, bool private) { static char buf[64]; switch(which) { case 5: // device status - write_escape_code_to_child(self, CSI, "0n"); + write_escape_code_to_child(self, ESC_CSI, "0n"); break; case 6: // cursor position x = self->cursor->x; y = self->cursor->y; @@ -2070,7 +2010,7 @@ report_device_status(Screen *self, unsigned int which, bool private) { if (self->modes.mDECOM) y -= MAX(y, self->margin_top); // 1-based indexing int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%uR", (private ? "?": ""), y + 1, x + 1); - if (sz > 0) write_escape_code_to_child(self, CSI, buf); + if (sz > 0) write_escape_code_to_child(self, ESC_CSI, buf); break; } } @@ -2114,7 +2054,7 @@ report_mode_status(Screen *self, unsigned int which, bool private) { ans = self->pending_mode.activated_at ? 1 : 2; break; } int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%u$y", (private ? "?" : ""), which, ans); - if (sz > 0) write_escape_code_to_child(self, CSI, buf); + if (sz > 0) write_escape_code_to_child(self, ESC_CSI, buf); } void @@ -2173,7 +2113,7 @@ set_icon(Screen *self, PyObject *icon) { void set_dynamic_color(Screen *self, unsigned int code, PyObject *color) { - if (color == NULL) { CALLBACK("set_dynamic_color", "Is", code, ""); } + if (color == NULL) { CALLBACK("set_dynamic_color", "I", code); } else { CALLBACK("set_dynamic_color", "IO", code, color); } } @@ -2185,36 +2125,29 @@ clipboard_control(Screen *self, int code, PyObject *data) { void file_transmission(Screen *self, PyObject *data) { - if (PyUnicode_READY(data) != 0) { PyErr_Clear(); return; } CALLBACK("file_transmission", "O", data); } static void -parse_prompt_mark(Screen *self, PyObject *parts, PromptKind *pk) { - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(parts); i++) { - PyObject *token = PyList_GET_ITEM(parts, i); - if (PyUnicode_CompareWithASCIIString(token, "k=s") == 0) *pk = SECONDARY_PROMPT; - else if (PyUnicode_CompareWithASCIIString(token, "redraw=0") == 0) self->prompt_settings.redraws_prompts_at_all = 0; +parse_prompt_mark(Screen *self, char *buf, PromptKind *pk) { + char *saveptr, *str = buf; + while (true) { + const char *token = strtok_r(str, ";", &saveptr); str = NULL; + if (token == NULL) return; + if (strcmp(token, "k=s") == 0) *pk = SECONDARY_PROMPT; + else if (strcmp(token, "redraw=0") == 0) self->prompt_settings.redraws_prompts_at_all = 0; } } void -shell_prompt_marking(Screen *self, PyObject *data) { - if (PyUnicode_READY(data) != 0) { PyErr_Clear(); return; } - if (PyUnicode_GET_LENGTH(data) > 0 && self->cursor->y < self->lines) { - Py_UCS4 ch = PyUnicode_READ_CHAR(data, 0); +shell_prompt_marking(Screen *self, char *buf) { + if (self->cursor->y < self->lines) { + char ch = buf[0]; switch (ch) { case 'A': { PromptKind pk = PROMPT_START; self->prompt_settings.redraws_prompts_at_all = 1; - if (PyUnicode_FindChar(data, ';', 0, PyUnicode_GET_LENGTH(data), 1)) { - RAII_PyObject(sep, PyUnicode_FromString(";")); - if (sep) { - RAII_PyObject(parts, PyUnicode_Split(data, sep, -1)); - if (parts) parse_prompt_mark(self, parts, &pk); - } - } - if (PyErr_Occurred()) PyErr_Print(); + parse_prompt_mark(self, buf+1, &pk); self->linebuf->line_attrs[self->cursor->y].prompt_kind = pk; if (pk == PROMPT_START) CALLBACK("cmd_output_marking", "O", Py_False); @@ -2225,11 +2158,7 @@ shell_prompt_marking(Screen *self, PyObject *data) { break; } } - if (global_state.debug_rendering) { - fprintf(stderr, "prompt_marking: x=%d y=%d op=", self->cursor->x, self->cursor->y); - PyObject_Print(data, stderr, 0); - fprintf(stderr, "\n"); - } + if (global_state.debug_rendering) fprintf(stderr, "prompt_marking: x=%d y=%d op=%s\n", self->cursor->x, self->cursor->y, buf); } static bool @@ -2262,7 +2191,7 @@ screen_history_scroll_to_prompt(Screen *self, int num_of_prompts_to_jump) { void set_color_table_color(Screen *self, unsigned int code, PyObject *color) { - if (color == NULL) { CALLBACK("set_color_table_color", "Is", code, ""); } + if (color == NULL) { CALLBACK("set_color_table_color", "I", code); } else { CALLBACK("set_color_table_color", "IO", code, color); } } @@ -2315,7 +2244,7 @@ screen_report_color_stack(Screen *self) { colorprofile_report_stack(self->color_profile, &idx, &count); char buf[128] = {0}; snprintf(buf, arraysz(buf), "%u;%u#Q", idx, count); - write_escape_code_to_child(self, CSI, buf); + write_escape_code_to_child(self, ESC_CSI, buf); } void screen_handle_kitty_dcs(Screen *self, const char *callback_name, PyObject *cmd) { @@ -2323,17 +2252,15 @@ void screen_handle_kitty_dcs(Screen *self, const char *callback_name, PyObject * } void -screen_request_capabilities(Screen *self, char c, PyObject *q) { +screen_request_capabilities(Screen *self, char c, const char *query) { static char buf[128]; int shape = 0; - const char *query; switch(c) { - case '+': - CALLBACK("request_capabilities", "O", q); - break; + case '+': { + CALLBACK("request_capabilities", "s", query); + } break; case '$': // report status DECRQSS - query = PyUnicode_AsUTF8(q); if (strcmp(" q", query) == 0) { // cursor shape DECSCUSR switch(self->cursor->shape) { @@ -2358,7 +2285,7 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) { } else { shape = snprintf(buf, sizeof(buf), "0$r"); } - if (shape > 0) write_escape_code_to_child(self, DCS, buf); + if (shape > 0) write_escape_code_to_child(self, ESC_DCS, buf); break; } } @@ -3573,17 +3500,12 @@ draw(Screen *self, PyObject *src) { Py_RETURN_NONE; } -extern void -parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, int *params, PyObject *dump_callback, const char *report_name, Region *region); - static PyObject* apply_sgr(Screen *self, PyObject *src) { if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; } if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); } - Py_UCS4 *buf = PyUnicode_AsUCS4Copy(src); - if (!buf) return NULL; int params[MAX_PARAMS] = {0}; - parse_sgr(self, buf, PyUnicode_GET_LENGTH(src), params, NULL, "parse_sgr", NULL); + parse_sgr(self, (const uint8_t*)PyUnicode_AsUTF8(src), PyUnicode_GET_LENGTH(src), params, "parse_sgr", NULL); Py_RETURN_NONE; } @@ -4356,9 +4278,9 @@ paste_(Screen *self, PyObject *bytes, bool allow_bracketed_paste) { } else { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; } - if (allow_bracketed_paste && self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_START); + if (allow_bracketed_paste && self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, ESC_CSI, BRACKETED_PASTE_START); write_to_child(self, data, sz); - if (allow_bracketed_paste && self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_END); + if (allow_bracketed_paste && self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, ESC_CSI, BRACKETED_PASTE_END); Py_RETURN_NONE; } @@ -4381,7 +4303,7 @@ focus_changed(Screen *self, PyObject *has_focus_) { self->has_focus = has_focus; if (has_focus) self->has_activity_since_last_focus = false; else if (screen_is_overlay_active(self)) deactivate_overlay_line(self); - if (self->modes.mFOCUS_TRACKING) write_escape_code_to_child(self, CSI, has_focus ? "I" : "O"); + if (self->modes.mFOCUS_TRACKING) write_escape_code_to_child(self, ESC_CSI, has_focus ? "I" : "O"); Py_RETURN_TRUE; } Py_RETURN_FALSE; @@ -4521,7 +4443,6 @@ line_edge_colors(Screen *self, PyObject *a UNUSED) { WRAP0(update_only_line_graphics_data) WRAP0(bell) - #define MND(name, args) {#name, (PyCFunction)name, args, #name}, #define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O) @@ -4637,6 +4558,7 @@ static PyGetSetDef getsetters[] = { static PyMemberDef members[] = { {"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"}, {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"}, + {"vt_parser", T_OBJECT_EX, offsetof(Screen, vt_parser), READONLY, "vt_parser"}, {"last_reported_cwd", T_OBJECT, offsetof(Screen, last_reported_cwd), READONLY, "last_reported_cwd"}, {"grman", T_OBJECT_EX, offsetof(Screen, grman), READONLY, "grman"}, {"color_profile", T_OBJECT_EX, offsetof(Screen, color_profile), READONLY, "color_profile"}, diff --git a/kitty/screen.h b/kitty/screen.h index 71a8b842e..ca5c50567 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -6,6 +6,7 @@ #pragma once +#include "vt-parser.h" #include "graphics.h" #include "monotonic.h" #define MAX_PARAMS 256 @@ -17,7 +18,6 @@ typedef struct { mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS; MouseTrackingMode mouse_tracking_mode; MouseTrackingProtocol mouse_tracking_protocol; - bool eight_bit_controls; // S8C1T } ScreenModes; typedef struct { @@ -58,9 +58,6 @@ typedef struct { #define SAVEPOINTS_SZ 256 typedef struct { - uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset; - unsigned int current_charset; - bool use_latin1; Cursor cursor; bool mDECOM, mDECAWM, mDECSCNM; bool is_valid; @@ -92,15 +89,12 @@ typedef struct { CellPixelSize cell_size; OverlayLine overlay_line; id_type window_id; - uint32_t utf8_codepoint, *g0_charset, *g1_charset, *g_charset; - UTF8State utf8_state; - unsigned int current_charset; Selections selections, url_ranges; struct { unsigned int cursor_x, cursor_y, scrolled_by; index_type lines, columns; } last_rendered; - bool use_latin1, is_dirty, scroll_changed, reload_all_gpu_data; + bool is_dirty, scroll_changed, reload_all_gpu_data; Cursor *cursor; Savepoint main_savepoint, alt_savepoint; PyObject *callbacks, *test_child; @@ -113,9 +107,6 @@ typedef struct { ColorProfile *color_profile; monotonic_t start_visual_bell_at; - uint32_t parser_buf[8*1024]; - unsigned int parser_state, parser_text_start, parser_buf_pos; - bool parser_has_pending_text; uint8_t read_buf[READ_BUF_SZ], *write_buf; monotonic_t new_input_at; size_t read_buf_sz, write_buf_sz, write_buf_used; @@ -167,6 +158,7 @@ typedef struct { struct { uint8_t stack[16], count; } main_pointer_shape_stack, alternate_pointer_shape_stack; + Parser *vt_parser; } Screen; @@ -221,26 +213,21 @@ void screen_repeat_character(Screen *self, unsigned int count); void screen_delete_characters(Screen *self, unsigned int count); void screen_erase_characters(Screen *self, unsigned int count); void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom); -void screen_change_charset(Screen *, uint32_t to); void screen_handle_cmd(Screen *, PyObject *cmd); void screen_push_colors(Screen *, unsigned int); void screen_pop_colors(Screen *, unsigned int); void screen_report_color_stack(Screen *); void screen_handle_kitty_dcs(Screen *, const char *callback_name, PyObject *cmd); -void screen_designate_charset(Screen *, uint32_t which, uint32_t as); -void screen_use_latin1(Screen *, bool); void set_title(Screen *self, PyObject*); void desktop_notify(Screen *self, unsigned int, PyObject*); void set_icon(Screen *self, PyObject*); void set_dynamic_color(Screen *self, unsigned int code, PyObject*); void clipboard_control(Screen *self, int code, PyObject*); -void shell_prompt_marking(Screen *self, PyObject*); +void shell_prompt_marking(Screen *self, char *buf); void file_transmission(Screen *self, PyObject*); void set_color_table_color(Screen *self, unsigned int code, PyObject*); void process_cwd_notification(Screen *self, unsigned int code, PyObject*); -uint32_t* translation_table(uint32_t which); -void screen_request_capabilities(Screen *, char, PyObject *); -void screen_set_8bit_controls(Screen *, bool); +void screen_request_capabilities(Screen *, char, const char *); void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start_modifier); void select_graphic_rendition(Screen *self, int *params, unsigned int count, Region*); void report_device_status(Screen *self, unsigned int which, bool UNUSED); @@ -285,6 +272,7 @@ int screen_cursor_at_a_shell_prompt(const Screen *); bool screen_fake_move_cursor_to_position(Screen *, index_type x, index_type y); bool screen_send_signal_for_key(Screen *, char key); bool get_line_edge_colors(Screen *self, color_type *left, color_type *right); +void parse_sgr(Screen *screen, const uint8_t *buf, unsigned int num, int *params, const char *report_name, Region *region); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/vt-parser.c b/kitty/vt-parser.c index 6583e7c42..aca5bc69c 100644 --- a/kitty/vt-parser.c +++ b/kitty/vt-parser.c @@ -5,12 +5,16 @@ * Distributed under terms of the GPL3 license. */ -// TODO: Delete latin1_charset -// TODO: Delete C1 controls from control-codes.h +// TODO: Implement utf-8 parsing for screen_draw with reset +// TODO: Fix dump_commands for OSC and DCS commands that used to take strings but now take memoryview +// TODO: Test clipboard kitten with 52 and 5522 +// TODO: Test shell integration with secondary prompts +// TODO: Test screen_request_capabilities +// TODO: Test that C1 characters are ignored by screen_draw() #include "vt-parser.h" #include "screen.h" -#define NO_C1_CONTROLS 1 +#include "base64.h" #include "control-codes.h" #define EXTENDED_OSC_SENTINEL 0x1bu @@ -36,19 +40,6 @@ case '8': \ case '9': -#define IS_ESCAPED_CHAR \ - case '%': \ - case '(': \ - case ')': \ - case '*': \ - case '+': \ - case '-': \ - case '.': \ - case '/': \ - case ' ': \ - case '#' - - #ifdef DUMP_COMMANDS static void _report_error(PyObject *dump_callback, const char *fmt, ...) { @@ -91,7 +82,7 @@ _report_params(PyObject *dump_callback, const char *name, int *params, unsigned #define GET_MACRO(_1,_2,_3,NAME,...) NAME #define REPORT_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__) -#define REPORT_VA_COMMAND(...) Py_XDECREF(PyObject_CallFunction(dump_callback, __VA_ARGS__)); PyErr_Clear(); +#define REPORT_VA_COMMAND(...) Py_XDECREF(PyObject_CallFunction(self->dump_callback, __VA_ARGS__)); PyErr_Clear(); #define REPORT_DRAW(ch) \ Py_XDECREF(PyObject_CallFunction(self->dump_callback, "sC", "draw", ch)); PyErr_Clear(); @@ -125,14 +116,41 @@ _report_params(PyObject *dump_callback, const char *name, int *params, unsigned #endif // }}} +// Utils {{{ +static const uint64_t pow_10_array[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000 +}; + +static int64_t +utoi(const uint8_t *buf, unsigned int sz) { + int64_t ans = 0; + const uint8_t *p = buf; + int mult = 1; + if (sz && *p == '-') { + mult = -1; p++; sz--; + } + // Ignore leading zeros + while(sz > 0) { + if (*p == '0') { p++; sz--; } + else break; + } + if (sz < sizeof(pow_10_array)/sizeof(pow_10_array[0])) { + for (int i = sz-1, j=0; i >= 0; i--, j++) { + ans += (p[i] - '0') * pow_10_array[j]; + } + } + return ans * mult; +} +// }}} + typedef enum VTEState { VTE_NORMAL, VTE_ESC, VTE_CSI, VTE_OSC, VTE_DCS, VTE_APC, VTE_PM } VTEState; typedef struct PS { id_type window_id; - uint8_t parser_buf[PARSER_BUF_SZ]; - size_t parser_buf_pos; + unsigned parser_buf_pos; + bool extended_osc_code; VTEState vte_state; struct { monotonic_t activated_at, wait_time; @@ -147,8 +165,1153 @@ typedef struct PS { const uint8_t *input_data; size_t input_sz, input_pos; monotonic_t now; + uint8_t parser_buf[PARSER_BUF_SZ + 8]; // +8 to ensure we can always zero terminate } PS; +// Normal mode {{{ + +static void +dispatch_normal_mode_byte(PS *self) { +#define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(name); name(self->screen); break; + uint8_t ch = self->input_data[self->input_pos++]; + switch(ch) { + case BEL: + CALL_SCREEN_HANDLER(screen_bell); + case BS: + CALL_SCREEN_HANDLER(screen_backspace); + case HT: + CALL_SCREEN_HANDLER(screen_tab); + case LF: + case VT: + case FF: + CALL_SCREEN_HANDLER(screen_linefeed); + case CR: + CALL_SCREEN_HANDLER(screen_carriage_return); + case SI: + REPORT_ERROR("Ignoring request to change charset as we only support UTF-8"); break; + case SO: + REPORT_ERROR("Ignoring request to change charset as we only support UTF-8"); break; + case ESC: + SET_STATE(VTE_ESC); break; + case NUL: + case DEL: + break; // no-op + default: + REPORT_DRAW(ch); + screen_draw(self->screen, ch, true); + break; + } +#undef CALL_SCREEN_HANDLER +} +// }}} + +// 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 void +dispatch_esc_mode_byte(PS *self) { +#define CALL_ED(name) REPORT_COMMAND(name); name(self->screen); SET_STATE(0); +#define CALL_ED1(name, ch) REPORT_COMMAND(name, ch); name(self->screen, ch); SET_STATE(0); +#define CALL_ED2(name, a, b) REPORT_COMMAND(name, a, b); name(self->screen, a, b); SET_STATE(0); + uint8_t ch = self->input_data[self->input_pos++]; + switch(self->parser_buf_pos) { + case 0: + switch (ch) { + case ESC_DCS: + SET_STATE(VTE_DCS); break; + case ESC_OSC: + SET_STATE(VTE_OSC); break; + case ESC_CSI: + SET_STATE(VTE_CSI); break; + case ESC_APC: + SET_STATE(VTE_APC); break; + case ESC_PM: + SET_STATE(VTE_PM); break; + case ESC_RIS: + CALL_ED(screen_reset); break; + case ESC_IND: + CALL_ED(screen_index); break; + case ESC_NEL: + CALL_ED(screen_nel); break; + case ESC_RI: + CALL_ED(screen_reverse_index); break; + case ESC_HTS: + CALL_ED(screen_set_tab_stop); break; + case ESC_DECSC: + CALL_ED(screen_save_cursor); break; + case ESC_DECRC: + CALL_ED(screen_restore_cursor); break; + case ESC_DECKPNM: + CALL_ED(screen_normal_keypad_mode); break; + case ESC_DECKPAM: + CALL_ED(screen_alternate_keypad_mode); break; + IS_ESCAPED_CHAR: + self->parser_buf[self->parser_buf_pos++] = ch; + break; + default: + REPORT_ERROR("%s0x%x", "Unknown char after ESC: ", ch); + SET_STATE(0); break; + } + break; + default: + switch(self->parser_buf[0]) { + 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': + REPORT_ERROR("Ignoring attempt to designate charset as we support only UTF-8"); + 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", self->parser_buf[0], ch); break; + } + SET_STATE(0); + break; + } +#undef CALL_ED +#undef CALL_ED1 +} // }}} + +// OSC {{{ +static bool +is_extended_osc(const PS *self) { + return self->parser_buf_pos > 2 && self->parser_buf[0] == EXTENDED_OSC_SENTINEL && self->parser_buf[1] == 1 && self->parser_buf[2] == ';'; +} + +static void +continue_osc_52(PS *self) { + self->parser_buf[0] = '5'; self->parser_buf[1] = '2'; self->parser_buf[2] = ';'; + self->parser_buf[3] = ';'; self->parser_buf_pos = 4; +} + +static bool +handle_extended_osc_code(PS *self) { + // Handle extra long OSC 52 codes + if (self->parser_buf[0] != '5' || self->parser_buf[1] != '2' || self->parser_buf[2] != ';') return false; + self->parser_buf[0] = EXTENDED_OSC_SENTINEL; self->parser_buf[1] = 1; + return true; +} + +static bool +accumulate_osc(PS *self) { + uint8_t ch = self->input_data[self->input_pos++]; + self->extended_osc_code = false; + switch(ch) { + case BEL: + return true; + case NUL: + case DEL: + break; + case ESC_ST: + if (self->parser_buf_pos > 0 && self->parser_buf[self->parser_buf_pos - 1] == ESC) { + self->parser_buf_pos--; + return true; + } + /* fallthrough */ + default: + if (self->parser_buf_pos >= PARSER_BUF_SZ - 1) { + if (handle_extended_osc_code(self)) self->extended_osc_code = true; + else REPORT_ERROR("OSC sequence too long (> %d bytes) truncating.", PARSER_BUF_SZ); + return true; + } + self->parser_buf[self->parser_buf_pos++] = ch; + break; + } + 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, size_t pos, size_t size) { + if (!size) return; // ignore empty OSC 8 since it must have two semi-colons to be valid, which means one semi-colon here + char *buf = (char*)self->parser_buf + pos; + buf[size] = 0; // this is safe because we have an extra 8 bytes after PARSER_BUF_SZ + + 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) { +#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 {\ + PyObject *mv = PyMemoryView_FromMemory((char*)self->parser_buf + i, limit - i, PyBUF_READ); \ + if (mv) { +#define END_DISPATCH Py_CLEAR(mv); } PyErr_Clear(); break; } + + const unsigned int limit = self->parser_buf_pos; + int code=0; + unsigned int i; + for (i = 0; i < MIN(limit, 5u); i++) { + if (self->parser_buf[i] < '0' || self->parser_buf[i] > '9') break; + } + if (i > 0) { + code = utoi(self->parser_buf, i); + if (i < limit && self->parser_buf[i] == ';') i++; + } else { + if (is_extended_osc(self)) { + // partial OSC 52 + i = 3; + code = -52; + } + } + switch(code) { + case 0: + START_DISPATCH + DISPATCH_OSC(set_title); + DISPATCH_OSC(set_icon); + END_DISPATCH + case 1: + START_DISPATCH + DISPATCH_OSC(set_icon); + END_DISPATCH + case 2: + START_DISPATCH + DISPATCH_OSC(set_title); + END_DISPATCH + case 4: + case 104: + START_DISPATCH + DISPATCH_OSC_WITH_CODE(set_color_table_color); + END_DISPATCH + case 6: + case 7: + START_DISPATCH + DISPATCH_OSC_WITH_CODE(process_cwd_notification); + END_DISPATCH + case 8: + dispatch_hyperlink(self, i, limit-i); + break; + case 9: + case 99: + case 777: + case 1337: + START_DISPATCH + DISPATCH_OSC_WITH_CODE(desktop_notify) + END_DISPATCH + 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 52: case -52: case 5522: + START_DISPATCH + DISPATCH_OSC_WITH_CODE(clipboard_control); + if (code == -52) continue_osc_52(self); + END_DISPATCH + case 133: + START_DISPATCH + REPORT_OSC2(shell_prompt_marking, code, mv); + if (limit - 1 > 0) { + self->parser_buf[limit] = 0; // safe to do as we have 8 extra bytes after PARSER_BUF_SZ + shell_prompt_marking(self->screen, (char*)self->parser_buf + i); + } + END_DISPATCH + 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 697: + REPORT_ERROR("Ignoring OSC 697, typically used by Fig for shell integration"); + break; + default: + REPORT_ERROR("Unknown OSC code: %u", code); + break; + } +#undef DISPATCH_OSC +#undef DISPATCH_OSC_WITH_CODE +#undef START_DISPATCH +#undef END_DISPATCH +} + +// }}} + +// DCS {{{ + +static bool +accumulate_dcs(PS *self) { + uint8_t ch = self->input_data[self->input_pos++]; + switch(ch) { + case NUL: + case DEL: + break; + case ESC: +START_ALLOW_CASE_RANGE + case 32 ... 126: +END_ALLOW_CASE_RANGE + if (self->parser_buf_pos > 0 && self->parser_buf[self->parser_buf_pos-1] == ESC) { + if (ch == '\\') { self->parser_buf_pos--; return true; } + REPORT_ERROR("DCS sequence contained ESC without trailing \\ at pos: %u ignoring the sequence", self->parser_buf_pos); + SET_STATE(ESC); return false; + } + if (self->parser_buf_pos >= PARSER_BUF_SZ - 1) { + REPORT_ERROR("DCS sequence too long, truncating."); + return true; + } + self->parser_buf[self->parser_buf_pos++] = ch; + break; + default: + REPORT_ERROR("DCS sequence contained non-printable character: 0x%x ignoring the sequence", ch); + } + return false; +} + + +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; +} + +#define PENDING_MODE_CHAR '=' + +static void +dispatch_dcs(PS *self) { + if (self->parser_buf_pos < 2) return; + switch(self->parser_buf[0]) { + case '+': + case '$': + if (self->parser_buf[1] == 'q') { + self->parser_buf[self->parser_buf_pos] = 0; // safe to do since we have 8 extra bytes after PARSER_BUF_SZ + PyObject *mv = PyMemoryView_FromMemory((char*)self->parser_buf + 2, self->parser_buf_pos-2, PyBUF_READ); + if (mv) { + REPORT_OSC2(screen_request_capabilities, (char)self->parser_buf[0], mv); + Py_DECREF(mv); + } else PyErr_Clear(); + screen_request_capabilities(self->screen, (char)self->parser_buf[0], (char*)self->parser_buf + 2); + } else { + REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)self->parser_buf[0], self->parser_buf[1]); + } + break; + case PENDING_MODE_CHAR: + if (self->parser_buf_pos > 2 && (self->parser_buf[1] == '1' || self->parser_buf[1] == '2') && self->parser_buf[2] == 's') { + if (self->parser_buf[1] == '1') { + self->pending_mode.activated_at = monotonic(); + REPORT_COMMAND(screen_start_pending_mode); + } else { + // ignore stop without matching start, see queue_pending_bytes() + // for how stop is detected while in pending mode. + REPORT_ERROR("Pending mode stop command issued while not in pending mode, this can" + " be either a bug in the terminal application or caused by a timeout with no data" + " received for too long or by too much data in pending mode"); + REPORT_COMMAND(screen_stop_pending_mode); + } + } else { + REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)self->parser_buf[0], self->parser_buf[1]); + } + break; + case '@': + if (startswith(self->parser_buf + 1, self->parser_buf_pos - 2, "kitty-", sizeof("kitty-") - 1)) { + if (startswith(self->parser_buf + 7, self->parser_buf_pos - 2, "cmd{", sizeof("cmd{") - 1)) { + PyObject *cmd = PyMemoryView_FromMemory((char*)self->parser_buf + 10, self->parser_buf_pos - 10, PyBUF_READ); + if (cmd != NULL) { + REPORT_OSC2(screen_handle_cmd, (char)self->parser_buf[0], cmd); + screen_handle_cmd(self->screen, cmd); + Py_DECREF(cmd); + } else PyErr_Clear(); +#define IF_SIMPLE_PREFIX(prefix, func) \ + if (startswith(self->parser_buf + 7, self->parser_buf_pos - 1, prefix, sizeof(prefix) - 1)) { \ + const size_t pp_size = sizeof("kitty") + sizeof(prefix); \ + PyObject *msg = PyMemoryView_FromMemory((char*)self->parser_buf + pp_size, self->parser_buf_pos - pp_size, PyBUF_READ); \ + if (msg != NULL) { \ + REPORT_OSC2(func, (char)self->parser_buf[0], msg); \ + screen_handle_kitty_dcs(self->screen, #func, msg); \ + Py_DECREF(msg); \ + } else PyErr_Clear(); + + } else IF_SIMPLE_PREFIX("overlay-ready|", handle_overlay_ready) + } else IF_SIMPLE_PREFIX("kitten-result|", handle_kitten_result) + } else IF_SIMPLE_PREFIX("print|", handle_remote_print) + } else IF_SIMPLE_PREFIX("echo|", handle_remote_echo) + } else IF_SIMPLE_PREFIX("ssh|", handle_remote_ssh) + } else IF_SIMPLE_PREFIX("ask|", handle_remote_askpass) + } else IF_SIMPLE_PREFIX("clone|", handle_remote_clone) + } else IF_SIMPLE_PREFIX("edit|", handle_remote_edit) +#undef IF_SIMPLE_PREFIX + } else { + self->parser_buf[self->parser_buf_pos] = 0; // safe to do as we have 8 extra bytes after PARSER_BUF_SZ + REPORT_ERROR("Unrecognized DCS @ code: %s", self->parser_buf); + } + } + break; + default: + REPORT_ERROR("Unrecognized DCS code: 0x%x", self->parser_buf[0]); + break; + } +} + +// }}} + +// CSI {{{ + +#define CSI_SECONDARY \ + case ' ': \ + case '!': \ + case '"': \ + case '#': \ + case '$': \ + case '%': \ + case '&': \ + case '\'': \ + case '(': \ + case ')': \ + case '*': \ + case '+': \ + case ',': \ + case '-': \ + case '.': \ + case '/': + + + +static bool +accumulate_csi(PS *self) { +#define ENSURE_SPACE \ + if (self->parser_buf_pos > PARSER_BUF_SZ - 1) { \ + REPORT_ERROR("CSI sequence too long, ignoring"); \ + SET_STATE(0); \ + return false; \ + } + + uint8_t ch = self->input_data[self->input_pos++]; + switch(ch) { + IS_DIGIT + CSI_SECONDARY + case ':': + case ';': + ENSURE_SPACE; + self->parser_buf[self->parser_buf_pos++] = ch; + break; + case '?': + case '>': + case '<': + case '=': + if (self->parser_buf_pos != 0) { + REPORT_ERROR("Invalid character in CSI: 0x%x, ignoring the sequence", ch); + SET_STATE(0); + return false; + } + ENSURE_SPACE; + self->parser_buf[self->parser_buf_pos++] = ch; + break; +START_ALLOW_CASE_RANGE + case 'a' ... 'z': + case 'A' ... 'Z': +END_ALLOW_CASE_RANGE + case '@': + case '`': + case '{': + case '|': + case '}': + case '~': + self->parser_buf[self->parser_buf_pos] = ch; + return true; + case BEL: + case BS: + case HT: + case LF: + case VT: + case FF: + case CR: + case SO: + case SI: + self->input_pos--; + dispatch_normal_mode_byte(self); + break; + case NUL: + case DEL: + SET_STATE(0); + break; // no-op + default: + REPORT_ERROR("Invalid character in CSI: 0x%x, ignoring the sequence", ch); + SET_STATE(0); + return false; + + } + return false; +#undef ENSURE_SPACE +} + + +#ifdef DUMP_COMMANDS +static void +parse_sgr_dump(PS *self, uint8_t *buf, unsigned int num, int *params, const char *report_name UNUSED, Region *region) { + Screen *screen = self->screen; +#else +void +parse_sgr(Screen *screen, const uint8_t *buf, unsigned int num, int *params, const char *report_name UNUSED, Region *region) { +#endif + enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 }; + enum State state = START; + unsigned int num_params, num_start, i; + +#define READ_PARAM { params[num_params++] = utoi(buf + num_start, i - num_start); } +#define SEND_SGR { REPORT_PARAMS(report_name, params, num_params, region); select_graphic_rendition(screen, params, num_params, region); state = START; num_params = 0; } + + for (i=0, num_start=0, num_params=0; i < num && num_params < MAX_PARAMS; i++) { + switch(buf[i]) { + IS_DIGIT + switch(state) { + case START: + num_start = i; + state = NORMAL; + num_params = 0; + break; + default: + break; + } + break; + case ';': + switch(state) { + case START: + params[num_params++] = 0; + SEND_SGR; + break; + case NORMAL: + READ_PARAM; + switch(params[0]) { + case 38: + case 48: + case 58: + state = COLOR; + num_start = i + 1; + break; + default: + SEND_SGR; + break; + } + break; + case MULTIPLE: + READ_PARAM; + SEND_SGR; + break; + case COLOR: + READ_PARAM; + switch(params[1]) { + case 2: + state = COLOR3; + break; + case 5: + state = COLOR1; + break; + default: + REPORT_ERROR("Invalid SGR color code with unknown color type: %u", params[1]); + return; + } + num_start = i + 1; + break; + case COLOR1: + READ_PARAM; + SEND_SGR; + break; + case COLOR3: + READ_PARAM; + if (num_params == 5) { SEND_SGR; } + else num_start = i + 1; + break; + } + break; + case ':': + switch(state) { + case START: + REPORT_ERROR("Invalid SGR code containing ':' at an invalid location: %u", i); + return; + case NORMAL: + READ_PARAM; + state = MULTIPLE; + num_start = i + 1; + break; + case MULTIPLE: + READ_PARAM; + num_start = i + 1; + break; + case COLOR: + case COLOR1: + case COLOR3: + REPORT_ERROR("Invalid SGR code containing disallowed character: %c (U+%x)", buf[i], buf[i]); + return; + } + break; + default: + REPORT_ERROR("Invalid SGR code containing disallowed character: %c (U+%x)", buf[i], buf[i]); + return; + } + } + switch(state) { + case START: + if (num_params < MAX_PARAMS) params[num_params++] = 0; + SEND_SGR; + break; + case COLOR1: + case NORMAL: + case MULTIPLE: + if (i > num_start && num_params < MAX_PARAMS) { READ_PARAM; } + if (num_params) { SEND_SGR; } + else { REPORT_ERROR("Incomplete SGR code"); } + break; + case COLOR: + REPORT_ERROR("Invalid SGR code containing incomplete semi-colon separated color sequence"); + break; + case COLOR3: + if (i > num_start && num_params < MAX_PARAMS) READ_PARAM; + if (num_params != 5) { + REPORT_ERROR("Invalid SGR code containing incomplete semi-colon separated color sequence"); + break; + } + if (num_params) { SEND_SGR; } + else { REPORT_ERROR("Incomplete SGR code"); } + break; + } +#undef READ_PARAM +#undef SEND_SGR +} + +static unsigned int +parse_region(Region *r, uint8_t *buf, unsigned int num) { + unsigned int i, start, num_params = 0; + int params[8] = {0}; + for (i=0, start=0; i < num && num_params < 4; i++) { + switch(buf[i]) { + IS_DIGIT + break; + default: + if (i > start) params[num_params++] = utoi(buf + start, i - start); + else if (i == start && buf[i] == ';') params[num_params++] = 0; + start = i + 1; + break; + } + } + + switch(num_params) { + case 0: + break; + case 1: + r->top = params[0]; + break; + case 2: + r->top = params[0]; r->left = params[1]; + break; + case 3: + r->top = params[0]; r->left = params[1]; r->bottom = params[2]; + break; + default: + r->top = params[0]; r->left = params[1]; r->bottom = params[2]; r->right = params[3]; + break; + } + return i; +} + +static 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* +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 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 +dispatch_csi(PS *self) { +#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 SET_MODE(func) \ + p1 = start_modifier == '?' ? 5 : 0; \ + for (i = 0; i < num_params; i++) { \ + NON_NEGATIVE_PARAM(params[i]); \ + REPORT_COMMAND(func, params[i], start_modifier == '?'); \ + func(self->screen, params[i] << p1); \ + } \ + break; + +#define NO_MODIFIERS(modifier, special, special_msg) { \ + if (start_modifier || end_modifier) { \ + if (special && modifier == special) { REPORT_ERROR(special_msg); } \ + else { REPORT_ERROR("CSI code %s has unsupported start modifier: %s or end modifier: %s", csi_letter(code), csi_letter(start_modifier), csi_letter(end_modifier));} \ + break; \ + } \ +} + + char start_modifier = 0, end_modifier = 0; + uint8_t *buf = self->parser_buf, code = self->parser_buf[self->parser_buf_pos]; + unsigned int num = self->parser_buf_pos, start, i, num_params=0; + static int params[MAX_PARAMS] = {0}, p1, p2; + bool private; + if (buf[0] == '>' || buf[0] == '<' || buf[0] == '?' || buf[0] == '!' || buf[0] == '=') { + start_modifier = (char)self->parser_buf[0]; + buf++; num--; + } + if (num > 0) { + switch(buf[num-1]) { + CSI_SECONDARY + end_modifier = (char)buf[--num]; + break; + } + } + if (code == SGR && !start_modifier && !end_modifier) { +#ifdef DUMP_COMMANDS + parse_sgr_dump(self, buf, num, params, "select_graphic_rendition", NULL); +#else + parse_sgr(self->screen, buf, num, params, "select_graphic_rendition", NULL); +#endif + return; + } + if (code == 'r' && !start_modifier && end_modifier == '$') { + // DECCARA + Region r = {0}; + unsigned int consumed = parse_region(&r, buf, num); + num -= consumed; buf += consumed; + parse_sgr(self->screen, buf, num, params, "deccara", &r); + return; + } + + for (i=0, start=0; i < num; i++) { + switch(buf[i]) { + IS_DIGIT + break; + case '-': + if (i > start) { + REPORT_ERROR("CSI code can contain hyphens only at the start of numbers"); + return; + } + break; + default: + if (i > start) params[num_params++] = utoi(buf + start, i - start); + else if (i == start && buf[i] == ';') params[num_params++] = 0; + if (num_params >= MAX_PARAMS) { i = num; start = num + 1; } + else { start = i + 1; break; } + } + } + if (i > start) params[num_params++] = utoi(buf + start, i - start); + switch(code) { + case ICH: + NO_MODIFIERS(end_modifier, ' ', "Shift left escape code not implemented"); + CALL_CSI_HANDLER1(screen_insert_characters, 1); + case REP: + CALL_CSI_HANDLER1(screen_repeat_character, 1); + case CUU: + NO_MODIFIERS(end_modifier, ' ', "Shift right escape code not implemented"); + CALL_CSI_HANDLER1(screen_cursor_up2, 1); + case CUD: + case VPR: + CALL_CSI_HANDLER1(screen_cursor_down, 1); + case CUF: + case HPR: + CALL_CSI_HANDLER1(screen_cursor_forward, 1); + case CUB: + CALL_CSI_HANDLER1(screen_cursor_back1, 1); + case CNL: + CALL_CSI_HANDLER1(screen_cursor_down1, 1); + case CPL: + CALL_CSI_HANDLER1(screen_cursor_up1, 1); + case CHA: + case HPA: + CALL_CSI_HANDLER1(screen_cursor_to_column, 1); + case VPA: + CALL_CSI_HANDLER1(screen_cursor_to_line, 1); + case CBT: + CALL_CSI_HANDLER1(screen_backtab, 1); + case CHT: + CALL_CSI_HANDLER1(screen_tabn, 1); + case CUP: + case HVP: + CALL_CSI_HANDLER2(screen_cursor_position, 1, 1); + case ED: + CALL_CSI_HANDLER1P(screen_erase_in_display, 0, '?'); + case EL: + CALL_CSI_HANDLER1P(screen_erase_in_line, 0, '?'); + case IL: + CALL_CSI_HANDLER1(screen_insert_lines, 1); + case DL: + CALL_CSI_HANDLER1(screen_delete_lines, 1); + case DCH: + if (end_modifier == '#' && !start_modifier) { + CALL_CSI_HANDLER1(screen_push_colors, 0); + } else { + CALL_CSI_HANDLER1(screen_delete_characters, 1); + } + case 'Q': + if (end_modifier == '#' && !start_modifier) { CALL_CSI_HANDLER1(screen_pop_colors, 0); } + REPORT_ERROR("Unknown CSI Q sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params); + break; + case 'R': + if (end_modifier == '#' && !start_modifier) { + REPORT_COMMAND(screen_report_color_stack); + screen_report_color_stack(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: + SET_MODE(screen_set_mode); + case RM: + SET_MODE(screen_reset_mode); + case DSR: + CALL_CSI_HANDLER1P(report_device_status, 0, '?'); + case 's': + if (!start_modifier && !end_modifier && !num_params) { + REPORT_COMMAND(screen_save_cursor); + screen_save_cursor(self->screen); + break; + } else if (start_modifier == '?' && !end_modifier) { + if (!num_params) { + REPORT_COMMAND(screen_save_modes); + screen_save_modes(self->screen); + } else { SET_MODE(screen_save_mode); } + break; + } + REPORT_ERROR("Unknown CSI s sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params); + break; + case 't': + if (!num_params) { + REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c' and no parameters", start_modifier, end_modifier); + break; + } + if (start_modifier || end_modifier) { + REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c', %u parameters and first parameter: %d", start_modifier, end_modifier, num_params, params[0]); + break; + } + switch(params[0]) { + case 4: + case 8: + log_error("Escape codes to resize text area are not supported"); + break; + case 14: + case 16: + case 18: + CALL_CSI_HANDLER1(screen_report_size, 0); + break; + case 22: + case 23: + 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 { SET_MODE(screen_restore_mode); } + 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) { + REPORT_ERROR( + "The application is trying to use xterm's modifyOtherKeys." + " This is superseded by the kitty keyboard protocol: https://sw.kovidgoyal.net/kitty/keyboard-protocol/" + " the application should be updated to use that" + ); + 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)); + } +} + +// }}} + +// APC mode {{{ + +#include "parse-graphics-command.h" + +static void +dispatch_apc(PS *self) { + if (self->parser_buf_pos < 2) return; + switch(self->parser_buf[0]) { + case 'G': + parse_graphics_code(self, self->parser_buf, self->parser_buf_pos); + break; + default: + REPORT_ERROR("Unrecognized APC code: 0x%x", self->parser_buf[0]); + break; + } +} + +// }}} + +// PM mode {{{ +static void +dispatch_pm(PS *self) { + if (self->parser_buf_pos < 2) return; + switch(self->parser_buf[0]) { + default: + REPORT_ERROR("Unrecognized PM code: 0x%x", self->parser_buf[0]); + break; + } +} + + +// }}} + +static bool +accumulate_oth(PS *self) { + uint8_t ch = self->input_data[self->input_pos++]; + switch(ch) { + case BEL: + return true; + case DEL: + case NUL: + break; + case ESC_ST: + if (self->parser_buf_pos > 0 && self->parser_buf[self->parser_buf_pos - 1] == ESC) { + self->parser_buf_pos--; + return true; + } + /* fallthrough */ + default: + if (self->parser_buf_pos >= PARSER_BUF_SZ - 1) { + REPORT_ERROR("OTH sequence too long, truncating."); + return true; + } + self->parser_buf[self->parser_buf_pos++] = ch; + break; + } + return false; +} #define dispatch_single_byte(dispatch, watch_for_pending) { \ switch(self->vte_state) { \ @@ -160,7 +1323,6 @@ typedef struct PS { break; \ case VTE_OSC: \ { \ - self->extended_osc_code = false; \ if (accumulate_osc(self)) { \ dispatch##_osc(self); \ if (self->extended_osc_code) { \ @@ -256,11 +1418,46 @@ pending_escape_code(PS *self, char_type start_ch, char_type end_ch) { static void pending_pm(PS *self) { pending_escape_code(self, ESC_PM, ESC_ST); } static void pending_apc(PS *self) { pending_escape_code(self, ESC_APC, ESC_ST); } +static void +pending_osc(PS *self) { + const bool extended = is_extended_osc(self); + pending_escape_code(self, ESC_OSC, ESC_ST); + if (extended) continue_osc_52(self); +} + +#define pb(i) self->parser_buf[i] +static void +pending_dcs(PS *self) { + if (self->parser_buf_pos >= 3 && pb(0) == '=' && (pb(1) == '1' || pb(1) == '2') && pb(2) == 's') { + self->pending_mode.activated_at = pb(1) == '1' ? monotonic() : 0; + if (pb(1) == '1') { + REPORT_COMMAND(screen_start_pending_mode); + self->pending_mode.activated_at = monotonic(); + } else { + self->pending_mode.stop_escape_code_type = ESC_DCS; + self->pending_mode.activated_at = 0; + } + } else pending_escape_code(self, ESC_DCS, ESC_ST); +} + +static void +pending_csi(PS *self) { + if (self->parser_buf_pos == 5 && pb(0) == '?' && pb(1) == '2' && pb(2) == '0' && pb(3) == '2' && pb(4) == '6' && (pb(5) == 'h' || pb(5) == 'l')) { + if (pb(5) == 'h') { + REPORT_COMMAND(screen_set_mode, 2026, 1); + self->pending_mode.activated_at = monotonic(); + } else { + self->pending_mode.activated_at = 0; + self->pending_mode.stop_escape_code_type = ESC_CSI; + } + } else pending_escape_code(self, ESC_CSI, pb(self->parser_buf_pos)); +} +#undef pb static void queue_pending_bytes(PS *self) { for (; self->input_pos < self->input_sz; self->input_pos++) { - dispatch_single_byte(pending, if (!screen->pending_mode.activated_at) goto end); + dispatch_single_byte(pending, if (!self->pending_mode.activated_at) goto end); } end: FLUSH_DRAW; @@ -290,17 +1487,16 @@ dump_partial_escape_code_to_pending(PS *self) { static void parse_bytes_watching_for_pending(PS *self) { for (; self->input_pos < self->input_sz; self->input_pos++) { - dispatch_single_byte(dispatch, if (screen->pending_mode.activated_at) goto end); + dispatch_single_byte(dispatch, if (self->pending_mode.activated_at) goto end); } end: FLUSH_DRAW; } static void -do_parse_vte(PS *self) { +do_parse_vt(PS *self) { enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING}; enum STATE state = START; - size_t read_buf_pos = 0; VTEState vte_state_at_start_of_pending = VTE_NORMAL; do { @@ -359,15 +1555,34 @@ do_parse_vte(PS *self) { } // Boilerplate {{{ +#define setup_worker \ + self->input_data = screen->read_buf; self->input_sz = screen->read_buf_sz; self->dump_callback = dump_callback; \ + self->now = now; self->input_pos = 0; self->screen = screen; + #ifdef DUMP_COMMANDS void -parse_vte_dump(Parser *p) { - do_parse_vte((PS*)p->state); +parse_vt_dump(Parser *p) { + do_parse_vt((PS*)p->state); +} + +void +parse_worker_dump(Screen *screen, PyObject *dump_callback, monotonic_t now) { + PS *self = (PS*)screen->vt_parser->state; + setup_worker; + do_parse_vt(self); } #else +static void +parse_vt(Parser *p) { + do_parse_vt((PS*)p->state); +} +extern void parse_vt_dump(Parser *p); + void -parse_vte(Parser *p) { - do_parse_vte((PS*)p->state); +parse_worker(Screen *screen, PyObject *dump_callback, monotonic_t now) { + PS *self = (PS*)screen->vt_parser->state; + setup_worker; + do_parse_vt(self); } #endif @@ -376,11 +1591,11 @@ static PyObject* new(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_parser(window_id); + return (PyObject*) alloc_vt_parser(window_id); } -static void -dealloc(Parser* self) { +void +free_vt_parser(Parser* self) { if (self->state) { PS *s = (PS*)self->state; PyMem_Free(s->pending_mode.buf); s->pending_mode.buf = NULL; @@ -389,25 +1604,38 @@ dealloc(Parser* self) { Py_TYPE(self)->tp_free((PyObject*)self); } +static void +reset(PS *self) { + self->parser_buf_pos = 0; + self->extended_osc_code = false; + self->vte_state = VTE_NORMAL; + self->pending_mode.activated_at = 0; +} + +void +reset_vt_parser(Parser *self) { + reset((PS*)self->state); +} + extern PyTypeObject Screen_Type; static PyObject* -py_parse_vte(Parser *p, PyObject *args) { +py_parse(Parser *p, PyObject *args) { PS *self = (PS*)p->state; const uint8_t *data; Py_ssize_t sz; PyObject *dump_callback = NULL; if (!PyArg_ParseTuple(args, "O!y#|O", &Screen_Type, &self->screen, &data, &sz, &dump_callback)) return NULL; self->input_data = data; self->input_sz = sz; self->dump_callback = dump_callback; - self->now = monotonic(); - if (dump_callback) parse_vte_dump(p); else parse_vte(p); + self->input_pos = 0; self->now = monotonic(); + if (dump_callback) parse_vt_dump(p); else parse_vt(p); self->input_data = NULL; self->input_sz = 0; self->dump_callback = NULL; self->screen = NULL; Py_RETURN_NONE; } static PyMethodDef methods[] = { - {"parse_vte", (PyCFunction)py_parse_vte, METH_VARARGS, ""}, + {"parse_bytes", (PyCFunction)py_parse, METH_VARARGS, ""}, {NULL}, }; @@ -415,7 +1643,7 @@ PyTypeObject Parser_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Parser", .tp_basicsize = sizeof(Parser), - .tp_dealloc = (destructor)dealloc, + .tp_dealloc = (destructor)free_vt_parser, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "VT Escape code parser", .tp_methods = methods, @@ -423,7 +1651,7 @@ PyTypeObject Parser_Type = { }; Parser* -alloc_parser(id_type window_id) { +alloc_vt_parser(id_type window_id) { Parser *self = (Parser*)Parser_Type.tp_alloc(&Parser_Type, 1); if (self != NULL) { self->state = PyMem_Calloc(1, sizeof(PS)); diff --git a/kitty/vt-parser.h b/kitty/vt-parser.h index ca56114e8..fdc9995b8 100644 --- a/kitty/vt-parser.h +++ b/kitty/vt-parser.h @@ -17,6 +17,6 @@ typedef struct Parser { } Parser; -Parser* alloc_parser(id_type window_id); -void parse_vte(Parser*); -void parse_vte_dump(Parser*); +Parser* alloc_vt_parser(id_type window_id); +void free_vt_parser(Parser*); +void reset_vt_parser(Parser*); diff --git a/kitty/window.py b/kitty/window.py index 7c21c8e82..f1d4703d1 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -44,13 +44,13 @@ from .fast_data_types import ( CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, - DCS, + ESC_DCS, + ESC_OSC, GLFW_MOD_CONTROL, GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT, NO_CURSOR_SHAPE, - OSC, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, @@ -190,14 +190,16 @@ class CwdRequest: return window.get_cwd_of_child(oldest=self.request_type is CwdRequestType.oldest) or '' -def process_title_from_child(title: str, is_base64: bool) -> str: +def process_title_from_child(title: memoryview, is_base64: bool, default_title: str) -> str: if is_base64: from base64 import standard_b64decode try: - title = standard_b64decode(title).decode('utf-8', 'replace') + stitle = standard_b64decode(title).decode('utf-8', 'replace') except Exception: - title = 'undecodeable title' - return sanitize_title(title) + stitle = 'undecodeable title' + else: + stitle = str(title, 'utf-8', 'replace') + return sanitize_title(stitle or default_title) @lru_cache(maxsize=64) @@ -446,7 +448,7 @@ def cmd_output(screen: Screen, which: CommandOutput = CommandOutput.last_run, as return ''.join(lines) -def process_remote_print(msg: str) -> str: +def process_remote_print(msg: memoryview) -> str: from base64 import standard_b64decode from .cli import green @@ -1004,7 +1006,8 @@ class Window: ukey, has_equal, uval = val.partition('=') self.set_user_var(ukey, (base64_decode(uval) if uval else b'') if has_equal == '=' else None) - def desktop_notify(self, osc_code: int, raw_data: str) -> None: + def desktop_notify(self, osc_code: int, raw_datab: memoryview) -> None: + raw_data = str(raw_datab, 'utf-8', 'replace') if osc_code == 1337: self.osc_1337(raw_data) if osc_code == 777: @@ -1016,9 +1019,6 @@ class Window: if cmd is not None and osc_code == 99: self.prev_osc99_cmd = cmd - def use_utf8(self, on: bool) -> None: - get_boss().child_monitor.set_iutf8_winid(self.id, on) - def on_mouse_event(self, event: Dict[str, Any]) -> bool: event['mods'] = event.get('mods', 0) & mod_mask ev = MouseEvent(**event) @@ -1119,13 +1119,13 @@ class Window: # Cancel IME composition after loses focus update_ime_position_for_window(self.id, False, -1) - def title_changed(self, new_title: Optional[str], is_base64: bool = False) -> None: - self.child_title = process_title_from_child(new_title or self.default_title, is_base64) + def title_changed(self, new_title: Optional[memoryview], is_base64: bool = False) -> None: + self.child_title = process_title_from_child(new_title or memoryview(b''), is_base64, self.default_title) self.call_watchers(self.watchers.on_title_change, {'title': self.child_title, 'from_child': True}) if self.override_title is None: self.title_updated() - def icon_changed(self, new_icon: object) -> None: + def icon_changed(self, new_icon: memoryview) -> None: pass # TODO: Implement this @property @@ -1190,20 +1190,20 @@ class Window: r |= r << 8 g |= g << 8 b |= b << 8 - self.screen.send_escape_code_to_child(OSC, f'{code};rgb:{r:04x}/{g:04x}/{b:04x}') + self.screen.send_escape_code_to_child(ESC_OSC, f'{code};rgb:{r:04x}/{g:04x}/{b:04x}') def report_notification_activated(self, identifier: str) -> None: identifier = sanitize_identifier_pat().sub('', identifier) - self.screen.send_escape_code_to_child(OSC, f'99;i={identifier};') + self.screen.send_escape_code_to_child(ESC_OSC, f'99;i={identifier};') - def set_dynamic_color(self, code: int, value: Union[str, bytes]) -> None: - if isinstance(value, bytes): - value = value.decode('utf-8') + def set_dynamic_color(self, code: int, value: Union[str, bytes, memoryview] = '') -> None: + if isinstance(value, (bytes, memoryview)): + value = str(value, 'utf-8', 'replace') if code == 22: ret = set_pointer_shape(self.screen, value, self.os_window_id) if ret: - self.screen.send_escape_code_to_child(OSC, '22:' + ret) + self.screen.send_escape_code_to_child(ESC_OSC, '22:' + ret) color_changes: Dict[DynamicColor, Optional[str]] = {} for val in value.split(';'): w = DYNAMIC_COLOR_CODES.get(code) @@ -1218,7 +1218,8 @@ class Window: if color_changes: self.change_colors(color_changes) - def set_color_table_color(self, code: int, value: str) -> None: + def set_color_table_color(self, code: int, bvalue: Optional[memoryview] = None) -> None: + value = str(bvalue or b'', 'utf-8', 'replace') cp = self.screen.color_profile if code == 4: changed = False @@ -1247,12 +1248,12 @@ class Window: def request_capabilities(self, q: str) -> None: for result in get_capabilities(q, get_options()): - self.screen.send_escape_code_to_child(DCS, result) + self.screen.send_escape_code_to_child(ESC_DCS, result) - def handle_remote_cmd(self, cmd: str) -> None: + def handle_remote_cmd(self, cmd: memoryview) -> None: get_boss().handle_remote_cmd(cmd, self) - def handle_remote_echo(self, msg: str) -> None: + def handle_remote_echo(self, msg: memoryview) -> None: from base64 import standard_b64decode data = standard_b64decode(msg) # ensure we are not writing any control char back as this can lead to command injection on shell prompts @@ -1260,12 +1261,12 @@ class Window: data = re.sub(rb'[^ -~]', b'', data) self.write_to_child(data) - def handle_remote_ssh(self, msg: str) -> None: + def handle_remote_ssh(self, msg: memoryview) -> None: from kittens.ssh.utils import get_ssh_data for line in get_ssh_data(msg, f'{os.getpid()}-{self.id}'): self.write_to_child(line) - def handle_kitten_result(self, msg: str) -> None: + def handle_kitten_result(self, msg: memoryview) -> None: import base64 self.kitten_result = json.loads(base64.b85decode(msg)) for processor in self.kitten_result_processors: @@ -1278,17 +1279,18 @@ class Window: def add_kitten_result_processor(self, callback: Callable[['Window', Any], None]) -> None: self.kitten_result_processors.append(callback) - def handle_overlay_ready(self, msg: str) -> None: + def handle_overlay_ready(self, msg: memoryview) -> None: boss = get_boss() tab = boss.tab_for_window(self) if tab is not None: tab.move_window_to_top_of_group(self) - def append_remote_data(self, msg: str) -> str: - if not msg: + def append_remote_data(self, msgb: memoryview) -> str: + if not msgb: cdata = ''.join(self.current_remote_data) self.current_remote_data = [] return cdata + msg = str(msgb, 'utf-8', 'replace') num, rest = msg.split(':', 1) max_size = get_options().clipboard_max_size * 1024 * 1024 if num == '0' or sum(map(len, self.current_remote_data)) > max_size: @@ -1296,13 +1298,13 @@ class Window: self.current_remote_data.append(rest) return '' - def handle_remote_edit(self, msg: str) -> None: + def handle_remote_edit(self, msg: memoryview) -> None: cdata = self.append_remote_data(msg) if cdata: from .launch import remote_edit remote_edit(cdata, self) - def handle_remote_clone(self, msg: str) -> None: + def handle_remote_clone(self, msg: memoryview) -> None: cdata = self.append_remote_data(msg) if cdata: ac = get_options().allow_cloning @@ -1321,8 +1323,9 @@ class Window: from .launch import clone_and_launch clone_and_launch(cdata, self) - def handle_remote_askpass(self, msg: str) -> None: + def handle_remote_askpass(self, msgb: memoryview) -> None: from .shm import SharedMemory + msg = str(msgb, 'utf-8') with SharedMemory(name=msg, readonly=True) as shm: shm.seek(1) data = json.loads(shm.read_data_with_size()) @@ -1350,17 +1353,17 @@ class Window: else: log_error(f'Ignoring ask request with unknown type: {data["type"]}') - def handle_remote_print(self, msg: str) -> None: + def handle_remote_print(self, msg: memoryview) -> None: text = process_remote_print(msg) print(text, end='', flush=True) def send_cmd_response(self, response: Any) -> None: - self.screen.send_escape_code_to_child(DCS, '@kitty-cmd' + json.dumps(response)) + self.screen.send_escape_code_to_child(ESC_DCS, '@kitty-cmd' + json.dumps(response)) - def file_transmission(self, data: str) -> None: + def file_transmission(self, data: memoryview) -> None: self.file_transmission_control.handle_serialized_command(data) - def clipboard_control(self, data: str, is_partial: Optional[bool] = False) -> None: + def clipboard_control(self, data: memoryview, is_partial: Optional[bool] = False) -> None: if is_partial is None: self.clipboard_request_manager.parse_osc_5522(data) else: diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index 2c206d380..c575c6ff3 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -16,7 +16,7 @@ from pty import CHILD, STDIN_FILENO, STDOUT_FILENO, fork from unittest import TestCase from kitty.config import finalize_keys, finalize_mouse_mappings -from kitty.fast_data_types import Cursor, HistoryBuf, LineBuf, Screen, get_options, parse_bytes, set_options +from kitty.fast_data_types import Cursor, HistoryBuf, LineBuf, Screen, get_options, set_options from kitty.options.parse import merge_result_dicts from kitty.options.types import Options, defaults from kitty.types import MouseEvent @@ -323,7 +323,7 @@ class PTY: break bytes_read += len(data) self.received_bytes += data - parse_bytes(self.screen, data) + self.screen.vt_parser.parse_bytes(self.screen, data) return bytes_read def wait_till(self, q, timeout=10): diff --git a/setup.py b/setup.py index 472f59013..efeb089a7 100755 --- a/setup.py +++ b/setup.py @@ -614,8 +614,6 @@ def get_vcs_rev() -> str: def get_source_specific_defines(env: Env, src: str) -> Tuple[str, Optional[List[str]]]: - if src == 'kitty/parser_dump.c': - return 'kitty/parser.c', ['DUMP_COMMANDS'] if src == 'kitty/vt-parser-dump.c': return 'kitty/vt-parser.c', ['DUMP_COMMANDS'] if src == 'kitty/data-types.c': @@ -770,7 +768,6 @@ def find_c_files() -> Tuple[List[str], List[str]]: ans.append(os.path.join('kitty', x)) elif ext == '.h': headers.append(os.path.join('kitty', x)) - ans.append('kitty/parser_dump.c') ans.append('kitty/vt-parser-dump.c') return ans, headers