From 3fa6bec91213525b031f76a67c6b685912dc6720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 2 Jan 2022 18:28:40 +0100 Subject: [PATCH] dcs: implement XTGETTCAP We emit one DCS reply for each queried capability (like Kitty, but unlike XTerm), as this allows us to a) not skip any capabilities in the query, and b) reply with correct success/fail flag for each capability. We do not batch the entire reply - as soon as the reply for _one_ capability is done, we write it to the PTY. Closes #846 --- dcs.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) 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 {