Implement progressive enhancement of key event reporting

This commit is contained in:
Kovid Goyal 2021-01-11 17:21:07 +05:30
parent 295e8db04c
commit a30ea2b7f8
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
8 changed files with 227 additions and 93 deletions

View File

@ -21,11 +21,11 @@ advanced usages. The protocol is based on initial work in `fixterms
<http://www.leonerd.org.uk/hacks/fixterms/>`_, however, it corrects various <http://www.leonerd.org.uk/hacks/fixterms/>`_, however, it corrects various
issues in that proposal, namely: issues in that proposal, namely:
* No way to disambiguate Esc keypresses, other than using 8-bit controls * No way to disambiguate :kbd:`Esc` keypresses, other than using 8-bit controls
which are undesirable for other reasons which are undesirable for other reasons
* Incorrectly encoding shifted keys when shift modifier is used * Incorrectly encoding shifted keys when shift modifier is used
* No way to not have :kbd:`Alt+letter` key presses generate escape codes that * No way to have non-conflicting escape codes for :kbd:`alt+letter,
conflict with other escape codes ctrl+letter, ctrl+alt+letter` key presses
* No way to specify both shifted and unshifted keys for robust shortcut * No way to specify both shifted and unshifted keys for robust shortcut
matching (think matching :kbd:`ctrl+shift+equal` and :kbd:`ctrl+plus`) matching (think matching :kbd:`ctrl+shift+equal` and :kbd:`ctrl+plus`)
* No way to specify alternate layout key. This is useful for keyboard layouts * No way to specify alternate layout key. This is useful for keyboard layouts
@ -62,6 +62,8 @@ are separated by the semi-colon and sub-fields by the colon. Only the
escape code is terminated by the ``u`` character (the byte ``0x75``). escape code is terminated by the ``u`` character (the byte ``0x75``).
.. _key_codes:
Key codes Key codes
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -113,6 +115,8 @@ and so on. If the modifier field is not present in the escape code, its default
value is ``1`` which means no modifiers. value is ``1`` which means no modifiers.
.. _event_types:
Event types Event types
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -130,8 +134,8 @@ has value ``1`` and is the default if no event type sub field is present. The
.. note:: Key events that result in text are reported as plain UTF-8 text, so .. note:: Key events that result in text are reported as plain UTF-8 text, so
events are not supported for them, unless the application requests key events are not supported for them, unless the application requests *key
report mode, see below. report mode*, see below.
Non-Unicode keys Non-Unicode keys
@ -145,11 +149,90 @@ names to code points for these keys is in the
:ref:`Functional key definition table below <functional>`. :ref:`Functional key definition table below <functional>`.
Progressive enhancement
--------------------------
While, in theory, every key event could be completely represented by this
protocol and all would be hunk-dory, in reality there is a vast universe of
existing terminal programs that expect legacy control codes for key events and
that are not likely to ever be updated. To support these, in default mode,
the terminal will emit legacy escape codes for compatibility. If a terminal
program wants more robust key handling, it can request it from the terminal,
via the mechanism described here. Each enhancement is described in detail
below. The escape code for requesting enhancements is::
CSI = flags ; mode u
Here ``flags`` is a decimal encoded integer to specify a set of bit-flags. The
meanings of the flags are given below. The second, ``mode`` parameter is
optional (defaulting to ``1``) and specifies how the flags are applied.
The value ``1`` means all set bits are set and all unset bits are reset.
The value ``2`` means all set bits are set, unset bits are left unchanged.
The value ``3`` means all set bits are reset, unset bits are left unchanged.
.. csv-table:: The progressive enhancement flags
:header: "Bit", "Meaning"
"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"
The program running in the terminal can query the terminal for the
current values of the flags by sending::
CSI ? u
The terminal will reply with::
CSI ? flags u
The program can also push/pop the current flags onto a stack in the
terminal with::
CSI > flags u # for push, if flags ommitted default to zero
CSI < number u # to pop number entries, defaulting to 1 if unspecified
Terminals should limit the size of the stack as appropriate, to prevent
Denial-of-Service attacks. Terminals must maintain separate stacks for the main
and alternate screens. If a pop request is received that empties the stack,
all flags are reset. If a push request is received and the stack is full, the
oldest entry from the stack must be evicted.
Disambiguate escape codes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This type of progressive enhancement fixes the problem of some legacy key
press encodings overlapping with other control codes. For instance, pressing
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
ASCII letter (from 32 (i.e. space) to 126 (i.e. ~)).
Report event types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This type of progressive enhancement causes the terminal to report key repeat
and key release events. Normally only key press events are reported and key
repeat events are treated as key press events. See :ref:`event_types` for
details on how these are reported.
Report alternate keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
.. _functional: .. _functional:
Functional key definitions Functional key definitions
---------------------------- ----------------------------
.. {{{
.. start functional key table (auto generated by gen-key-constants.py do not edit) .. start functional key table (auto generated by gen-key-constants.py do not edit)
.. csv-table:: Functional key codes .. csv-table:: Functional key codes
@ -261,3 +344,4 @@ Functional key definitions
"MUTE_VOLUME", "E067" "MUTE_VOLUME", "E067"
.. end functional key table .. end functional key table
.. }}}

