[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
0

Configure Feed

Select the types of activity you want to include in your feed.

tty / src / input.c
30 kB 1058 lines
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}