mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-04 01:40:21 -05:00
commit
2d3d8ca3d0
9 changed files with 525 additions and 26 deletions
|
|
@ -46,7 +46,9 @@
|
||||||
when auto-detecting URLs.
|
when auto-detecting URLs.
|
||||||
* [SGR-Pixels (1016) mouse extended coordinates](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates) is now supported
|
* [SGR-Pixels (1016) mouse extended coordinates](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates) is now supported
|
||||||
(https://codeberg.org/dnkl/foot/issues/762).
|
(https://codeberg.org/dnkl/foot/issues/762).
|
||||||
|
* `XTGETTCAP` - builtin terminfo. See
|
||||||
|
[README.md::XTGETTCAP](README.md#xtgettcap) for details
|
||||||
|
(https://codeberg.org/dnkl/foot/issues/846).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
||||||
49
README.md
49
README.md
|
|
@ -30,6 +30,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
|
||||||
1. [DPI and font size](#dpi-and-font-size)
|
1. [DPI and font size](#dpi-and-font-size)
|
||||||
1. [Supported OSCs](#supported-oscs)
|
1. [Supported OSCs](#supported-oscs)
|
||||||
1. [Programmatically checking if running in foot](#programmatically-checking-if-running-in-foot)
|
1. [Programmatically checking if running in foot](#programmatically-checking-if-running-in-foot)
|
||||||
|
1. [XTGETTCAP](#xtgettcap)
|
||||||
1. [Credits](#Credits)
|
1. [Credits](#Credits)
|
||||||
1. [Bugs](#bugs)
|
1. [Bugs](#bugs)
|
||||||
1. [Contact](#contact)
|
1. [Contact](#contact)
|
||||||
|
|
@ -432,6 +433,54 @@ e.g. “1.8.2” for a regular release, or “1.8.2-36-g7db8e06f” for a git
|
||||||
build.
|
build.
|
||||||
|
|
||||||
|
|
||||||
|
# XTGETTCAP
|
||||||
|
|
||||||
|
`XTGETTCAP` is an escape sequence initially introduced by XTerm, and
|
||||||
|
also implemented (and extended, to some degree) by Kitty.
|
||||||
|
|
||||||
|
It allows querying the terminal for terminfo
|
||||||
|
capabilities. Applications using this feature do not need to use the
|
||||||
|
classic, file-based, terminfo definition. For example, if all
|
||||||
|
applications used this feature, you would no longer have to install
|
||||||
|
foot’s terminfo on remote hosts you SSH into.
|
||||||
|
|
||||||
|
XTerm’s implementation (as of XTerm-370) only supports querying key
|
||||||
|
(as in keyboard keys) capabilities, and three custom capabilities:
|
||||||
|
|
||||||
|
* `TN` - terminal name
|
||||||
|
* `Co` - number of colors (alias for the `colors` capability)
|
||||||
|
* `RGB` - number of bits per color channel (different semantics from
|
||||||
|
the `RGB` capability in file-based terminfo definitions!).
|
||||||
|
|
||||||
|
Kitty has extended this, and also supports querying all integer and
|
||||||
|
string capabilities.
|
||||||
|
|
||||||
|
Foot supports this, and extends it even further, to also include
|
||||||
|
boolean capabilities. This means foot’s entire terminfo can be queried
|
||||||
|
via `XTGETTCAP`.
|
||||||
|
|
||||||
|
Note that both Kitty and foot handles **responses** to
|
||||||
|
multi-capability queries slightly differently, compared to XTerm.
|
||||||
|
|
||||||
|
XTerm will send a single DCS reply, with `;`-separated
|
||||||
|
capability/value pairs. There are a couple of issues with this:
|
||||||
|
|
||||||
|
* The success/fail flag in the beginning of the response is always `1`
|
||||||
|
(success), unless the very **first** queried capability is invalid.
|
||||||
|
* XTerm will not respond **at all** to an invalid capability, unless
|
||||||
|
it’s the first one in the `XTGETTCAP` query.
|
||||||
|
* XTerm will end the response at the first invalid capability.
|
||||||
|
|
||||||
|
In other words, if you send a large multi-capability query, you will
|
||||||
|
only get responses up to, but not including, the first invalid
|
||||||
|
capability. All subsequent capabilities will be dropped.
|
||||||
|
|
||||||
|
Kitty and foot on the other hand, send one DCS response for **each**
|
||||||
|
capability in the multi query. This allows us to send a proper
|
||||||
|
success/fail flag for each queried capability. Responses for **all**
|
||||||
|
queried capabilities are **always** sent. No queries are ever dropped.
|
||||||
|
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
* [Ordoviz](https://codeberg.org/Ordoviz), for designing and
|
* [Ordoviz](https://codeberg.org/Ordoviz), for designing and
|
||||||
|
|
|
||||||
194
dcs.c
194
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,185 @@ 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 */
|
||||||
|
const char *p = terminfo_capabilities;
|
||||||
|
size_t left = sizeof(terminfo_capabilities);
|
||||||
|
|
||||||
|
const char *last_cap = NULL;
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
const char *cap = p;
|
||||||
|
const char *val = cap + strlen(cap) + 1;
|
||||||
|
|
||||||
|
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
|
||||||
|
xassert(size <= left);
|
||||||
|
p += size;
|
||||||
|
left -= size;
|
||||||
|
|
||||||
|
if (last_cap != NULL)
|
||||||
|
xassert(strcmp(last_cap, cap) < 0);
|
||||||
|
|
||||||
|
last_cap = cap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
lookup_capability(const char *name, const char **value)
|
||||||
|
{
|
||||||
|
const char *p = terminfo_capabilities;
|
||||||
|
size_t left = sizeof(terminfo_capabilities);
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
const char *cap = p;
|
||||||
|
const char *val = cap + strlen(cap) + 1;
|
||||||
|
|
||||||
|
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
|
||||||
|
xassert(size <= left);
|
||||||
|
p += size;
|
||||||
|
left -= size;
|
||||||
|
|
||||||
|
int r = strcmp(cap, name);
|
||||||
|
if (r == 0) {
|
||||||
|
*value = val;
|
||||||
|
return true;
|
||||||
|
} else if (r > 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*value = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
const struct foot_terminfo_entry *entry =
|
||||||
|
bsearch(name, terminfo_capabilities, ALEN(terminfo_capabilities),
|
||||||
|
sizeof(*entry), &terminfo_entry_compar);
|
||||||
|
#endif
|
||||||
|
const char *value;
|
||||||
|
bool valid_capability = lookup_capability(name, &value);
|
||||||
|
xassert(!valid_capability || value != NULL);
|
||||||
|
|
||||||
|
LOG_DBG("XTGETTCAP: cap=%s (%.*s), value=%s",
|
||||||
|
name, (int)len, hex_cap_name,
|
||||||
|
valid_capability ? value : "<invalid>");
|
||||||
|
|
||||||
|
if (!valid_capability)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (value[0] == '\0') {
|
||||||
|
/* Boolean */
|
||||||
|
term_to_slave(term, "\033P1+r", 5);
|
||||||
|
term_to_slave(term, hex_cap_name, len);
|
||||||
|
term_to_slave(term, "\033\\", 2);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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(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 = 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 +250,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 +284,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 {
|
||||||
|
|
|
||||||
|
|
@ -679,6 +679,9 @@ and are terminated by *\\E\\* (ST).
|
||||||
: Begin application synchronized updates
|
: Begin application synchronized updates
|
||||||
| \\EP = 2 s \\E\\
|
| \\EP = 2 s \\E\\
|
||||||
: End application synchronized updates
|
: End application synchronized updates
|
||||||
|
| \\EP + q <hex encoded capability name> \\E\\
|
||||||
|
: Query builtin terminfo database (XTGETTCAP)
|
||||||
|
|
||||||
|
|
||||||
# FOOTNOTE
|
# FOOTNOTE
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,53 @@ distro package for foot's terminfo entries, you can install foot's
|
||||||
terminfo entries manually, by copying *foot* and *foot-direct* to
|
terminfo entries manually, by copying *foot* and *foot-direct* to
|
||||||
*~/.terminfo/f/*.
|
*~/.terminfo/f/*.
|
||||||
|
|
||||||
|
# XTGETTCAP
|
||||||
|
|
||||||
|
*XTGETTCAP* is an escape sequence initially introduced by XTerm, and
|
||||||
|
also implemented (and extended, to some degree) by Kitty.
|
||||||
|
|
||||||
|
It allows querying the terminal for terminfo classic, file-based,
|
||||||
|
terminfo definition. For example, if all applications used this
|
||||||
|
feature, you would no longer have to install foot’s terminfo on remote
|
||||||
|
hosts you SSH into.
|
||||||
|
|
||||||
|
XTerm’s implementation (as of XTerm-370) only supports querying key
|
||||||
|
(as in keyboard keys) capabilities, and three custom capabilities:
|
||||||
|
|
||||||
|
- TN - terminal name
|
||||||
|
- Co - number of colors (alias for the colors capability)
|
||||||
|
- RGB - number of bits per color channel (different semantics from
|
||||||
|
the RGB capability in file-based terminfo definitions!).
|
||||||
|
|
||||||
|
Kitty has extended this, and also supports querying all integer and
|
||||||
|
string capabilities.
|
||||||
|
|
||||||
|
Foot supports this, and extends it even further, to also include
|
||||||
|
boolean capabilities. This means foot’s entire terminfo can be queried
|
||||||
|
via *XTGETTCAP*.
|
||||||
|
|
||||||
|
Note that both Kitty and foot handles responses to multi-capability
|
||||||
|
queries slightly differently, compared to XTerm.
|
||||||
|
|
||||||
|
XTerm will send a single DCS reply, with ;-separated
|
||||||
|
capability/value pairs. There are a couple of issues with this:
|
||||||
|
|
||||||
|
- The success/fail flag in the beginning of the response is always 1
|
||||||
|
(success), unless the very first queried capability is invalid.
|
||||||
|
- XTerm will not respond at all to an invalid capability, unless it’s
|
||||||
|
the first one in the XTGETTCAP query.
|
||||||
|
- XTerm will end the response at the first invalid capability.
|
||||||
|
|
||||||
|
In other words, if you send a large multi-capability query, you will
|
||||||
|
only get responses up to, but not including, the first invalid
|
||||||
|
capability. All subsequent capabilities will be dropped.
|
||||||
|
|
||||||
|
Kitty and foot on the other hand, send one DCS response for each
|
||||||
|
capability in the multi query. This allows us to send a proper
|
||||||
|
success/fail flag for each queried capability. Responses for all
|
||||||
|
queried capabilities are always sent. No queries are ever dropped.
|
||||||
|
|
||||||
|
|
||||||
# ENVIRONMENT
|
# ENVIRONMENT
|
||||||
|
|
||||||
The following environment variables are used by foot:
|
The following environment variables are used by foot:
|
||||||
|
|
|
||||||
12
meson.build
12
meson.build
|
|
@ -136,6 +136,16 @@ version = custom_target(
|
||||||
output: 'version.h',
|
output: 'version.h',
|
||||||
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
|
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
|
||||||
|
|
||||||
|
python = find_program('python3', native: true)
|
||||||
|
generate_builtin_terminfo_py = files('scripts/generate-builtin-terminfo.py')
|
||||||
|
foot_terminfo = files('foot.info')
|
||||||
|
builtin_terminfo = custom_target(
|
||||||
|
'generate_builtin_terminfo',
|
||||||
|
output: 'foot-terminfo.h',
|
||||||
|
command: [python, generate_builtin_terminfo_py,
|
||||||
|
'@default_terminfo@', foot_terminfo, 'foot', '@OUTPUT@']
|
||||||
|
)
|
||||||
|
|
||||||
common = static_library(
|
common = static_library(
|
||||||
'common',
|
'common',
|
||||||
'log.c', 'log.h',
|
'log.c', 'log.h',
|
||||||
|
|
@ -213,7 +223,7 @@ executable(
|
||||||
'url-mode.c', 'url-mode.h',
|
'url-mode.c', 'url-mode.h',
|
||||||
'user-notification.c', 'user-notification.h',
|
'user-notification.c', 'user-notification.h',
|
||||||
'wayland.c', 'wayland.h',
|
'wayland.c', 'wayland.h',
|
||||||
wl_proto_src + wl_proto_headers, version,
|
builtin_terminfo, wl_proto_src + wl_proto_headers, version,
|
||||||
dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc,
|
dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc,
|
||||||
tllist, fcft],
|
tllist, fcft],
|
||||||
link_with: pgolib,
|
link_with: pgolib,
|
||||||
|
|
|
||||||
195
scripts/generate-builtin-terminfo.py
Executable file
195
scripts/generate-builtin-terminfo.py
Executable file
|
|
@ -0,0 +1,195 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from typing import Dict, Union
|
||||||
|
|
||||||
|
|
||||||
|
class Capability:
|
||||||
|
def __init__(self, name: str, value: Union[bool, int, str]):
|
||||||
|
self._name = name
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> Union[bool, int, str]:
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self._name < other._name
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self._name <= other._name
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self._name == other._name
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self._name != other._name
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self._name > other._name
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self._name >= other._name
|
||||||
|
|
||||||
|
|
||||||
|
class BoolCapability(Capability):
|
||||||
|
def __init__(self, name: str):
|
||||||
|
super().__init__(name, True)
|
||||||
|
|
||||||
|
|
||||||
|
class IntCapability(Capability):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StringCapability(Capability):
|
||||||
|
def __init__(self, name: str, value: str):
|
||||||
|
# Expand \E to literal ESC in non-parameterized capabilities
|
||||||
|
if '%' not in value:
|
||||||
|
value = re.sub(r'\\E([0-7])', r'\\033" "\1', value)
|
||||||
|
value = re.sub(r'\\E', r'\\033', value)
|
||||||
|
else:
|
||||||
|
# Need to double-escape \E in C string literals
|
||||||
|
value = value.replace('\\E', '\\\\E')
|
||||||
|
|
||||||
|
# Don’t escape ‘:’
|
||||||
|
value = value.replace('\\:', ':')
|
||||||
|
|
||||||
|
super().__init__(name, value)
|
||||||
|
|
||||||
|
|
||||||
|
class Fragment:
|
||||||
|
def __init__(self, name: str, description: str):
|
||||||
|
self._name = name
|
||||||
|
self._description = description
|
||||||
|
self._caps = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def caps(self) -> Dict[str, Capability]:
|
||||||
|
return self._caps
|
||||||
|
|
||||||
|
def add_capability(self, cap: Capability):
|
||||||
|
assert cap.name not in self._caps
|
||||||
|
self._caps[cap.name] = cap
|
||||||
|
|
||||||
|
def del_capability(self, name: str):
|
||||||
|
del self._caps[name]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('source_entry_name')
|
||||||
|
parser.add_argument('source', type=argparse.FileType('r'))
|
||||||
|
parser.add_argument('target_entry_name')
|
||||||
|
parser.add_argument('target', type=argparse.FileType('w'))
|
||||||
|
|
||||||
|
opts = parser.parse_args()
|
||||||
|
source_entry_name = opts.source_entry_name
|
||||||
|
target_entry_name = opts.target_entry_name
|
||||||
|
source = opts.source
|
||||||
|
target = opts.target
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
for l in source.readlines():
|
||||||
|
l = l.strip()
|
||||||
|
if l.startswith('#'):
|
||||||
|
continue
|
||||||
|
lines.append(l)
|
||||||
|
|
||||||
|
fragments = {}
|
||||||
|
cur_fragment = None
|
||||||
|
|
||||||
|
for m in re.finditer(
|
||||||
|
r'(?P<name>(?P<entry_name>[-+\w@]+)\|(?P<entry_desc>.+?),)|'
|
||||||
|
r'(?P<bool_cap>(?P<bool_name>\w+),)|'
|
||||||
|
r'(?P<int_cap>(?P<int_name>\w+)#(?P<int_val>(0x)?[0-9a-fA-F]+),)|'
|
||||||
|
r'(?P<str_cap>(?P<str_name>\w+)=(?P<str_val>(.+?)),)',
|
||||||
|
''.join(lines)):
|
||||||
|
|
||||||
|
if m.group('name') is not None:
|
||||||
|
name = m.group('entry_name')
|
||||||
|
description = m.group('entry_desc')
|
||||||
|
|
||||||
|
assert name not in fragments
|
||||||
|
fragments[name] = Fragment(name, description)
|
||||||
|
cur_fragment = fragments[name]
|
||||||
|
|
||||||
|
elif m.group('bool_cap') is not None:
|
||||||
|
name = m.group('bool_name')
|
||||||
|
cur_fragment.add_capability(BoolCapability(name))
|
||||||
|
|
||||||
|
elif m.group('int_cap') is not None:
|
||||||
|
name = m.group('int_name')
|
||||||
|
value = int(m.group('int_val'), 0)
|
||||||
|
cur_fragment.add_capability(IntCapability(name, value))
|
||||||
|
|
||||||
|
elif m.group('str_cap') is not None:
|
||||||
|
name = m.group('str_name')
|
||||||
|
value = m.group('str_val')
|
||||||
|
cur_fragment.add_capability(StringCapability(name, value))
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
# Expand ‘use’ capabilities
|
||||||
|
for frag in fragments.values():
|
||||||
|
for cap in frag.caps.values():
|
||||||
|
if cap.name == 'use':
|
||||||
|
use_frag = fragments[cap.value]
|
||||||
|
for use_cap in use_frag.caps.values():
|
||||||
|
frag.add_capability(use_cap)
|
||||||
|
|
||||||
|
|
||||||
|
frag.del_capability(cap.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
entry = fragments[source_entry_name]
|
||||||
|
|
||||||
|
try:
|
||||||
|
entry.del_capability('RGB')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
entry.add_capability(IntCapability('Co', 256))
|
||||||
|
entry.add_capability(StringCapability('TN', target_entry_name))
|
||||||
|
entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel
|
||||||
|
|
||||||
|
terminfo_parts = []
|
||||||
|
for cap in sorted(entry.caps.values()):
|
||||||
|
name = cap.name
|
||||||
|
value = str(cap.value)
|
||||||
|
|
||||||
|
# Escape ‘“‘
|
||||||
|
name = name.replace('"', '\"')
|
||||||
|
value = value.replace('"', '\"')
|
||||||
|
|
||||||
|
terminfo_parts.append(name)
|
||||||
|
if isinstance(cap, BoolCapability):
|
||||||
|
terminfo_parts.append('')
|
||||||
|
else:
|
||||||
|
terminfo_parts.append(value)
|
||||||
|
|
||||||
|
terminfo = '\\0" "'.join(terminfo_parts)
|
||||||
|
|
||||||
|
target.write('#pragma once\n')
|
||||||
|
target.write('\n')
|
||||||
|
target.write(f'static const char terminfo_capabilities[] = "{terminfo}";')
|
||||||
|
target.write('\n')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
25
uri.c
25
uri.c
|
|
@ -9,30 +9,9 @@
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "util.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
|
|
||||||
enum {
|
|
||||||
HEX_DIGIT_INVALID = 16
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint8_t
|
|
||||||
hex2nibble(char c)
|
|
||||||
{
|
|
||||||
switch (c) {
|
|
||||||
case '0': case '1': case '2': case '3': case '4':
|
|
||||||
case '5': case '6': case '7': case '8': case '9':
|
|
||||||
return c - '0';
|
|
||||||
|
|
||||||
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
|
||||||
return c - 'a' + 10;
|
|
||||||
|
|
||||||
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
|
||||||
return c - 'A' + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HEX_DIGIT_INVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
uri_parse(const char *uri, size_t len,
|
uri_parse(const char *uri, size_t len,
|
||||||
char **scheme, char **user, char **password, char **host,
|
char **scheme, char **user, char **password, char **host,
|
||||||
|
|
@ -118,7 +97,7 @@ uri_parse(const char *uri, size_t len,
|
||||||
|
|
||||||
LOG_DBG("user: \"%.*s\"", (int)user_len, start);
|
LOG_DBG("user: \"%.*s\"", (int)user_len, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
start = user_pw_end + 1;
|
start = user_pw_end + 1;
|
||||||
left = len - (start - uri);
|
left = len - (start - uri);
|
||||||
auth_left -= user_pw_len + 1;
|
auth_left -= user_pw_len + 1;
|
||||||
|
|
|
||||||
22
util.h
22
util.h
|
|
@ -35,3 +35,25 @@ sdbm_hash(const char *s)
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
HEX_DIGIT_INVALID = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline uint8_t
|
||||||
|
hex2nibble(char c)
|
||||||
|
{
|
||||||
|
switch (c) {
|
||||||
|
case '0': case '1': case '2': case '3': case '4':
|
||||||
|
case '5': case '6': case '7': case '8': case '9':
|
||||||
|
return c - '0';
|
||||||
|
|
||||||
|
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
||||||
|
return c - 'a' + 10;
|
||||||
|
|
||||||
|
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
||||||
|
return c - 'A' + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HEX_DIGIT_INVALID;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue