|
| 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