kitty/kitty/key_encoding.c
2021-01-19 09:01:52 +05:30

385 lines
13 KiB
C

/*
* key_encoding.c
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "keys.h"
#include "charsets.h"
typedef enum { SHIFT=1, ALT=2, CTRL=4, SUPER=8 } ModifierMasks;
typedef enum { PRESS = 0, REPEAT = 1, RELEASE = 2} KeyAction;
typedef struct {
uint32_t key, shifted_key, alternate_key;
struct {
bool shift, alt, ctrl, super;
unsigned value;
char encoded[4];
} mods;
KeyAction action;
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, add_text;
char encoded_mods[4];
const char *text;
KeyAction action;
} EncodingData;
static inline bool
is_modifier_key(uint32_t key) {
START_ALLOW_CASE_RANGE
switch (key) {
case GLFW_FKEY_LEFT_SHIFT ... GLFW_FKEY_RIGHT_SUPER:
case GLFW_FKEY_CAPS_LOCK:
case GLFW_FKEY_SCROLL_LOCK:
case GLFW_FKEY_NUM_LOCK:
return true;
default:
return false;
}
END_ALLOW_CASE_RANGE
}
static inline void
convert_glfw_mods(int mods, KeyEvent *ev) {
ev->mods.alt = (mods & GLFW_MOD_ALT) > 0, ev->mods.ctrl = (mods & GLFW_MOD_CONTROL) > 0, ev->mods.shift = (mods & GLFW_MOD_SHIFT) > 0, ev->mods.super = (mods & GLFW_MOD_SUPER) > 0;
ev->mods.value = ev->mods.shift ? SHIFT : 0;
if (ev->mods.alt) ev->mods.value |= ALT;
if (ev->mods.ctrl) ev->mods.value |= CTRL;
if (ev->mods.super) ev->mods.value |= SUPER;
snprintf(ev->mods.encoded, sizeof(ev->mods.encoded), "%u", ev->mods.value + 1);
}
static inline void
init_encoding_data(EncodingData *ans, const KeyEvent *ev) {
ans->add_actions = ev->report_all_event_types && ev->action != PRESS;
ans->has_mods = ev->mods.encoded[0] && ev->mods.encoded[0] != '1';
ans->add_alternates = ev->report_alternate_key && ((ev->shifted_key > 0 && ev->mods.shift) || ev->alternate_key > 0);
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 || 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 (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;
return pos;
}
static inline uint32_t
convert_kp_key_to_normal_key(uint32_t key_number) {
switch(key_number) {
#define S(x) case GLFW_FKEY_KP_##x: key_number = GLFW_FKEY_##x; break;
S(ENTER) S(HOME) S(END) S(INSERT) S(DELETE) S(PAGE_UP) S(PAGE_DOWN)
S(UP) S(DOWN) S(LEFT) S(RIGHT)
#undef S
case GLFW_FKEY_KP_0:
case GLFW_FKEY_KP_9: key_number = '0' + (key_number - GLFW_FKEY_KP_0); break;
case GLFW_FKEY_KP_DECIMAL: key_number = '.'; break;
case GLFW_FKEY_KP_DIVIDE: key_number = '/'; break;
case GLFW_FKEY_KP_MULTIPLY: key_number = '*'; break;
case GLFW_FKEY_KP_SUBTRACT: key_number = '-'; break;
case GLFW_FKEY_KP_ADD: key_number = '+'; break;
case GLFW_FKEY_KP_EQUAL: key_number = '='; break;
}
return key_number;
}
static int
encode_function_key(const KeyEvent *ev, char *output) {
#define SIMPLE(val) return snprintf(output, KEY_BUFFER_SIZE, "%s", val);
char csi_trailer = 'u';
uint32_t key_number = ev->key;
if (ev->cursor_key_mode && !ev->disambiguate && !ev->report_all_event_types) {
switch(key_number) {
case GLFW_FKEY_UP: SIMPLE("\x1bOA");
case GLFW_FKEY_DOWN: SIMPLE("\x1bOB");
case GLFW_FKEY_RIGHT: SIMPLE("\x1bOC");
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;
}
}
if (!ev->mods.value) {
if (!ev->disambiguate && !ev->report_text && key_number == GLFW_FKEY_ESCAPE) SIMPLE("\x1b");
if (!ev->report_text) {
switch(key_number) {
case GLFW_FKEY_ENTER: SIMPLE("\r");
case GLFW_FKEY_BACKSPACE: SIMPLE("\x7f");
case GLFW_FKEY_TAB: SIMPLE("\t");
default: break;
}
}
}
if (ev->mods.value == ALT && !ev->disambiguate) {
switch(key_number) {
case GLFW_FKEY_TAB: SIMPLE("\x1b\t");
case GLFW_FKEY_ENTER: SIMPLE("\x1b\r");
case GLFW_FKEY_BACKSPACE: SIMPLE("\x1b\x7f");
}
}
if (ev->mods.value == SHIFT && key_number == GLFW_FKEY_TAB && !ev->disambiguate) { SIMPLE("\x1b[Z"); }
if (ev->mods.value == CTRL && key_number == GLFW_FKEY_BACKSPACE && !ev->disambiguate) { SIMPLE("\x08"); }
#undef SIMPLE
#define S(number, trailer) key_number = number; csi_trailer = trailer; break
switch(key_number) {
/* start special numbers (auto generated by gen-key-constants.py do not edit) */
case GLFW_FKEY_ESCAPE: S(27, 'u');
case GLFW_FKEY_ENTER: S(13, 'u');
case GLFW_FKEY_TAB: S(9, 'u');
case GLFW_FKEY_BACKSPACE: S(127, 'u');
case GLFW_FKEY_INSERT: S(2, '~');
case GLFW_FKEY_DELETE: S(3, '~');
case GLFW_FKEY_LEFT: S(1, 'D');
case GLFW_FKEY_RIGHT: S(1, 'C');
case GLFW_FKEY_UP: S(1, 'A');
case GLFW_FKEY_DOWN: S(1, 'B');
case GLFW_FKEY_PAGE_UP: S(5, '~');
case GLFW_FKEY_PAGE_DOWN: S(6, '~');
case GLFW_FKEY_HOME: S(1, 'H');
case GLFW_FKEY_END: S(1, 'F');
case GLFW_FKEY_F1: S(1, 'P');
case GLFW_FKEY_F2: S(1, 'Q');
case GLFW_FKEY_F3: S(1, 'R');
case GLFW_FKEY_F4: S(1, 'S');
case GLFW_FKEY_F5: S(15, '~');
case GLFW_FKEY_F6: S(17, '~');
case GLFW_FKEY_F7: S(18, '~');
case GLFW_FKEY_F8: S(19, '~');
case GLFW_FKEY_F9: S(20, '~');
case GLFW_FKEY_F10: S(21, '~');
case GLFW_FKEY_F11: S(23, '~');
case GLFW_FKEY_F12: S(24, '~');
/* end special numbers */
default: break;
}
#undef S
EncodingData ed = {0};
init_encoding_data(&ed, ev);
ed.key = key_number;
ed.add_alternates = false;
return serialize(&ed, output, csi_trailer);
}
static char
ctrled_key(const char key) { // {{{
switch(key) {
/* start ctrl mapping (auto generated by gen-key-constants.py do not edit) */
case ' ': return 0;
case '/': return 31;
case '0': return 48;
case '1': return 49;
case '2': return 0;
case '3': return 27;
case '4': return 28;
case '5': return 29;
case '6': return 30;
case '7': return 31;
case '8': return 127;
case '9': return 57;
case '?': return 127;
case '@': return 0;
case '[': return 27;
case '\\': return 28;
case ']': return 29;
case '^': return 30;
case '_': return 31;
case 'a': return 1;
case 'b': return 2;
case 'c': return 3;
case 'd': return 4;
case 'e': return 5;
case 'f': return 6;
case 'g': return 7;
case 'h': return 8;
case 'i': return 9;
case 'j': return 10;
case 'k': return 11;
case 'l': return 12;
case 'm': return 13;
case 'n': return 14;
case 'o': return 15;
case 'p': return 16;
case 'q': return 17;
case 'r': return 18;
case 's': return 19;
case 't': return 20;
case 'u': return 21;
case 'v': return 22;
case 'w': return 23;
case 'x': return 24;
case 'y': return 25;
case 'z': return 26;
case '~': return 30;
/* end ctrl mapping */
default:
return key;
}
} // }}}
static int
encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) {
if (!ev->mods.value) return snprintf(output, KEY_BUFFER_SIZE, "%c", (char)ev->key);
char key = ev->key;
unsigned mods = ev->mods.value;
if (mods & SHIFT) {
const char shifted = ev->shifted_key;
if (shifted && shifted != key && (!(mods & CTRL) || key < 'a' || key > 'z')) {
key = shifted;
mods &= ~SHIFT;
}
}
if (ev->mods.value == SHIFT)
return snprintf(output, KEY_BUFFER_SIZE, "%c", key);
if (mods == ALT)
return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", key);
if (mods == CTRL)
return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key));
if (mods == (CTRL | ALT))
return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ctrled_key(key));
return 0;
}
static inline bool
is_legacy_ascii_key(uint32_t key) {
START_ALLOW_CASE_RANGE
switch (key) {
case 'a' ... 'z':
case '0' ... '9':
case '`':
case '-':
case '=':
case '[':
case ']':
case '\\':
case ';':
case '\'':
case ',':
case '.':
case '/':
return true;
default:
return false;
}
END_ALLOW_CASE_RANGE
}
static int
encode_key(const KeyEvent *ev, char *output) {
if (!ev->report_all_event_types && ev->action == RELEASE) return 0;
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 && !ed.add_text;
if (simple_encoding_ok) {
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) || (ev->shifted_key && is_legacy_ascii_key(ev->shifted_key))) {
int ret = encode_printable_ascii_key_legacy(ev, output);
if (ret > 0) return ret;
} else if (ev->key == ' ' && ev->mods.value == CTRL) {
output[0] = 0;
return 1;
}
if ((ev->mods.value == CTRL || ev->mods.value == ALT || ev->mods.value == (CTRL | ALT)) && ev->alternate_key && !is_legacy_ascii_key(ev->key) && is_legacy_ascii_key(ev->alternate_key)) {
KeyEvent alternate = *ev;
alternate.key = ev->alternate_key;
alternate.alternate_key = 0;
alternate.shifted_key = 0;
int ret = encode_printable_ascii_key_legacy(&alternate, output);
if (ret > 0) return ret;
}
}
}
return serialize(&ed, output, 'u');
}
static inline bool
is_ascii_control_char(char c) {
return c <= 31 || c == 127;
}
int
encode_glfw_key_event(const GLFWkeyevent *e, const bool cursor_key_mode, const unsigned key_encoding_flags, char *output) {
KeyEvent ev = {
.key = e->key, .shifted_key = e->shifted_key, .alternate_key = e->alternate_key,
.text = e->text,
.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_text = key_encoding_flags & 8,
.embed_text = key_encoding_flags & 16
};
if (!ev.report_text && is_modifier_key(e->key)) return 0;
ev.has_text = e->text && !is_ascii_control_char(e->text[0]);
if (!ev.key && !ev.has_text) return 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);
}
switch (e->action) {
case GLFW_PRESS: ev.action = PRESS; break;
case GLFW_REPEAT: ev.action = REPEAT; break;
case GLFW_RELEASE: ev.action = RELEASE; break;
}
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);
}