View File

@ -160,8 +160,10 @@ encode_function_key(const KeyEvent *ev, char *output) {
static int static int
encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) { encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) {
char shifted_key = 0; if (!ev->mods.value) return snprintf(output, KEY_BUFFER_SIZE, "%c", (char)ev->key);
if (ev->disambiguate) return 0;
char shifted_key = 0;
if ('a' <= ev->key && ev->key <= 'z') shifted_key = ev->key + ('A' - 'a'); if ('a' <= ev->key && ev->key <= 'z') shifted_key = ev->key + ('A' - 'a');
switch(ev->key) { switch(ev->key) {
#define S(which, val) case which: shifted_key = val; break; #define S(which, val) case which: shifted_key = val; break;
@ -169,14 +171,13 @@ encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) {
S('`', '~') S('-', '_') S('=', '+') S('[', '{') S(']', '}') S('\\', '|') S(';', ':') S('\'', '"') S(',', '<') S('.', '>') S('/', '?') S('`', '~') S('-', '_') S('=', '+') S('[', '{') S(']', '}') S('\\', '|') S(';', ':') S('\'', '"') S(',', '<') S('.', '>') S('/', '?')
#undef S #undef S
} }
shifted_key = (shifted_key && ev->mods.shift) ? shifted_key : (char)ev->key;
if (!ev->mods.value) return snprintf(output, KEY_BUFFER_SIZE, "%c", (char)ev->key); if ((ev->mods.value == ALT || ev->mods.value == (SHIFT | ALT)))
if (!ev->disambiguate) { return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", shifted_key);
if ((ev->mods.value == ALT || ev->mods.value == (SHIFT | ALT))) if (ev->mods.value == CTRL)
return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", (shifted_key && ev->mods.shift) ? shifted_key : (char)ev->key); return snprintf(output, KEY_BUFFER_SIZE, "%c", ev->key & 0x1f);
} if (ev->mods.value == (CTRL | ALT))
if (ev->mods.value == CTRL && (ev->key != 'i' && ev->key != 'm' && ev->key != '[' && ev->key != '@')) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ev->key & 0x1f);
return snprintf(output, KEY_BUFFER_SIZE, "%c", ev->key & 0x7f);
return 0; return 0;
} }

View File

