foot/dcs.c
Daniel Eklöf 5a032c4c6f
xtgettcap: switch from a table based internal structure, to a single char array
Having the builtin terminfo DB as a table, with one entry per
capability/value pair was ineffective memory-wise - we ended up adding
~20K to the final binary size.

This patch changes the internal representation from a table, to a
single NULL-separated (and NULL-terminated) char array:

  cap1-name \0 cap1-value \0 cap2-name \0 cap2-value \0

The capabilities are still sorted; to lookup a capability we need to
scan from the beginning until we either find a match, or until the
capabilities from the DB sort higher (lexicographically) than the
searched-for capability.

The terminfo char array is 3.3K - more better than before.
2022-01-13 13:42:12 +01:00

311 lines
7.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "dcs.h"
#include <string.h>
#define LOG_MODULE "dcs"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "foot-terminfo.h"
#include "sixel.h"
#include "util.h"
#include "vt.h"
#include "xmalloc.h"
static void
bsu(struct terminal *term)
{
/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
size_t n = term->vt.dcs.idx;
if (unlikely(n > 0))
LOG_DBG("BSU with unknown params: %.*s)", (int)n, term->vt.dcs.data);
term_enable_app_sync_updates(term);
}
static void
esu(struct terminal *term)
{
/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
size_t n = term->vt.dcs.idx;
if (unlikely(n > 0))
LOG_DBG("ESU with unknown params: %.*s)", (int)n, term->vt.dcs.data);
term_disable_app_sync_updates(term);
}
/* Decode hex-encoded string *inline*. NULL terminates */
static char *
hex_decode(const char *s, size_t len)
{
if (len % 2)
return NULL;
char *hex = xmalloc(len / 2 + 1);
char *o = hex;
/* TODO: error checking */
for (size_t i = 0; i < len; i += 2) {
uint8_t nib1 = hex2nibble(*s); s++;
uint8_t nib2 = hex2nibble(*s); s++;
if (nib1 == HEX_DIGIT_INVALID || nib2 == HEX_DIGIT_INVALID)
goto err;
*o = nib1 << 4 | nib2; o++;
}
*o = '\0';
return hex;
err:
free(hex);
return NULL;
}
UNITTEST
{
/* Verify table is sorted */
const char *p = terminfo_capabilities;
size_t left = sizeof(terminfo_capabilities);
const char *last_cap = NULL;
while (left > 0) {
const char *cap = p;
const char *val = cap + strlen(cap) + 1;
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
xassert(size <= left);
p += size;
left -= size;
if (last_cap != NULL)
xassert(strcmp(last_cap, cap) < 0);
last_cap = cap;
}
}
static bool
lookup_capability(const char *name, const char **value)
{
const char *p = terminfo_capabilities;
size_t left = sizeof(terminfo_capabilities);
while (left > 0) {
const char *cap = p;
const char *val = cap + strlen(cap) + 1;
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
xassert(size <= left);
p += size;
left -= size;
int r = strcmp(cap, name);
if (r == 0) {
*value = val;
return true;
} else if (r > 0)
break;
}
*value = NULL;
return false;
}
static void
xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
{
char *name = hex_decode(hex_cap_name, len);
if (name == NULL)
goto err;
#if 0
const struct foot_terminfo_entry *entry =
bsearch(name, terminfo_capabilities, ALEN(terminfo_capabilities),
sizeof(*entry), &terminfo_entry_compar);
#endif
const char *value;
bool valid_capability = lookup_capability(name, &value);
xassert(!valid_capability || value != NULL);
LOG_DBG("XTGETTCAP: cap=%s (%.*s), value=%s",
name, (int)len, hex_cap_name,
valid_capability ? value : "<invalid>");
if (!valid_capability)
goto err;
if (value[0] == '\0') {
/* Boolean */
term_to_slave(term, "\033P1+r", 5);
term_to_slave(term, hex_cap_name, len);
term_to_slave(term, "\033\\", 2);
goto out;
}
/*
* Reply format:
* \EP 1 + r cap=value \E\\
* Where cap and value are hex encoded ascii strings
*/
char *reply = xmalloc(
5 + /* DCS 1 + r (\EP1+r) */
len + /* capability name, hex encoded */
1 + /* = */
strlen(value) * 2 + /* capability value, hex encoded */
2 + /* ST (\E\\) */
1);
int idx = sprintf(reply, "\033P1+r%.*s=", (int)len, hex_cap_name);
for (const char *c = value; *c != '\0'; c++) {
uint8_t nib1 = (uint8_t)*c >> 4;
uint8_t nib2 = (uint8_t)*c & 0xf;
reply[idx] = nib1 >= 0xa ? 'A' + nib1 - 0xa : '0' + nib1; idx++;
reply[idx] = nib2 >= 0xa ? 'A' + nib2 - 0xa : '0' + nib2; idx++;
}
reply[idx] = '\033'; idx++;
reply[idx] = '\\'; idx++;
term_to_slave(term, reply, idx);
free(reply);
goto out;
err:
term_to_slave(term, "\033P0+r", 5);
term_to_slave(term, hex_cap_name, len);
term_to_slave(term, "\033\\", 2);
out:
free(name);
}
static void
xtgettcap_unhook(struct terminal *term)
{
size_t left = term->vt.dcs.idx;
const char *const end = (const char *)&term->vt.dcs.data[left];
const char *p = (const char *)term->vt.dcs.data;
while (true) {
const char *sep = memchr(p, ';', left);
size_t cap_len;
if (sep == NULL) {
/* Last capability */
cap_len = end - p;
} else {
cap_len = sep - p;
}
xtgettcap_reply(term, p, cap_len);
left -= cap_len + 1;
p += cap_len + 1;
if (sep == NULL)
break;
}
}
void
dcs_hook(struct terminal *term, uint8_t final)
{
LOG_DBG("hook: %c (intermediate(s): %.2s, param=%d)", final,
(const char *)&term->vt.private, vt_param_get(term, 0, 0));
xassert(term->vt.dcs.data == NULL);
xassert(term->vt.dcs.size == 0);
xassert(term->vt.dcs.put_handler == NULL);
xassert(term->vt.dcs.unhook_handler == NULL);
switch (term->vt.private) {
case 0:
switch (final) {
case 'q': {
int p1 = vt_param_get(term, 0, 0);
int p2 = vt_param_get(term, 1,0);
int p3 = vt_param_get(term, 2, 0);
sixel_init(term, p1, p2, p3);
term->vt.dcs.put_handler = &sixel_put;
term->vt.dcs.unhook_handler = &sixel_unhook;
break;
}
}
break;
case '=':
switch (final) {
case 's':
switch (vt_param_get(term, 0, 0)) {
case 1: term->vt.dcs.unhook_handler = &bsu; return;
case 2: term->vt.dcs.unhook_handler = &esu; return;
}
break;
}
break;
case '+':
switch (final) {
case 'q': /* XTGETTCAP */
term->vt.dcs.unhook_handler = &xtgettcap_unhook;
break;
}
break;
}
}
static bool
ensure_size(struct terminal *term, size_t required_size)
{
if (required_size <= term->vt.dcs.size)
return true;
size_t new_size = (required_size + 127) / 128 * 128;
xassert(new_size > 0);
uint8_t *new_data = realloc(term->vt.dcs.data, new_size);
if (new_data == NULL) {
LOG_ERRNO("failed to increase size of DCS buffer");
return false;
}
term->vt.dcs.data = new_data;
term->vt.dcs.size = new_size;
return true;
}
void
dcs_put(struct terminal *term, uint8_t c)
{
/* LOG_DBG("PUT: %c", c); */
if (term->vt.dcs.put_handler != NULL)
term->vt.dcs.put_handler(term, c);
else {
if (!ensure_size(term, term->vt.dcs.idx + 1))
return;
term->vt.dcs.data[term->vt.dcs.idx++] = c;
}
}
void
dcs_unhook(struct terminal *term)
{
if (term->vt.dcs.unhook_handler != NULL)
term->vt.dcs.unhook_handler(term);
term->vt.dcs.unhook_handler = NULL;
term->vt.dcs.put_handler = NULL;
free(term->vt.dcs.data);
term->vt.dcs.data = NULL;
term->vt.dcs.size = 0;
term->vt.dcs.idx = 0;
}