[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
1/* input.c — VT/ANSI escape sequence parser
2 *
3 * Decodes raw terminal input bytes into structured InputEvent records.
4 * All state lives in an arena-allocated InputState; no libc required.
5 *
6 * Public API (exported to WASM):
7 * input_size — compute arena size
8 * input_init — initialize parser state in provided memory
9 * input_scan — feed bytes, produce events
10 * input_count — number of events from last scan
11 * input_event — pointer to event by index
12 * input_pending — deadline for pending ESC (0 = none)
13 */
14
15#include "input.h"
16#include "trie.h"
17#include "mem.h"
18#include "utf8.h"
19
20#define SCAN_BUFFER_SIZE 4096
21#define MAX_EVENTS 128
22
23/* ── State ────────────────────────────────────────────────────────── */
24
25struct InputState {
26 int esc_latency_ms;
27 char buf[SCAN_BUFFER_SIZE];
28 int len;
29 double esc_time;
30 struct InputEvent events[MAX_EVENTS];
31 int count;
32 int trie_len;
33 Trie trie;
34};
35
36/* ── Helpers ──────────────────────────────────────────────────────── */
37
38static void shift(struct InputState *st, int n) {
39 st->len -= n;
40 if (st->len > 0) {
41 /* move remaining bytes to front */
42 for (int i = 0; i < st->len; i++)
43 st->buf[i] = st->buf[i + n];
44 }
45}
46
47static struct InputEvent *emit(struct InputState *st) {
48 if (st->count >= MAX_EVENTS)
49 return &st->events[MAX_EVENTS - 1]; /* saturate */
50 struct InputEvent *ev = &st->events[st->count++];
51 memset(ev, 0, sizeof(*ev));
52 return ev;
53}
54
55/* ── Mouse parsing ────────────────────────────────────────────────── */
56
57#define MOUSE_BUTTON_MASK 3
58#define MOUSE_BUTTON_LEFT 0
59#define MOUSE_BUTTON_MIDDLE 1
60#define MOUSE_BUTTON_RIGHT 2
61#define MOUSE_BUTTON_RELEASE 3
62#define MOUSE_WHEEL_BIT 64
63
64static uint16_t mouse_button(int b) {
65 switch (b & MOUSE_BUTTON_MASK) {
66 case MOUSE_BUTTON_LEFT:
67 if (b & MOUSE_WHEEL_BIT) {
68 return KEY_MOUSE_WHEEL_UP;
69 } else {
70 return KEY_MOUSE_LEFT;
71 }
72 case MOUSE_BUTTON_MIDDLE:
73 if (b & MOUSE_WHEEL_BIT) {
74 return KEY_MOUSE_WHEEL_DOWN;
75 } else {
76 return KEY_MOUSE_MIDDLE;
77 }
78 case MOUSE_BUTTON_RIGHT:
79 return KEY_MOUSE_RIGHT;
80 case MOUSE_BUTTON_RELEASE:
81 return KEY_MOUSE_RELEASE;
82 default:
83 return KEY_MOUSE_LEFT;
84 }
85}
86
87static int parse_mouse_vt200(struct InputState *st, struct InputEvent *ev) {
88 /* \x1b[M + 3 bytes = 6 total */
89 if (st->len < 6)
90 return PARSE_NEED_MORE;
91 if (st->buf[0] != '\x1b' || st->buf[1] != '[' || st->buf[2] != 'M')
92 return PARSE_ERR;
93
94 int b = st->buf[3] - 0x20;
95 ev->type = EVENT_MOUSE;
96 ev->key = mouse_button(b);
97 ev->mod = (b & 32) ? MOD_MOTION : 0;
98 if (ev->key == KEY_MOUSE_RELEASE)
99 ev->mod |= MOD_RELEASE;
100 ev->x = ((uint8_t)st->buf[4]) - 0x21;
101 ev->y = ((uint8_t)st->buf[5]) - 0x21;
102
103 shift(st, 6);
104 return PARSE_OK;
105}
106
107static int parse_mouse_sgr(struct InputState *st, struct InputEvent *ev) {
108 /* \x1b[< btn ; x ; y {M|m} */
109 if (st->len < 3)
110 return PARSE_NEED_MORE;
111 if (st->buf[0] != '\x1b' || st->buf[1] != '[' || st->buf[2] != '<')
112 return PARSE_ERR;
113
114 int num[3] = {-1, -1, -1};
115 int ni = 0;
116 int cur = -1;
117 char trail = ' ';
118 int i = 3;
119
120 while (i < st->len && ni < 3) {
121 char c = st->buf[i];
122 if (c >= '0' && c <= '9') {
123 if (cur == -1)
124 cur = 0;
125 cur = cur * 10 + (c - '0');
126 } else if (cur != -1 &&
127 ((ni < 2 && c == ';') || (ni == 2 && (c == 'm' || c == 'M')))) {
128 num[ni++] = cur;
129 cur = -1;
130 trail = c;
131 } else {
132 return PARSE_ERR;
133 }
134 i++;
135 }
136
137 if (num[2] == -1)
138 return PARSE_NEED_MORE;
139
140 ev->type = EVENT_MOUSE;
141 ev->key = mouse_button(num[0]);
142 ev->mod = (num[0] & 32) ? MOD_MOTION : 0;
143 if (trail == 'm')
144 ev->mod |= MOD_RELEASE;
145 ev->x = (num[1] - 1 < 0) ? 0 : num[1] - 1;
146 ev->y = (num[2] - 1 < 0) ? 0 : num[2] - 1;
147
148 shift(st, i);
149 return PARSE_OK;
150}
151
152static int parse_mouse_urxvt(struct InputState *st, struct InputEvent *ev) {
153 /* \x1b[ btn ; x ; y M (no '<' prefix, digits start at buf[2]) */
154 if (st->len < 3)
155 return PARSE_NEED_MORE;
156 if (st->buf[0] != '\x1b' || st->buf[1] != '[')
157 return PARSE_ERR;
158 /* must start with a digit at buf[2] */
159 if (st->buf[2] < '0' || st->buf[2] > '9')
160 return PARSE_ERR;
161
162 int num[3] = {-1, -1, -1};
163 int ni = 0;
164 int cur = -1;
165 int i = 2;
166
167 while (i < st->len && ni < 3) {
168 char c = st->buf[i];
169 if (c >= '0' && c <= '9') {
170 if (cur == -1)
171 cur = 0;
172 cur = cur * 10 + (c - '0');
173 } else if (cur != -1 && ((ni < 2 && c == ';') || (ni == 2 && c == 'M'))) {
174 num[ni++] = cur;
175 cur = -1;
176 } else {
177 return PARSE_ERR;
178 }
179 i++;
180 }
181
182 if (num[2] == -1)
183 return PARSE_NEED_MORE;
184
185 int b = num[0] - 0x20;
186 ev->type = EVENT_MOUSE;
187 ev->key = mouse_button(b);
188 ev->mod = (b & 32) ? MOD_MOTION : 0;
189 if (ev->key == KEY_MOUSE_RELEASE)
190 ev->mod |= MOD_RELEASE;
191 ev->x = (num[1] - 1 < 0) ? 0 : num[1] - 1;
192 ev->y = (num[2] - 1 < 0) ? 0 : num[2] - 1;
193
194 shift(st, i);
195 return PARSE_OK;
196}
197
198static int parse_mouse(struct InputState *st, struct InputEvent *ev) {
199 if (st->len < 2)
200 return PARSE_NEED_MORE;
201 if (st->buf[0] != '\x1b' || st->buf[1] != '[')
202 return PARSE_ERR;
203 if (st->len < 3)
204 return PARSE_NEED_MORE;
205
206 int rv;
207 if (st->buf[2] == 'M') {
208 rv = parse_mouse_vt200(st, ev);
209 } else if (st->buf[2] == '<') {
210 rv = parse_mouse_sgr(st, ev);
211 } else if (st->buf[2] >= '0' && st->buf[2] <= '9') {
212 rv = parse_mouse_urxvt(st, ev);
213 } else {
214 rv = PARSE_ERR;
215 }
216 return rv;
217}
218
219/* ── Kitty keyboard protocol (CSI u) ──────────────────────────────── */
220
221static uint16_t kitty_key(int cp) {
222 switch (cp) {
223 case 9:
224 return KEY_TAB;
225 case 13:
226 return KEY_ENTER;
227 case 27:
228 return KEY_ESC;
229 case 127:
230 return KEY_BACKSPACE;
231 case 57344:
232 return KEY_ESC;
233 case 57345:
234 return KEY_ENTER;
235 case 57346:
236 return KEY_TAB;
237 case 57347:
238 return KEY_BACKSPACE;
239 case 57348:
240 return KEY_INSERT;
241 case 57349:
242 return KEY_DELETE;
243 case 57350:
244 return KEY_ARROW_LEFT;
245 case 57351:
246 return KEY_ARROW_RIGHT;
247 case 57352:
248 return KEY_ARROW_UP;
249 case 57353:
250 return KEY_ARROW_DOWN;
251 case 57354:
252 return KEY_PGUP;
253 case 57355:
254 return KEY_PGDN;
255 case 57356:
256 return KEY_HOME;
257 case 57357:
258 return KEY_END;
259 case 57376:
260 return KEY_F1;
261 case 57377:
262 return KEY_F2;
263 case 57378:
264 return KEY_F3;
265 case 57379:
266 return KEY_F4;
267 case 57380:
268 return KEY_F5;
269 case 57381:
270 return KEY_F6;
271 case 57382:
272 return KEY_F7;
273 case 57383:
274 return KEY_F8;
275 case 57384:
276 return KEY_F9;
277 case 57385:
278 return KEY_F10;
279 case 57386:
280 return KEY_F11;
281 case 57387:
282 return KEY_F12;
283 case 57399:
284 return KEY_NUMPAD_0;
285 case 57400:
286 return KEY_NUMPAD_1;
287 case 57401:
288 return KEY_NUMPAD_2;
289 case 57402:
290 return KEY_NUMPAD_3;
291 case 57403:
292 return KEY_NUMPAD_4;
293 case 57404:
294 return KEY_NUMPAD_5;
295 case 57405:
296 return KEY_NUMPAD_6;
297 case 57406:
298 return KEY_NUMPAD_7;
299 case 57407:
300 return KEY_NUMPAD_8;
301 case 57408:
302 return KEY_NUMPAD_9;
303 case 57409:
304 return KEY_NUMPAD_DECIMAL;
305 case 57410:
306 return KEY_NUMPAD_DIVIDE;
307 case 57411:
308 return KEY_NUMPAD_MULTIPLY;
309 case 57412:
310 return KEY_NUMPAD_SUBTRACT;
311 case 57413:
312 return KEY_NUMPAD_ADD;
313 case 57414:
314 return KEY_NUMPAD_ENTER;
315 case 57415:
316 return KEY_NUMPAD_EQUAL;
317 case 57441:
318 return KEY_SHIFT_LEFT;
319 case 57442:
320 return KEY_CONTROL_LEFT;
321 case 57443:
322 return KEY_ALT_LEFT;
323 case 57444:
324 return KEY_SUPER_LEFT;
325 case 57445:
326 return KEY_HYPER_LEFT;
327 case 57446:
328 return KEY_META_LEFT;
329 case 57447:
330 return KEY_SHIFT_RIGHT;
331 case 57448:
332 return KEY_CONTROL_RIGHT;
333 case 57449:
334 return KEY_ALT_RIGHT;
335 case 57450:
336 return KEY_SUPER_RIGHT;
337 case 57451:
338 return KEY_HYPER_RIGHT;
339 case 57452:
340 return KEY_META_RIGHT;
341 case 57358:
342 return KEY_CAPS_LOCK;
343 case 57359:
344 return KEY_NUM_LOCK;
345 case 57360:
346 return KEY_SCROLL_LOCK;
347 default:
348 return 0;
349 }
350}
351
352static uint8_t kitty_mod(int mod) {
353 if (mod <= 1)
354 return 0;
355 int bits = mod - 1;
356 uint8_t out = 0;
357 if (bits & 1)
358 out |= MOD_SHIFT;
359 if (bits & 2)
360 out |= MOD_ALT;
361 if (bits & 4)
362 out |= MOD_CTRL;
363 return out;
364}
365
366static int parse_csi_u(struct InputState *st, struct InputEvent *ev) {
367 /* CSI key:shifted:base ; mod:action ; text u */
368 if (st->len < 2)
369 return PARSE_NEED_MORE;
370 if (st->buf[0] != '\x1b' || st->buf[1] != '[')
371 return PARSE_ERR;
372 if (st->len < 4)
373 return PARSE_NEED_MORE;
374 if (st->buf[2] < '0' && st->buf[2] != ';')
375 return PARSE_ERR;
376 if (st->buf[2] > '9' && st->buf[2] != ';')
377 return PARSE_ERR;
378
379 /* fields[param][sub]: up to 3 params, up to 3 sub-fields each */
380 int fields[3][3];
381 for (int p = 0; p < 3; p++)
382 for (int s = 0; s < 3; s++)
383 fields[p][s] = -1;
384
385 int param = 0;
386 int sub = 0;
387 int cur = -1;
388 int i = 2;
389 int done = 0;
390 int text_start = -1;
391 int text_end = -1;
392
393 while (i < st->len && !done) {
394 char c = st->buf[i];
395 if (c >= '0' && c <= '9') {
396 if (cur == -1)
397 cur = 0;
398 cur = cur * 10 + (c - '0');
399 } else if (c == ':') {
400 if (param < 3 && sub < 3)
401 fields[param][sub] = cur;
402 cur = -1;
403 sub++;
404 if (param == 2 && text_start == -1)
405 text_start = i + 1;
406 } else if (c == ';') {
407 if (param < 3 && sub < 3)
408 fields[param][sub] = cur;
409 cur = -1;
410 param++;
411 sub = 0;
412 } else if (c == 'u') {
413 if (param < 3 && sub < 3)
414 fields[param][sub] = cur;
415 if (param == 2 && text_start != -1) {
416 text_end = i;
417 }
418 done = 1;
419 } else {
420 return PARSE_ERR;
421 }
422 i++;
423 }
424
425 if (!done)
426 return PARSE_NEED_MORE;
427
428 int cp = fields[0][0];
429 int mod = fields[1][0];
430
431 if (cp == -1)
432 cp = 0;
433 if (mod == -1)
434 mod = 1;
435
436 ev->type = EVENT_KEY;
437 ev->mod = kitty_mod(mod);
438
439 uint16_t key = kitty_key(cp);
440 if (key) {
441 ev->key = key;
442 } else {
443 ev->ch = (uint32_t)cp;
444 }
445
446 /* action */
447 if (fields[1][1] > 0) {
448 ev->action = (uint8_t)fields[1][1];
449 }
450
451 /* alternate keys */
452 if (fields[0][1] > 0) {
453 ev->shifted = (uint32_t)fields[0][1];
454 }
455 if (fields[0][2] > 0) {
456 ev->base = (uint32_t)fields[0][2];
457 }
458
459 /* associated text: decode codepoints from param 2 */
460 if (param >= 2 && fields[2][0] >= 0) {
461 /* find the start of param 2 by counting semicolons */
462 int off = 2;
463 int sc = 0;
464 for (int j = 2; j < i; j++) {
465 if (st->buf[j] == ';') {
466 sc++;
467 if (sc == 2) {
468 off = j + 1;
469 break;
470 }
471 }
472 }
473 /* parse colon-separated codepoints */
474 int tc = 0;
475 int val = -1;
476 for (int j = off; j < i - 1 && tc < MAX_TEXT_CODEPOINTS; j++) {
477 char c = st->buf[j];
478 if (c >= '0' && c <= '9') {
479 if (val == -1) val = 0;
480 val = val * 10 + (c - '0');
481 } else if (c == ':') {
482 if (val >= 0)
483 ev->text[tc++] = (uint32_t)val;
484 val = -1;
485 }
486 }
487 if (val >= 0 && tc < MAX_TEXT_CODEPOINTS)
488 ev->text[tc++] = (uint32_t)val;
489 ev->text_len = (uint8_t)tc;
490 }
491
492 shift(st, i);
493 return PARSE_OK;
494}
495
496static uint16_t csi_legacy_key(char term, int number) {
497 switch (term) {
498 case 'A': return KEY_ARROW_UP;
499 case 'B': return KEY_ARROW_DOWN;
500 case 'C': return KEY_ARROW_RIGHT;
501 case 'D': return KEY_ARROW_LEFT;
502 case 'H': return KEY_HOME;
503 case 'F': return KEY_END;
504 case 'P': return KEY_F1;
505 case 'Q': return KEY_F2;
506 case 'S': return KEY_F4;
507 case '~':
508 switch (number) {
509 case 2: return KEY_INSERT;
510 case 3: return KEY_DELETE;
511 case 5: return KEY_PGUP;
512 case 6: return KEY_PGDN;
513 case 7: return KEY_HOME;
514 case 8: return KEY_END;
515 case 11: return KEY_F1;
516 case 12: return KEY_F2;
517 case 13: return KEY_F3;
518 case 14: return KEY_F4;
519 case 15: return KEY_F5;
520 case 17: return KEY_F6;
521 case 18: return KEY_F7;
522 case 19: return KEY_F8;
523 case 20: return KEY_F9;
524 case 21: return KEY_F10;
525 case 23: return KEY_F11;
526 case 24: return KEY_F12;
527 default: return 0;
528 }
529 default: return 0;
530 }
531}
532
533/* Parse Kitty-enhanced legacy CSI sequences (non-u terminators).
534 * Format: CSI [number] [; mod[:action]] terminator
535 * Handles A-D, F, H, P, Q, S, ~ terminators with optional :action */
536static int parse_csi_legacy(struct InputState *st, struct InputEvent *ev) {
537 if (st->len < 2)
538 return PARSE_NEED_MORE;
539 if (st->buf[0] != '\x1b' || st->buf[1] != '[')
540 return PARSE_ERR;
541 if (st->len < 3)
542 return PARSE_NEED_MORE;
543
544 int number = -1;
545 int mod = -1;
546 int action = -1;
547 int param = 0;
548 int sub = 0;
549 int i = 2;
550 int cur = -1;
551 char term = 0;
552
553 while (i < st->len) {
554 char c = st->buf[i];
555 if (c >= '0' && c <= '9') {
556 if (cur == -1)
557 cur = 0;
558 cur = cur * 10 + (c - '0');
559 } else if (c == ';') {
560 if (param == 0)
561 number = cur;
562 else if (param == 1 && sub == 0)
563 mod = cur;
564 cur = -1;
565 param++;
566 sub = 0;
567 } else if (c == ':') {
568 if (param == 1 && sub == 0)
569 mod = cur;
570 cur = -1;
571 sub++;
572 } else if ((c >= 'A' && c <= 'D') || c == 'F' || c == 'H' ||
573 c == 'P' || c == 'Q' || c == 'S' || c == '~') {
574 if (param == 0)
575 number = cur;
576 else if (param == 1 && sub == 0)
577 mod = cur;
578 else if (param == 1 && sub > 0)
579 action = cur;
580 term = c;
581 i++;
582 break;
583 } else {
584 return PARSE_ERR;
585 }
586 i++;
587 }
588
589 if (term == 0)
590 return PARSE_NEED_MORE;
591
592 uint16_t key = csi_legacy_key(term, number > 0 ? number : 0);
593 if (key == 0)
594 return PARSE_ERR;
595
596 ev->type = EVENT_KEY;
597 ev->key = key;
598 ev->mod = kitty_mod(mod > 0 ? mod : 1);
599 if (action > 0)
600 ev->action = (uint8_t)action;
601
602 shift(st, i);
603 return PARSE_OK;
604}
605
606/* ── Cap table (xterm defaults) ───────────────────────────────────── */
607
608struct CapEntry {
609 const char *seq;
610 uint16_t key;
611 uint8_t mod;
612};
613
614static const struct CapEntry base_caps[] = {
615 /* xterm base keys (no modifiers) */
616 {"\x1bOP", KEY_F1, 0},
617 {"\x1bOQ", KEY_F2, 0},
618 {"\x1bOR", KEY_F3, 0},
619 {"\x1bOS", KEY_F4, 0},
620 {"\x1b[15~", KEY_F5, 0},
621 {"\x1b[17~", KEY_F6, 0},
622 {"\x1b[18~", KEY_F7, 0},
623 {"\x1b[19~", KEY_F8, 0},
624 {"\x1b[20~", KEY_F9, 0},
625 {"\x1b[21~", KEY_F10, 0},
626 {"\x1b[23~", KEY_F11, 0},
627 {"\x1b[24~", KEY_F12, 0},
628 {"\x1b[2~", KEY_INSERT, 0},
629 {"\x1b[3~", KEY_DELETE, 0},
630 {"\x1bOH", KEY_HOME, 0},
631 {"\x1bOF", KEY_END, 0},
632 {"\x1b[5~", KEY_PGUP, 0},
633 {"\x1b[6~", KEY_PGDN, 0},
634 {"\x1bOA", KEY_ARROW_UP, 0},
635 {"\x1bOB", KEY_ARROW_DOWN, 0},
636 {"\x1bOD", KEY_ARROW_LEFT, 0},
637 {"\x1bOC", KEY_ARROW_RIGHT, 0},
638 {"\x1b[Z", KEY_BACKTAB, 0},
639 /* alternate arrow sequences (CSI, used by many terminals) */
640 {"\x1b[A", KEY_ARROW_UP, 0},
641 {"\x1b[B", KEY_ARROW_DOWN, 0},
642 {"\x1b[D", KEY_ARROW_LEFT, 0},
643 {"\x1b[C", KEY_ARROW_RIGHT, 0},
644 /* alternate home/end (CSI ~ style) */
645 {"\x1b[1~", KEY_HOME, 0},
646 {"\x1b[4~", KEY_END, 0},
647 {"\x1b[H", KEY_HOME, 0},
648 {"\x1b[F", KEY_END, 0},
649 {0, 0, 0},
650};
651
652/* xterm modifier combos — modifier code: 2=Shift 3=Alt 4=Alt+Shift
653 * 5=Ctrl 6=Ctrl+Shift 7=Ctrl+Alt 8=Ctrl+Alt+Shift */
654static const struct CapEntry mod_caps[] = {
655 /* arrows */
656 {"\x1b[1;2A", KEY_ARROW_UP, MOD_SHIFT},
657 {"\x1b[1;3A", KEY_ARROW_UP, MOD_ALT},
658 {"\x1b[1;4A", KEY_ARROW_UP, MOD_ALT | MOD_SHIFT},
659 {"\x1b[1;5A", KEY_ARROW_UP, MOD_CTRL},
660 {"\x1b[1;6A", KEY_ARROW_UP, MOD_CTRL | MOD_SHIFT},
661 {"\x1b[1;7A", KEY_ARROW_UP, MOD_CTRL | MOD_ALT},
662 {"\x1b[1;8A", KEY_ARROW_UP, MOD_CTRL | MOD_ALT | MOD_SHIFT},
663
664 {"\x1b[1;2B", KEY_ARROW_DOWN, MOD_SHIFT},
665 {"\x1b[1;3B", KEY_ARROW_DOWN, MOD_ALT},
666 {"\x1b[1;4B", KEY_ARROW_DOWN, MOD_ALT | MOD_SHIFT},
667 {"\x1b[1;5B", KEY_ARROW_DOWN, MOD_CTRL},
668 {"\x1b[1;6B", KEY_ARROW_DOWN, MOD_CTRL | MOD_SHIFT},
669 {"\x1b[1;7B", KEY_ARROW_DOWN, MOD_CTRL | MOD_ALT},
670 {"\x1b[1;8B", KEY_ARROW_DOWN, MOD_CTRL | MOD_ALT | MOD_SHIFT},
671
672 {"\x1b[1;2C", KEY_ARROW_RIGHT, MOD_SHIFT},
673 {"\x1b[1;3C", KEY_ARROW_RIGHT, MOD_ALT},
674 {"\x1b[1;4C", KEY_ARROW_RIGHT, MOD_ALT | MOD_SHIFT},
675 {"\x1b[1;5C", KEY_ARROW_RIGHT, MOD_CTRL},
676 {"\x1b[1;6C", KEY_ARROW_RIGHT, MOD_CTRL | MOD_SHIFT},
677 {"\x1b[1;7C", KEY_ARROW_RIGHT, MOD_CTRL | MOD_ALT},
678 {"\x1b[1;8C", KEY_ARROW_RIGHT, MOD_CTRL | MOD_ALT | MOD_SHIFT},
679
680 {"\x1b[1;2D", KEY_ARROW_LEFT, MOD_SHIFT},
681 {"\x1b[1;3D", KEY_ARROW_LEFT, MOD_ALT},
682 {"\x1b[1;4D", KEY_ARROW_LEFT, MOD_ALT | MOD_SHIFT},
683 {"\x1b[1;5D", KEY_ARROW_LEFT, MOD_CTRL},
684 {"\x1b[1;6D", KEY_ARROW_LEFT, MOD_CTRL | MOD_SHIFT},
685 {"\x1b[1;7D", KEY_ARROW_LEFT, MOD_CTRL | MOD_ALT},
686 {"\x1b[1;8D", KEY_ARROW_LEFT, MOD_CTRL | MOD_ALT | MOD_SHIFT},
687
688 /* home/end */
689 {"\x1b[1;2H", KEY_HOME, MOD_SHIFT},
690 {"\x1b[1;3H", KEY_HOME, MOD_ALT},
691 {"\x1b[1;4H", KEY_HOME, MOD_ALT | MOD_SHIFT},
692 {"\x1b[1;5H", KEY_HOME, MOD_CTRL},
693 {"\x1b[1;6H", KEY_HOME, MOD_CTRL | MOD_SHIFT},
694 {"\x1b[1;7H", KEY_HOME, MOD_CTRL | MOD_ALT},
695 {"\x1b[1;8H", KEY_HOME, MOD_CTRL | MOD_ALT | MOD_SHIFT},
696
697 {"\x1b[1;2F", KEY_END, MOD_SHIFT},
698 {"\x1b[1;3F", KEY_END, MOD_ALT},
699 {"\x1b[1;4F", KEY_END, MOD_ALT | MOD_SHIFT},
700 {"\x1b[1;5F", KEY_END, MOD_CTRL},
701 {"\x1b[1;6F", KEY_END, MOD_CTRL | MOD_SHIFT},
702 {"\x1b[1;7F", KEY_END, MOD_CTRL | MOD_ALT},
703 {"\x1b[1;8F", KEY_END, MOD_CTRL | MOD_ALT | MOD_SHIFT},
704
705 /* insert */
706 {"\x1b[2;2~", KEY_INSERT, MOD_SHIFT},
707 {"\x1b[2;3~", KEY_INSERT, MOD_ALT},
708 {"\x1b[2;4~", KEY_INSERT, MOD_ALT | MOD_SHIFT},
709 {"\x1b[2;5~", KEY_INSERT, MOD_CTRL},
710 {"\x1b[2;6~", KEY_INSERT, MOD_CTRL | MOD_SHIFT},
711 {"\x1b[2;7~", KEY_INSERT, MOD_CTRL | MOD_ALT},
712 {"\x1b[2;8~", KEY_INSERT, MOD_CTRL | MOD_ALT | MOD_SHIFT},
713
714 /* delete */
715 {"\x1b[3;2~", KEY_DELETE, MOD_SHIFT},
716 {"\x1b[3;3~", KEY_DELETE, MOD_ALT},
717 {"\x1b[3;4~", KEY_DELETE, MOD_ALT | MOD_SHIFT},
718 {"\x1b[3;5~", KEY_DELETE, MOD_CTRL},
719 {"\x1b[3;6~", KEY_DELETE, MOD_CTRL | MOD_SHIFT},
720 {"\x1b[3;7~", KEY_DELETE, MOD_CTRL | MOD_ALT},
721 {"\x1b[3;8~", KEY_DELETE, MOD_CTRL | MOD_ALT | MOD_SHIFT},
722
723 /* pgup */
724 {"\x1b[5;2~", KEY_PGUP, MOD_SHIFT},
725 {"\x1b[5;3~", KEY_PGUP, MOD_ALT},
726 {"\x1b[5;4~", KEY_PGUP, MOD_ALT | MOD_SHIFT},
727 {"\x1b[5;5~", KEY_PGUP, MOD_CTRL},
728 {"\x1b[5;6~", KEY_PGUP, MOD_CTRL | MOD_SHIFT},
729 {"\x1b[5;7~", KEY_PGUP, MOD_CTRL | MOD_ALT},
730 {"\x1b[5;8~", KEY_PGUP, MOD_CTRL | MOD_ALT | MOD_SHIFT},
731
732 /* pgdn */
733 {"\x1b[6;2~", KEY_PGDN, MOD_SHIFT},
734 {"\x1b[6;3~", KEY_PGDN, MOD_ALT},
735 {"\x1b[6;4~", KEY_PGDN, MOD_ALT | MOD_SHIFT},
736 {"\x1b[6;5~", KEY_PGDN, MOD_CTRL},
737 {"\x1b[6;6~", KEY_PGDN, MOD_CTRL | MOD_SHIFT},
738 {"\x1b[6;7~", KEY_PGDN, MOD_CTRL | MOD_ALT},
739 {"\x1b[6;8~", KEY_PGDN, MOD_CTRL | MOD_ALT | MOD_SHIFT},
740
741 /* F1–F4 */
742 {"\x1b[1;2P", KEY_F1, MOD_SHIFT},
743 {"\x1b[1;3P", KEY_F1, MOD_ALT},
744 {"\x1b[1;4P", KEY_F1, MOD_ALT | MOD_SHIFT},
745 {"\x1b[1;5P", KEY_F1, MOD_CTRL},
746 {"\x1b[1;6P", KEY_F1, MOD_CTRL | MOD_SHIFT},
747 {"\x1b[1;7P", KEY_F1, MOD_CTRL | MOD_ALT},
748 {"\x1b[1;8P", KEY_F1, MOD_CTRL | MOD_ALT | MOD_SHIFT},
749
750 {"\x1b[1;2Q", KEY_F2, MOD_SHIFT},
751 {"\x1b[1;3Q", KEY_F2, MOD_ALT},
752 {"\x1b[1;4Q", KEY_F2, MOD_ALT | MOD_SHIFT},
753 {"\x1b[1;5Q", KEY_F2, MOD_CTRL},
754 {"\x1b[1;6Q", KEY_F2, MOD_CTRL | MOD_SHIFT},
755 {"\x1b[1;7Q", KEY_F2, MOD_CTRL | MOD_ALT},
756 {"\x1b[1;8Q", KEY_F2, MOD_CTRL | MOD_ALT | MOD_SHIFT},
757
758 {"\x1b[1;2R", KEY_F3, MOD_SHIFT},
759 {"\x1b[1;3R", KEY_F3, MOD_ALT},
760 {"\x1b[1;4R", KEY_F3, MOD_ALT | MOD_SHIFT},
761 {"\x1b[1;5R", KEY_F3, MOD_CTRL},
762 {"\x1b[1;6R", KEY_F3, MOD_CTRL | MOD_SHIFT},
763 {"\x1b[1;7R", KEY_F3, MOD_CTRL | MOD_ALT},
764 {"\x1b[1;8R", KEY_F3, MOD_CTRL | MOD_ALT | MOD_SHIFT},
765
766 {"\x1b[1;2S", KEY_F4, MOD_SHIFT},
767 {"\x1b[1;3S", KEY_F4, MOD_ALT},
768 {"\x1b[1;4S", KEY_F4, MOD_ALT | MOD_SHIFT},
769 {"\x1b[1;5S", KEY_F4, MOD_CTRL},
770 {"\x1b[1;6S", KEY_F4, MOD_CTRL | MOD_SHIFT},
771 {"\x1b[1;7S", KEY_F4, MOD_CTRL | MOD_ALT},
772 {"\x1b[1;8S", KEY_F4, MOD_CTRL | MOD_ALT | MOD_SHIFT},
773
774 /* F5–F12 */
775 {"\x1b[15;2~", KEY_F5, MOD_SHIFT},
776 {"\x1b[15;3~", KEY_F5, MOD_ALT},
777 {"\x1b[15;4~", KEY_F5, MOD_ALT | MOD_SHIFT},
778 {"\x1b[15;5~", KEY_F5, MOD_CTRL},
779 {"\x1b[15;6~", KEY_F5, MOD_CTRL | MOD_SHIFT},
780 {"\x1b[15;7~", KEY_F5, MOD_CTRL | MOD_ALT},
781 {"\x1b[15;8~", KEY_F5, MOD_CTRL | MOD_ALT | MOD_SHIFT},
782
783 {"\x1b[17;2~", KEY_F6, MOD_SHIFT},
784 {"\x1b[17;3~", KEY_F6, MOD_ALT},
785 {"\x1b[17;4~", KEY_F6, MOD_ALT | MOD_SHIFT},
786 {"\x1b[17;5~", KEY_F6, MOD_CTRL},
787 {"\x1b[17;6~", KEY_F6, MOD_CTRL | MOD_SHIFT},
788 {"\x1b[17;7~", KEY_F6, MOD_CTRL | MOD_ALT},
789 {"\x1b[17;8~", KEY_F6, MOD_CTRL | MOD_ALT | MOD_SHIFT},
790
791 {"\x1b[18;2~", KEY_F7, MOD_SHIFT},
792 {"\x1b[18;3~", KEY_F7, MOD_ALT},
793 {"\x1b[18;4~", KEY_F7, MOD_ALT | MOD_SHIFT},
794 {"\x1b[18;5~", KEY_F7, MOD_CTRL},
795 {"\x1b[18;6~", KEY_F7, MOD_CTRL | MOD_SHIFT},
796 {"\x1b[18;7~", KEY_F7, MOD_CTRL | MOD_ALT},
797 {"\x1b[18;8~", KEY_F7, MOD_CTRL | MOD_ALT | MOD_SHIFT},
798
799 {"\x1b[19;2~", KEY_F8, MOD_SHIFT},
800 {"\x1b[19;3~", KEY_F8, MOD_ALT},
801 {"\x1b[19;4~", KEY_F8, MOD_ALT | MOD_SHIFT},
802 {"\x1b[19;5~", KEY_F8, MOD_CTRL},
803 {"\x1b[19;6~", KEY_F8, MOD_CTRL | MOD_SHIFT},
804 {"\x1b[19;7~", KEY_F8, MOD_CTRL | MOD_ALT},
805 {"\x1b[19;8~", KEY_F8, MOD_CTRL | MOD_ALT | MOD_SHIFT},
806
807 {"\x1b[20;2~", KEY_F9, MOD_SHIFT},
808 {"\x1b[20;3~", KEY_F9, MOD_ALT},
809 {"\x1b[20;4~", KEY_F9, MOD_ALT | MOD_SHIFT},
810 {"\x1b[20;5~", KEY_F9, MOD_CTRL},
811 {"\x1b[20;6~", KEY_F9, MOD_CTRL | MOD_SHIFT},
812 {"\x1b[20;7~", KEY_F9, MOD_CTRL | MOD_ALT},
813 {"\x1b[20;8~", KEY_F9, MOD_CTRL | MOD_ALT | MOD_SHIFT},
814
815 {"\x1b[21;2~", KEY_F10, MOD_SHIFT},
816 {"\x1b[21;3~", KEY_F10, MOD_ALT},
817 {"\x1b[21;4~", KEY_F10, MOD_ALT | MOD_SHIFT},
818 {"\x1b[21;5~", KEY_F10, MOD_CTRL},
819 {"\x1b[21;6~", KEY_F10, MOD_CTRL | MOD_SHIFT},
820 {"\x1b[21;7~", KEY_F10, MOD_CTRL | MOD_ALT},
821 {"\x1b[21;8~", KEY_F10, MOD_CTRL | MOD_ALT | MOD_SHIFT},
822
823 {"\x1b[23;2~", KEY_F11, MOD_SHIFT},
824 {"\x1b[23;3~", KEY_F11, MOD_ALT},
825 {"\x1b[23;4~", KEY_F11, MOD_ALT | MOD_SHIFT},
826 {"\x1b[23;5~", KEY_F11, MOD_CTRL},
827 {"\x1b[23;6~", KEY_F11, MOD_CTRL | MOD_SHIFT},
828 {"\x1b[23;7~", KEY_F11, MOD_CTRL | MOD_ALT},
829 {"\x1b[23;8~", KEY_F11, MOD_CTRL | MOD_ALT | MOD_SHIFT},
830
831 {"\x1b[24;2~", KEY_F12, MOD_SHIFT},
832 {"\x1b[24;3~", KEY_F12, MOD_ALT},
833 {"\x1b[24;4~", KEY_F12, MOD_ALT | MOD_SHIFT},
834 {"\x1b[24;5~", KEY_F12, MOD_CTRL},
835 {"\x1b[24;6~", KEY_F12, MOD_CTRL | MOD_SHIFT},
836 {"\x1b[24;7~", KEY_F12, MOD_CTRL | MOD_ALT},
837 {"\x1b[24;8~", KEY_F12, MOD_CTRL | MOD_ALT | MOD_SHIFT},
838
839 {0, 0, 0},
840};
841
842/* ── Public API ───────────────────────────────────────────────────── */
843
844int input_size(void) { return align8((int)sizeof(struct InputState)); }
845
846struct InputState *input_init(void *mem, int esc_latency_ms) {
847 struct InputState *st = (struct InputState *)mem;
848 memset(st, 0, sizeof(struct InputState));
849 st->esc_latency_ms = esc_latency_ms;
850
851 /* build escape sequence trie from cap tables */
852 trie_init(st->trie, &st->trie_len);
853 for (int i = 0; base_caps[i].seq; i++)
854 trie_add(st->trie, &st->trie_len, base_caps[i].seq,
855 strlen(base_caps[i].seq), base_caps[i].key, base_caps[i].mod);
856 for (int i = 0; mod_caps[i].seq; i++)
857 trie_add(st->trie, &st->trie_len, mod_caps[i].seq, strlen(mod_caps[i].seq),
858 mod_caps[i].key, mod_caps[i].mod);
859
860 return st;
861}
862
863int input_scan(struct InputState *st, const char *buf, int len, double now) {
864
865 /* append incoming bytes (may be partial if buffer is full) */
866 int space = SCAN_BUFFER_SIZE - st->len;
867 int accepted = len < space ? len : space;
868 if (accepted > 0) {
869 memcpy(st->buf + st->len, buf, accepted);
870 st->len += accepted;
871 }
872
873 st->count = 0;
874
875 while (st->len > 0 && st->count < MAX_EVENTS) {
876 /* ── ESC handling ───────────────────────────────────────────── */
877 if ((uint8_t)st->buf[0] == 0x1b) {
878 /* lone ESC? */
879 if (st->len == 1) {
880 if (st->esc_time == 0)
881 st->esc_time = now;
882 if (now - st->esc_time >= (double)st->esc_latency_ms) {
883 struct InputEvent *ev = emit(st);
884 ev->type = EVENT_KEY;
885 ev->key = KEY_ESC;
886 shift(st, 1);
887 st->esc_time = 0;
888 continue;
889 }
890 /* pending — caller should retry after timeout */
891 return accepted;
892 }
893
894 /* try trie match */
895 {
896 int consumed = 0;
897 uint16_t key = 0;
898 uint8_t mod = 0;
899 int rv = trie_match(st->trie, st->buf, st->len, &consumed, &key, &mod);
900 if (rv == PARSE_OK) {
901 struct InputEvent *ev = emit(st);
902 ev->type = EVENT_KEY;
903 ev->key = key;
904 ev->mod = mod;
905 shift(st, consumed);
906 st->esc_time = 0;
907 continue;
908 }
909 if (rv == PARSE_NEED_MORE) {
910 return accepted;
911 }
912 }
913
914 /* try mouse */
915 {
916 struct InputEvent mev;
917 memset(&mev, 0, sizeof(mev));
918 int rv = parse_mouse(st, &mev);
919 if (rv == PARSE_OK) {
920 struct InputEvent *ev = emit(st);
921 *ev = mev;
922 st->esc_time = 0;
923 continue;
924 }
925 if (rv == PARSE_NEED_MORE) {
926 return accepted;
927 }
928 }
929
930 /* try CSI u (Kitty keyboard protocol) */
931 {
932 struct InputEvent kev;
933 memset(&kev, 0, sizeof(kev));
934 int rv = parse_csi_u(st, &kev);
935 if (rv == PARSE_OK) {
936 struct InputEvent *ev = emit(st);
937 *ev = kev;
938 st->esc_time = 0;
939 continue;
940 }
941 if (rv == PARSE_NEED_MORE) {
942 return accepted;
943 }
944 }
945
946 /* try Kitty-enhanced legacy CSI (arrows, fn keys, etc.) */
947 {
948 struct InputEvent kev;
949 memset(&kev, 0, sizeof(kev));
950 int rv = parse_csi_legacy(st, &kev);
951 if (rv == PARSE_OK) {
952 struct InputEvent *ev = emit(st);
953 *ev = kev;
954 st->esc_time = 0;
955 continue;
956 }
957 if (rv == PARSE_NEED_MORE) {
958 return accepted;
959 }
960 }
961
962 /* unrecognized ESC sequence: treat as Alt + next byte */
963 shift(st, 1);
964 st->esc_time = 0;
965
966 if (st->len > 0) {
967 uint8_t b = (uint8_t)st->buf[0];
968 struct InputEvent *ev = emit(st);
969 ev->type = EVENT_KEY;
970 ev->mod = MOD_ALT;
971
972 if (b < 0x20) {
973 ev->key = b;
974 ev->mod |= MOD_CTRL;
975 shift(st, 1);
976 } else if (b == 0x7f) {
977 ev->key = KEY_BACKSPACE;
978 shift(st, 1);
979 } else if (b >= 0x20 && b < 0x80) {
980 ev->ch = b;
981 shift(st, 1);
982 } else {
983 /* Alt + UTF-8 char */
984 int need = utf8_len(b);
985 if (need > st->len) {
986 return accepted; /* need more */
987 }
988 uint32_t cp;
989 utf8_decode(&cp, st->buf);
990 ev->ch = cp;
991 shift(st, need);
992 }
993 }
994 continue;
995 }
996
997 /* ── Control characters ─────────────────────────────────────── */
998 uint8_t b = (uint8_t)st->buf[0];
999
1000 if (b < 0x20) {
1001 struct InputEvent *ev = emit(st);
1002 ev->type = EVENT_KEY;
1003 ev->key = b;
1004 if (b != KEY_TAB && b != KEY_ENTER) {
1005 ev->mod = MOD_CTRL;
1006 }
1007 shift(st, 1);
1008 st->esc_time = 0;
1009 continue;
1010 }
1011
1012 /* ── Backspace (0x7f) ───────────────────────────────────────── */
1013 if (b == 0x7f) {
1014 struct InputEvent *ev = emit(st);
1015 ev->type = EVENT_KEY;
1016 ev->key = KEY_BACKSPACE;
1017 shift(st, 1);
1018 st->esc_time = 0;
1019 continue;
1020 }
1021
1022 /* ── Printable / UTF-8 ──────────────────────────────────────── */
1023 {
1024 int need = utf8_len(b);
1025 if (need > st->len) {
1026 return accepted; /* partial UTF-8 */
1027 }
1028 uint32_t cp;
1029 int n = utf8_decode(&cp, st->buf);
1030 if (n <= 0) {
1031 n = 1;
1032 cp = 0xfffd;
1033 }
1034 struct InputEvent *ev = emit(st);
1035 ev->type = EVENT_KEY;
1036 ev->ch = cp;
1037 shift(st, n);
1038 st->esc_time = 0;
1039 }
1040 }
1041
1042 return accepted;
1043}
1044
1045int input_count(struct InputState *st) { return st->count; }
1046
1047struct InputEvent *input_event(struct InputState *st, int index) {
1048 if (index < 0 || index >= st->count)
1049 return 0;
1050 return &st->events[index];
1051}
1052
1053int input_delay(struct InputState *st) {
1054 if (st->esc_time > 0) {
1055 return st->esc_latency_ms;
1056 }
1057 return 0;
1058}