2023-11-11 06:02:05 +03:00
|
|
|
#!/usr/bin/env python
|
2018-07-19 16:13:26 +03:00
|
|
|
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
|
2022-01-29 15:14:56 +03:00
|
|
|
import os
|
2018-07-19 16:26:10 +03:00
|
|
|
import subprocess
|
2023-10-14 05:14:18 +03:00
|
|
|
import sys
|
2018-07-19 16:13:26 +03:00
|
|
|
from collections import defaultdict
|
2020-03-14 12:07:11 +03:00
|
|
|
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
|
2018-07-19 16:13:26 +03:00
|
|
|
|
2023-10-15 07:21:03 +03:00
|
|
|
if __name__ == '__main__' and not __package__:
|
|
|
|
import __main__
|
|
|
|
__main__.__package__ = 'gen'
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
|
2020-03-05 18:08:51 +03:00
|
|
|
KeymapType = Dict[str, Tuple[str, Union[FrozenSet[str], str]]]
|
2018-07-19 16:13:26 +03:00
|
|
|
|
2020-03-05 18:08:51 +03:00
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def resolve_keys(keymap: KeymapType) -> DefaultDict[str, List[str]]:
|
2020-03-05 18:08:51 +03:00
|
|
|
ans: DefaultDict[str, List[str]] = defaultdict(list)
|
2018-07-19 16:13:26 +03:00
|
|
|
for ch, (attr, atype) in keymap.items():
|
2020-03-05 18:08:51 +03:00
|
|
|
if isinstance(atype, str) and atype in ('int', 'uint'):
|
|
|
|
q = atype
|
|
|
|
else:
|
|
|
|
q = 'flag'
|
|
|
|
ans[q].append(ch)
|
2018-07-19 16:13:26 +03:00
|
|
|
return ans
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def enum(keymap: KeymapType) -> str:
|
2018-07-19 16:13:26 +03:00
|
|
|
lines = []
|
|
|
|
for ch, (attr, atype) in keymap.items():
|
|
|
|
lines.append(f"{attr}='{ch}'")
|
|
|
|
return '''
|
|
|
|
enum KEYS {{
|
|
|
|
{}
|
|
|
|
}};
|
|
|
|
'''.format(',\n'.join(lines))
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def parse_key(keymap: KeymapType) -> str:
|
2018-07-19 16:13:26 +03:00
|
|
|
lines = []
|
|
|
|
for attr, atype in keymap.values():
|
2020-03-05 18:08:51 +03:00
|
|
|
vs = atype.upper() if isinstance(atype, str) and atype in ('uint', 'int') else 'FLAG'
|
2018-07-19 16:13:26 +03:00
|
|
|
lines.append(f'case {attr}: value_state = {vs}; break;')
|
|
|
|
return ' \n'.join(lines)
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str) -> str:
|
2018-07-19 16:13:26 +03:00
|
|
|
lines = []
|
|
|
|
for ch in type_map['flag']:
|
|
|
|
attr, allowed_values = keymap[ch]
|
2022-01-29 15:14:56 +03:00
|
|
|
q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values))
|
2018-07-19 16:13:26 +03:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def parse_number(keymap: KeymapType) -> Tuple[str, str]:
|
2018-07-19 16:13:26 +03:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def cmd_for_report(report_name: str, keymap: KeymapType, type_map: Dict[str, Any], payload_allowed: bool) -> str:
|
|
|
|
def group(atype: str, conv: str) -> Tuple[str, str]:
|
2018-07-19 16:13:26 +03:00
|
|
|
flag_fmt, flag_attrs = [], []
|
|
|
|
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
|
|
|
for ch in type_map[atype]:
|
2022-01-29 15:14:36 +03:00
|
|
|
flag_fmt.append(f's{cv}')
|
2018-07-19 16:13:26 +03:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def generate(
|
|
|
|
function_name: str,
|
|
|
|
callback_name: str,
|
|
|
|
report_name: str,
|
|
|
|
keymap: KeymapType,
|
|
|
|
command_class: str,
|
|
|
|
initial_key: str = 'a',
|
|
|
|
payload_allowed: bool = True
|
|
|
|
) -> str:
|
2018-07-19 16:13:26 +03:00
|
|
|
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;"
|
2019-06-05 18:08:43 +03:00
|
|
|
payload = ', PAYLOAD'
|
2018-07-19 16:13:26 +03:00
|
|
|
parr = 'static uint8_t payload[4096];'
|
|
|
|
payload_case = f'''
|
|
|
|
case PAYLOAD: {{
|
|
|
|
sz = screen->parser_buf_pos - pos;
|
2023-06-28 18:22:35 +03:00
|
|
|
g.payload_sz = sizeof(payload);
|
|
|
|
if (!base64_decode32(screen->parser_buf + pos, sz, payload, &g.payload_sz)) {{
|
2023-09-18 19:39:46 +03:00
|
|
|
REPORT_ERROR("Failed to parse {command_class} command payload with error: payload size (%zu) too large", sz); return; }}
|
2018-07-19 16:13:26 +03:00
|
|
|
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}';
|
2020-04-19 05:43:33 +03:00
|
|
|
if (screen->parser_buf[pos] == ';') state = AFTER_VALUE;
|
2018-07-19 16:13:26 +03:00
|
|
|
|
|
|
|
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};
|
|
|
|
}}
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def write_header(text: str, path: str) -> None:
|
2018-07-19 16:26:10 +03:00
|
|
|
with open(path, 'w') as f:
|
2022-01-29 15:14:56 +03:00
|
|
|
print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n')
|
2018-07-19 16:26:10 +03:00
|
|
|
print('#pragma once', file=f)
|
|
|
|
print(text, file=f)
|
|
|
|
subprocess.check_call(['clang-format', '-i', path])
|
|
|
|
|
|
|
|
|
2020-03-14 12:07:11 +03:00
|
|
|
def graphics_parser() -> None:
|
2018-07-19 16:13:26 +03:00
|
|
|
flag = frozenset
|
2020-03-05 18:08:51 +03:00
|
|
|
keymap: KeymapType = {
|
2021-07-22 16:28:59 +03:00
|
|
|
'a': ('action', flag('tTqpdfac')),
|
2021-01-27 20:10:43 +03:00
|
|
|
'd': ('delete_action', flag('aAiIcCfFnNpPqQxXyYzZ')),
|
2018-07-19 16:13:26 +03:00
|
|
|
't': ('transmission_type', flag('dfts')),
|
|
|
|
'o': ('compressed', flag('z')),
|
|
|
|
'f': ('format', 'uint'),
|
|
|
|
'm': ('more', 'uint'),
|
|
|
|
'i': ('id', 'uint'),
|
2020-12-16 15:01:15 +03:00
|
|
|
'I': ('image_number', 'uint'),
|
2020-12-02 02:53:08 +03:00
|
|
|
'p': ('placement_id', 'uint'),
|
2020-12-03 18:12:03 +03:00
|
|
|
'q': ('quiet', 'uint'),
|
2018-07-19 16:13:26 +03:00
|
|
|
'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'),
|
2021-03-22 19:46:40 +03:00
|
|
|
'C': ('cursor_movement', 'uint'),
|
2022-10-30 05:48:06 +03:00
|
|
|
'U': ('unicode_placement', 'uint'),
|
2023-10-27 12:57:30 +03:00
|
|
|
'P': ('parent_id', 'uint'),
|
|
|
|
'Q': ('parent_placement_id', 'uint'),
|
|
|
|
'H': ('offset_from_parent_x', 'int'),
|
|
|
|
'V': ('offset_from_parent_y', 'int'),
|
2018-07-19 16:13:26 +03:00
|
|
|
}
|
|
|
|
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
|
2018-07-19 16:26:10 +03:00
|
|
|
write_header(text, 'kitty/parse-graphics-command.h')
|
2018-07-19 16:13:26 +03:00
|
|
|
|
|
|
|
|
2023-10-14 05:22:02 +03:00
|
|
|
def main(args: List[str]=sys.argv) -> None:
|
2023-10-14 05:14:18 +03:00
|
|
|
graphics_parser()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
import runpy
|
|
|
|
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
m['main']([sys.executable, 'apc-parsers'])
|