mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
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
This commit is contained in:
parent
1a91cbecc7
commit
3fa6bec912
1 changed files with 147 additions and 1 deletions
148
dcs.c
148
dcs.c
|
|
@ -1,10 +1,14 @@
|
||||||
#include "dcs.h"
|
#include "dcs.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#define LOG_MODULE "dcs"
|
#define LOG_MODULE "dcs"
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "foot-terminfo.h"
|
||||||
#include "sixel.h"
|
#include "sixel.h"
|
||||||
|
#include "util.h"
|
||||||
#include "vt.h"
|
#include "vt.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
bsu(struct terminal *term)
|
bsu(struct terminal *term)
|
||||||
|
|
@ -30,6 +34,139 @@ esu(struct terminal *term)
|
||||||
term_disable_app_sync_updates(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 : "<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
|
void
|
||||||
dcs_hook(struct terminal *term, uint8_t final)
|
dcs_hook(struct terminal *term, uint8_t final)
|
||||||
{
|
{
|
||||||
|
|
@ -67,6 +204,14 @@ dcs_hook(struct terminal *term, uint8_t final)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
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
|
void
|
||||||
dcs_put(struct terminal *term, uint8_t c)
|
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)
|
if (term->vt.dcs.put_handler != NULL)
|
||||||
term->vt.dcs.put_handler(term, c);
|
term->vt.dcs.put_handler(term, c);
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue