mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-04 04:06:06 -05:00
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
265 lines
6.4 KiB
C
265 lines
6.4 KiB
C
#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 */
|
||
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 : "<invalid>");
|
||
|
||
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)
|
||
{
|
||
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;
|
||
}
|