diff --git a/dcs.c b/dcs.c index 79b04df9..6fed8010 100644 --- a/dcs.c +++ b/dcs.c @@ -1,10 +1,14 @@ #include "dcs.h" +#include #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) @@ -30,6 +34,139 @@ esu(struct terminal *term) 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 */ + for (size_t i = 1; i < ALEN(terminfo_capabilities); i++) { + xassert(strcmp(terminfo_capabilities[i - 1].name, + terminfo_capabilities[i].name ) <= 0); + } +} + +static int +terminfo_entry_compar(const void *_key, const void *_entry) +{ + const char *key = _key; + const struct foot_terminfo_entry *entry = _entry; + + return strcmp(key, entry->name); +} + +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; + + const struct foot_terminfo_entry *entry = + bsearch(name, terminfo_capabilities, ALEN(terminfo_capabilities), + sizeof(*entry), &terminfo_entry_compar); + + LOG_DBG("XTGETTCAP: cap=%s (%.*s), value=%s", + name, (int)len, hex_cap_name, + entry != NULL ? entry->value : ""); + + if (entry == NULL) + goto err; + + /* + * 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(entry->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 = entry->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) { @@ -67,6 +204,14 @@ dcs_hook(struct terminal *term, uint8_t final) break; } break; + + case '+': + switch (final) { + case 'q': /* XTGETTCAP */ + term->vt.dcs.unhook_handler = &xtgettcap_unhook; + break; + } + break; } } @@ -93,7 +238,8 @@ ensure_size(struct terminal *term, size_t required_size) void dcs_put(struct terminal *term, uint8_t c) { - LOG_DBG("PUT: %c", c); + /* LOG_DBG("PUT: %c", c); */ + if (term->vt.dcs.put_handler != NULL) term->vt.dcs.put_handler(term, c); else {