Docs for legacy functional encoding
This commit is contained in:
parent
a30ea2b7f8
commit
2b12bcc07f
@ -23,6 +23,9 @@ issues in that proposal, namely:
|
||||
|
||||
* No way to disambiguate :kbd:`Esc` keypresses, other than using 8-bit controls
|
||||
which are undesirable for other reasons
|
||||
* Incorrectly claims special keys are sometimes encoded using ``CSI letter`` encodings when it
|
||||
is actually ``ESC O letter``.
|
||||
* Makes no mention of cursor key mode and how it changes encodings
|
||||
* Incorrectly encoding shifted keys when shift modifier is used
|
||||
* No way to have non-conflicting escape codes for :kbd:`alt+letter,
|
||||
ctrl+letter, ctrl+alt+letter` key presses
|
||||
@ -96,6 +99,8 @@ sub-field for the shifted key, like this::
|
||||
CSI unicode-key-code::base-layout-key
|
||||
|
||||
|
||||
.. _modifiers:
|
||||
|
||||
Modifiers
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@ -144,11 +149,13 @@ Non-Unicode keys
|
||||
There are many keys that don't correspond to letters from human languages, and
|
||||
thus aren't represented in Unicode. Think of functional keys, such as
|
||||
:kbd:`Escape, Play, Pause, F1, Home, etc`. These are encoded using Unicode code
|
||||
points from the Private Use Area (``0xe000 - 0xf8ff``). The mapping of key
|
||||
points from the Private Use Area (``57344 - 63743``). The mapping of key
|
||||
names to code points for these keys is in the
|
||||
:ref:`Functional key definition table below <functional>`.
|
||||
|
||||
|
||||
.. _progressive_enhancement:
|
||||
|
||||
Progressive enhancement
|
||||
--------------------------
|
||||
|
||||
@ -176,7 +183,7 @@ The value ``3`` means all set bits are reset, unset bits are left unchanged.
|
||||
"0b1 (1)", "Disambiguate escape codes"
|
||||
"0b10 (2)", "Report key event types"
|
||||
"0b100 (4)", "Report alternate keys"
|
||||
"0b1000 (8)", "Report all keys as CSIu escape codes"
|
||||
"0b1000 (8)", "Report all keys as ``CSI u`` escape codes"
|
||||
|
||||
The program running in the terminal can query the terminal for the
|
||||
current values of the flags by sending::
|
||||
@ -208,7 +215,7 @@ the :kbd:`Esc` key generates the byte ``0x1b`` which also is used to indicate
|
||||
the start of an escape code. Similarly pressing the key :kbd:`alt+[` will
|
||||
generate the bytes used for CSI control codes. Turning on this flag will cause
|
||||
the terminal to report the :kbd:`Esc, alt+letter, ctrl+letter, ctrl+alt+letter`
|
||||
keys using CSIu sequences instead of legacy ones. Here letter is any printable
|
||||
keys using ``CSI u`` sequences instead of legacy ones. Here letter is any printable
|
||||
ASCII letter (from 32 (i.e. space) to 126 (i.e. ~)).
|
||||
|
||||
Report event types
|
||||
@ -227,121 +234,206 @@ This type of progressive enhancement causes the terminal to report alternate
|
||||
key values in addition to the main value, to aid in shortcut matching. See
|
||||
:ref:`key_codes` for details on how these are reported.
|
||||
|
||||
Legacy key event encoding
|
||||
--------------------------------
|
||||
|
||||
In the default mode, the terminal uses a legacy encoding for key events. In
|
||||
this encoding, only key press and repeat events are sent and there is no
|
||||
way to distinguish between them. Text is sent directly as UTF-8 bytes.
|
||||
|
||||
Any key events not described in this section are sent using the standard
|
||||
``CSI u`` encoding. This includes keys that are not encodeable in the legacy
|
||||
encoding, thereby increasing the space of useable key combinations even without
|
||||
progressive enhancement.
|
||||
|
||||
Legacy functional keys
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These keys are encoded using three schemes::
|
||||
|
||||
CSI number ; modifier ~
|
||||
CSI 1 ; modifier {ABCDFHPQRS}
|
||||
ESC O {ABCDFHPQRS}
|
||||
|
||||
In the above, if there are no modifiers, the modifier parameter is omitted.
|
||||
The modifier value is encoded as described in the :ref:`modifiers` section,
|
||||
above. When the second form is used, the number is always ``1`` and must be
|
||||
omitted if the modifiers field is also absent. The third form becomes the
|
||||
second form when modifiers are present.
|
||||
|
||||
These sequences must match entries in the terminfo database for maximum
|
||||
compatibility. The table below lists the key, its terminfo entry name and
|
||||
the escape code used for it by kitty. A different terminal would use whatever
|
||||
escape code is present in its terminfo database for the key.
|
||||
Some keys have an alternate representation when the terminal is in *cursor key
|
||||
mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in
|
||||
*cursor key mode* and only when no modifiers are present.
|
||||
|
||||
.. csv-table:: Legacy functional encoding
|
||||
:header: "Name", "Terminfo name", "Escape code"
|
||||
|
||||
"INSERT", "kich1", "CSI 2 ~"
|
||||
"DELETE", "kdch1", "CSI 3 ~"
|
||||
"PAGE_UP", "kpp", "CSI 5 ~"
|
||||
"PAGE_DOWN", "knp", "CSI 6 ~"
|
||||
"UP", "cuu1,kcuu1", "CSI A, ESC O A"
|
||||
"DOWN", "cud1,kcud1", "CSI B, ESC O B"
|
||||
"RIGHT", "cuf1,kcuf1", "CSI C, ESC O C"
|
||||
"LEFT", "cub1,kcub1", "CSI D, ESC O D"
|
||||
"HOME", "home,khome", "CSI H, ESC O H"
|
||||
"END", "-,kend", "CSI F, ESC O F"
|
||||
"F1", "kf1", "ESC O P"
|
||||
"F2", "kf2", "ESC O Q"
|
||||
"F3", "kf3", "ESC O R"
|
||||
"F4", "kf4", "ESC O S"
|
||||
"F5", "kf5", "CSI 15 ~"
|
||||
"F6", "kf6", "CSI 17 ~"
|
||||
"F7", "kf7", "CSI 18 ~"
|
||||
"F8", "kf8", "CSI 19 ~"
|
||||
"F9", "kf9", "CSI 20 ~"
|
||||
"F10", "kf10", "CSI 21 ~"
|
||||
"F11", "kf11", "CSI 23 ~"
|
||||
"F12", "kf12", "CSI 24 ~"
|
||||
|
||||
Finally, there are a few more functional keys that have special cased legacy
|
||||
encodings:
|
||||
|
||||
.. csv-table:: C0 controls
|
||||
:header: "Key", "Encodings"
|
||||
|
||||
"Enter", "Plain - 0xd, alt+Enter - 0x1b 0x1d"
|
||||
"Escape", "Plain - 0x1b, alt+Esc - 0x1b 0x1b"
|
||||
"Backspace", "Plain - 0x7f, alt+Backspace - 0x1b 0x7f, ctrl+Backspace - 0x08"
|
||||
"Space", "Plain - 0x20, ctrl+space - 0x0, alt+space - 0x1b 0x20"
|
||||
"Tab", "Plain - 0x09, shift+tab - CSI Z"
|
||||
|
||||
Note that :kbd:`Backspace` and :kbd:`ctrl+backspace` are swapped in some
|
||||
terminals.
|
||||
|
||||
Legacy text keys
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
|
||||
.. _functional:
|
||||
|
||||
Functional key definitions
|
||||
----------------------------
|
||||
|
||||
All numbers are in the Unicode Private Use Area (``57344 - 63743``) except
|
||||
for a handful of keys that use numbers under 32 (C0 control codes) for legacy
|
||||
compatibility reasons.
|
||||
|
||||
.. {{{
|
||||
.. start functional key table (auto generated by gen-key-constants.py do not edit)
|
||||
|
||||
.. csv-table:: Functional key codes
|
||||
:header: "Name", "Codepoint (base-16)"
|
||||
:header: "Name", "CSI sequence"
|
||||
|
||||
"ESCAPE", "E000"
|
||||
"ENTER", "E001"
|
||||
"TAB", "E002"
|
||||
"BACKSPACE", "E003"
|
||||
"INSERT", "E004"
|
||||
"DELETE", "E005"
|
||||
"LEFT", "E006"
|
||||
"RIGHT", "E007"
|
||||
"UP", "E008"
|
||||
"DOWN", "E009"
|
||||
"PAGE_UP", "E00A"
|
||||
"PAGE_DOWN", "E00B"
|
||||
"HOME", "E00C"
|
||||
"END", "E00D"
|
||||
"CAPS_LOCK", "E00E"
|
||||
"SCROLL_LOCK", "E00F"
|
||||
"NUM_LOCK", "E010"
|
||||
"PRINT_SCREEN", "E011"
|
||||
"PAUSE", "E012"
|
||||
"MENU", "E013"
|
||||
"F1", "E014"
|
||||
"F2", "E015"
|
||||
"F3", "E016"
|
||||
"F4", "E017"
|
||||
"F5", "E018"
|
||||
"F6", "E019"
|
||||
"F7", "E01A"
|
||||
"F8", "E01B"
|
||||
"F9", "E01C"
|
||||
"F10", "E01D"
|
||||
"F11", "E01E"
|
||||
"F12", "E01F"
|
||||
"F13", "E020"
|
||||
"F14", "E021"
|
||||
"F15", "E022"
|
||||
"F16", "E023"
|
||||
"F17", "E024"
|
||||
"F18", "E025"
|
||||
"F19", "E026"
|
||||
"F20", "E027"
|
||||
"F21", "E028"
|
||||
"F22", "E029"
|
||||
"F23", "E02A"
|
||||
"F24", "E02B"
|
||||
"F25", "E02C"
|
||||
"F26", "E02D"
|
||||
"F27", "E02E"
|
||||
"F28", "E02F"
|
||||
"F29", "E030"
|
||||
"F30", "E031"
|
||||
"F31", "E032"
|
||||
"F32", "E033"
|
||||
"F33", "E034"
|
||||
"F34", "E035"
|
||||
"F35", "E036"
|
||||
"KP_0", "E037"
|
||||
"KP_1", "E038"
|
||||
"KP_2", "E039"
|
||||
"KP_3", "E03A"
|
||||
"KP_4", "E03B"
|
||||
"KP_5", "E03C"
|
||||
"KP_6", "E03D"
|
||||
"KP_7", "E03E"
|
||||
"KP_8", "E03F"
|
||||
"KP_9", "E040"
|
||||
"KP_DECIMAL", "E041"
|
||||
"KP_DIVIDE", "E042"
|
||||
"KP_MULTIPLY", "E043"
|
||||
"KP_SUBTRACT", "E044"
|
||||
"KP_ADD", "E045"
|
||||
"KP_ENTER", "E046"
|
||||
"KP_EQUAL", "E047"
|
||||
"KP_SEPARATOR", "E048"
|
||||
"KP_LEFT", "E049"
|
||||
"KP_RIGHT", "E04A"
|
||||
"KP_UP", "E04B"
|
||||
"KP_DOWN", "E04C"
|
||||
"KP_PAGE_UP", "E04D"
|
||||
"KP_PAGE_DOWN", "E04E"
|
||||
"KP_HOME", "E04F"
|
||||
"KP_END", "E050"
|
||||
"KP_INSERT", "E051"
|
||||
"KP_DELETE", "E052"
|
||||
"LEFT_SHIFT", "E053"
|
||||
"LEFT_CONTROL", "E054"
|
||||
"LEFT_ALT", "E055"
|
||||
"LEFT_SUPER", "E056"
|
||||
"RIGHT_SHIFT", "E057"
|
||||
"RIGHT_CONTROL", "E058"
|
||||
"RIGHT_ALT", "E059"
|
||||
"RIGHT_SUPER", "E05A"
|
||||
"MEDIA_PLAY", "E05B"
|
||||
"MEDIA_PAUSE", "E05C"
|
||||
"MEDIA_PLAY_PAUSE", "E05D"
|
||||
"MEDIA_REVERSE", "E05E"
|
||||
"MEDIA_STOP", "E05F"
|
||||
"MEDIA_FAST_FORWARD", "E060"
|
||||
"MEDIA_REWIND", "E061"
|
||||
"MEDIA_TRACK_NEXT", "E062"
|
||||
"MEDIA_TRACK_PREVIOUS", "E063"
|
||||
"MEDIA_RECORD", "E064"
|
||||
"LOWER_VOLUME", "E065"
|
||||
"RAISE_VOLUME", "E066"
|
||||
"MUTE_VOLUME", "E067"
|
||||
"ESCAPE", "CSI 57344 ... u"
|
||||
"ENTER", "CSI 57345 ... u"
|
||||
"TAB", "CSI 57346 ... u"
|
||||
"BACKSPACE", "CSI 57347 ... u"
|
||||
"INSERT", "CSI 2 ... ~"
|
||||
"DELETE", "CSI 3 ... ~"
|
||||
"LEFT", "CSI 1 ... D"
|
||||
"RIGHT", "CSI 1 ... C"
|
||||
"UP", "CSI 1 ... A"
|
||||
"DOWN", "CSI 1 ... B"
|
||||
"PAGE_UP", "CSI 5 ... ~"
|
||||
"PAGE_DOWN", "CSI 6 ... ~"
|
||||
"HOME", "CSI 1 ... H or CSI 7 ... ~"
|
||||
"END", "CSI 1 ... F or CSI 8 ... ~"
|
||||
"CAPS_LOCK", "CSI 57358 ... u"
|
||||
"SCROLL_LOCK", "CSI 57359 ... u"
|
||||
"NUM_LOCK", "CSI 57360 ... u"
|
||||
"PRINT_SCREEN", "CSI 57361 ... u"
|
||||
"PAUSE", "CSI 57362 ... u"
|
||||
"MENU", "CSI 57363 ... u"
|
||||
"F1", "CSI 1 ... P or CSI 11 ... ~"
|
||||
"F2", "CSI 1 ... Q or CSI 12 ... ~"
|
||||
"F3", "CSI 1 ... R or CSI 13 ... ~"
|
||||
"F4", "CSI 1 ... S or CSI 14 ... ~"
|
||||
"F5", "CSI 15 ... ~"
|
||||
"F6", "CSI 17 ... ~"
|
||||
"F7", "CSI 18 ... ~"
|
||||
"F8", "CSI 19 ... ~"
|
||||
"F9", "CSI 20 ... ~"
|
||||
"F10", "CSI 21 ... ~"
|
||||
"F11", "CSI 23 ... ~"
|
||||
"F12", "CSI 24 ... ~"
|
||||
"F13", "CSI 57376 ... u"
|
||||
"F14", "CSI 57377 ... u"
|
||||
"F15", "CSI 57378 ... u"
|
||||
"F16", "CSI 57379 ... u"
|
||||
"F17", "CSI 57380 ... u"
|
||||
"F18", "CSI 57381 ... u"
|
||||
"F19", "CSI 57382 ... u"
|
||||
"F20", "CSI 57383 ... u"
|
||||
"F21", "CSI 57384 ... u"
|
||||
"F22", "CSI 57385 ... u"
|
||||
"F23", "CSI 57386 ... u"
|
||||
"F24", "CSI 57387 ... u"
|
||||
"F25", "CSI 57388 ... u"
|
||||
"F26", "CSI 57389 ... u"
|
||||
"F27", "CSI 57390 ... u"
|
||||
"F28", "CSI 57391 ... u"
|
||||
"F29", "CSI 57392 ... u"
|
||||
"F30", "CSI 57393 ... u"
|
||||
"F31", "CSI 57394 ... u"
|
||||
"F32", "CSI 57395 ... u"
|
||||
"F33", "CSI 57396 ... u"
|
||||
"F34", "CSI 57397 ... u"
|
||||
"F35", "CSI 57398 ... u"
|
||||
"KP_0", "CSI 57399 ... u"
|
||||
"KP_1", "CSI 57400 ... u"
|
||||
"KP_2", "CSI 57401 ... u"
|
||||
"KP_3", "CSI 57402 ... u"
|
||||
"KP_4", "CSI 57403 ... u"
|
||||
"KP_5", "CSI 57404 ... u"
|
||||
"KP_6", "CSI 57405 ... u"
|
||||
"KP_7", "CSI 57406 ... u"
|
||||
"KP_8", "CSI 57407 ... u"
|
||||
"KP_9", "CSI 57408 ... u"
|
||||
"KP_DECIMAL", "CSI 57409 ... u"
|
||||
"KP_DIVIDE", "CSI 57410 ... u"
|
||||
"KP_MULTIPLY", "CSI 57411 ... u"
|
||||
"KP_SUBTRACT", "CSI 57412 ... u"
|
||||
"KP_ADD", "CSI 57413 ... u"
|
||||
"KP_ENTER", "CSI 57414 ... u"
|
||||
"KP_EQUAL", "CSI 57415 ... u"
|
||||
"KP_SEPARATOR", "CSI 57416 ... u"
|
||||
"KP_LEFT", "CSI 57417 ... u"
|
||||
"KP_RIGHT", "CSI 57418 ... u"
|
||||
"KP_UP", "CSI 57419 ... u"
|
||||
"KP_DOWN", "CSI 57420 ... u"
|
||||
"KP_PAGE_UP", "CSI 57421 ... u"
|
||||
"KP_PAGE_DOWN", "CSI 57422 ... u"
|
||||
"KP_HOME", "CSI 57423 ... u"
|
||||
"KP_END", "CSI 57424 ... u"
|
||||
"KP_INSERT", "CSI 57425 ... u"
|
||||
"KP_DELETE", "CSI 57426 ... u"
|
||||
"LEFT_SHIFT", "CSI 57427 ... u"
|
||||
"LEFT_CONTROL", "CSI 57428 ... u"
|
||||
"LEFT_ALT", "CSI 57429 ... u"
|
||||
"LEFT_SUPER", "CSI 57430 ... u"
|
||||
"RIGHT_SHIFT", "CSI 57431 ... u"
|
||||
"RIGHT_CONTROL", "CSI 57432 ... u"
|
||||
"RIGHT_ALT", "CSI 57433 ... u"
|
||||
"RIGHT_SUPER", "CSI 57434 ... u"
|
||||
"MEDIA_PLAY", "CSI 57435 ... u"
|
||||
"MEDIA_PAUSE", "CSI 57436 ... u"
|
||||
"MEDIA_PLAY_PAUSE", "CSI 57437 ... u"
|
||||
"MEDIA_REVERSE", "CSI 57438 ... u"
|
||||
"MEDIA_STOP", "CSI 57439 ... u"
|
||||
"MEDIA_FAST_FORWARD", "CSI 57440 ... u"
|
||||
"MEDIA_REWIND", "CSI 57441 ... u"
|
||||
"MEDIA_TRACK_NEXT", "CSI 57442 ... u"
|
||||
"MEDIA_TRACK_PREVIOUS", "CSI 57443 ... u"
|
||||
"MEDIA_RECORD", "CSI 57444 ... u"
|
||||
"LOWER_VOLUME", "CSI 57445 ... u"
|
||||
"RAISE_VOLUME", "CSI 57446 ... u"
|
||||
"MUTE_VOLUME", "CSI 57447 ... u"
|
||||
|
||||
.. end functional key table
|
||||
.. }}}
|
||||
|
||||
@ -113,6 +113,16 @@ raise_volume XF86AudioRaiseVolume -
|
||||
mute_volume XF86AudioMute -
|
||||
''' # }}}
|
||||
|
||||
functional_encoding_overrides = {
|
||||
'insert': 2, 'delete': 3, 'page_up': 5, 'page_down': 6,
|
||||
'home': 7, 'end': 8, 'f1': 11, 'f2': 12, 'f3': 13, 'f4': 14,
|
||||
'f5': 15, 'f6': 17, 'f7': 18, 'f8': 19, 'f9': 20, 'f10': 21,
|
||||
'f11': 23, 'f12': 24
|
||||
}
|
||||
different_trailer_functionals = {
|
||||
'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'end': 'F', 'home': 'H',
|
||||
'f1': 'P', 'f2': 'Q', 'f3': 'R', 'f4': 'S'
|
||||
}
|
||||
functional_key_names: List[str] = []
|
||||
name_to_code: Dict[str, int] = {}
|
||||
name_to_xkb: Dict[str, str] = {}
|
||||
@ -175,11 +185,20 @@ def generate_functional_table() -> None:
|
||||
lines = [
|
||||
'',
|
||||
'.. csv-table:: Functional key codes',
|
||||
' :header: "Name", "Codepoint (base-16)"',
|
||||
' :header: "Name", "CSI sequence"',
|
||||
''
|
||||
]
|
||||
for name, code in name_to_code.items():
|
||||
lines.append(f' "{name.upper()}", "{code:X}"')
|
||||
if name in functional_encoding_overrides or name in different_trailer_functionals:
|
||||
code = oc = functional_encoding_overrides.get(name, code)
|
||||
trailer = different_trailer_functionals.get(name, '~')
|
||||
code = code if trailer == '~' else 1
|
||||
if code == 1 and name not in ('up', 'down', 'left', 'right'):
|
||||
trailer += f' or CSI {oc} ... ~'
|
||||
else:
|
||||
trailer = 'u'
|
||||
name = f'"{name.upper()}",'.ljust(25)
|
||||
lines.append(f' {name} "CSI {code} ... {trailer}"')
|
||||
lines.append('')
|
||||
patch_file('docs/keyboard-protocol.rst', 'functional key table', '\n'.join(lines), start_marker='.. ', end_marker='')
|
||||
|
||||
|
||||
@ -61,8 +61,9 @@ static inline int
|
||||
serialize(const EncodingData *data, char *output, const char csi_trailer) {
|
||||
int pos = 0;
|
||||
#define P(fmt, ...) pos += snprintf(output + pos, KEY_BUFFER_SIZE - 2 - pos, fmt, __VA_ARGS__)
|
||||
P("\x1b[%u", data->key);
|
||||
if (data->add_alternates && (data->shifted_key || data->alternate_key)) {
|
||||
P("\x1b%s", "[");
|
||||
if (data->key != 1 || data->add_alternates || data->has_mods || data->add_actions) P("%u", data->key);
|
||||
if (data->add_alternates) {
|
||||
P("%s", ":");
|
||||
if (data->shifted_key) P("%u", data->shifted_key);
|
||||
if (data->alternate_key) P(":%u", data->alternate_key);
|
||||
@ -92,10 +93,14 @@ encode_function_key(const KeyEvent *ev, char *output) {
|
||||
switch(key_number) {
|
||||
case GLFW_FKEY_UP: SIMPLE("\x1bOA");
|
||||
case GLFW_FKEY_DOWN: SIMPLE("\x1bOB");
|
||||
case GLFW_FKEY_LEFT: SIMPLE("\x1bOD");
|
||||
case GLFW_FKEY_RIGHT: SIMPLE("\x1bOC");
|
||||
case GLFW_FKEY_HOME: SIMPLE("\x1bOH");
|
||||
case GLFW_FKEY_LEFT: SIMPLE("\x1bOD");
|
||||
case GLFW_FKEY_END: SIMPLE("\x1bOF");
|
||||
case GLFW_FKEY_HOME: SIMPLE("\x1bOH");
|
||||
case GLFW_FKEY_F1: SIMPLE("\x1bOP");
|
||||
case GLFW_FKEY_F2: SIMPLE("\x1bOQ");
|
||||
case GLFW_FKEY_F3: SIMPLE("\x1bOR");
|
||||
case GLFW_FKEY_F4: SIMPLE("\x1bOS");
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@ -103,7 +108,7 @@ encode_function_key(const KeyEvent *ev, char *output) {
|
||||
switch(key_number) {
|
||||
case GLFW_FKEY_ENTER: SIMPLE("\r");
|
||||
case GLFW_FKEY_ESCAPE: {
|
||||
if (ev->disambiguate) { return encode_csi_string('u', "27u", output); }
|
||||
if (ev->disambiguate) { return encode_csi_string('u', "27", output); }
|
||||
SIMPLE("\x1b");
|
||||
}
|
||||
case GLFW_FKEY_BACKSPACE: SIMPLE("\x7f");
|
||||
@ -154,6 +159,7 @@ encode_function_key(const KeyEvent *ev, char *output) {
|
||||
#undef S
|
||||
EncodingData ed = {0};
|
||||
init_encoding_data(&ed, ev);
|
||||
ed.key = key_number;
|
||||
ed.add_alternates = false;
|
||||
return serialize(&ed, output, csi_trailer);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user