Skip to content

Commit 00aee53

Browse files
authored
feat: terminfo capability core (parser, detectTerminal) (#131)
1 parent 5acd533 commit 00aee53

10 files changed

Lines changed: 1068 additions & 2 deletions

File tree

‎Makefile‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ EXPORTS = \
3333
-Wl,--export=input_scan \
3434
-Wl,--export=input_count \
3535
-Wl,--export=input_event \
36-
-Wl,--export=input_delay
36+
-Wl,--export=input_delay \
37+
-Wl,--export=terminfo_size \
38+
-Wl,--export=terminfo_init \
39+
-Wl,--export=terminfo_parse \
40+
-Wl,--export=terminfo_grant \
41+
-Wl,--export=terminfo_confirm
3742

3843
LDFLAGS = -Wl,--no-entry \
3944
-Wl,--import-memory \

‎deno.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@bomb.sh/tty",
33
"license": "MIT",
44
"tasks": {
5-
"test": "deno test",
5+
"test": "deno test --allow-read --allow-write",
66
"fmt": "deno fmt && clang-format -i src/*.c src/*.h",
77
"fmt:check": "deno fmt --check && clang-format --dry-run --Werror src/*.c src/*.h",
88
"build:npm": "deno run -A tasks/build-npm.ts",

‎mod.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,12 @@ export * from "./term.ts";
33
export * from "./input.ts";
44
export * from "./settings.ts";
55
export * from "./termcodes.ts";
6+
export {
7+
type Capabilities,
8+
type Detection,
9+
type DetectOptions,
10+
detectTerminal,
11+
type KeyTable,
12+
MAX_TERMINFO,
13+
type Rgb,
14+
} from "./terminfo.ts";

‎src/module.c‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "../clay/clay.h"
44

55
#include "mem.c"
6+
#include "terminfo.c"
67
#include "buffer.c"
78
#include "cell.c"
89
#include "utf8.c"

‎src/terminfo.c‎

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
/* terminfo.c — shared terminal capability layer */
2+
3+
#include "terminfo.h"
4+
5+
#include "mem.h"
6+
7+
#define TERMINFO_MAGIC_LEGACY 0x011a
8+
#define TERMINFO_MAGIC_EXTENDED 0x021e
9+
10+
/* Standard capability indices (ncurses Caps). */
11+
#define TERMINFO_BOOL_AUTO_RIGHT_MARGIN 1
12+
#define TERMINFO_BOOL_EAT_NEWLINE_GLITCH 4
13+
#define TERMINFO_BOOL_BACK_COLOR_ERASE 28
14+
#define TERMINFO_NUM_MAX_COLORS 13
15+
#define TERMINFO_STR_ENTER_CA_MODE 28
16+
17+
int terminfo_size(void) { return align8(sizeof(struct TermInfo)); }
18+
19+
struct TermInfo *terminfo_init(void *mem) {
20+
struct TermInfo *ti = (struct TermInfo *)mem;
21+
ti->generation = 1;
22+
ti->colors = 256;
23+
ti->flags = TERMINFO_BACK_COLOR_ERASE | TERMINFO_AUTO_RIGHT_MARGIN |
24+
TERMINFO_EAT_NEWLINE_GLITCH | TERMINFO_ALTSCREEN;
25+
ti->confirmed = 0;
26+
ti->theme_fg = 0;
27+
ti->theme_bg = 0;
28+
ti->theme_cursor = 0;
29+
return ti;
30+
}
31+
32+
void terminfo_grant(struct TermInfo *ti, uint32_t flags) {
33+
if ((ti->flags & flags) == flags)
34+
return;
35+
ti->flags |= flags;
36+
ti->generation++;
37+
}
38+
39+
void terminfo_confirm(struct TermInfo *ti, uint32_t bit, int on) {
40+
uint32_t next = on ? (ti->flags | bit) : (ti->flags & ~bit);
41+
if (next == ti->flags)
42+
return;
43+
ti->flags = next;
44+
ti->generation++;
45+
}
46+
47+
static uint16_t rd_u16(const uint8_t *b, int off) {
48+
return (uint16_t)(b[off] | (b[off + 1] << 8));
49+
}
50+
51+
/* Signed terminfo number: -1 = absent, -2 = cancelled. */
52+
static int32_t rd_num(const uint8_t *b, int off, int width) {
53+
if (width == 2) {
54+
uint16_t v = rd_u16(b, off);
55+
if (v == 0xffff)
56+
return -1;
57+
if (v == 0xfffe)
58+
return -2;
59+
return (int32_t)v;
60+
}
61+
uint32_t v = (uint32_t)b[off] | ((uint32_t)b[off + 1] << 8) |
62+
((uint32_t)b[off + 2] << 16) | ((uint32_t)b[off + 3] << 24);
63+
return (int32_t)v;
64+
}
65+
66+
static int rd_i16(const uint8_t *b, int off) {
67+
uint16_t v = rd_u16(b, off);
68+
if (v == 0xffff)
69+
return -1;
70+
if (v == 0xfffe)
71+
return -2;
72+
return (int)v;
73+
}
74+
75+
static int ti_strnlen(const uint8_t *b, int max) {
76+
int n = 0;
77+
while (n < max && b[n])
78+
n++;
79+
return n;
80+
}
81+
82+
static int name_is(const uint8_t *table, int table_len, int off,
83+
const char *name) {
84+
if (off < 0 || off >= table_len)
85+
return 0;
86+
int i = 0;
87+
while (name[i]) {
88+
if (off + i >= table_len || table[off + i] != (uint8_t)name[i])
89+
return 0;
90+
i++;
91+
}
92+
return off + i < table_len && table[off + i] == 0;
93+
}
94+
95+
/* Extended capability block (after the standard sections). Returns 0 on
96+
* success, nonzero when the declared structure runs out of bounds.
97+
* Grants flag bits into *flags for RGB/Tc/Su booleans and Smulx. */
98+
static int parse_ext(const uint8_t *b, int len, int off, int numw,
99+
uint32_t *flags) {
100+
if (off & 1)
101+
off++;
102+
if (off + 10 > len)
103+
return 0; /* no extended block */
104+
105+
int eb = rd_i16(b, off);
106+
int en = rd_i16(b, off + 2);
107+
int es = rd_i16(b, off + 4);
108+
int table_strings = rd_i16(b, off + 6);
109+
int table_len = rd_i16(b, off + 8);
110+
if (eb < 0 || en < 0 || es < 0 || table_strings < 0 || table_len < 0)
111+
return 1;
112+
113+
int bools_off = off + 10;
114+
int nums_off = bools_off + eb;
115+
if (nums_off & 1)
116+
nums_off++;
117+
int offsets_off = nums_off + en * numw;
118+
int name_count = eb + en + es;
119+
int names_off = offsets_off + es * 2;
120+
int table_off = names_off + name_count * 2;
121+
if (table_off + table_len > len)
122+
return 1;
123+
124+
const uint8_t *table = b + table_off;
125+
126+
/* Value strings sit at the head of the table; names follow. Name
127+
* offsets are relative to the start of the names sub-table. */
128+
int names_base = 0;
129+
for (int i = 0; i < es; i++) {
130+
int v = rd_i16(b, offsets_off + i * 2);
131+
if (v < 0)
132+
continue;
133+
if (v >= table_len)
134+
return 1;
135+
int end = v + ti_strnlen(table + v, table_len - v) + 1;
136+
if (end > names_base)
137+
names_base = end;
138+
}
139+
140+
for (int i = 0; i < name_count; i++) {
141+
int noff = rd_i16(b, names_off + i * 2);
142+
if (noff < 0)
143+
continue;
144+
noff += names_base;
145+
146+
if (i < eb) {
147+
if (!b[bools_off + i])
148+
continue;
149+
if (name_is(table, table_len, noff, "Tc") ||
150+
name_is(table, table_len, noff, "RGB")) {
151+
*flags |= TERMINFO_TRUECOLOR;
152+
} else if (name_is(table, table_len, noff, "Su")) {
153+
*flags |= TERMINFO_STYLED_UNDERLINE;
154+
}
155+
} else if (i >= eb + en) {
156+
int v = rd_i16(b, offsets_off + (i - eb - en) * 2);
157+
if (v < 0)
158+
continue;
159+
if (name_is(table, table_len, noff, "Smulx")) {
160+
*flags |= TERMINFO_STYLED_UNDERLINE;
161+
}
162+
}
163+
}
164+
165+
return 0;
166+
}
167+
168+
const char *terminfo_str(const uint8_t *bytes, int len, int index,
169+
int *out_len) {
170+
if (!bytes || len < 12 || index < 0)
171+
return 0;
172+
uint16_t magic = rd_u16(bytes, 0);
173+
int numw;
174+
if (magic == TERMINFO_MAGIC_LEGACY) {
175+
numw = 2;
176+
} else if (magic == TERMINFO_MAGIC_EXTENDED) {
177+
numw = 4;
178+
} else {
179+
return 0;
180+
}
181+
182+
int name_size = rd_i16(bytes, 2);
183+
int bool_count = rd_i16(bytes, 4);
184+
int num_count = rd_i16(bytes, 6);
185+
int str_count = rd_i16(bytes, 8);
186+
int table_len = rd_i16(bytes, 10);
187+
if (name_size < 0 || bool_count < 0 || num_count < 0 || str_count < 0 ||
188+
table_len < 0)
189+
return 0;
190+
if (index >= str_count)
191+
return 0;
192+
193+
int nums_off = 12 + name_size + bool_count;
194+
if (nums_off & 1)
195+
nums_off++;
196+
int strs_off = nums_off + num_count * numw;
197+
int table_off = strs_off + str_count * 2;
198+
if (table_off + table_len > len)
199+
return 0;
200+
201+
int v = rd_i16(bytes, strs_off + index * 2);
202+
if (v < 0 || v >= table_len)
203+
return 0;
204+
int n = ti_strnlen(bytes + table_off + v, table_len - v);
205+
if (n == 0)
206+
return 0;
207+
if (out_len)
208+
*out_len = n;
209+
return (const char *)(bytes + table_off + v);
210+
}
211+
212+
int terminfo_parse(const uint8_t *bytes, int len, struct TermInfo *ti) {
213+
if (len < 12)
214+
return 1;
215+
216+
uint16_t magic = rd_u16(bytes, 0);
217+
int numw;
218+
if (magic == TERMINFO_MAGIC_LEGACY) {
219+
numw = 2;
220+
} else if (magic == TERMINFO_MAGIC_EXTENDED) {
221+
numw = 4;
222+
} else {
223+
return 2;
224+
}
225+
226+
int name_size = rd_i16(bytes, 2);
227+
int bool_count = rd_i16(bytes, 4);
228+
int num_count = rd_i16(bytes, 6);
229+
int str_count = rd_i16(bytes, 8);
230+
int table_len = rd_i16(bytes, 10);
231+
if (name_size < 0 || bool_count < 0 || num_count < 0 || str_count < 0 ||
232+
table_len < 0)
233+
return 3;
234+
235+
int bools_off = 12 + name_size;
236+
int nums_off = bools_off + bool_count;
237+
if (nums_off & 1)
238+
nums_off++;
239+
int strs_off = nums_off + num_count * numw;
240+
int table_off = strs_off + str_count * 2;
241+
int end = table_off + table_len;
242+
if (end > len)
243+
return 4;
244+
245+
uint32_t flags = 0;
246+
if (bool_count > TERMINFO_BOOL_AUTO_RIGHT_MARGIN &&
247+
bytes[bools_off + TERMINFO_BOOL_AUTO_RIGHT_MARGIN])
248+
flags |= TERMINFO_AUTO_RIGHT_MARGIN;
249+
if (bool_count > TERMINFO_BOOL_EAT_NEWLINE_GLITCH &&
250+
bytes[bools_off + TERMINFO_BOOL_EAT_NEWLINE_GLITCH])
251+
flags |= TERMINFO_EAT_NEWLINE_GLITCH;
252+
if (bool_count > TERMINFO_BOOL_BACK_COLOR_ERASE &&
253+
bytes[bools_off + TERMINFO_BOOL_BACK_COLOR_ERASE])
254+
flags |= TERMINFO_BACK_COLOR_ERASE;
255+
256+
int32_t colors = 0;
257+
if (num_count > TERMINFO_NUM_MAX_COLORS) {
258+
int32_t v = rd_num(bytes, nums_off + TERMINFO_NUM_MAX_COLORS * numw, numw);
259+
if (v > 0)
260+
colors = v;
261+
}
262+
if (colors >= (1 << 24))
263+
flags |= TERMINFO_TRUECOLOR;
264+
265+
if (str_count > TERMINFO_STR_ENTER_CA_MODE) {
266+
int v = rd_i16(bytes, strs_off + TERMINFO_STR_ENTER_CA_MODE * 2);
267+
if (v >= 0 && v < table_len)
268+
flags |= TERMINFO_ALTSCREEN;
269+
}
270+
271+
if (parse_ext(bytes, len, end, numw, &flags))
272+
return 5;
273+
274+
/* The entry describes the terminal completely for the capabilities it
275+
* owns: replace them, leave probe-only flags and theme fields alone. */
276+
uint32_t keep = ~(TERMINFO_TRUECOLOR | TERMINFO_BACK_COLOR_ERASE |
277+
TERMINFO_AUTO_RIGHT_MARGIN | TERMINFO_EAT_NEWLINE_GLITCH |
278+
TERMINFO_ALTSCREEN | TERMINFO_STYLED_UNDERLINE);
279+
ti->flags = (ti->flags & keep) | flags;
280+
ti->colors = (uint32_t)colors;
281+
ti->generation++;
282+
return 0;
283+
}

0 commit comments

Comments
 (0)