Implement progressive enhancement of key event reporting
This commit is contained in:
parent
295e8db04c
commit
a30ea2b7f8
@ -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
|
||||||
|
.. }}}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 '-':
|
||||||
|
|||||||
@ -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 {{{
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user