From b8d9629ee4b7232582ef9c5fc39452c1cecf3141 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Sep 2017 13:08:11 +0530 Subject: [PATCH] Start work on parsing of graphics escape code --- kitty/graphics.h | 15 +++++ kitty/parser.c | 139 +++++++++++++++++++++++++++++++++++++++++- kitty_tests/parser.py | 7 ++- 3 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 kitty/graphics.h diff --git a/kitty/graphics.h b/kitty/graphics.h new file mode 100644 index 000000000..e103df86e --- /dev/null +++ b/kitty/graphics.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2017 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +typedef struct { + unsigned char action, transmission_type; + uint32_t format, more, id; + uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines; + int32_t z_index; + char payload[4096]; +} GraphicsCommand; diff --git a/kitty/parser.c b/kitty/parser.c index c8126d097..ca91ea599 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -7,6 +7,7 @@ #include "data-types.h" #include "control-codes.h" #include "screen.h" +#include "graphics.h" #include // utils {{{ @@ -528,6 +529,140 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { } // }}} +// APC mode {{{ + +static inline void +parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { + unsigned int pos = 1; + enum GR_STATES { KEY, EQUAL, UINT, INT, FLAG, PAYLOAD }; + enum GR_STATES state = KEY, value_state = FLAG; + enum KEYS { + action='a', + transmission_type='t', + format = 'f', + more = 'm', + id = 'i', + width = 'w', + height = 'h', + x_offset = 'x', + y_offset = 'y', + data_height = 'v', + data_width = 's', + num_cells = 'c', + num_lines = 'r', + z_index = 'z' + }; + enum KEYS key = 'a'; + static GraphicsCommand g; + unsigned int i, code, ch; + bool is_negative; + memset(&g, 0, sizeof(g)); + + while (pos < screen->parser_buf_pos - 1) { + switch(state) { + + case KEY: + ch = screen->parser_buf[pos++]; + switch(ch) { + case ',': + break; + case ';': + state = PAYLOAD; + break; +#define KS(n, vs) case n: state = EQUAL; value_state = vs; key = ch; break +#define U(x) KS(x, UINT) + KS(action, FLAG); KS(transmission_type, FLAG); KS(z_index, INT); + U(format); U(more); U(id); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); +#undef U +#undef KS + default: + REPORT_ERROR("Malformed graphics control block, invalid key character: 0x%x", key); + return; + } + break; + + case EQUAL: + if (screen->parser_buf[pos++] != '=') { + REPORT_ERROR("Malformed graphics control block, no = after key"); + return; + } + state = value_state; + break; + + case FLAG: + switch(key) { +#define F(a) case a: g.a = screen->parser_buf[pos++]; break + F(action); F(transmission_type); + default: + break; + } + break; +#undef F + +#define READ_UINT \ + for (i = pos; i < MIN(screen->parser_buf_pos, pos + 5); i++) { \ + if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; \ + } \ + if (i == pos) { REPORT_ERROR("Malformed graphics control block, expecting an integer value"); return; } \ + code = utoi(screen->parser_buf + pos, i - pos); + + case INT: + is_negative = false; + if(screen->parser_buf[pos] == '-') { is_negative = true; pos++; } +#define U(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break + READ_UINT; + switch(key) { + U(z_index); + default: break; + } + break; +#undef U + case UINT: + READ_UINT; +#define U(x) case x: g.x = code; break + switch(key) { + U(format); U(more); U(id); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); + default: break; + } + break; +#undef U +#undef SET_ATTR +#undef READ_UINT + case PAYLOAD: + break; // TODO + } + } +} + +static inline void +dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { + if (screen->parser_buf_pos < 2) return; + switch(screen->parser_buf[0]) { + case 'G': + parse_graphics_code(screen, dump_callback); + break; + default: + REPORT_ERROR("Unrecognized APC code: 0x%x", screen->parser_buf[0]); + break; + } +} + +// }}} + +// PM mode {{{ +static inline void +dispatch_pm(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { + if (screen->parser_buf_pos < 2) return; + switch(screen->parser_buf[0]) { + default: + REPORT_ERROR("Unrecognized PM code: 0x%x", screen->parser_buf[0]); + break; + } +} + + +// }}} + // Parse loop {{{ static inline bool @@ -689,10 +824,10 @@ dispatch_unicode_char(Screen *screen, uint32_t codepoint, PyObject DUMP_UNUSED * if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen, dump_callback); SET_STATE(0); } break; case APC: - if (accumulate_oth(screen, codepoint, dump_callback)) { SET_STATE(0); } + 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)) { SET_STATE(0); } + 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); } diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 585d3385a..76c592433 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -185,6 +185,9 @@ class TestParser(BaseTest): def test_oth_codes(self): s = self.create_screen() pb = partial(self.parse_bytes_dump, s) - for prefix in '\033_', '\033^', '\u009e', '\u009f': + for prefix in '\033_', '\u009f': for suffix in '\u009c', '\033\\': - pb('a{}+\\++{}bcde'.format(prefix, suffix), 'abcde') + pb('a{}+\\++{}bcde'.format(prefix, suffix), ('draw', 'a'), ('Unrecognized APC code: 0x2b',), ('draw', 'bcde')) + for prefix in '\033^', '\u009e': + for suffix in '\u009c', '\033\\': + pb('a{}+\\++{}bcde'.format(prefix, suffix), ('draw', 'a'), ('Unrecognized PM code: 0x2b',), ('draw', 'bcde'))