@ -111,7 +111,7 @@ on_key_input(GLFWkeyevent *ev) {
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
} }
char encoded_key[KEY_BUFFER_SIZE] = {0}; char encoded_key[KEY_BUFFER_SIZE] = {0};
int size = encode_glfw_key_event(ev, screen->modes.mDECCKM, screen->key_encoding_flags, encoded_key); int size = encode_glfw_key_event(ev, screen->modes.mDECCKM, screen_current_key_encoding_flags(screen), encoded_key);
if (size == SEND_TEXT_TO_CHILD) { if (size == SEND_TEXT_TO_CHILD) {
schedule_write_to_child(w->id, 1, text, strlen(text)); schedule_write_to_child(w->id, 1, text, strlen(text));
debug("sent text to child\n"); debug("sent text to child\n");
@ -130,19 +130,20 @@ fake_scroll(Window *w, int amount, bool upwards) {
GLFWkeyevent ev = {.key = key }; GLFWkeyevent ev = {.key = key };
char encoded_key[KEY_BUFFER_SIZE] = {0}; char encoded_key[KEY_BUFFER_SIZE] = {0};
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
uint8_t flags = screen_current_key_encoding_flags(screen);
while (amount-- > 0) { while (amount-- > 0) {
ev.action = GLFW_PRESS; ev.action = GLFW_PRESS;
int size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, screen->key_encoding_flags, encoded_key); int size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, flags, encoded_key);
if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size); if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size);
ev.action = GLFW_RELEASE; ev.action = GLFW_RELEASE;
size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, screen->key_encoding_flags, encoded_key); size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, flags, encoded_key);
if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size); if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size);
} }
} }
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL; #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
#define M(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} #define M(name, arg_type) {#name, (PyCFunction)(void (*) (void))(py##name), arg_type, NULL}
PYWRAP1(key_for_native_key_name) { PYWRAP1(key_for_native_key_name) {
const char *name; const char *name;

View File

@ -683,7 +683,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
unsigned int num = screen->parser_buf_pos, start, i, num_params=0, p1, p2; unsigned int num = screen->parser_buf_pos, start, i, num_params=0, p1, p2;
static unsigned int params[MAX_PARAMS] = {0}; static unsigned int params[MAX_PARAMS] = {0};
bool private; bool private;
if (buf[0] == '>' || buf[0] == '?' || buf[0] == '!' || buf[0] == '=' || buf[0] == '-') { if (buf[0] == '>' || buf[0] == '<' || buf[0] == '?' || buf[0] == '!' || buf[0] == '=' || buf[0] == '-') {
start_modifier = (char)screen->parser_buf[0]; start_modifier = (char)screen->parser_buf[0];
buf++; num--; buf++; num--;
} }
@ -835,6 +835,23 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
screen_restore_cursor(screen); screen_restore_cursor(screen);
break; break;
} }
if (!end_modifier && start_modifier == '?') {
REPORT_COMMAND(screen_report_key_encoding_flags);
screen_report_key_encoding_flags(screen);
break;
}
if (!end_modifier && start_modifier == '=') {
CALL_CSI_HANDLER2(screen_set_key_encoding_flags, 0, 1);
break;
}
if (!end_modifier && start_modifier == '>') {
CALL_CSI_HANDLER1(screen_push_key_encoding_flags, 0);
break;
}
if (!end_modifier && start_modifier == '<') {
CALL_CSI_HANDLER1(screen_pop_key_encoding_flags, 1);
break;
}
REPORT_ERROR("Unknown CSI u sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params); REPORT_ERROR("Unknown CSI u sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
break; break;
case 'r': case 'r':
@ -1090,6 +1107,7 @@ accumulate_csi(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
break; break;
case '?': case '?':
case '>': case '>':
case '<':
case '!': case '!':
case '=': case '=':
case '-': case '-':

View File

@ -135,6 +135,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->tabstops = self->main_tabstops; self->tabstops = self->main_tabstops;
init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->main_tabstops, self->columns);
init_tabstops(self->alt_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns);
self->key_encoding_flags = self->main_key_encoding_flags;
if (!init_overlay_line(self, self->columns)) { Py_CLEAR(self); return NULL; } if (!init_overlay_line(self, self->columns)) { Py_CLEAR(self); return NULL; }
self->hyperlink_pool = alloc_hyperlink_pool(); self->hyperlink_pool = alloc_hyperlink_pool();
if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); } if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); }
@ -150,7 +151,8 @@ void
screen_reset(Screen *self) { screen_reset(Screen *self) {
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true); if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
if (self->overlay_line.is_active) deactivate_overlay_line(self); if (self->overlay_line.is_active) deactivate_overlay_line(self);
self->key_encoding_flags = 0; memset(self->main_key_encoding_flags, 0, sizeof(self->main_key_encoding_flags));
memset(self->alt_key_encoding_flags, 0, sizeof(self->alt_key_encoding_flags));
self->last_graphic_char = 0; self->last_graphic_char = 0;
self->main_savepoint.is_valid = false; self->main_savepoint.is_valid = false;
self->alt_savepoint.is_valid = false; self->alt_savepoint.is_valid = false;
@ -718,12 +720,14 @@ screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_scree
if (save_cursor) screen_save_cursor(self); if (save_cursor) screen_save_cursor(self);
self->linebuf = self->alt_linebuf; self->linebuf = self->alt_linebuf;
self->tabstops = self->alt_tabstops; self->tabstops = self->alt_tabstops;
self->key_encoding_flags = self->alt_key_encoding_flags;
self->grman = self->alt_grman; self->grman = self->alt_grman;
screen_cursor_position(self, 1, 1); screen_cursor_position(self, 1, 1);
cursor_reset(self->cursor); cursor_reset(self->cursor);
} else { } else {
self->linebuf = self->main_linebuf; self->linebuf = self->main_linebuf;
self->tabstops = self->main_tabstops; self->tabstops = self->main_tabstops;
self->key_encoding_flags = self->main_key_encoding_flags;
if (save_cursor) screen_restore_cursor(self); if (save_cursor) screen_restore_cursor(self);
self->grman = self->main_grman; self->grman = self->main_grman;
} }
@ -833,6 +837,54 @@ screen_set_8bit_controls(Screen *self, bool yes) {
self->modes.eight_bit_controls = yes; self->modes.eight_bit_controls = yes;
} }
uint8_t
screen_current_key_encoding_flags(Screen *self) {
for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) {
if (self->key_encoding_flags[i] & 0x80) return self->key_encoding_flags[i] & 0xf;
}
return 0;
}
void
screen_report_key_encoding_flags(Screen *self) {
char buf[16] = {0};
snprintf(buf, sizeof(buf), "?%uu", screen_current_key_encoding_flags(self));
write_escape_code_to_child(self, CSI, buf);
}
void
screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how) {
unsigned idx = 0;
for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) {
if (self->key_encoding_flags[i] & 0x80) { idx = i; break; }
}
uint8_t q = val & 0xf;
if (how == 1) self->key_encoding_flags[idx] = q;
else if (how == 2) self->key_encoding_flags[idx] |= q;
else if (how == 3) self->key_encoding_flags[idx] &= ~q;
self->key_encoding_flags[idx] |= 0x80;
}
void
screen_push_key_encoding_flags(Screen *self, uint32_t val) {
uint8_t q = val & 0xf;
const unsigned sz = arraysz(self->main_key_encoding_flags);
unsigned current_idx = 0;
for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) {
if (self->key_encoding_flags[i] & 0x80) { current_idx = i; break; }
}
if (current_idx == sz - 1) memmove(self->key_encoding_flags, self->key_encoding_flags + 1, (sz - 1) * sizeof(self->main_key_encoding_flags[0]));
else self->key_encoding_flags[current_idx++] |= 0x80;
self->key_encoding_flags[current_idx] = 0x80 | q;
}
void
screen_pop_key_encoding_flags(Screen *self, uint32_t num) {
for (unsigned i = arraysz(self->main_key_encoding_flags); num && i-- > 0; ) {
if (self->key_encoding_flags[i] & 0x80) { num--; self->key_encoding_flags[i] = 0; }
}
}
// }}} // }}}
// Cursor {{{ // Cursor {{{

