diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
| commit | 4384435941b8c7658856b381d071aee2e0035e89 (patch) | |
| tree | 5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /src/input.c | |
term: move the term core to subprojects/term
The core now builds, tests, and benchmarks alone, as a Meson subproject
with no dependency on the main tree.
- include/term/ holds term.h and the header-only unicode/. src/ holds
the core and its private headers. The main tree gets only
include/term through term_dep, so it cannot reach a private header.
- The term tests, the term benchmarks, the Kitty parser suite, and
tools/amalgamate.py and tools/generate_unicode.py move with it. The
tests run as term:<name>.
- The main build passes its -march (arch_flag) and b_ndebug to the
subproject. A directory configured before this commit needs
meson configure -Dterm:arch_flag=-march=... once.
- The term tests compile with -UNDEBUG. They do work inside assert.
- The subproject exports term_test_dep with term_api.h, for the main
tree's render, thumbnail, color, and selection tests.
- The Kitty bridge used struct vector2i and has not compiled since the
public API redesign. It builds again. 2 of its 14 tests fail: REP
after a tab, and a cursor report after a resize.
- dirgraph.py drops the term and unicode layers. Its self-test uses pty.
- The symbols test ignores the __odr_asan symbols that ASan adds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/input.c')
| -rw-r--r-- | src/input.c | 771 |
1 files changed, 771 insertions, 0 deletions
diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..49e41b2 --- /dev/null +++ b/src/input.c @@ -0,0 +1,771 @@ +#include "term.h" +#include "unicode/utf8.h" + +#include <string.h> + +struct seqbuf { + char *p; + size_t left; + bool overflow; +}; + +static inline void sb_init(struct seqbuf *sb, char *buf, size_t buf_sz) +{ + sb->p = buf; + sb->left = buf_sz; + sb->overflow = false; +} + +static inline void sb_ch(struct seqbuf *sb, char c) +{ + if (!sb->left) { sb->overflow = true; return; } + *sb->p++ = c; + sb->left--; +} + +static inline void sb_u32(struct seqbuf *sb, uint32_t val) +{ + char tmp[10]; + size_t n = 0; + do { + tmp[n++] = (char)('0' + (val % 10)); + val /= 10; + } while (val); + while (n) sb_ch(sb, tmp[--n]); +} + +static inline void sb_str(struct seqbuf *sb, const char *s, size_t len) +{ + for (size_t i = 0; i < len; i++) sb_ch(sb, s[i]); +} + +static inline size_t sb_len(const struct seqbuf *sb, const char *buf) +{ + return (size_t)(sb->p - buf); +} + +static inline void sb_bytes(struct seqbuf *sb, const char *s, size_t len) +{ + if (len > sb->left) { sb->overflow = true; return; } + memcpy(sb->p, s, len); + sb->p += len; + sb->left -= len; +} + +struct kitty_desc { + uint32_t code; + char final; +}; + +static const struct kitty_desc kitty_desc[TERM_KEY_COUNT] = { + [TERM_KEY_RETURN] = { 13, 'u' }, + [TERM_KEY_BACKSPACE] = { 127, 'u' }, + [TERM_KEY_TAB] = { 9, 'u' }, + [TERM_KEY_ESCAPE] = { 27, 'u' }, + + [TERM_KEY_UP] = { 1, 'A' }, + [TERM_KEY_DOWN] = { 1, 'B' }, + [TERM_KEY_RIGHT] = { 1, 'C' }, + [TERM_KEY_LEFT] = { 1, 'D' }, + [TERM_KEY_HOME] = { 1, 'H' }, + [TERM_KEY_END] = { 1, 'F' }, + [TERM_KEY_PAGE_UP] = { 5, '~' }, + [TERM_KEY_PAGE_DOWN] = { 6, '~' }, + [TERM_KEY_INSERT] = { 2, '~' }, + [TERM_KEY_DELETE] = { 3, '~' }, + + [TERM_KEY_F1] = { 1, 'P' }, + [TERM_KEY_F2] = { 1, 'Q' }, + [TERM_KEY_F3] = { 13, '~' }, + [TERM_KEY_F4] = { 1, 'S' }, + [TERM_KEY_F5] = { 15, '~' }, + [TERM_KEY_F6] = { 17, '~' }, + [TERM_KEY_F7] = { 18, '~' }, + [TERM_KEY_F8] = { 19, '~' }, + [TERM_KEY_F9] = { 20, '~' }, + [TERM_KEY_F10] = { 21, '~' }, + [TERM_KEY_F11] = { 23, '~' }, + [TERM_KEY_F12] = { 24, '~' }, + [TERM_KEY_F13] = { 57376, 'u' }, + [TERM_KEY_F14] = { 57377, 'u' }, + [TERM_KEY_F15] = { 57378, 'u' }, + [TERM_KEY_F16] = { 57379, 'u' }, + [TERM_KEY_F17] = { 57380, 'u' }, + [TERM_KEY_F18] = { 57381, 'u' }, + [TERM_KEY_F19] = { 57382, 'u' }, + [TERM_KEY_F20] = { 57383, 'u' }, + [TERM_KEY_F21] = { 57384, 'u' }, + [TERM_KEY_F22] = { 57385, 'u' }, + [TERM_KEY_F23] = { 57386, 'u' }, + [TERM_KEY_F24] = { 57387, 'u' }, + [TERM_KEY_F25] = { 57388, 'u' }, + [TERM_KEY_F26] = { 57389, 'u' }, + [TERM_KEY_F27] = { 57390, 'u' }, + [TERM_KEY_F28] = { 57391, 'u' }, + [TERM_KEY_F29] = { 57392, 'u' }, + [TERM_KEY_F30] = { 57393, 'u' }, + [TERM_KEY_F31] = { 57394, 'u' }, + [TERM_KEY_F32] = { 57395, 'u' }, + [TERM_KEY_F33] = { 57396, 'u' }, + [TERM_KEY_F34] = { 57397, 'u' }, + [TERM_KEY_F35] = { 57398, 'u' }, + + [TERM_KEY_KP_0] = { 57399, 'u' }, + [TERM_KEY_KP_1] = { 57400, 'u' }, + [TERM_KEY_KP_2] = { 57401, 'u' }, + [TERM_KEY_KP_3] = { 57402, 'u' }, + [TERM_KEY_KP_4] = { 57403, 'u' }, + [TERM_KEY_KP_5] = { 57404, 'u' }, + [TERM_KEY_KP_6] = { 57405, 'u' }, + [TERM_KEY_KP_7] = { 57406, 'u' }, + [TERM_KEY_KP_8] = { 57407, 'u' }, + [TERM_KEY_KP_9] = { 57408, 'u' }, + [TERM_KEY_KP_DECIMAL] = { 57409, 'u' }, + [TERM_KEY_KP_DIVIDE] = { 57410, 'u' }, + [TERM_KEY_KP_MULTIPLY] = { 57411, 'u' }, + [TERM_KEY_KP_SUBTRACT] = { 57412, 'u' }, + [TERM_KEY_KP_ADD] = { 57413, 'u' }, + [TERM_KEY_KP_ENTER] = { 57414, 'u' }, + [TERM_KEY_KP_EQUAL] = { 57415, 'u' }, + [TERM_KEY_KP_SEPARATOR] = { 57416, 'u' }, + [TERM_KEY_KP_LEFT] = { 57417, 'u' }, + [TERM_KEY_KP_RIGHT] = { 57418, 'u' }, + [TERM_KEY_KP_UP] = { 57419, 'u' }, + [TERM_KEY_KP_DOWN] = { 57420, 'u' }, + [TERM_KEY_KP_PAGE_UP] = { 57421, 'u' }, + [TERM_KEY_KP_PAGE_DOWN] = { 57422, 'u' }, + [TERM_KEY_KP_HOME] = { 57423, 'u' }, + [TERM_KEY_KP_END] = { 57424, 'u' }, + [TERM_KEY_KP_INSERT] = { 57425, 'u' }, + [TERM_KEY_KP_DELETE] = { 57426, 'u' }, + [TERM_KEY_KP_BEGIN] = { 1, 'E' }, + + [TERM_KEY_CAPS_LOCK] = { 57358, 'u' }, + [TERM_KEY_SCROLL_LOCK] = { 57359, 'u' }, + [TERM_KEY_NUM_LOCK] = { 57360, 'u' }, + [TERM_KEY_PRINT_SCREEN] = { 57361, 'u' }, + [TERM_KEY_PAUSE] = { 57362, 'u' }, + [TERM_KEY_MENU] = { 57363, 'u' }, + + [TERM_KEY_MEDIA_PLAY] = { 57428, 'u' }, + [TERM_KEY_MEDIA_PAUSE] = { 57429, 'u' }, + [TERM_KEY_MEDIA_STOP] = { 57432, 'u' }, + [TERM_KEY_MEDIA_FORWARD] = { 57433, 'u' }, + [TERM_KEY_MEDIA_REWIND] = { 57434, 'u' }, + [TERM_KEY_MEDIA_NEXT] = { 57435, 'u' }, + [TERM_KEY_MEDIA_PREV] = { 57436, 'u' }, + [TERM_KEY_MEDIA_RECORD] = { 57437, 'u' }, + [TERM_KEY_VOLUME_DOWN] = { 57438, 'u' }, + [TERM_KEY_VOLUME_UP] = { 57439, 'u' }, + [TERM_KEY_MUTE] = { 57440, 'u' }, + + [TERM_KEY_LSHIFT] = { 57441, 'u' }, + [TERM_KEY_LCTRL] = { 57442, 'u' }, + [TERM_KEY_LALT] = { 57443, 'u' }, + [TERM_KEY_LSUPER] = { 57444, 'u' }, + [TERM_KEY_LHYPER] = { 57445, 'u' }, + [TERM_KEY_LMETA] = { 57446, 'u' }, + [TERM_KEY_RSHIFT] = { 57447, 'u' }, + [TERM_KEY_RCTRL] = { 57448, 'u' }, + [TERM_KEY_RALT] = { 57449, 'u' }, + [TERM_KEY_RSUPER] = { 57450, 'u' }, + [TERM_KEY_RHYPER] = { 57451, 'u' }, + [TERM_KEY_RMETA] = { 57452, 'u' }, + [TERM_KEY_ISO_LEVEL3_SHIFT] = { 57453, 'u' }, + [TERM_KEY_ISO_LEVEL5_SHIFT] = { 57454, 'u' }, +}; + +struct legacy_desc { + uint8_t tilde; + char final; + bool app_cursor; + char keypad_ss3; + enum term_key equiv; +}; + +static const struct legacy_desc legacy_desc[TERM_KEY_COUNT] = { + [TERM_KEY_UP] = { .tilde = 0, .final = 'A', .app_cursor = true }, + [TERM_KEY_DOWN] = { .tilde = 0, .final = 'B', .app_cursor = true }, + [TERM_KEY_RIGHT] = { .tilde = 0, .final = 'C', .app_cursor = true }, + [TERM_KEY_LEFT] = { .tilde = 0, .final = 'D', .app_cursor = true }, + [TERM_KEY_HOME] = { .tilde = 0, .final = 'H', .app_cursor = true }, + [TERM_KEY_END] = { .tilde = 0, .final = 'F', .app_cursor = true }, + [TERM_KEY_PAGE_UP] = { .tilde = 5, .final = '~' }, + [TERM_KEY_PAGE_DOWN] = { .tilde = 6, .final = '~' }, + [TERM_KEY_INSERT] = { .tilde = 2, .final = '~' }, + [TERM_KEY_DELETE] = { .tilde = 3, .final = '~' }, + + [TERM_KEY_F1] = { .tilde = 0, .final = 'P' }, + [TERM_KEY_F2] = { .tilde = 0, .final = 'Q' }, + [TERM_KEY_F3] = { .tilde = 0, .final = 'R' }, + [TERM_KEY_F4] = { .tilde = 0, .final = 'S' }, + [TERM_KEY_F5] = { .tilde = 15, .final = '~' }, + [TERM_KEY_F6] = { .tilde = 17, .final = '~' }, + [TERM_KEY_F7] = { .tilde = 18, .final = '~' }, + [TERM_KEY_F8] = { .tilde = 19, .final = '~' }, + [TERM_KEY_F9] = { .tilde = 20, .final = '~' }, + [TERM_KEY_F10] = { .tilde = 21, .final = '~' }, + [TERM_KEY_F11] = { .tilde = 23, .final = '~' }, + [TERM_KEY_F12] = { .tilde = 24, .final = '~' }, + + [TERM_KEY_KP_0] = { .keypad_ss3 = 'p' }, + [TERM_KEY_KP_1] = { .keypad_ss3 = 'q' }, + [TERM_KEY_KP_2] = { .keypad_ss3 = 'r' }, + [TERM_KEY_KP_3] = { .keypad_ss3 = 's' }, + [TERM_KEY_KP_4] = { .keypad_ss3 = 't' }, + [TERM_KEY_KP_5] = { .keypad_ss3 = 'u' }, + [TERM_KEY_KP_6] = { .keypad_ss3 = 'v' }, + [TERM_KEY_KP_7] = { .keypad_ss3 = 'w' }, + [TERM_KEY_KP_8] = { .keypad_ss3 = 'x' }, + [TERM_KEY_KP_9] = { .keypad_ss3 = 'y' }, + [TERM_KEY_KP_DECIMAL] = { .keypad_ss3 = 'n' }, + [TERM_KEY_KP_DIVIDE] = { .keypad_ss3 = 'o' }, + [TERM_KEY_KP_MULTIPLY] = { .keypad_ss3 = 'j' }, + [TERM_KEY_KP_SUBTRACT] = { .keypad_ss3 = 'm' }, + [TERM_KEY_KP_ADD] = { .keypad_ss3 = 'k' }, + [TERM_KEY_KP_ENTER] = { .keypad_ss3 = 'M', .equiv = TERM_KEY_RETURN }, + [TERM_KEY_KP_LEFT] = { .equiv = TERM_KEY_LEFT }, + [TERM_KEY_KP_RIGHT] = { .equiv = TERM_KEY_RIGHT }, + [TERM_KEY_KP_UP] = { .equiv = TERM_KEY_UP }, + [TERM_KEY_KP_DOWN] = { .equiv = TERM_KEY_DOWN }, + [TERM_KEY_KP_PAGE_UP] = { .equiv = TERM_KEY_PAGE_UP }, + [TERM_KEY_KP_PAGE_DOWN] = { .equiv = TERM_KEY_PAGE_DOWN }, + [TERM_KEY_KP_HOME] = { .equiv = TERM_KEY_HOME }, + [TERM_KEY_KP_END] = { .equiv = TERM_KEY_END }, + [TERM_KEY_KP_INSERT] = { .equiv = TERM_KEY_INSERT }, + [TERM_KEY_KP_DELETE] = { .equiv = TERM_KEY_DELETE }, + [TERM_KEY_KP_BEGIN] = { .tilde = 0, .final = 'E' }, +}; + +enum input_mod_key { + MOD_KEY_SHIFT = 0, + MOD_KEY_CTRL, + MOD_KEY_ALT, + MOD_KEY_SUPER, + MOD_KEY_HYPER, + MOD_KEY_META, + MOD_KEY_ISO3, + MOD_KEY_ISO5, + MOD_KEY_COUNT, +}; + +struct mod_desc { + enum input_mod_key group; + enum term_key partner; +}; + +static const struct mod_desc mod_desc[TERM_KEY_COUNT] = { + [TERM_KEY_LSHIFT] = { MOD_KEY_SHIFT, TERM_KEY_RSHIFT }, + [TERM_KEY_RSHIFT] = { MOD_KEY_SHIFT, TERM_KEY_LSHIFT }, + [TERM_KEY_LCTRL] = { MOD_KEY_CTRL, TERM_KEY_RCTRL }, + [TERM_KEY_RCTRL] = { MOD_KEY_CTRL, TERM_KEY_LCTRL }, + [TERM_KEY_LALT] = { MOD_KEY_ALT, TERM_KEY_RALT }, + [TERM_KEY_RALT] = { MOD_KEY_ALT, TERM_KEY_LALT }, + [TERM_KEY_LSUPER] = { MOD_KEY_SUPER, TERM_KEY_RSUPER }, + [TERM_KEY_RSUPER] = { MOD_KEY_SUPER, TERM_KEY_LSUPER }, + [TERM_KEY_LHYPER] = { MOD_KEY_HYPER, TERM_KEY_RHYPER }, + [TERM_KEY_RHYPER] = { MOD_KEY_HYPER, TERM_KEY_LHYPER }, + [TERM_KEY_LMETA] = { MOD_KEY_META, TERM_KEY_RMETA }, + [TERM_KEY_RMETA] = { MOD_KEY_META, TERM_KEY_LMETA }, + [TERM_KEY_ISO_LEVEL3_SHIFT] = { MOD_KEY_ISO3, TERM_KEY_NONE }, + [TERM_KEY_ISO_LEVEL5_SHIFT] = { MOD_KEY_ISO5, TERM_KEY_NONE }, +}; + +static const unsigned mod_group_input_mod[MOD_KEY_COUNT] = { + TERM_MOD_SHIFT, TERM_MOD_CTRL, TERM_MOD_ALT, + TERM_MOD_SUPER, TERM_MOD_HYPER, TERM_MOD_META, + 0, 0, +}; + +static inline bool key_is_modifier(enum term_key k) +{ + return (unsigned)k < TERM_KEY_COUNT && + (mod_desc[k].partner != TERM_KEY_NONE || + mod_desc[k].group == MOD_KEY_ISO3 || + mod_desc[k].group == MOD_KEY_ISO5); +} + +static inline bool key_is_lock(enum term_key k) +{ + return k == TERM_KEY_CAPS_LOCK || k == TERM_KEY_SCROLL_LOCK || + k == TERM_KEY_NUM_LOCK; +} + +static unsigned effective_mods(const struct term_keyboard_state *s, + const struct term_key_event *ev) +{ + unsigned mods = ev->mods; + if (!key_is_modifier(ev->key)) return mods; + + const struct mod_desc *d = &mod_desc[ev->key]; + if (d->group >= MOD_KEY_ISO3) return mods; + + unsigned bit = mod_group_input_mod[d->group]; + bool other_held = term_keyboard_key_down(s, d->partner); + if (ev->action != TERM_KEY_RELEASE || other_held) mods |= bit; + else mods &= ~bit; + return mods; +} + +static unsigned lock_mod_bit(enum term_key key) +{ + if (key == TERM_KEY_CAPS_LOCK) return TERM_MOD_CAPS; + if (key == TERM_KEY_NUM_LOCK) return TERM_MOD_NUM; + return 0; +} + +static unsigned toggle_locks(enum term_key key, enum term_key_action action, + unsigned mods) +{ + unsigned bit = lock_mod_bit(key); + if (bit && action == TERM_KEY_PRESS) mods ^= bit; + return mods; +} + +/* Convert logical input modifiers to terminal protocol bits. */ +static unsigned protocol_modifiers(unsigned mods) +{ + return ((mods & TERM_MOD_SHIFT) ? 1u : 0u) | + ((mods & TERM_MOD_ALT) ? 2u : 0u) | + ((mods & TERM_MOD_CTRL) ? 4u : 0u) | + ((mods & TERM_MOD_SUPER) ? 8u : 0u) | + ((mods & TERM_MOD_HYPER) ? 16u : 0u) | + ((mods & TERM_MOD_META) ? 32u : 0u) | + ((mods & TERM_MOD_CAPS) ? 64u : 0u) | + ((mods & TERM_MOD_NUM) ? 128u : 0u); +} + +static void format_kitty(struct seqbuf *sb, uint32_t code, char final, + unsigned flags, bool functional, uint32_t shifted, + uint32_t base, unsigned mods, unsigned event, + bool report_text, uint32_t text) +{ + char tmp[64]; + struct seqbuf body; + sb_init(&body, tmp, sizeof(tmp)); + + if (final != 'u' && final != '~' && code <= 1 && !shifted && !base && + !mods && !(flags & TERM_KEYBOARD_EVENT_TYPES) && !report_text) { + + sb_ch(&body, '\033'); + sb_ch(&body, '['); + sb_ch(&body, final); + } else { + sb_ch(&body, '\033'); + sb_ch(&body, '['); + sb_u32(&body, code); + if ((flags & TERM_KEYBOARD_ALTERNATE_KEYS) && !functional) { + if (shifted) { + sb_ch(&body, ':'); + sb_u32(&body, shifted); + } + if (base) { + sb_ch(&body, ':'); + if (!shifted) sb_ch(&body, ':'); + sb_u32(&body, base); + } + } + if (mods || (flags & TERM_KEYBOARD_EVENT_TYPES) || report_text) { + sb_ch(&body, ';'); + sb_u32(&body, mods + 1); + } + if ((flags & TERM_KEYBOARD_EVENT_TYPES) && event != 1) { + sb_ch(&body, ':'); + sb_u32(&body, event); + } + if (report_text) { + sb_ch(&body, ';'); + sb_u32(&body, text); + } + sb_ch(&body, final); + } + + if (body.overflow) { + sb->overflow = true; + return; + } + sb_str(sb, tmp, sb_len(&body, tmp)); +} + +static bool encode_kitty(const struct term_keyboard_state *s, + const struct term_key_event *ev, + const struct term_modes *m, + struct seqbuf *sb) +{ + unsigned flags = m->keyboard; + unsigned event = ev->action == TERM_KEY_PRESS ? 1u : + ev->action == TERM_KEY_REPEAT ? 2u : 3u; + if (!flags) return ev->action == TERM_KEY_RELEASE; + + const struct kitty_desc *d = &kitty_desc[ev->key]; + bool functional = d->code != 0; + char final = functional ? d->final : 'u'; + bool all = (flags & TERM_KEYBOARD_ALL_KEYS) != 0; + + if ((key_is_modifier(ev->key) || key_is_lock(ev->key)) && !all) + return true; + + unsigned mods = effective_mods(s, ev); + mods = toggle_locks(ev->key, ev->action, mods); + + bool plain_control = ev->key == TERM_KEY_RETURN || ev->key == TERM_KEY_TAB || + ev->key == TERM_KEY_BACKSPACE; + if (ev->action == TERM_KEY_RELEASE && + (!(flags & TERM_KEYBOARD_EVENT_TYPES) || (!all && plain_control))) + return true; + + const unsigned command_mods = TERM_MOD_ALT | TERM_MOD_CTRL | + TERM_MOD_SUPER | TERM_MOD_HYPER | + TERM_MOD_META; + uint32_t text = ev->text; + bool has_text = text >= 32 && !(text >= 127 && text < 160) && + !(mods & command_mods); + if (!all && has_text) return ev->action == TERM_KEY_RELEASE; + if (!all && plain_control && !(protocol_modifiers(mods) & 63)) + return ev->action == TERM_KEY_RELEASE; + if (!(flags & (TERM_KEYBOARD_DISAMBIGUATE | TERM_KEYBOARD_EVENT_TYPES | TERM_KEYBOARD_ALL_KEYS))) + return ev->action == TERM_KEY_RELEASE; + + uint32_t code = d->code, shifted = 0, base = 0; + if (!functional) { + code = ev->codepoint; + if (flags & TERM_KEYBOARD_ALTERNATE_KEYS) { + shifted = (mods & TERM_MOD_SHIFT) ? ev->shifted : 0; + base = ev->base; + if (shifted == code) shifted = 0; + if (base == code) base = 0; + } + } + if (!code) return true; + + bool report_text = all && (flags & TERM_KEYBOARD_ASSOCIATED_TEXT) && has_text && + ev->action != TERM_KEY_RELEASE; + format_kitty(sb, code, final, flags, functional, shifted, base, protocol_modifiers(mods), + event, report_text, text); + return true; +} + +static uint32_t control_base_char(const struct term_key_event *ev) +{ + uint32_t ch = ((ev->mods & TERM_MOD_SHIFT) && ev->shifted) + ? ev->shifted : ev->codepoint; + return ch < 0x80 ? ch : 0; +} + +static bool encode_legacy(const struct term_key_event *ev, + const struct term_modes *m, + struct seqbuf *sb) +{ + if (m->app_keypad && (unsigned)ev->keypad < TERM_KEY_COUNT && + legacy_desc[ev->keypad].keypad_ss3) { + sb_ch(sb, '\033'); + sb_ch(sb, 'O'); + sb_ch(sb, legacy_desc[ev->keypad].keypad_ss3); + return true; + } + + uint32_t ch = control_base_char(ev); + if ((ev->mods & TERM_MOD_CTRL) && ch) { + if ((ch >= 'a' && ch <= 'z') || (ch >= '@' && ch <= '_')) { + sb_ch(sb, (char)(ch & 0x1f)); + return true; + } + if (ch == ' ') { sb_ch(sb, 0); return true; } + if (ch == '-') { sb_ch(sb, 0x1f); return true; } + } + + unsigned csi = protocol_modifiers(ev->mods) & 63; + unsigned p = csi + 1; + bool shift = (ev->mods & TERM_MOD_SHIFT) != 0; + + enum term_key k = ev->key; + if ((unsigned)k < TERM_KEY_COUNT && legacy_desc[k].equiv != TERM_KEY_NONE) + k = legacy_desc[k].equiv; + + if ((unsigned)k < TERM_KEY_COUNT) { + const struct legacy_desc *d = &legacy_desc[k]; + if (d->final) { + if (d->tilde) { + sb_ch(sb, '\033'); + sb_ch(sb, '['); + sb_u32(sb, d->tilde); + if (csi) { + sb_ch(sb, ';'); + sb_u32(sb, p); + } + sb_ch(sb, '~'); + return true; + } + if (csi) { + sb_ch(sb, '\033'); + sb_ch(sb, '['); + sb_ch(sb, '1'); + sb_ch(sb, ';'); + sb_u32(sb, p); + sb_ch(sb, d->final); + return true; + } + sb_ch(sb, '\033'); + sb_ch(sb, d->app_cursor && !m->app_cursor ? '[' : 'O'); + sb_ch(sb, d->final); + return true; + } + } + + switch (k) { + case TERM_KEY_RETURN: sb_ch(sb, '\r'); return true; + case TERM_KEY_BACKSPACE: + sb_ch(sb, m->backarrow_bs ? '\x08' : '\x7f'); + return true; + case TERM_KEY_TAB: + if (shift) { sb_str(sb, "\033[Z", 3); } + else { sb_ch(sb, '\t'); } + return true; + case TERM_KEY_ESCAPE: sb_ch(sb, '\033'); return true; + default: break; + } + + if (ev->text && !ev->text_deferred) { + char tmp[8]; + size_t n = (size_t)utf8_encode(ev->text, tmp); + sb_str(sb, tmp, n); + return true; + } + return false; +} + +void term_keyboard_update(struct term_keyboard_state *s, const struct term_key_event *ev) +{ + if (!s || !ev || ev->key <= TERM_KEY_NONE || ev->key >= TERM_KEY_COUNT) + return; + if (ev->action == TERM_KEY_RELEASE) + s->down[ev->key >> 6] &= ~(1ull << (ev->key & 63)); + else if (ev->action == TERM_KEY_PRESS || ev->action == TERM_KEY_REPEAT) + s->down[ev->key >> 6] |= 1ull << (ev->key & 63); +} + +bool term_keyboard_key_down(const struct term_keyboard_state *s, enum term_key key) +{ + return s && key > TERM_KEY_NONE && (unsigned)key < TERM_KEY_COUNT && + ((s->down[key >> 6] >> (key & 63)) & 1); +} + +struct term_input_result term_input_encode_key(const struct term_keyboard_state *s, + const struct term_key_event *ev, + const struct term_modes *m, + char *buf, size_t buf_sz) +{ + struct term_input_result out = {0}; + if (!s || !ev || !m || !buf || + (ev->action != TERM_KEY_PRESS && ev->action != TERM_KEY_REPEAT && + ev->action != TERM_KEY_RELEASE) || + (unsigned)ev->key >= TERM_KEY_COUNT) + return out; + + struct seqbuf sb; + sb_init(&sb, buf, buf_sz); + + bool handled = encode_kitty(s, ev, m, &sb); + if (!handled) { + if (ev->action == TERM_KEY_RELEASE) + return out; + + bool alt = (ev->mods & TERM_MOD_ALT) != 0; + struct seqbuf body; + char tmp[64]; + if (alt) { + sb_init(&body, tmp, sizeof(tmp)); + } else { + body = sb; + } + + handled = encode_legacy(ev, m, &body); + if (handled) { + if (body.overflow) { + sb.overflow = true; + } else { + size_t n = sb_len(&body, alt ? tmp : buf); + if (alt) { + bool already_esc = n && tmp[0] == '\033'; + size_t prefix = !already_esc; + if (n + prefix <= buf_sz) { + if (prefix) buf[0] = '\033'; + memcpy(buf + prefix, tmp, n); + n += prefix; + } else { + sb.overflow = true; + n = 0; + } + } + out.len = n; + } + } + } else { + out.len = sb_len(&sb, buf); + } + + if (!handled) + return out; + if (sb.overflow) { + out.status = TERM_INPUT_BUFFER_FULL; + out.len = 0; + } else { + out.status = out.len ? TERM_INPUT_BYTES : TERM_INPUT_CONSUMED; + } + return out; +} + +struct term_input_result term_input_encode_mouse(const struct term_mouse_event *ev, + const struct term_modes *m, + char *buf, size_t buf_sz) +{ + struct term_input_result out = {0}; + if (!ev || !m || !buf) return out; + + if (!m->mouse || (ev->mods & TERM_MOD_SHIFT)) return out; + + out.status = TERM_INPUT_CONSUMED; + if (ev->kind == TERM_MOUSE_LEAVE) return out; + struct term_vector2i cell = ev->pos; + if (cell.x < 0 || cell.y < 0 || cell.x >= m->grid.x || cell.y >= m->grid.y) + return out; + if (m->mouse == TERM_MOUSE_X10 && (ev->kind == TERM_MOUSE_RELEASE || ev->kind == TERM_MOUSE_MOTION)) { + return out; + } + if (ev->kind == TERM_MOUSE_MOTION && (m->mouse == TERM_MOUSE_NORMAL || + (m->mouse == TERM_MOUSE_BUTTON && ev->button == 3))) { + return out; + } + int button; + if (ev->button >= TERM_MOUSE_BUTTON_PRIMARY && ev->button <= TERM_MOUSE_BUTTON_NONE) + button = ev->button; + else if (ev->button >= TERM_MOUSE_BUTTON_EXTRA_1 && ev->button <= TERM_MOUSE_BUTTON_EXTRA_5) + button = 8 + ev->button - TERM_MOUSE_BUTTON_EXTRA_1; + else if (ev->button >= TERM_MOUSE_WHEEL_UP && ev->button <= TERM_MOUSE_WHEEL_RIGHT) + button = 64 + ev->button - TERM_MOUSE_WHEEL_UP; + else { + return out; + } + + int mods = 0; + if (ev->mods & TERM_MOD_ALT) mods |= 8; + if (ev->mods & TERM_MOD_CTRL) mods |= 16; + + int code = button | mods | (ev->kind == TERM_MOUSE_MOTION ? 32 : 0); + int legacy_code = ev->kind == TERM_MOUSE_RELEASE ? 3 | mods : code; + struct term_vector2i report = m->mouse_encoding == TERM_MOUSE_ENC_SGR_PIXELS + ? ev->pixel : cell; + if (report.x < 0 || report.y < 0) + return out; + uint32_t report_x = (uint32_t)report.x + 1; + uint32_t report_y = (uint32_t)report.y + 1; + + struct seqbuf sb; + sb_init(&sb, buf, buf_sz); + + if (m->mouse_encoding == TERM_MOUSE_ENC_SGR || + m->mouse_encoding == TERM_MOUSE_ENC_SGR_PIXELS) { + sb_ch(&sb, '\033'); + sb_ch(&sb, '['); + sb_ch(&sb, '<'); + sb_u32(&sb, (uint32_t)code); + sb_ch(&sb, ';'); + sb_u32(&sb, (uint32_t)report_x); + sb_ch(&sb, ';'); + sb_u32(&sb, (uint32_t)report_y); + sb_ch(&sb, ev->kind == TERM_MOUSE_RELEASE ? 'm' : 'M'); + } else if (m->mouse_encoding == TERM_MOUSE_ENC_URXVT) { + sb_ch(&sb, '\033'); + sb_ch(&sb, '['); + sb_u32(&sb, (uint32_t)(legacy_code + 32)); + sb_ch(&sb, ';'); + sb_u32(&sb, (uint32_t)(cell.x + 1)); + sb_ch(&sb, ';'); + sb_u32(&sb, (uint32_t)(cell.y + 1)); + sb_ch(&sb, 'M'); + } else if (m->mouse_encoding == TERM_MOUSE_ENC_UTF8) { + const unsigned values[3] = { + (unsigned)legacy_code + 32, (unsigned)cell.x + 33, + (unsigned)cell.y + 33, + }; + sb_str(&sb, "\033[M", 3); + for (int i = 0; i < 3; i++) { + char tmp[8]; + size_t n = (size_t)utf8_encode(values[i], tmp); + sb_str(&sb, tmp, n); + } + } else { + if (legacy_code > 223 || cell.x > 222 || cell.y > 222) + return out; + sb_ch(&sb, '\033'); + sb_ch(&sb, '['); + sb_ch(&sb, 'M'); + sb_ch(&sb, (char)(legacy_code + 32)); + sb_ch(&sb, (char)(cell.x + 33)); + sb_ch(&sb, (char)(cell.y + 33)); + } + + if (sb.overflow) { + out.status = TERM_INPUT_BUFFER_FULL; + out.len = 0; + return out; + } + out.len = sb_len(&sb, buf); + out.status = TERM_INPUT_BYTES; + return out; +} + +/* Bidi controls can make pasted text look different from what runs. */ +static bool is_direction_format(uint32_t cp) +{ + return cp == 0x061c || + (cp >= 0x200e && cp <= 0x200f) || + (cp >= 0x202a && cp <= 0x202e) || + (cp >= 0x2066 && cp <= 0x2069) || + cp == 0xfeff; +} + +struct term_input_result term_input_encode_paste(const struct term_modes *m, + const char *text, size_t len, + char *buf, size_t capacity) +{ + struct term_input_result out = {0}; + if (!m || !buf || (!text && len)) return out; + + struct seqbuf sb; + sb_init(&sb, buf, capacity); + if (m->bracketed_paste) sb_bytes(&sb, "\033[200~", 6); + size_t body = sb_len(&sb, buf); + for (size_t i = 0; i < len;) { + size_t start = i; + while (i < len && (unsigned char)text[i] >= 0x20 && (unsigned char)text[i] < 0x7f) + i++; + if (i > start) { + sb_bytes(&sb, text + start, i - start); + continue; + } + uint32_t cp; + int n = utf8_decode_one(text + i, len - i, &cp); + if (!n) { i++; continue; } + if (cp == '\r' || cp == '\n') { + sb_ch(&sb, '\r'); + if (cp == '\r' && i + 1 < len && text[i + 1] == '\n') i++; + } else if (cp == '\t') { + sb_ch(&sb, '\t'); + } else if (cp >= 0xa0 && !is_direction_format(cp)) { + sb_bytes(&sb, text + i, (size_t)n); + } + i += (size_t)n; + } + + out.status = TERM_INPUT_CONSUMED; + if (!sb.overflow && sb_len(&sb, buf) == body) return out; + if (m->bracketed_paste) sb_bytes(&sb, "\033[201~", 6); + if (sb.overflow) { + out.status = TERM_INPUT_BUFFER_FULL; + return out; + } + out.len = sb_len(&sb, buf); + out.status = TERM_INPUT_BYTES; + return out; +} |
