skip to content
==> src/input.c <==
1/* Key decoding after crossterm 0.29's unix parser (parse_event): the subset of sequences xterm,
2 * VTE, tmux and the Linux console send for these keys. */
3#include "tui/input.h"
4
5#include "tui/utf8.h"
6
7static tui_key mk(tui_key_code c, uint8_t mods) { return (tui_key){c, 0, 0, mods, TUI_KEY_PRESS}; }
8static tui_key fkey(uint8_t f, uint8_t mods) { return (tui_key){TUI_KEY_F, 0, f, mods, TUI_KEY_PRESS}; }
9
10/* xterm's modifier parameter: 1 + (shift 1 | alt 2 | ctrl 4). */
11static uint8_t mods_of(unsigned p) {
12    if (p < 2) return 0;
13    p--;
14    return (uint8_t)(((p & 1) ? TUI_MOD_SHIFT : 0) | ((p & 2) ? TUI_MOD_ALT : 0) | ((p & 4) ? TUI_MOD_CONTROL : 0));
15}
16
17static size_t csi(const char *in, size_t len, tui_key *k) {
18    /* in[0] == ESC, in[1] == '['. Parameters, then a final byte 0x40..0x7E. */
19    unsigned params[4] = {0, 0, 0, 0};
20    int np = 0;
21    size_t i = 2;
22    if (i < len && (in[i] == '<' || in[i] == '?' || in[i] == '>')) i++; /* private markers: consumed, ignored */
23    for (; i < len; i++) {
24        unsigned char c = (unsigned char)in[i];
25        if (c >= '0' && c <= '9') {
26            if (np == 0) np = 1;
27            if (np <= 4) params[np - 1] = params[np - 1] * 10 + (c - '0');
28        } else if (c == ';' || c == ':') {
29            if (np == 0) np = 1;
30            np++;
31        } else if (c >= 0x40 && c <= 0x7E) {
32            uint8_t m = np >= 2 ? mods_of(params[1]) : 0;
33            *k = mk(TUI_KEY_NONE, 0);
34            switch (c) {
35            case 'A': *k = mk(TUI_KEY_UP, m); break;
36            case 'B': *k = mk(TUI_KEY_DOWN, m); break;
37            case 'C': *k = mk(TUI_KEY_RIGHT, m); break;
38            case 'D': *k = mk(TUI_KEY_LEFT, m); break;
39            case 'H': *k = mk(TUI_KEY_HOME, m); break;
40            case 'F': *k = mk(TUI_KEY_END, m); break;
41            case 'Z': *k = mk(TUI_KEY_BACKTAB, TUI_MOD_SHIFT); break;
42            case 'P': *k = fkey(1, m); break;
43            case 'Q': *k = fkey(2, m); break;
44            case 'R': *k = fkey(3, m); break;
45            case 'S': *k = fkey(4, m); break;
46            case '~':
47                switch (params[0]) {
48                case 1: case 7: *k = mk(TUI_KEY_HOME, m); break;
49                case 2: *k = mk(TUI_KEY_INSERT, m); break;
50                case 3: *k = mk(TUI_KEY_DELETE, m); break;
51                case 4: case 8: *k = mk(TUI_KEY_END, m); break;
52                case 5: *k = mk(TUI_KEY_PAGE_UP, m); break;
53                case 6: *k = mk(TUI_KEY_PAGE_DOWN, m); break;
54                case 11: case 12: case 13: case 14: case 15: *k = fkey((uint8_t)(params[0] == 15 ? 5 : params[0] - 10), m); break;
55                case 17: case 18: case 19: case 20: case 21: *k = fkey((uint8_t)(params[0] - 11), m); break;
56                case 23: case 24: *k = fkey((uint8_t)(params[0] - 12), m); break;
57                default: break;
58                }
59                break;
60            default: break; /* mouse, focus, cursor reports: consumed */
61            }
62            return i + 1;
63        } else if (c < 0x20 || c > 0x7E) {
64            *k = mk(TUI_KEY_NONE, 0);
65            return i; /* malformed: drop what came before */
66        }
67    }
68    return 0; /* incomplete */
69}
70
71size_t tui_input_parse(const char *in, size_t len, bool quiet, tui_key *k) {
72    *k = mk(TUI_KEY_NONE, 0);
73    if (len == 0) return 0;
74    unsigned char b = (unsigned char)in[0];
75    if (b == 0x1B) {
76        if (len == 1) {
77            if (!quiet) return 0;
78            *k = mk(TUI_KEY_ESC, 0);
79            return 1;
80        }
81        if (in[1] == '[') return csi(in, len, k);
82        if (in[1] == 'O') {
83            if (len < 3) return quiet ? (*k = mk(TUI_KEY_ESC, 0), 1) : 0;
84            switch (in[2]) {
85            case 'A': *k = mk(TUI_KEY_UP, 0); break;
86            case 'B': *k = mk(TUI_KEY_DOWN, 0); break;
87            case 'C': *k = mk(TUI_KEY_RIGHT, 0); break;
88            case 'D': *k = mk(TUI_KEY_LEFT, 0); break;
89            case 'H': *k = mk(TUI_KEY_HOME, 0); break;
90            case 'F': *k = mk(TUI_KEY_END, 0); break;
91            case 'P': *k = fkey(1, 0); break;
92            case 'Q': *k = fkey(2, 0); break;
93            case 'R': *k = fkey(3, 0); break;
94            case 'S': *k = fkey(4, 0); break;
95            default: break;
96            }
97            return 3;
98        }
99        if (in[1] == 0x1B) {
100            *k = mk(TUI_KEY_ESC, 0);
101            return 1;
102        }
103        /* Alt + key. */
104        tui_key inner;
105        size_t n = tui_input_parse(in + 1, len - 1, quiet, &inner);
106        if (n == 0) return 0;
107        inner.mods |= TUI_MOD_ALT;
108        *k = inner;
109        return n + 1;
110    }
111    if (b == '\r' || b == '\n') {
112        *k = mk(TUI_KEY_ENTER, 0);
113        return 1;
114    }
115    if (b == '\t') {
116        *k = mk(TUI_KEY_TAB, 0);
117        return 1;
118    }
119    if (b == 0x7F || b == 0x08) {
120        *k = mk(TUI_KEY_BACKSPACE, 0);
121        return 1;
122    }
123    if (b == 0) {
124        *k = (tui_key){TUI_KEY_CHAR, ' ', 0, TUI_MOD_CONTROL, TUI_KEY_PRESS};
125        return 1;
126    }
127    if (b >= 0x01 && b <= 0x1A) {
128        *k = (tui_key){TUI_KEY_CHAR, (uint32_t)('a' + b - 1), 0, TUI_MOD_CONTROL, TUI_KEY_PRESS};
129        return 1;
130    }
131    if (b >= 0x1C && b <= 0x1F) {
132        *k = (tui_key){TUI_KEY_CHAR, (uint32_t)('4' + b - 0x1C), 0, TUI_MOD_CONTROL, TUI_KEY_PRESS};
133        return 1;
134    }
135    /* A character: wait for the rest of a UTF-8 sequence. */
136    size_t need = b < 0x80 ? 1 : b >= 0xF0 ? 4 : b >= 0xE0 ? 3 : b >= 0xC0 ? 2 : 1;
137    if (len < need) return quiet ? (*k = mk(TUI_KEY_NONE, 0), len) : 0;
138    size_t i = 0;
139    uint32_t cp = tui_utf8_next(in, need, &i);
140    *k = tui_key_char(cp);
141    if (cp < 0x80 && cp >= 'A' && cp <= 'Z') k->mods = TUI_MOD_SHIFT;
142    return i;
143}