2020-01-12 11:55:22 +01:00
|
|
|
|
#include "dcs.h"
|
2022-01-02 18:28:40 +01:00
|
|
|
|
#include <string.h>
|
2020-01-12 11:55:22 +01:00
|
|
|
|
|
|
|
|
|
|
#define LOG_MODULE "dcs"
|
|
|
|
|
|
#define LOG_ENABLE_DBG 0
|
|
|
|
|
|
#include "log.h"
|
2022-01-02 18:28:40 +01:00
|
|
|
|
#include "foot-terminfo.h"
|
2020-02-21 21:53:23 +01:00
|
|
|
|
#include "sixel.h"
|
2022-01-02 18:28:40 +01:00
|
|
|
|
#include "util.h"
|
2020-01-12 11:55:22 +01:00
|
|
|
|
#include "vt.h"
|
2022-01-02 18:28:40 +01:00
|
|
|
|
#include "xmalloc.h"
|
2020-01-12 11:55:22 +01:00
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
bsu(struct terminal *term)
|
|
|
|
|
|
{
|
2020-01-12 12:49:42 +01:00
|
|
|
|
/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
|
|
|
|
|
|
|
2021-04-25 21:04:46 +01:00
|
|
|
|
size_t n = term->vt.dcs.idx;
|
|
|
|
|
|
if (unlikely(n > 0))
|
|
|
|
|
|
LOG_DBG("BSU with unknown params: %.*s)", (int)n, term->vt.dcs.data);
|
2020-01-12 12:21:31 +01:00
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
|
term_enable_app_sync_updates(term);
|
2020-01-12 11:55:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
esu(struct terminal *term)
|
|
|
|
|
|
{
|
2020-01-12 12:49:42 +01:00
|
|
|
|
/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
|
|
|
|
|
|
|
2021-04-25 21:04:46 +01:00
|
|
|
|
size_t n = term->vt.dcs.idx;
|
|
|
|
|
|
if (unlikely(n > 0))
|
|
|
|
|
|
LOG_DBG("ESU with unknown params: %.*s)", (int)n, term->vt.dcs.data);
|
2020-01-12 12:28:00 +01:00
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
|
term_disable_app_sync_updates(term);
|
2020-01-12 11:55:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-02 18:28:40 +01:00
|
|
|
|
/* 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-12 11:55:22 +01:00
|
|
|
|
void
|
|
|
|
|
|
dcs_hook(struct terminal *term, uint8_t final)
|
|
|
|
|
|
{
|
vt: don’t ignore extra private/intermediate characters
Take ‘\E(#0’ for example - this is *not* the same as ‘\E(0’.
Up until now, foot has however treated them as the same escape,
because the handler for ‘\E(0’ didn’t verify there weren’t any _other_
private characters present.
Fix this by turning the ‘private’ array into a single 4-byte
integer. This allows us to match *all* privates with a single
comparison.
Private characters are added to the LSB first, and MSB last. This
means we can check for single privates in pretty much the same way as
before:
switch (term->vt.private) {
case ‘?’:
...
break;
}
Checking for two (or more) is much uglier, but foot only supports
a *single* escape with two privates, and no escapes with three or
more:
switch (term->vt.private) {
case 0x243f: /* ‘?$’ */
...
break;
}
The ‘clear’ action remains simple (and fast), with a single write
operation.
Collecting privates is potentially _slightly_ more complex than
before; we now need mask and compare, instead of simply comparing,
when checking how many privates we already have.
We _could_ add a counter, which would make collecting privates easier,
but this would add an additional write to the ‘clean’ action which is
really bad since it’s in the hot path.
2020-12-16 14:30:49 +01:00
|
|
|
|
LOG_DBG("hook: %c (intermediate(s): %.2s, param=%d)", final,
|
|
|
|
|
|
(const char *)&term->vt.private, vt_param_get(term, 0, 0));
|
2020-01-12 11:55:22 +01:00
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
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);
|
2020-01-12 11:55:22 +01:00
|
|
|
|
|
vt: don’t ignore extra private/intermediate characters
Take ‘\E(#0’ for example - this is *not* the same as ‘\E(0’.
Up until now, foot has however treated them as the same escape,
because the handler for ‘\E(0’ didn’t verify there weren’t any _other_
private characters present.
Fix this by turning the ‘private’ array into a single 4-byte
integer. This allows us to match *all* privates with a single
comparison.
Private characters are added to the LSB first, and MSB last. This
means we can check for single privates in pretty much the same way as
before:
switch (term->vt.private) {
case ‘?’:
...
break;
}
Checking for two (or more) is much uglier, but foot only supports
a *single* escape with two privates, and no escapes with three or
more:
switch (term->vt.private) {
case 0x243f: /* ‘?$’ */
...
break;
}
The ‘clear’ action remains simple (and fast), with a single write
operation.
Collecting privates is potentially _slightly_ more complex than
before; we now need mask and compare, instead of simply comparing,
when checking how many privates we already have.
We _could_ add a counter, which would make collecting privates easier,
but this would add an additional write to the ‘clean’ action which is
really bad since it’s in the hot path.
2020-12-16 14:30:49 +01:00
|
|
|
|
switch (term->vt.private) {
|
2020-02-21 21:53:23 +01:00
|
|
|
|
case 0:
|
|
|
|
|
|
switch (final) {
|
2021-03-09 17:23:55 +01:00
|
|
|
|
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);
|
2020-02-21 21:53:23 +01:00
|
|
|
|
term->vt.dcs.put_handler = &sixel_put;
|
|
|
|
|
|
term->vt.dcs.unhook_handler = &sixel_unhook;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-03-09 17:23:55 +01:00
|
|
|
|
}
|
2020-02-21 21:53:23 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
2020-01-12 11:55:22 +01:00
|
|
|
|
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;
|
2022-01-02 18:28:40 +01:00
|
|
|
|
|
|
|
|
|
|
case '+':
|
|
|
|
|
|
switch (final) {
|
|
|
|
|
|
case 'q': /* XTGETTCAP */
|
|
|
|
|
|
term->vt.dcs.unhook_handler = &xtgettcap_unhook;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2020-01-12 11:55:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(new_size > 0);
|
2020-01-12 11:55:22 +01:00
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2022-01-02 18:28:40 +01:00
|
|
|
|
/* LOG_DBG("PUT: %c", c); */
|
|
|
|
|
|
|
2020-02-21 21:53:23 +01:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2020-01-12 11:55:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2020-02-21 21:53:23 +01:00
|
|
|
|
term->vt.dcs.put_handler = NULL;
|
2020-01-12 11:55:22 +01:00
|
|
|
|
|
|
|
|
|
|
free(term->vt.dcs.data);
|
|
|
|
|
|
term->vt.dcs.data = NULL;
|
|
|
|
|
|
term->vt.dcs.size = 0;
|
|
|
|
|
|
term->vt.dcs.idx = 0;
|
|
|
|
|
|
}
|