View File

@ -129,7 +129,7 @@ typedef struct {
HYPERLINK_POOL_HANDLE hyperlink_pool; HYPERLINK_POOL_HANDLE hyperlink_pool;
ANSIBuf as_ansi_buf; ANSIBuf as_ansi_buf;
char_type last_graphic_char; char_type last_graphic_char;
unsigned key_encoding_flags; uint8_t main_key_encoding_flags[8], alt_key_encoding_flags[8], *key_encoding_flags;
} Screen; } Screen;
@ -223,9 +223,15 @@ void screen_rescale_images(Screen *self);
void screen_report_size(Screen *, unsigned int which); void screen_report_size(Screen *, unsigned int which);
void screen_manipulate_title_stack(Screen *, unsigned int op, unsigned int which); void screen_manipulate_title_stack(Screen *, unsigned int op, unsigned int which);
void screen_draw_overlay_text(Screen *self, const char *utf8_text); void screen_draw_overlay_text(Screen *self, const char *utf8_text);
void screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how);
void screen_push_key_encoding_flags(Screen *self, uint32_t val);
void screen_pop_key_encoding_flags(Screen *self, uint32_t num);
uint8_t screen_current_key_encoding_flags(Screen *self);
void screen_report_key_encoding_flags(Screen *self);
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(bell)
DECLARE_CH_SCREEN_HANDLER(backspace) DECLARE_CH_SCREEN_HANDLER(backspace)
DECLARE_CH_SCREEN_HANDLER(tab) DECLARE_CH_SCREEN_HANDLER(tab)
DECLARE_CH_SCREEN_HANDLER(linefeed) DECLARE_CH_SCREEN_HANDLER(linefeed)
DECLARE_CH_SCREEN_HANDLER(carriage_return) DECLARE_CH_SCREEN_HANDLER(carriage_return)
#undef DECLARE_CH_SCREEN_HANDLER

