diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d7cd49b..10ec7463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,21 @@ ## Unreleased ### Added ### Changed + +* All `XTGETTCAP` capabilities are now in the `tigetstr()` format: + + - parameterized string capabilities were previously "source + encoded", meaning e.g. `\E` where not "decoded" into `\x1b`. + - Control characters were also "source encoded", meaning they were + returned as e.g. "^G" instead of `\x07` ([#1701][1701]). + + In other words, if, after this change, `XTGETTCAP` returns a string + that is different compared to `tigetstr()`, then it is likely a bug + in foot's implementation of `XTGETTCAP`. + +[1701]: https://codeberg.org/dnkl/foot/issues/1701 + + ### Deprecated ### Removed ### Fixed diff --git a/scripts/generate-builtin-terminfo.py b/scripts/generate-builtin-terminfo.py index acbf5279..61bd4a3c 100755 --- a/scripts/generate-builtin-terminfo.py +++ b/scripts/generate-builtin-terminfo.py @@ -50,27 +50,33 @@ class IntCapability(Capability): class StringCapability(Capability): def __init__(self, name: str, value: str): - # Expand \E to literal ESC in non-parameterized capabilities - if '%' not in value: - # Ensure e.g. \E7 doesn’t get translated to “\0337”, which - # would be interpreted as octal 337 by the C compiler - value = re.sub(r'\\E([0-7])', r'\\033" "\1', value) + # see terminfo(5) for valid escape sequences - # Replace \E with an actual escape - value = re.sub(r'\\E', r'\\033', value) + # Control characters + def translate_ctrl_chr(m): + ctrl = m.group(1) + if ctrl == '?': + return chr(0x7f) + return chr(ord(ctrl) - ord('@')) + value = re.sub('\^([@A-Z[\\\\\]^_?])', translate_ctrl_chr, value) - # Don’t escape ‘:’ - value = value.replace('\\:', ':') + # Ensure e.g. \E7 (or \e7) doesn’t get translated to “\0337”, + # which would be interpreted as octal 337 by the C compiler + value = re.sub(r'(\\E|\\e)([0-7])', r'\\033" "\2', value) - else: - value = value.replace("\\", "\\\\") - # # Need to double-escape backslashes. These only occur in - # # ‘\E\’ combos. Note that \E itself is updated below - # value = value.replace('\\E\\\\', '\\E\\\\\\\\') + # Replace \E and \e with ESC + value = re.sub(r'\\E|\\e', r'\\033', value) - # # Need to double-escape \E in C string literals - # value = value.replace('\\E', '\\\\E') + # Unescape ,:^ + value = re.sub(r'\\(,|:|\^)', r'\1', value) + # Replace \s with space + value = value.replace('\\s', ' ') + + # Let \\, \n, \r, \t, \b and \f "fall through", to the C string literal + + if re.search(r'\\l', value): + raise NotImplementedError('\\l escape sequence') super().__init__(name, value) diff --git a/utils/xtgettcap.c b/utils/xtgettcap.c index b3ab712a..069a9ecb 100644 --- a/utils/xtgettcap.c +++ b/utils/xtgettcap.c @@ -103,7 +103,7 @@ main(int argc, const char *const *argv) if (isprint(buf[i])) printf("%c", buf[i]); else if (buf[i] == '\033') - printf("\033[1;31m\\E\033[m"); + printf("\033[1;31m\033[m"); else printf("%02x", (uint8_t)buf[i]); } @@ -158,12 +158,30 @@ main(int argc, const char *const *argv) printf(" \033[%dm", color); for (size_t i = 0 ; i < len; i++) { - if (isprint(decoded[i])) + if (isprint(decoded[i])) { + /* All printable characters */ printf("%c", decoded[i]); - else if (decoded[i] == '\033') - printf("\033[1;31m\\E\033[22;%dm", color); - else + } + + else if (decoded[i] == '\033') { + /* ESC */ + printf("\033[1;31m\033[22;%dm", color); + } + + else if (decoded[i] >= '\x00' && decoded[i] <= '\x5f') { + /* Control characters, e.g. ^G etc */ + printf("\033[1m^%c\033[22m", decoded[i] + '@'); + } + + else if (decoded[i] == '\x7f') { + /* Control character ^? */ + printf("\033[1m^?\033[22m"); + } + + else { + /* Unknown: print hex representation */ printf("\033[1m%02x\033[22m", (uint8_t)decoded[i]); + } } printf("\033[m\r\n"); replies++;