From da28548905dab7c60c3fcec4975ccfa23e315909 Mon Sep 17 00:00:00 2001 From: Dylan Araps Date: Fri, 27 Feb 2026 13:41:56 +0200 Subject: 0.99.0 --- lib/term_key.h | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 lib/term_key.h (limited to 'lib/term_key.h') diff --git a/lib/term_key.h b/lib/term_key.h new file mode 100644 index 0000000..f92ef80 --- /dev/null +++ b/lib/term_key.h @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_TERM_KEY_H +#define DYLAN_TERM_KEY_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "utf8.h" +#include "util.h" + +// +// Store encoded special keys alongside utf8 codepoints in an unused range. +// +#define KEY_TAG 0x80000000u +#define KEY_SYM 0x40000000u +#define KEY_MOD_SHIFT 27u +#define KEY_MOD_MASK (0x7u << KEY_MOD_SHIFT) +#define KEY_TXT_MASK 0x001FFFFFu +#define KEY_SYM_MASK 0x000000FFu +#define KEY_IS_SYM(k) ((((u32)(k)) & (KEY_TAG|KEY_SYM)) == (KEY_TAG|KEY_SYM)) +#define KEY_GET_MOD(k) ((((u32)(k)) & KEY_MOD_MASK) >> KEY_MOD_SHIFT) + +#define K(m, c) \ +((u32)(m) == 0u ? (u32)(c) : (KEY_IS_SYM(c) \ +? (((u32)(c) & ~KEY_MOD_MASK) | (((u32)(m) & 0x7u) << KEY_MOD_SHIFT)) \ +: (KEY_TAG | (((u32)(m) & 0x7u) << KEY_MOD_SHIFT) | ((u32)(c) & KEY_TXT_MASK)))) + +#define KEY_REG(id) (KEY_TAG|KEY_SYM | ((u32)(id) & KEY_SYM_MASK)) + +#define MOD_SHIFT (1u << 0) +#define MOD_ALT (1u << 1) +#define MOD_CTRL (1u << 2) + +#define KEY_ESCAPE 27 +#define KEY_BACKSPACE 127 +#define KEY_TAB K(MOD_CTRL, 'i') +#define KEY_SHIFT_TAB K(MOD_SHIFT|MOD_CTRL, 'i') +#define KEY_ENTER K(MOD_CTRL, 'm') +#define KEY_UP KEY_REG(1) +#define KEY_DOWN KEY_REG(2) +#define KEY_LEFT KEY_REG(3) +#define KEY_RIGHT KEY_REG(4) +#define KEY_HOME KEY_REG(5) +#define KEY_END KEY_REG(6) +#define KEY_PAGE_UP KEY_REG(7) +#define KEY_PAGE_DOWN KEY_REG(8) +#define KEY_INSERT KEY_REG(9) +#define KEY_DELETE KEY_REG(10) +#define KEY_F1 KEY_REG(11) +#define KEY_F2 KEY_REG(12) +#define KEY_F3 KEY_REG(13) +#define KEY_F4 KEY_REG(14) +#define KEY_F5 KEY_REG(15) +#define KEY_F6 KEY_REG(16) +#define KEY_F7 KEY_REG(17) +#define KEY_F8 KEY_REG(18) +#define KEY_F9 KEY_REG(19) +#define KEY_F10 KEY_REG(20) +#define KEY_F11 KEY_REG(21) +#define KEY_F12 KEY_REG(22) +#define KEY_PASTE KEY_REG(23) +#define KEY_PASTE_END KEY_REG(24) + +struct term_key { + u8 b[64]; + u16 l; + u32 c; +}; + +static inline u32 +term_key_csi_tilde(u8 c, u32 m) +{ + switch (c) { + case 1: return K(m, KEY_HOME); + case 2: return K(m, KEY_INSERT); + case 3: return K(m, KEY_DELETE); + case 4: return K(m, KEY_END); + case 5: return K(m, KEY_PAGE_UP); + case 6: return K(m, KEY_PAGE_DOWN); + case 7: return K(m, KEY_HOME); + case 8: return K(m, KEY_END); + case 11: return K(m, KEY_F1); + case 12: return K(m, KEY_F2); + case 13: return K(m, KEY_F3); + case 14: return K(m, KEY_F4); + case 15: return K(m, KEY_F5); + case 17: return K(m, KEY_F6); + case 18: return K(m, KEY_F7); + case 19: return K(m, KEY_F8); + case 20: return K(m, KEY_F9); + case 21: return K(m, KEY_F10); + case 23: return K(m, KEY_F11); + case 24: return K(m, KEY_F12); + case 200: return KEY_PASTE; + case 201: return KEY_PASTE_END; + default: return 0; + } +} + +static inline u32 +term_key_csi_final(u8 c, u32 m) +{ + switch (c) { + case 'A': return K(m, KEY_UP); + case 'B': return K(m, KEY_DOWN); + case 'C': return K(m, KEY_RIGHT); + case 'D': return K(m, KEY_LEFT); + case 'H': return K(m, KEY_HOME); + case 'F': return K(m, KEY_END); + case 'Z': return K(MOD_SHIFT|MOD_CTRL | m, 'i'); + default: return 0; + } +} + +static inline u32 +term_key_csi_ss3(u8 c) +{ + switch (c) { + case 'P': return K(0, KEY_F1); + case 'Q': return K(0, KEY_F2); + case 'R': return K(0, KEY_F3); + case 'S': return K(0, KEY_F4); + case 'A': return K(0, KEY_UP); + case 'B': return K(0, KEY_DOWN); + case 'C': return K(0, KEY_RIGHT); + case 'D': return K(0, KEY_LEFT); + case 'H': return K(0, KEY_HOME); + case 'F': return K(0, KEY_END); + default: return 0; + } +} + +static inline s32 +term_key_csi_int(const u8 *b, usize l, usize *i) +{ + u32 v = 0; + usize j = *i; + if (j >= l || b[j] < '0' || b[j] > '9') return -1; + for (; j < l && b[j] >= '0' && b[j] <= '9'; j++) + v = v * 10u + (u32)(b[j] - '0'); + *i = j; + return (s32) v; +} + +static inline u32 +term_key_csi_xterm_mod(u32 x) +{ + u32 m = 0; + if (x < 2 || x > 8) return m; + if (x & 1u) m |= MOD_ALT; + if (x & 2u) m |= MOD_SHIFT; + if (x & 4u) m |= MOD_CTRL; + return m; +} + +static inline bool +term_key_csi_end(u8 c) +{ + return c >= 0x40 && c <= 0x7E; +} + +static inline bool +term_key_csi_read(int fd, struct term_key *k) +{ + for (;;) { + if (k->l >= sizeof(k->b)) + return false; + if (read(fd, &k->b[k->l], 1) != 1) + return false; + u8 c = k->b[k->l++]; + if (term_key_csi_end(c)) + break; + } + return true; +} + +static inline bool +term_key_ss3(int fd, struct term_key *k) +{ + if (read(fd, &k->b[k->l], 1) != 1) return false; + k->l++; + k->c = term_key_csi_ss3(k->b[2]); + return !!k->c; +} + +static inline bool +term_key_csi(int fd, struct term_key *k) +{ + if (!term_key_csi_read(fd, k)) return false; + usize n = k->l; + if (n < 3) return false; + u8 f = k->b[n - 1]; + usize i = 2; + if (i < n && (k->b[i] == '?' || k->b[i] == '>' || k->b[i] == '<')) + i++; + s32 p1 = term_key_csi_int(k->b, n, &i); + s32 p2 = -1; + if (p1 != -1 && i < n && k->b[i] == ';') { + i++; + p2 = term_key_csi_int(k->b, n, &i); + } + u32 m = p2 != -1 ? term_key_csi_xterm_mod(p2) : 0; + if (f == '~' && p1 != -1) + k->c = term_key_csi_tilde(p1, m); + else + k->c = term_key_csi_final(f, m); + return !!k->c; +} + +static inline bool +term_key_utf8(int fd, struct term_key *k, usize o, u32 m) +{ + u8 c = k->b[o]; + if (c >= 1 && c <= 26) { + k->c = K(m | MOD_CTRL, 'a' + (c - 1)); + k->l = o + 1; + return true; + } + if (c >= 0xC0) { + usize l = utf8_expected(c); + if (!l) return false; + for (usize i = 1; i < l; i++) + if (read(fd, &k->b[o + i], 1) != 1) + return false; + int e; + u32 cp; + utf8_decode_untrusted(&k->b[o], &cp, &e); + if (e) return false; + k->c = K(m, cp); + k->l = o + l; + return true; + } + k->c = K(m, c); + k->l = o + 1; + return true; +} + +static inline bool +term_key_read(int fd, struct term_key *k) +{ + if (read(fd, k->b, 1) != 1) return false; + k->l = 1; + if (unlikely(k->b[0] == '\033')) { + struct timeval tv = { .tv_sec = 0, .tv_usec = 30000 }; + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + if (select(fd + 1, &rfds, NULL, NULL, &tv) <= 0) { + k->c = k->b[0]; + return true; + } + if (read(fd, &k->b[1], 1) != 1) return false; + k->l = 2; + switch (k->b[1]) { + case '[': return term_key_csi(fd, k); + case 'O': return term_key_ss3(fd, k); + default: return term_key_utf8(fd, k, 1, MOD_ALT); + } + } + return term_key_utf8(fd, k, 0, 0); +} + +#endif // DYLAN_TERM_KEY_H + -- cgit v1.2.3