View File

@ -2,82 +2,11 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from functools import partial
import kitty.fast_data_types as defines import kitty.fast_data_types as defines
from kitty.keys import (
interpret_key_event, modify_complex_key, modify_key_bytes, smkx_key_map
)
from . import BaseTest from . import BaseTest
class DummyWindow: class TestKeys(BaseTest):
def __init__(self):
self.screen = self
self.extended_keyboard = False
self.cursor_key_mode = True
class TestParser(BaseTest):
def test_modify_complex_key(self):
self.ae(modify_complex_key('kcuu1', 4), b'\033[1;4A')
self.ae(modify_complex_key('kcuu1', 3), b'\033[1;3A')
self.ae(modify_complex_key('kf5', 3), b'\033[15;3~')
self.assertRaises(ValueError, modify_complex_key, 'kri', 3)
def test_interpret_key_event(self):
# test rmkx/smkx
w = DummyWindow()
def k(expected, key, mods=0):
actual = interpret_key_event(
getattr(defines, 'GLFW_KEY_' + key),
0,
mods,
w,
defines.GLFW_PRESS,
)
self.ae(b'\033' + expected.encode('ascii'), actual)
for ckm, mch in {True: 'O', False: '['}.items():
w.cursor_key_mode = ckm
for name, ch in {
'UP': 'A',
'DOWN': 'B',
'RIGHT': 'C',
'LEFT': 'D',
'HOME': 'H',
'END': 'F',
}.items():
k(mch + ch, name)
w.cursor_key_mode = True
# test remaining special keys
for key, num in zip('INSERT DELETE PAGE_UP PAGE_DOWN'.split(), '2356'):
k('[' + num + '~', key)
for key, num in zip('1234', 'PQRS'):
k('O' + num, 'F' + key)
for key, num in zip(range(5, 13), (15, 17, 18, 19, 20, 21, 23, 24)):
k('[' + str(num) + '~', 'F{}'.format(key))
# test modifiers
SPECIAL_KEYS = 'UP DOWN RIGHT LEFT HOME END INSERT DELETE PAGE_UP PAGE_DOWN '
for i in range(1, 13):
SPECIAL_KEYS += 'F{} '.format(i)
SPECIAL_KEYS = SPECIAL_KEYS.strip().split()
for mods, num in zip(('CONTROL', 'ALT', 'SHIFT+ALT'), '534'):
fmods = 0
num = int(num)
for m in mods.split('+'):
fmods |= getattr(defines, 'GLFW_MOD_' + m)
km = partial(k, mods=fmods)
for key in SPECIAL_KEYS:
keycode = getattr(defines, 'GLFW_KEY_' + key)
base_key = smkx_key_map[keycode]
km(modify_key_bytes(base_key, num).decode('ascii')[1:], key)
def test_encode_mouse_event(self): def test_encode_mouse_event(self):
NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL = range(4) NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL = range(4)

View File

@ -728,6 +728,49 @@ class TestScreen(BaseTest):
self.ae(str(s.linebuf), '0\n5\n6\n7\n\n') self.ae(str(s.linebuf), '0\n5\n6\n7\n\n')
self.ae(str(s.historybuf), '') self.ae(str(s.historybuf), '')
def test_key_encoding_flags_stack(self):
s = self.create_screen()
c = s.callbacks
def w(code, p1='', p2=''):
p = f'{p1}'
if p2:
p += f';{p2}'
return parse_bytes(s, f'\033[{code}{p}u'.encode('ascii'))
def ac(flags):
parse_bytes(s, '\033[?u'.encode('ascii'))
self.ae(c.wtcbuf, f'\033[?{flags}u'.encode('ascii'))
c.clear()
ac(0)
w('=', 0b1001)
ac(0b1001)
w('=', 0b0011, 2)
ac(0b1011)
w('=', 0b0110, 3)
ac(0b1001)
s.reset()
ac(0)
w('>', 0b0011)
ac(0b0011)
w('=', 0b1111)
ac(0b1111)
w('>', 0b10)
ac(0b10)
w('<')
ac(0b1111)
for i in range(10):
w('<')
ac(0)
s.reset()
for i in range(1, 16):
w('>', i)
ac(15)
w('<'), ac(14), w('<'), ac(13)
def test_color_stack(self): def test_color_stack(self):
s = self.create_screen() s = self.create_screen()
c = s.callbacks c = s.callbacks