diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index 88f33bb2d..6ddf011ec 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -372,9 +372,9 @@ Key Value Default Description **Keys for deleting images** ----------------------------------------------------------- ``d`` Single character. ``a`` What to delete. - ``(a, A, c, C, p, - P, q, Q, x, X, y, - Y, z, Z)``. + ``(a, A, c, C, i, + I, p, P, q, Q, x, X, + y, Y, z, Z)``. ======= ==================== ========= ================= diff --git a/gen-apc-parsers.py b/gen-apc-parsers.py new file mode 100755 index 000000000..4dc0257bd --- /dev/null +++ b/gen-apc-parsers.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2018, Kovid Goyal + +from collections import defaultdict + + +def resolve_keys(keymap): + ans = defaultdict(list) + for ch, (attr, atype) in keymap.items(): + if atype not in ('int', 'uint'): + atype = 'flag' + ans[atype].append(ch) + return ans + + +def enum(keymap): + lines = [] + for ch, (attr, atype) in keymap.items(): + lines.append(f"{attr}='{ch}'") + return ''' + enum KEYS {{ + {} + }}; + '''.format(',\n'.join(lines)) + + +def parse_key(keymap): + lines = [] + for attr, atype in keymap.values(): + vs = atype.upper() if atype in ('uint', 'int') else 'FLAG' + lines.append(f'case {attr}: value_state = {vs}; break;') + return ' \n'.join(lines) + + +def parse_flag(keymap, type_map, command_class): + lines = [] + for ch in type_map['flag']: + attr, allowed_values = keymap[ch] + q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values) + lines.append(f''' + case {attr}: {{ + g.{attr} = screen->parser_buf[pos++] & 0xff; + if ({q}) {{ + REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr}); + return; + }}; + }} + break; + ''') + return ' \n'.join(lines) + + +def parse_number(keymap): + int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int'] + uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint'] + return '; '.join(int_keys), '; '.join(uint_keys) + + +def cmd_for_report(report_name, keymap, type_map, payload_allowed): + def group(atype, conv): + flag_fmt, flag_attrs = [], [] + cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype] + for ch in type_map[atype]: + flag_fmt.append('s' + cv) + attr = keymap[ch][0] + flag_attrs.append(f'"{attr}", {conv}g.{attr}') + return ' '.join(flag_fmt), ', '.join(flag_attrs) + + flag_fmt, flag_attrs = group('flag', '') + int_fmt, int_attrs = group('int', '(int)') + uint_fmt, uint_attrs = group('uint', '(unsigned int)') + + fmt = f'{flag_fmt} {uint_fmt} {int_fmt}' + if payload_allowed: + ans = [f'REPORT_VA_COMMAND("s {{{fmt} sI}} y#", "{report_name}",'] + else: + ans = [f'REPORT_VA_COMMAND("s {{{fmt}}}", "{report_name}",'] + ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs))) + if payload_allowed: + ans.append(', "payload_sz", g.payload_sz, payload, g.payload_sz') + ans.append(');') + return '\n'.join(ans) + + +def generate(function_name, callback_name, report_name, keymap, command_class, initial_key='a', payload_allowed=True): + type_map = resolve_keys(keymap) + keys_enum = enum(keymap) + handle_key = parse_key(keymap) + flag_keys = parse_flag(keymap, type_map, command_class) + int_keys, uint_keys = parse_number(keymap) + report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed) + if payload_allowed: + payload_after_value = "case ';': state = PAYLOAD; break;" + payload = payload = ', PAYLOAD' + parr = 'static uint8_t payload[4096];' + payload_case = f''' + case PAYLOAD: {{ + sz = screen->parser_buf_pos - pos; + const char *err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz); + if (err != NULL) {{ REPORT_ERROR("Failed to parse {command_class} command payload with error: %s", err); return; }} + pos = screen->parser_buf_pos; + }} + break; + ''' + callback = f'{callback_name}(screen, &g, payload)' + else: + payload_after_value = payload = parr = payload_case = '' + callback = f'{callback_name}(screen, &g)' + + return f''' +static inline void +{function_name}(Screen *screen, PyObject UNUSED *dump_callback) {{ + unsigned int pos = 1; + enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }}; + enum PARSER_STATES state = KEY, value_state = FLAG; + static {command_class} g; + unsigned int i, code; + uint64_t lcode; + bool is_negative; + memset(&g, 0, sizeof(g)); + size_t sz; + {parr} + {keys_enum} + enum KEYS key = '{initial_key}'; + + while (pos < screen->parser_buf_pos) {{ + switch(state) {{ + case KEY: + key = screen->parser_buf[pos++]; + state = EQUAL; + switch(key) {{ + {handle_key} + default: + REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key); + return; + }} + 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]); + return; + }} + state = value_state; + break; + + case FLAG: + switch(key) {{ + {flag_keys} + default: + break; + }} + state = AFTER_VALUE; + break; + + 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; \\ + }} \\ + 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; \\ + 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++; }} +#define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break + READ_UINT; + switch(key) {{ + {int_keys}; + default: break; + }} + state = AFTER_VALUE; + break; +#undef I + case UINT: + READ_UINT; +#define U(x) case x: g.x = code; break + switch(key) {{ + {uint_keys}; + default: break; + }} + state = AFTER_VALUE; + break; +#undef U +#undef READ_UINT + + case AFTER_VALUE: + switch (screen->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]); + return; + case ',': + state = KEY; + break; + {payload_after_value} + }} + break; + + {payload_case} + + }} // end switch + }} // end while + + switch(state) {{ + case EQUAL: + REPORT_ERROR("Malformed {command_class} control block, no = after key"); return; + case INT: + case UINT: + REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return; + case FLAG: + REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return; + default: + break; + }} + + {report_cmd} + + {callback}; +}} + ''' + + +def graphics_parser(): + flag = frozenset + keymap = { + 'a': ('action', flag('tTqpd')), + 'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')), + 't': ('transmission_type', flag('dfts')), + 'o': ('compressed', flag('z')), + 'f': ('format', 'uint'), + 'm': ('more', 'uint'), + 'i': ('id', 'uint'), + 'w': ('width', 'uint'), + 'h': ('height', 'uint'), + 'x': ('x_offset', 'uint'), + 'y': ('y_offset', 'uint'), + 'v': ('data_height', 'uint'), + 's': ('data_width', 'uint'), + 'S': ('data_sz', 'uint'), + 'O': ('data_offset', 'uint'), + 'c': ('num_cells', 'uint'), + 'r': ('num_lines', 'uint'), + 'X': ('cell_x_offset', 'uint'), + 'Y': ('cell_y_offset', 'uint'), + 'z': ('z_index', 'int'), + } + text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand') + + with open('kitty/parse-graphics-command.h', 'w') as f: + print('#pragma once', file=f) + print(text, file=f) + + +graphics_parser() diff --git a/kitty/parse-graphics-command.h b/kitty/parse-graphics-command.h new file mode 100644 index 000000000..18d8f637b --- /dev/null +++ b/kitty/parse-graphics-command.h @@ -0,0 +1,209 @@ +#pragma once + +static inline void +parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { + unsigned int pos = 1; + enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE , PAYLOAD }; + enum PARSER_STATES state = KEY, value_state = FLAG; + static GraphicsCommand g; + unsigned int i, code; + uint64_t lcode; + bool is_negative; + memset(&g, 0, sizeof(g)); + size_t sz; + static uint8_t payload[4096]; + + enum KEYS { + action='a', +delete_action='d', +transmission_type='t', +compressed='o', +format='f', +more='m', +id='i', +width='w', +height='h', +x_offset='x', +y_offset='y', +data_height='v', +data_width='s', +data_sz='S', +data_offset='O', +num_cells='c', +num_lines='r', +cell_x_offset='X', +cell_y_offset='Y', +z_index='z' + }; + + enum KEYS key = 'a'; + + while (pos < screen->parser_buf_pos) { + switch(state) { + case KEY: + key = screen->parser_buf[pos++]; + state = EQUAL; + switch(key) { + case action: value_state = FLAG; break; +case delete_action: value_state = FLAG; break; +case transmission_type: value_state = FLAG; break; +case compressed: value_state = FLAG; break; +case format: value_state = UINT; break; +case more: value_state = UINT; break; +case id: value_state = UINT; break; +case width: value_state = UINT; break; +case height: value_state = UINT; break; +case x_offset: value_state = UINT; break; +case y_offset: value_state = UINT; break; +case data_height: value_state = UINT; break; +case data_width: value_state = UINT; break; +case data_sz: value_state = UINT; break; +case data_offset: value_state = UINT; break; +case num_cells: value_state = UINT; break; +case num_lines: value_state = UINT; break; +case cell_x_offset: value_state = UINT; break; +case cell_y_offset: value_state = UINT; break; +case z_index: value_state = INT; break; + default: + REPORT_ERROR("Malformed GraphicsCommand control block, invalid key character: 0x%x", key); + return; + } + break; + + case EQUAL: + if (screen->parser_buf[pos++] != '=') { + REPORT_ERROR("Malformed GraphicsCommand control block, no = after key, found: 0x%x instead", screen->parser_buf[pos-1]); + return; + } + state = value_state; + break; + + case FLAG: + switch(key) { + + case action: { + g.action = screen->parser_buf[pos++] & 0xff; + if (g.action != 'q' && g.action != 't' && g.action != 'p' && g.action != 'T' && g.action != 'd') { + REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag value for action: 0x%x", g.action); + return; + }; + } + break; + + + case delete_action: { + g.delete_action = screen->parser_buf[pos++] & 0xff; + if (g.delete_action != 'A' && g.delete_action != 'q' && g.delete_action != 'x' && g.delete_action != 'Y' && g.delete_action != 'z' && g.delete_action != 'a' && g.delete_action != 'Z' && g.delete_action != 'p' && g.delete_action != 'Q' && g.delete_action != 'c' && g.delete_action != 'P' && g.delete_action != 'y' && g.delete_action != 'C' && g.delete_action != 'i' && g.delete_action != 'X' && g.delete_action != 'I') { + REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag value for delete_action: 0x%x", g.delete_action); + return; + }; + } + break; + + + case transmission_type: { + g.transmission_type = screen->parser_buf[pos++] & 0xff; + if (g.transmission_type != 't' && g.transmission_type != 'f' && g.transmission_type != 's' && g.transmission_type != 'd') { + REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag value for transmission_type: 0x%x", g.transmission_type); + return; + }; + } + break; + + + case compressed: { + g.compressed = screen->parser_buf[pos++] & 0xff; + if (g.compressed != 'z') { + REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag value for compressed: 0x%x", g.compressed); + return; + }; + } + break; + + default: + break; + } + state = AFTER_VALUE; + break; + + 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; \ + } \ + if (i == pos) { REPORT_ERROR("Malformed GraphicsCommand control block, expecting an integer value for key: %c", key & 0xFF); return; } \ + lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \ + if (lcode > UINT32_MAX) { REPORT_ERROR("Malformed GraphicsCommand control block, number is too large"); return; } \ + code = lcode; + + is_negative = false; + if(screen->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) { + I(z_index); + default: break; + } + state = AFTER_VALUE; + break; +#undef I + 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(data_sz); U(data_offset); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset); + default: break; + } + state = AFTER_VALUE; + break; +#undef U +#undef READ_UINT + + case AFTER_VALUE: + switch (screen->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]); + return; + case ',': + state = KEY; + break; + case ';': state = PAYLOAD; break; + } + break; + + + case PAYLOAD: { + sz = screen->parser_buf_pos - pos; + const char *err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz); + if (err != NULL) { REPORT_ERROR("Failed to parse GraphicsCommand command payload with error: %s", err); return; } + pos = screen->parser_buf_pos; + } + break; + + + } // end switch + } // end while + + switch(state) { + case EQUAL: + REPORT_ERROR("Malformed GraphicsCommand control block, no = after key"); return; + case INT: + case UINT: + REPORT_ERROR("Malformed GraphicsCommand control block, expecting an integer value"); return; + case FLAG: + REPORT_ERROR("Malformed GraphicsCommand control block, expecting a flag value"); return; + default: + break; + } + + REPORT_VA_COMMAND("s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si sI} y#", "graphics_command", +"action", g.action, "delete_action", g.delete_action, "transmission_type", g.transmission_type, "compressed", g.compressed, + "format", (unsigned int)g.format, "more", (unsigned int)g.more, "id", (unsigned int)g.id, "width", (unsigned int)g.width, "height", (unsigned int)g.height, "x_offset", (unsigned int)g.x_offset, "y_offset", (unsigned int)g.y_offset, "data_height", (unsigned int)g.data_height, "data_width", (unsigned int)g.data_width, "data_sz", (unsigned int)g.data_sz, "data_offset", (unsigned int)g.data_offset, "num_cells", (unsigned int)g.num_cells, "num_lines", (unsigned int)g.num_lines, "cell_x_offset", (unsigned int)g.cell_x_offset, "cell_y_offset", (unsigned int)g.cell_y_offset, + "z_index", (int)g.z_index +, "payload_sz", g.payload_sz, payload, g.payload_sz +); + + screen_handle_graphics_command(screen, &g, payload); +} + diff --git a/kitty/parser.c b/kitty/parser.c index 9ff85f555..aa45f24e6 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -824,161 +824,7 @@ 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, AFTER_VALUE, PAYLOAD }; - enum GR_STATES state = KEY, value_state = FLAG; - enum KEYS { - action='a', - delete_action='d', - transmission_type='t', - compressed='o', - format = 'f', - more = 'm', - id = 'i', - width = 'w', - height = 'h', - x_offset = 'x', - y_offset = 'y', - data_height = 'v', - data_width = 's', - data_sz = 'S', - data_offset = 'O', - num_cells = 'c', - num_lines = 'r', - cell_x_offset = 'X', - cell_y_offset = 'Y', - z_index = 'z' - }; - enum KEYS key = 'a'; - static GraphicsCommand g; - unsigned int i, code; - uint64_t lcode; - bool is_negative; - memset(&g, 0, sizeof(g)); - static uint8_t payload[4096]; - size_t sz; - const char *err; - - while (pos < screen->parser_buf_pos) { - switch(state) { - - case KEY: - key = screen->parser_buf[pos++]; - switch(key) { -#define KS(n, vs) case n: state = EQUAL; value_state = vs; break -#define U(x) KS(x, UINT) - KS(action, FLAG); KS(delete_action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT); - U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset); -#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, found: 0x%x instead", screen->parser_buf[pos-1]); - return; - } - state = value_state; - break; - - case FLAG: - switch(key) { -#define F(a) case a: g.a = screen->parser_buf[pos++] & 0xff; break - F(action); F(delete_action); F(transmission_type); F(compressed); - default: - break; - } - state = AFTER_VALUE; - break; -#undef F - - 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; \ - } \ - if (i == pos) { REPORT_ERROR("Malformed graphics control block, expecting an integer value for key: %c", key & 0xFF); return; } \ - lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \ - if (lcode > UINT32_MAX) { REPORT_ERROR("id is too large"); return; } \ - code = lcode; - - 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; - } - state = AFTER_VALUE; - 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(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset); - default: break; - } - state = AFTER_VALUE; - break; -#undef U -#undef SET_ATTR -#undef READ_UINT - case AFTER_VALUE: - switch (screen->parser_buf[pos++]) { - case ',': - state = KEY; - break; - case ';': - state = PAYLOAD; - break; - default: - REPORT_ERROR("Malformed graphics control block, expecting a comma or semi-colon after a value, found: 0x%x", screen->parser_buf[pos - 1]); - return; - } - break; - - case PAYLOAD: - sz = screen->parser_buf_pos - pos; - err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz); - if (err != NULL) { REPORT_ERROR("Failed to parse graphics command payload with error: %s", err); return; } - pos = screen->parser_buf_pos; - break; - } - } - switch(state) { - case EQUAL: - REPORT_ERROR("Malformed graphics control block, no = after key"); return; - case INT: - case UINT: - REPORT_ERROR("Malformed graphics control block, expecting an integer value"); return; - case FLAG: - REPORT_ERROR("Malformed graphics control block, expecting a flag value"); return; - default: - break; - } -#define A(x) #x, g.x -#define U(x) #x, (unsigned int)(g.x) -#define I(x) #x, (int)(g.x) - REPORT_VA_COMMAND("s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command", - A(action), A(delete_action), A(transmission_type), A(compressed), - U(format), U(more), U(id), U(data_sz), U(data_offset), - U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), U(cell_x_offset), U(cell_y_offset), - U(payload_sz), I(z_index), - payload, g.payload_sz - ); -#undef U -#undef A -#undef I - screen_handle_graphics_command(screen, &g, payload); -} +#include "parse-graphics-command.h" static inline void dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index b64553e3b..aae5452c1 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -265,19 +265,19 @@ class TestParser(BaseTest): pb = partial(self.parse_bytes_dump, s) uint32_max = 2**32 - 1 t('i=%d' % uint32_max, id=uint32_max) - e('i=%d' % (uint32_max + 1), 'id is too large') + e('i=%d' % (uint32_max + 1), 'Malformed GraphicsCommand control block, number is too large') pb('\033_Gi=12\033\\', c(id=12)) t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9, payload_sz=1) t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9, payload_sz=7) t('a=t,t=d,s=100,z=9', action='t', transmission_type='d', data_width=100, z_index=9) - e(',s=1', 'Malformed graphics control block, invalid key character: 0x2c') - e('W=1', 'Malformed graphics control block, invalid key character: 0x57') - e('1=1', 'Malformed graphics control block, invalid key character: 0x31') - e('a=t,,w=2', 'Malformed graphics control block, invalid key character: 0x2c') - e('s', 'Malformed graphics control block, no = after key') - e('s=', 'Malformed graphics control block, expecting an integer value') - e('s==', 'Malformed graphics control block, expecting an integer value for key: s') - e('s=1=', 'Malformed graphics control block, expecting a comma or semi-colon after a value, found: 0x3d') + e(',s=1', 'Malformed GraphicsCommand control block, invalid key character: 0x2c') + e('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57') + e('1=1', 'Malformed GraphicsCommand control block, invalid key character: 0x31') + e('a=t,,w=2', 'Malformed GraphicsCommand control block, invalid key character: 0x2c') + e('s', 'Malformed GraphicsCommand control block, no = after key') + e('s=', 'Malformed GraphicsCommand control block, expecting an integer value') + e('s==', 'Malformed GraphicsCommand control block, expecting an integer value for key: s') + e('s=1=', 'Malformed GraphicsCommand control block, expecting a comma or semi-colon after a value, found: 0x3d') def test_deccara(self): s = self.create_screen()