Implement reporting of all keys as escape codes with text
This commit is contained in:
parent
819bd5cd70
commit
47a901385f
@ -44,7 +44,7 @@ sending an escape code to toggle the mode.
|
||||
|
||||
The central escape code used to encode key events is::
|
||||
|
||||
CSI unicode-key-code:alternate-key-codes ; modifiers:event-type u
|
||||
CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
|
||||
|
||||
Spaces in the above definition are present for clarity and should be ignored.
|
||||
``CSI`` is the bytes ``0x1b 0x5b``. All parameters are decimal numbers. Fields
|
||||
@ -133,6 +133,21 @@ field is present. The ``repeat`` type is ``2`` and the ``release`` type is
|
||||
events are not supported for them, unless the application requests *key
|
||||
report mode*, see below.
|
||||
|
||||
.. _text_as_codepoints:
|
||||
|
||||
Text as code points
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The terminal can optionally send the text associated with key events as a
|
||||
sequence of Unicode code points. This behavior is opt-in by the progressive
|
||||
enhancement mechanism described below. Some examples::
|
||||
|
||||
shift+a -> CSI 97 ; 2 ; 65 u # The text 'A' is reported as 65
|
||||
option+a -> CSI 97 ; ; 229 u # The text 'å' is reported as 229
|
||||
|
||||
If multiple code points are present, they must be separated by colons.
|
||||
If no known key is associated with the text the key number ``0`` must be used.
|
||||
|
||||
|
||||
Non-Unicode keys
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -174,7 +189,8 @@ 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 ``CSI u`` escape codes"
|
||||
"0b1000 (8)", "Report all keys as escape codes"
|
||||
"0b10000 (16)", "Report associated text"
|
||||
|
||||
The program running in the terminal can query the terminal for the
|
||||
current values of the flags by sending::
|
||||
@ -229,7 +245,7 @@ much easier to integrate into the application event loop.
|
||||
Report event types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This type of progressive enhancement causes the terminal to report key repeat
|
||||
This progressive enhancement (``0b10``) 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.
|
||||
@ -238,9 +254,30 @@ 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.
|
||||
This progressive enhancement (``0b100``) 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.
|
||||
|
||||
Report all keys as escape codes
|
||||
----------------------------------
|
||||
|
||||
Key events that generate text, such as plain key presses without modifiers,
|
||||
result in just the text being sent, in the legacy protocol. There is no way to
|
||||
be notified of key repeat/release events. These types of events are needed for
|
||||
some applications, such as games (think of movement using the ``WASD`` keys).
|
||||
|
||||
This progressive enhancement (``0b1000``) turns on key reporting even for key
|
||||
events that generate next. When it is enabled, text will not be sent, instead
|
||||
only key events are sent. If the text is needed as well, combine with the
|
||||
Report associated text enhancement below.
|
||||
|
||||
|
||||
Report associated text
|
||||
------------------------
|
||||
|
||||
This progressive enhancement (``0b10000``) causes key events that generate text
|
||||
to be reported as ``CSI u`` escape codes with the text embedded in the escape
|
||||
code. See :ref:`text_as_codepoints` above for details on the mechanism.
|
||||
|
||||
Legacy key event encoding
|
||||
--------------------------------
|
||||
@ -332,8 +369,10 @@ For legacy compatibility, the keys
|
||||
following algorithm:
|
||||
|
||||
#. If the :kbd:`alt` key is pressed output the byte for ``ESC (0x1b)``
|
||||
#. If the :kbd:`ctrl` modifier is pressed mask the seventh bit ``(& 0b111111)`` in the key's ASCII code number and output that
|
||||
#. Otherwise, if the :kbd:`shift` modifier is pressed, output the shifted key, for example, ``A`` for ``a`` and ``$`` for ``4``.
|
||||
#. If the :kbd:`ctrl` modifier is pressed mask the seventh bit ``(0b1000000)``
|
||||
in the key's ASCII code number and output that
|
||||
#. Otherwise, if the :kbd:`shift` modifier is pressed, output the shifted key,
|
||||
for example, ``A`` for ``a`` and ``$`` for ``4``.
|
||||
#. Otherwise, output the key unmodified
|
||||
|
||||
Additionally, :kbd:`ctrl+space` is output as the NULL byte ``(0x0)``.
|
||||
|
||||
@ -18,15 +18,16 @@ typedef struct {
|
||||
char encoded[4];
|
||||
} mods;
|
||||
KeyAction action;
|
||||
bool cursor_key_mode, disambiguate, report_all_event_types, report_alternate_key;
|
||||
bool cursor_key_mode, disambiguate, report_all_event_types, report_alternate_key, report_text, embed_text;
|
||||
const char *text;
|
||||
bool has_text;
|
||||
} KeyEvent;
|
||||
|
||||
typedef struct {
|
||||
uint32_t key, shifted_key, alternate_key;
|
||||
bool add_alternates, has_mods, add_actions;
|
||||
bool add_alternates, has_mods, add_actions, add_text;
|
||||
char encoded_mods[4];
|
||||
const char *text;
|
||||
KeyAction action;
|
||||
} EncodingData;
|
||||
|
||||
@ -55,24 +56,42 @@ init_encoding_data(EncodingData *ans, const KeyEvent *ev) {
|
||||
if (ans->add_alternates) { if (ev->mods.shift) ans->shifted_key = ev->shifted_key; ans->alternate_key = ev->alternate_key; }
|
||||
ans->action = ev->action;
|
||||
ans->key = ev->key;
|
||||
ans->add_text = ev->embed_text && ev->text && ev->text[0];
|
||||
ans->text = ev->text;
|
||||
memcpy(ans->encoded_mods, ev->mods.encoded, sizeof(ans->encoded_mods));
|
||||
}
|
||||
|
||||
static inline int
|
||||
serialize(const EncodingData *data, char *output, const char csi_trailer) {
|
||||
int pos = 0;
|
||||
bool second_field_not_empty = data->has_mods || data->add_actions;
|
||||
bool third_field_not_empty = data->add_text;
|
||||
#define P(fmt, ...) pos += snprintf(output + pos, KEY_BUFFER_SIZE - 2 - pos, fmt, __VA_ARGS__)
|
||||
P("\x1b%s", "[");
|
||||
if (data->key != 1 || data->add_alternates || data->has_mods || data->add_actions) P("%u", data->key);
|
||||
if (data->key != 1 || data->add_alternates || second_field_not_empty || third_field_not_empty) 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);
|
||||
}
|
||||
if (data->has_mods || data->add_actions) {
|
||||
P(";%s", data->encoded_mods);
|
||||
if (second_field_not_empty || third_field_not_empty) {
|
||||
P("%s", ";");
|
||||
if (second_field_not_empty) P("%s", data->encoded_mods);
|
||||
if (data->add_actions) P(":%u", data->action + 1);
|
||||
}
|
||||
if (third_field_not_empty) {
|
||||
const char *p = data->text;
|
||||
uint32_t codep, state = UTF8_ACCEPT;
|
||||
bool first = true;
|
||||
while(*p) {
|
||||
if (decode_utf8(&state, &codep, *p) == UTF8_ACCEPT) {
|
||||
if (first) { P(";%u", codep); first = false; }
|
||||
else P(":%u", codep);
|
||||
}
|
||||
state = UTF8_ACCEPT;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
#undef P
|
||||
output[pos++] = csi_trailer;
|
||||
output[pos] = 0;
|
||||
@ -287,11 +306,14 @@ encode_key(const KeyEvent *ev, char *output) {
|
||||
if (GLFW_FKEY_FIRST <= ev->key && ev->key <= GLFW_FKEY_LAST) return encode_function_key(ev, output);
|
||||
EncodingData ed = {0};
|
||||
init_encoding_data(&ed, ev);
|
||||
bool simple_encoding_ok = !ed.add_actions && !ed.add_alternates;
|
||||
bool simple_encoding_ok = !ed.add_actions && !ed.add_alternates && !ed.add_text;
|
||||
|
||||
if (simple_encoding_ok) {
|
||||
if (!ed.has_mods) return encode_utf8(ev->key, output);
|
||||
if (!ev->disambiguate) {
|
||||
if (!ed.has_mods) {
|
||||
if (ev->report_text) return serialize(&ed, output, 'u');
|
||||
return encode_utf8(ev->key, output);
|
||||
}
|
||||
if (!ev->disambiguate && !ev->report_text) {
|
||||
if (is_legacy_ascii_key(ev->key)) {
|
||||
int ret = encode_printable_ascii_key_legacy(ev, output);
|
||||
if (ret > 0) return ret;
|
||||
@ -318,9 +340,12 @@ encode_glfw_key_event(const GLFWkeyevent *e, const bool cursor_key_mode, const u
|
||||
.cursor_key_mode = cursor_key_mode,
|
||||
.disambiguate = key_encoding_flags & 1,
|
||||
.report_all_event_types = key_encoding_flags & 2,
|
||||
.report_alternate_key = key_encoding_flags & 4
|
||||
.report_alternate_key = key_encoding_flags & 4,
|
||||
.report_text = key_encoding_flags & 8,
|
||||
.embed_text = key_encoding_flags & 16
|
||||
};
|
||||
ev.has_text = e->text && !is_ascii_control_char(e->text[0]);
|
||||
bool send_text_standalone = !ev.report_text;
|
||||
if (!ev.disambiguate && GLFW_FKEY_KP_0 <= ev.key && ev.key <= GLFW_FKEY_KP_DELETE) {
|
||||
ev.key = convert_kp_key_to_normal_key(ev.key);
|
||||
}
|
||||
@ -329,7 +354,7 @@ encode_glfw_key_event(const GLFWkeyevent *e, const bool cursor_key_mode, const u
|
||||
case GLFW_REPEAT: ev.action = REPEAT; break;
|
||||
case GLFW_RELEASE: ev.action = RELEASE; break;
|
||||
}
|
||||
if (ev.has_text && (ev.action == PRESS || ev.action == REPEAT)) return SEND_TEXT_TO_CHILD;
|
||||
if (send_text_standalone && ev.has_text && (ev.action == PRESS || ev.action == REPEAT)) return SEND_TEXT_TO_CHILD;
|
||||
convert_glfw_mods(e->mods, &ev);
|
||||
return encode_key(&ev, output);
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
#include "glfw-wrapper.h"
|
||||
#include <limits.h>
|
||||
|
||||
#define KEY_BUFFER_SIZE 32
|
||||
#define KEY_BUFFER_SIZE 128
|
||||
#define SEND_TEXT_TO_CHILD INT_MIN
|
||||
#define debug(...) if (OPT(debug_keyboard)) printf(__VA_ARGS__);
|
||||
|
||||
|
||||
@ -840,7 +840,7 @@ screen_set_8bit_controls(Screen *self, bool 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;
|
||||
if (self->key_encoding_flags[i] & 0x80) return self->key_encoding_flags[i] & 0x7f;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -858,7 +858,7 @@ screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how) {
|
||||
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;
|
||||
uint8_t q = val & 0x7f;
|
||||
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;
|
||||
@ -867,7 +867,7 @@ screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how) {
|
||||
|
||||
void
|
||||
screen_push_key_encoding_flags(Screen *self, uint32_t val) {
|
||||
uint8_t q = val & 0xf;
|
||||
uint8_t q = val & 0x7f;
|
||||
const unsigned sz = arraysz(self->main_key_encoding_flags);
|
||||
unsigned current_idx = 0;
|
||||
for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) {
|
||||
|
||||
@ -15,11 +15,11 @@ class TestKeys(BaseTest):
|
||||
shift, alt, ctrl, super = defines.GLFW_MOD_SHIFT, defines.GLFW_MOD_ALT, defines.GLFW_MOD_CONTROL, defines.GLFW_MOD_SUPER # noqa
|
||||
press, repeat, release = defines.GLFW_PRESS, defines.GLFW_REPEAT, defines.GLFW_RELEASE # noqa
|
||||
|
||||
def csi(mods=0, num=1, action=1, shifted_key=0, alternate_key=0, trailer='u'):
|
||||
def csi(mods=0, num=1, action=1, shifted_key=0, alternate_key=0, text=None, trailer='u'):
|
||||
ans = '\033['
|
||||
if isinstance(num, str):
|
||||
num = ord(num)
|
||||
if num != 1 or mods or shifted_key or alternate_key:
|
||||
if num != 1 or mods or shifted_key or alternate_key or text:
|
||||
ans += f'{num}'
|
||||
if shifted_key or alternate_key:
|
||||
if isinstance(shifted_key, str):
|
||||
@ -29,7 +29,7 @@ class TestKeys(BaseTest):
|
||||
if isinstance(alternate_key, str):
|
||||
alternate_key = ord(alternate_key)
|
||||
ans += f':{alternate_key}'
|
||||
if mods or action > 1:
|
||||
if mods or action > 1 or text:
|
||||
m = 0
|
||||
if mods & shift:
|
||||
m |= 1
|
||||
@ -39,9 +39,14 @@ class TestKeys(BaseTest):
|
||||
m |= 4
|
||||
if mods & super:
|
||||
m |= 8
|
||||
ans += f';{m+1}'
|
||||
if action > 1:
|
||||
ans += f':{action}'
|
||||
if action > 1 or m:
|
||||
ans += f';{m+1}'
|
||||
if action > 1:
|
||||
ans += f':{action}'
|
||||
elif text:
|
||||
ans += ';'
|
||||
if text:
|
||||
ans += ';' + ':'.join(map(str, map(ord, text)))
|
||||
return ans + trailer
|
||||
|
||||
def mods_test(key, plain=None, shift=None, ctrl=None, alt=None, calt=None, cshift=None, ashift=None, csi_num=None, trailer='u'):
|
||||
@ -419,6 +424,19 @@ class TestKeys(BaseTest):
|
||||
ae(aq(ord('a'), alternate_key=ord('A')), csi(num='a', alternate_key='A'))
|
||||
ae(aq(ord('a'), mods=shift, shifted_key=ord('A'), alternate_key=ord('b')), csi(shift, 'a', shifted_key='A', alternate_key='b'))
|
||||
|
||||
# test report all keys
|
||||
kq = partial(enc, key_encoding_flags=0b1000)
|
||||
ae(kq(ord('a')), csi(num='a'))
|
||||
ae(kq(ord('a'), action=defines.GLFW_REPEAT), csi(num='a'))
|
||||
ae(kq(ord('a'), mods=ctrl), csi(ctrl, num='a'))
|
||||
ae(kq(defines.GLFW_FKEY_UP), '\x1b[A')
|
||||
|
||||
# test embed text
|
||||
eq = partial(enc, key_encoding_flags=0b11000)
|
||||
ae(eq(ord('a'), text='a'), csi(num='a', text='a'))
|
||||
ae(eq(ord('a'), mods=shift, text='A'), csi(shift, num='a', text='A'))
|
||||
ae(eq(ord('a'), mods=shift, text='AB'), csi(shift, num='a', text='AB'))
|
||||
|
||||
def test_encode_mouse_event(self):
|
||||
NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL = range(4)
|
||||
L, M, R = 1, 2, 3
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user