/* * key_encoding.c * Copyright (C) 2021 Kovid Goyal * * 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; bool is_modifier_key(const uint32_t key) { START_ALLOW_CASE_RANGE switch (key) { case GLFW_FKEY_LEFT_SHIFT ... GLFW_FKEY_ISO_LEVEL5_SHIFT: 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 ? 0 : 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; UTF8State 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 legacy_functional_key_encoding_with_modifiers(uint32_t key_number, const KeyEvent *ev, char *output) { const char *prefix = ev->mods.value & ALT ? "\x1b" : ""; const char *main_bytes = ""; switch (key_number) { case GLFW_FKEY_ENTER: main_bytes = "\x0d"; break; case GLFW_FKEY_ESCAPE: main_bytes = "\x1b"; break; case GLFW_FKEY_BACKSPACE: main_bytes = ev->mods.value & CTRL ? "\x08" : "\x7f"; break; case GLFW_FKEY_TAB: if (ev->mods.value & SHIFT) { prefix = ev->mods.value & ALT ? "\x1b\x1b" : "\x1b"; main_bytes = "[Z"; } else { main_bytes = "\t"; } break; default: return -1; } return snprintf(output, KEY_BUFFER_SIZE, "%s%s", prefix, main_bytes); } 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; bool legacy_mode = !ev->report_all_event_types && !ev->disambiguate; if (ev->cursor_key_mode && legacy_mode && !ev->mods.value) { 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; } } } else if (legacy_mode) { int num = legacy_functional_key_encoding_with_modifiers(key_number, ev, output); if (num > -1) return num; } #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)); if (key == ' ') { if (mods == (CTRL | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key)); if (mods == (ALT | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", 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 '`': case '~': case '-': case '_': case '=': case '+': case '[': case '{': case ']': case '}': case '\\': case '|': 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; } 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 startswith_ascii_control_char(const char *p) { if (!p || !*p) return true; uint32_t codep; UTF8State state = UTF8_ACCEPT; while(*p) { if (decode_utf8(&state, &codep, *p) == UTF8_ACCEPT) { return codep < 32 || codep == 127; } state = UTF8_ACCEPT; p++; } return false; } 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 && !startswith_ascii_control_char(e->text); 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); }