Add a new graphics protocol key, N=1, to request that transmitted image/frame data is kept only in memory and not written to the graphics disk cache file. This is useful for transient high-frequency updates such as video-like streams, where the latest frame is the only useful data and persisting each frame to the disk cache causes unnecessary write traffic. The implementation keeps the existing graphics cache abstraction intact: memory-only entries can still be read back by animation, composition, and frame coalescing paths. Only persistence to the disk cache file is skipped. The default behavior is unchanged when N is omitted or set to zero.
359 lines
12 KiB
Python
Executable file
359 lines
12 KiB
Python
Executable file
#!/usr/bin/env python
|
|
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from collections import defaultdict
|
|
from typing import Any, DefaultDict, Union
|
|
|
|
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__))))
|
|
|
|
KeymapType = dict[str, tuple[str, Union[frozenset[str], str]]]
|
|
|
|
|
|
def resolve_keys(keymap: KeymapType) -> DefaultDict[str, list[str]]:
|
|
ans: DefaultDict[str, list[str]] = defaultdict(list)
|
|
for ch, (attr, atype) in keymap.items():
|
|
if isinstance(atype, str) and atype in ('int', 'uint'):
|
|
q = atype
|
|
else:
|
|
q = 'flag'
|
|
ans[q].append(ch)
|
|
return ans
|
|
|
|
|
|
def enum(keymap: KeymapType) -> str:
|
|
lines = []
|
|
for ch, (attr, atype) in keymap.items():
|
|
lines.append(f"{attr}='{ch}'")
|
|
return '''
|
|
enum KEYS {{
|
|
{}
|
|
}};
|
|
'''.format(',\n'.join(lines))
|
|
|
|
|
|
def parse_key(keymap: KeymapType) -> str:
|
|
lines = []
|
|
for attr, atype in keymap.values():
|
|
vs = atype.upper() if isinstance(atype, str) and atype in ('uint', 'int') else 'FLAG'
|
|
lines.append(f'case {attr}: value_state = {vs}; break;')
|
|
return ' \n'.join(lines)
|
|
|
|
|
|
def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str) -> str:
|
|
lines = []
|
|
for ch in type_map['flag']:
|
|
attr, allowed_values = keymap[ch]
|
|
q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values))
|
|
lines.append(f'''
|
|
case {attr}: {{
|
|
g.{attr} = parser_buf[pos++];
|
|
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: KeymapType) -> tuple[str, str]:
|
|
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: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool, payload_is_base64: bool) -> str:
|
|
def group(atype: str, conv: str) -> tuple[str, str]:
|
|
flag_fmt, flag_attrs = [], []
|
|
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
|
for ch in type_map[atype]:
|
|
flag_fmt.append(f'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("K s {{{fmt} ss#}}", self->window_id, "{report_name}",\n']
|
|
else:
|
|
ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}",\n']
|
|
if flag_attrs:
|
|
ans.append(f'{flag_attrs},\n')
|
|
if uint_attrs:
|
|
ans.append(f'{uint_attrs},\n')
|
|
if int_attrs:
|
|
ans.append(f'{int_attrs},\n')
|
|
if payload_allowed:
|
|
if payload_is_base64:
|
|
ans.append('"", (char*)parser_buf, g.payload_sz')
|
|
else:
|
|
ans.append('"", (char*)parser_buf + payload_start, g.payload_sz')
|
|
ans.append(');')
|
|
return '\n'.join(ans)
|
|
|
|
|
|
def generate(
|
|
function_name: str,
|
|
callback_name: str,
|
|
report_name: str,
|
|
keymap: KeymapType,
|
|
command_class: str,
|
|
initial_key: str = 'a',
|
|
payload_allowed: bool = True,
|
|
payload_is_base64: bool = True,
|
|
start_parsing_at: int = 1,
|
|
field_sep: str = ',',
|
|
post_init: str = '',
|
|
) -> str:
|
|
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, payload_is_base64)
|
|
post_init_line = f'\n {post_init}' if post_init else ''
|
|
extra_init = ''
|
|
if payload_allowed:
|
|
payload_after_value = "case ';': state = PAYLOAD; break;"
|
|
payload = ', PAYLOAD'
|
|
if payload_is_base64:
|
|
payload_case = f'''
|
|
case PAYLOAD: {{
|
|
sz = parser_buf_pos - pos;
|
|
g.payload_sz = MAX(BUF_EXTRA, sz);
|
|
if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {{
|
|
g.payload_sz = MAX(BUF_EXTRA, sz);
|
|
REPORT_ERROR("Failed to parse {command_class} command payload with error: \
|
|
invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }}
|
|
pos = parser_buf_pos;
|
|
}} break;
|
|
'''
|
|
callback = f'{callback_name}(self->screen, &g, parser_buf)'
|
|
else:
|
|
payload_case = '''
|
|
case PAYLOAD: {
|
|
sz = parser_buf_pos - pos;
|
|
payload_start = pos;
|
|
g.payload_sz = sz;
|
|
pos = parser_buf_pos;
|
|
} break;
|
|
'''
|
|
extra_init = 'size_t payload_start = 0;'
|
|
callback = f'{callback_name}(self->screen, &g, parser_buf + payload_start)'
|
|
|
|
else:
|
|
payload_after_value = payload = payload_case = ''
|
|
callback = f'{callback_name}(self->screen, &g)'
|
|
|
|
return f'''
|
|
#include "base64.h"
|
|
|
|
static inline void
|
|
{function_name}(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) {{
|
|
unsigned int pos = {start_parsing_at};
|
|
{extra_init}
|
|
enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
|
|
enum PARSER_STATES state = KEY, value_state = FLAG;
|
|
{command_class} g = {{0}};{post_init_line}
|
|
unsigned int i, code;
|
|
uint64_t lcode; int64_t accumulator;
|
|
bool is_negative; (void)is_negative;
|
|
size_t sz;
|
|
{keys_enum}
|
|
enum KEYS key = '{initial_key}';
|
|
if (parser_buf[pos] == ';') state = AFTER_VALUE;
|
|
|
|
while (pos < parser_buf_pos) {{
|
|
switch(state) {{
|
|
case KEY:
|
|
key = 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 (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;
|
|
break;
|
|
|
|
case FLAG:
|
|
switch(key) {{
|
|
{flag_keys}
|
|
default:
|
|
break;
|
|
}}
|
|
state = AFTER_VALUE;
|
|
break;
|
|
|
|
case INT:
|
|
#define READ_UINT \\
|
|
for (i = pos, accumulator=0; i < MIN(parser_buf_pos, pos + 10); i++) {{ \\
|
|
int64_t n = parser_buf[i] - '0'; if (n < 0 || n > 9) break; \\
|
|
accumulator += n * digit_multipliers[i - pos]; \\
|
|
}} \\
|
|
if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\
|
|
lcode = accumulator / digit_multipliers[i - pos - 1]; pos = i; \\
|
|
if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\
|
|
code = lcode;
|
|
|
|
is_negative = false;
|
|
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) {{
|
|
{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 (parser_buf[pos++]) {{
|
|
default:
|
|
REPORT_ERROR("Malformed {command_class} control block, expecting a {field_sep} or semi-colon after a value, found: 0x%x",
|
|
parser_buf[pos - 1]);
|
|
return;
|
|
case '{field_sep}':
|
|
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 write_header(text: str, path: str) -> None:
|
|
with open(path, 'w') as f:
|
|
print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n')
|
|
print('#pragma once', file=f)
|
|
print(text, file=f)
|
|
subprocess.check_call(['clang-format', '-i', path])
|
|
|
|
|
|
def parsers() -> None:
|
|
flag = frozenset
|
|
keymap: KeymapType = {
|
|
'a': ('action', flag('tTqpdfac')),
|
|
'd': ('delete_action', flag('aAiIcCfFnNpPqQrRxXyYzZ')),
|
|
't': ('transmission_type', flag('dfts')),
|
|
'o': ('compressed', flag('z')),
|
|
'f': ('format', 'uint'),
|
|
'm': ('more', 'uint'),
|
|
'i': ('id', 'uint'),
|
|
'I': ('image_number', 'uint'),
|
|
'p': ('placement_id', 'uint'),
|
|
'q': ('quiet', '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'),
|
|
'C': ('cursor_movement', 'uint'),
|
|
'U': ('unicode_placement', 'uint'),
|
|
'P': ('parent_id', 'uint'),
|
|
'Q': ('parent_placement_id', 'uint'),
|
|
'N': ('no_disk_cache', 'uint'),
|
|
'H': ('offset_from_parent_x', 'int'),
|
|
'V': ('offset_from_parent_y', 'int'),
|
|
}
|
|
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
|
|
write_header(text, 'kitty/parse-graphics-command.h')
|
|
|
|
keymap = {
|
|
'w': ('width', 'uint'),
|
|
's': ('scale', 'uint'),
|
|
'n': ('subscale_n', 'uint'),
|
|
'd': ('subscale_d', 'uint'),
|
|
'v': ('vertical_align', 'uint'),
|
|
'h': ('horizontal_align', 'uint'),
|
|
}
|
|
text = generate(
|
|
'parse_multicell_code', 'screen_handle_multicell_command', 'multicell_command', keymap, 'MultiCellCommand',
|
|
payload_is_base64=False, start_parsing_at=0, field_sep=':')
|
|
write_header(text, 'kitty/parse-multicell-command.h')
|
|
|
|
keymap = {
|
|
't': ('type', flag('aAmMrRopPqeEk')),
|
|
'm': ('more', 'uint'),
|
|
'i': ('client_id', 'uint'),
|
|
'o': ('operation', 'uint'),
|
|
'x': ('cell_x', 'int'),
|
|
'y': ('cell_y', 'int'),
|
|
'X': ('pixel_x', 'int'),
|
|
'Y': ('pixel_y', 'int'),
|
|
}
|
|
text = generate(
|
|
'parse_dnd_code', 'screen_handle_dnd_command', 'dnd_command', keymap, 'DnDCommand',
|
|
payload_is_base64=False, start_parsing_at=0, field_sep=':')
|
|
write_header(text, 'kitty/parse-dnd-command.h')
|
|
|
|
|
|
def main(args: list[str]=sys.argv) -> None:
|
|
parsers()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import runpy
|
|
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
|
m['main']([sys.executable, 'apc-parsers'])
|