| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "query.h" | ||
| 2 | #include "cursor.h" | ||
| 3 | #include "feature.h" | ||
| 4 | #include "util/log.h" | ||
| 5 | #include "util/str-util.h" | ||
| 6 | #include "util/string-view.h" | ||
| 7 | #include "util/strtonum.h" | ||
| 8 | #include "util/utf8.h" | ||
| 9 | |||
| 10 | // https://vt100.net/docs/vt510-rm/DECRPM | ||
| 11 | typedef enum { | ||
| 12 | DECRPM_NOT_RECOGNIZED = 0, | ||
| 13 | DECRPM_SET = 1, | ||
| 14 | DECRPM_RESET = 2, | ||
| 15 | DECRPM_PERMANENTLY_SET = 3, | ||
| 16 | DECRPM_PERMANENTLY_RESET = 4, | ||
| 17 | } TermPrivateModeStatus; | ||
| 18 | |||
| 19 | 15 | static const char *decrpm_status_to_str(TermPrivateModeStatus val) | |
| 20 | { | ||
| 21 |
6/6✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→4) taken 9 times.
✓ Branch 2 (2→5) taken 1 times.
✓ Branch 3 (2→6) taken 1 times.
✓ Branch 4 (2→7) taken 1 times.
✓ Branch 5 (2→8) taken 1 times.
|
15 | switch (val) { |
| 22 | case DECRPM_NOT_RECOGNIZED: return "not recognized"; | ||
| 23 | 2 | case DECRPM_SET: return "set"; | |
| 24 | 9 | case DECRPM_RESET: return "reset"; | |
| 25 | 1 | case DECRPM_PERMANENTLY_SET: return "permanently set"; | |
| 26 | 1 | case DECRPM_PERMANENTLY_RESET: return "permanently reset"; | |
| 27 | } | ||
| 28 | 1 | return "INVALID"; | |
| 29 | } | ||
| 30 | |||
| 31 | 15 | static const char *decrpm_mode_to_str(unsigned int mode_param) | |
| 32 | { | ||
| 33 |
10/10✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 1 times.
✓ Branch 2 (2→5) taken 1 times.
✓ Branch 3 (2→6) taken 1 times.
✓ Branch 4 (2→7) taken 1 times.
✓ Branch 5 (2→8) taken 1 times.
✓ Branch 6 (2→9) taken 1 times.
✓ Branch 7 (2→10) taken 6 times.
✓ Branch 8 (2→11) taken 1 times.
✓ Branch 9 (2→12) taken 1 times.
|
15 | switch (mode_param) { |
| 34 | case 7: return "auto-wrap mode"; | ||
| 35 | 1 | case 25: return "cursor visibility"; | |
| 36 | 1 | case 45: return "reverse-wraparound mode"; | |
| 37 | 1 | case 67: return "backspace sends BS"; | |
| 38 | 1 | case 1036: return "metaSendsEscape"; | |
| 39 | 1 | case 1039: return "altSendsEscape"; | |
| 40 | 1 | case 1049: return "alternate screen buffer"; | |
| 41 | 1 | case 2004: return "bracketed paste"; | |
| 42 | 6 | case 2026: return "synchronized updates"; | |
| 43 | } | ||
| 44 | 1 | return "unknown"; | |
| 45 | } | ||
| 46 | |||
| 47 | 3 | static const char *da2_param_to_name(unsigned int type_param) | |
| 48 | { | ||
| 49 |
3/12✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
✗ Branch 2 (2→5) not taken.
✗ Branch 3 (2→6) not taken.
✗ Branch 4 (2→7) not taken.
✗ Branch 5 (2→8) not taken.
✗ Branch 6 (2→9) not taken.
✗ Branch 7 (2→10) not taken.
✗ Branch 8 (2→11) not taken.
✗ Branch 9 (2→12) not taken.
✓ Branch 10 (2→13) taken 1 times.
✓ Branch 11 (2→14) taken 1 times.
|
3 | switch (type_param) { |
| 50 | case 0: return "VT100"; | ||
| 51 | 1 | case 1: return "VT220"; | |
| 52 | ✗ | case 2: return "VT240"; // "VT240 or VT241" | |
| 53 | ✗ | case 18: return "VT330"; | |
| 54 | ✗ | case 19: return "VT340"; | |
| 55 | ✗ | case 24: return "VT320"; | |
| 56 | ✗ | case 32: return "VT382"; | |
| 57 | ✗ | case 41: return "VT420"; | |
| 58 | ✗ | case 61: return "VT510"; | |
| 59 | ✗ | case 64: return "VT520"; | |
| 60 | ✗ | case 65: return "VT525"; | |
| 61 | } | ||
| 62 | 1 | return "unknown"; | |
| 63 | } | ||
| 64 | |||
| 65 | 6 | static bool decrpm_is_set_or_reset(TermPrivateModeStatus status) | |
| 66 | { | ||
| 67 | 6 | return status == DECRPM_SET || status == DECRPM_RESET; | |
| 68 | } | ||
| 69 | |||
| 70 | 27 | static KeyCode tflag(TermFeatureFlags flags) | |
| 71 | { | ||
| 72 | 27 | BUG_ON(flags & KEYCODE_QUERY_REPLY_BIT); | |
| 73 | 27 | return KEYCODE_QUERY_REPLY_BIT | flags; | |
| 74 | } | ||
| 75 | |||
| 76 | 1 | static TermFeatureFlags da1_params_to_features(const TermControlParams *csi) | |
| 77 | { | ||
| 78 | 1 | TermFeatureFlags flags = 0; | |
| 79 |
2/2✓ Branch 0 (7→3) taken 2 times.
✓ Branch 1 (7→8) taken 1 times.
|
3 | for (size_t i = 1, n = csi->nparams; i < n; i++) { |
| 80 |
2/3✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
✓ Branch 2 (3→6) taken 1 times.
|
2 | switch (csi->params[i][0]) { |
| 81 | 1 | case 22: | |
| 82 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=2%202%20%20%E2%87%92%C2%A0%20ANSI%20color | ||
| 83 | 1 | flags |= TFLAG_8_COLOR; | |
| 84 | 1 | break; | |
| 85 | ✗ | case 52: | |
| 86 | // https://github.com/contour-terminal/contour/issues/1761#issuecomment-2944492097 | ||
| 87 | // https://github.com/contour-terminal/vt-extensions/blob/master/clipboard-extension.md#feature-detection | ||
| 88 | // https://codeberg.org/dnkl/foot/pulls/2130 | ||
| 89 | // https://github.com/wezterm/wezterm/pull/7046/files | ||
| 90 | // https://github.com/microsoft/terminal/pull/19034 | ||
| 91 | // https://github.com/tmux/tmux/issues/4532 | ||
| 92 | // https://github.com/tmux/tmux/pull/4539 | ||
| 93 | ✗ | flags |= TFLAG_OSC52_COPY; | |
| 94 | ✗ | break; | |
| 95 | } | ||
| 96 | } | ||
| 97 | 1 | return flags; | |
| 98 | } | ||
| 99 | |||
| 100 | 38 | KeyCode parse_csi_query_reply(const TermControlParams *csi, uint8_t prefix) | |
| 101 | { | ||
| 102 | // NOTE: The main conditions below must check ALL of these values, in | ||
| 103 | // addition to the prefix byte | ||
| 104 |
2/2✓ Branch 0 (2→3) taken 18 times.
✓ Branch 1 (2→4) taken 20 times.
|
38 | uint8_t intermediate = csi->nr_intermediate ? csi->intermediate[0] : 0; |
| 105 | 38 | uint8_t final = csi->final_byte; | |
| 106 | 38 | unsigned int nparams = csi->nparams; | |
| 107 | |||
| 108 |
3/4✓ Branch 0 (4→5) taken 37 times.
✓ Branch 1 (4→6) taken 1 times.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 37 times.
|
38 | if (unlikely(csi->have_subparams || csi->nr_intermediate > 1)) { |
| 109 | 1 | goto ignore; | |
| 110 | } | ||
| 111 | |||
| 112 | // https://vt100.net/docs/vt510-rm/DA1.html | ||
| 113 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Primary%20DA,-) | ||
| 114 |
3/4✓ Branch 0 (7→8) taken 3 times.
✓ Branch 1 (7→17) taken 34 times.
✓ Branch 2 (8→9) taken 3 times.
✗ Branch 3 (8→17) not taken.
|
37 | if (prefix == '?' && final == 'c' && intermediate == 0 && nparams >= 1) { |
| 115 | 3 | unsigned int code = csi->params[0][0]; | |
| 116 |
2/2✓ Branch 0 (9→10) taken 1 times.
✓ Branch 1 (9→12) taken 2 times.
|
3 | if (code >= 61 && code <= 65) { |
| 117 | 1 | LOG_INFO("DA1 reply with P=%u (device level %u)", code, code - 60); | |
| 118 | 1 | return tflag(TFLAG_QUERY_L2 | TFLAG_QUERY_L3 | da1_params_to_features(csi)); | |
| 119 | } | ||
| 120 |
2/2✓ Branch 0 (12→13) taken 1 times.
✓ Branch 1 (12→15) taken 1 times.
|
2 | if (code >= 1 && code <= 12) { |
| 121 | 1 | LOG_INFO("DA1 reply with P=%u (VT100 series)", code); | |
| 122 | 1 | return tflag(TFLAG_QUERY_L2); | |
| 123 | } | ||
| 124 | 1 | LOG_INFO("DA1 reply with P=%u (unknown)", code); | |
| 125 | 1 | return KEY_IGNORE; | |
| 126 | } | ||
| 127 | |||
| 128 | // https://vt100.net/docs/vt510-rm/DA2.html | ||
| 129 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Secondary%20DA,-) | ||
| 130 |
3/4✓ Branch 0 (17→18) taken 3 times.
✓ Branch 1 (17→23) taken 31 times.
✓ Branch 2 (18→19) taken 3 times.
✗ Branch 3 (18→23) not taken.
|
34 | if (prefix == '>' && final == 'c' && intermediate == 0 && nparams == 3) { |
| 131 | 3 | unsigned int type = csi->params[0][0]; | |
| 132 | 3 | unsigned int firmware = csi->params[1][0]; | |
| 133 | 3 | unsigned int pc = csi->params[2][0]; | |
| 134 | 3 | const char *name = da2_param_to_name(type); | |
| 135 | 3 | LOG_INFO("DA2 reply: %u (%s); %u; %u", type, name, firmware, pc); | |
| 136 |
3/4✓ Branch 0 (20→21) taken 1 times.
✓ Branch 1 (20→22) taken 2 times.
✗ Branch 2 (21→22) not taken.
✓ Branch 3 (21→52) taken 1 times.
|
3 | if (type == 0 && firmware == 136 && pc == 0) { |
| 137 | // This is the DA2 response sent by PuTTY, which doesn't parse | ||
| 138 | // DCS sequences in accordance with ECMA-48 and so shouldn't | ||
| 139 | // be sent the queries in term_put_level_3_queries() | ||
| 140 | return KEY_IGNORE; | ||
| 141 | } | ||
| 142 | 2 | return tflag(TFLAG_QUERY_L3); | |
| 143 | } | ||
| 144 | |||
| 145 |
4/4✓ Branch 0 (23→24) taken 18 times.
✓ Branch 1 (23→33) taken 13 times.
✓ Branch 2 (24→25) taken 15 times.
✓ Branch 3 (24→33) taken 3 times.
|
31 | if (prefix == '?' && final == 'y' && intermediate == '$' && nparams == 2) { |
| 146 | // DECRPM reply to DECRQM query (CSI ? Ps; Pm $ y) | ||
| 147 | 15 | unsigned int mode = csi->params[0][0]; | |
| 148 | 15 | unsigned int status = csi->params[1][0]; | |
| 149 | 15 | const char *mstr = decrpm_mode_to_str(mode); | |
| 150 | 15 | const char *sstr = decrpm_status_to_str(status); | |
| 151 | 15 | LOG_DEBUG("DECRPM %u (%s) reply: %u (%s)", mode, mstr, status, sstr); | |
| 152 |
2/2✓ Branch 0 (26→27) taken 1 times.
✓ Branch 1 (26→28) taken 14 times.
|
15 | if (mode == 1036 && status == DECRPM_RESET) { |
| 153 | 1 | return tflag(TFLAG_META_ESC); | |
| 154 | } | ||
| 155 |
2/2✓ Branch 0 (28→29) taken 1 times.
✓ Branch 1 (28→30) taken 13 times.
|
14 | if (mode == 1039 && status == DECRPM_RESET) { |
| 156 | 1 | return tflag(TFLAG_ALT_ESC); | |
| 157 | } | ||
| 158 |
4/4✓ Branch 0 (30→31) taken 6 times.
✓ Branch 1 (30→52) taken 7 times.
✓ Branch 2 (31→32) taken 2 times.
✓ Branch 3 (31→52) taken 4 times.
|
13 | if (mode == 2026 && decrpm_is_set_or_reset(status)) { |
| 159 | 2 | return tflag(TFLAG_SYNC); | |
| 160 | } | ||
| 161 | return KEY_IGNORE; | ||
| 162 | } | ||
| 163 | |||
| 164 |
4/4✓ Branch 0 (33→34) taken 6 times.
✓ Branch 1 (33→37) taken 10 times.
✓ Branch 2 (34→35) taken 3 times.
✓ Branch 3 (34→37) taken 3 times.
|
16 | if (prefix == '?' && final == 'u' && intermediate == 0 && nparams == 1) { |
| 165 | // Kitty keyboard protocol flags (CSI ? flags u) | ||
| 166 | 3 | unsigned int flags = csi->params[0][0]; | |
| 167 | 3 | LOG_DEBUG("query reply for kittykbd flags: 0x%x", flags); | |
| 168 | // Interpret reply with any flags to mean "supported" | ||
| 169 | 3 | return tflag(TFLAG_KITTY_KEYBOARD); | |
| 170 | } | ||
| 171 | |||
| 172 |
3/4✓ Branch 0 (37→38) taken 7 times.
✓ Branch 1 (37→49) taken 6 times.
✗ Branch 2 (38→39) not taken.
✓ Branch 3 (38→40) taken 7 times.
|
13 | if (prefix == '>' && final == 'm' && intermediate == 0 && nparams >= 1) { |
| 173 | 7 | unsigned int code = csi->params[0][0]; | |
| 174 |
2/2✓ Branch 0 (40→41) taken 6 times.
✓ Branch 1 (40→47) taken 1 times.
|
7 | if (code == 4 && nparams <= 2) { |
| 175 | // XTMODKEYS 4 reply to XTQMODKEYS 4 query (CSI > 4 ; Pv m) | ||
| 176 |
2/2✓ Branch 0 (41→42) taken 4 times.
✓ Branch 1 (41→43) taken 2 times.
|
6 | unsigned int val = (nparams == 1) ? 0 : csi->params[1][0]; |
| 177 | 6 | LOG_DEBUG("XTMODKEYS 4 reply: modifyOtherKeys=%u", val); | |
| 178 |
2/2✓ Branch 0 (44→45) taken 5 times.
✓ Branch 1 (44→46) taken 1 times.
|
6 | return (val <= 2) ? tflag(TFLAG_MODIFY_OTHER_KEYS) : KEY_IGNORE; |
| 179 | } | ||
| 180 | 1 | LOG_DEBUG("XTMODKEYS %u reply with %u params", code, nparams); | |
| 181 | 1 | return KEY_IGNORE; | |
| 182 | } | ||
| 183 | |||
| 184 | 6 | ignore: | |
| 185 | // TODO: Also log intermediate and nparams | ||
| 186 | 7 | LOG_INFO("unhandled CSI with '%c' param prefix and final byte '%c'", prefix, (char)final); | |
| 187 | 7 | return KEY_IGNORE; | |
| 188 | } | ||
| 189 | |||
| 190 | 8 | static StringView hex_decode_str(StringView input, char *outbuf, size_t bufsize) | |
| 191 | { | ||
| 192 | 8 | StringView empty = STRING_VIEW_INIT; | |
| 193 | 8 | size_t n = input.length; | |
| 194 |
4/6✓ Branch 0 (2→3) taken 4 times.
✓ Branch 1 (2→5) taken 4 times.
✓ Branch 2 (3→4) taken 4 times.
✗ Branch 3 (3→5) not taken.
✗ Branch 4 (4→5) not taken.
✓ Branch 5 (4→9) taken 4 times.
|
8 | if (n == 0 || n & 1 || n / 2 > bufsize) { |
| 195 | 4 | return empty; | |
| 196 | } | ||
| 197 | |||
| 198 |
2/2✓ Branch 0 (9→6) taken 13 times.
✓ Branch 1 (9→10) taken 4 times.
|
17 | for (size_t i = 0, j = 0; i < n; ) { |
| 199 | 13 | unsigned int a = hex_decode(input.data[i++]); | |
| 200 | 13 | unsigned int b = hex_decode(input.data[i++]); | |
| 201 |
1/2✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→8) taken 13 times.
|
13 | if (unlikely((a | b) > 0xF)) { |
| 202 | ✗ | return empty; | |
| 203 | } | ||
| 204 | 13 | outbuf[j++] = (unsigned char)((a << 4) | b); | |
| 205 | } | ||
| 206 | |||
| 207 | 4 | return string_view(outbuf, n / 2); | |
| 208 | } | ||
| 209 | |||
| 210 | 1 | static size_t make_printable_ctlseq(StringView seq, char *buf, size_t buflen) | |
| 211 | { | ||
| 212 | 1 | MakePrintableFlags flags = MPF_C0_SYMBOLS; | |
| 213 | 1 | return u_make_printable(seq.data, seq.length, buf, buflen, flags); | |
| 214 | } | ||
| 215 | |||
| 216 | 4 | static KeyCode parse_xtgettcap_reply(const char *data, size_t len) | |
| 217 | { | ||
| 218 | 4 | size_t pos = 3; | |
| 219 | 4 | StringView empty = STRING_VIEW_INIT; | |
| 220 |
2/2✓ Branch 0 (2→3) taken 3 times.
✓ Branch 1 (2→4) taken 1 times.
|
4 | StringView cap_hex = (pos < len) ? get_delim(data, &pos, len, '=') : empty; |
| 221 |
2/2✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→7) taken 3 times.
|
4 | StringView val_hex = (pos < len) ? string_view(data + pos, len - pos) : empty; |
| 222 | |||
| 223 | 4 | char cbuf[8], vbuf[64]; | |
| 224 | 4 | StringView cap = hex_decode_str(cap_hex, cbuf, sizeof(cbuf)); | |
| 225 | 4 | StringView val = hex_decode_str(val_hex, vbuf, sizeof(vbuf)); | |
| 226 | |||
| 227 |
3/4✓ Branch 0 (10→11) taken 2 times.
✓ Branch 1 (10→30) taken 2 times.
✓ Branch 2 (11→12) taken 2 times.
✗ Branch 3 (11→30) not taken.
|
4 | if (data[0] == '1' && cap.length >= 2) { |
| 228 |
3/4✓ Branch 0 (13→14) taken 1 times.
✓ Branch 1 (13→16) taken 1 times.
✓ Branch 2 (14→15) taken 1 times.
✗ Branch 3 (14→16) not taken.
|
2 | if (strview_equal_cstring(cap, "bce") && val.length == 0) { |
| 229 | 1 | return tflag(TFLAG_BACK_COLOR_ERASE); | |
| 230 | } | ||
| 231 |
2/4✓ Branch 0 (17→18) taken 1 times.
✗ Branch 1 (17→21) not taken.
✓ Branch 2 (19→20) taken 1 times.
✗ Branch 3 (19→21) not taken.
|
1 | if (strview_equal_cstring(cap, "tsl") && strview_equal_cstring(val, "\033]2;")) { |
| 232 | 1 | return tflag(TFLAG_SET_WINDOW_TITLE); | |
| 233 | } | ||
| 234 | ✗ | if (strview_equal_cstring(cap, "rep") && strview_has_suffix(val, "b")) { | |
| 235 | ✗ | return tflag(TFLAG_ECMA48_REPEAT); | |
| 236 | } | ||
| 237 | ✗ | if (strview_equal_cstring(cap, "Ms") && val.length >= 6) { | |
| 238 | // All 71 entries with this cap in the ncurses terminfo database | ||
| 239 | // use OSC 52, with only slight differences (BEL vs. ST), so | ||
| 240 | // there's really no reason to check the value. | ||
| 241 | // Source: https://gitlab.com/craigbarnes/lua-terminfo-parser/-/blob/master/examples/output/cap-values-Ms.txt#:~:text=Totals,-%2D | ||
| 242 | ✗ | return tflag(TFLAG_OSC52_COPY); | |
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | 2 | char ebuf[8 + (U_SET_CHAR_MAXLEN * (sizeof(cbuf) + sizeof(vbuf)))]; | |
| 247 | 2 | size_t i = 0; | |
| 248 |
2/2✓ Branch 0 (30→31) taken 1 times.
✓ Branch 1 (30→37) taken 1 times.
|
2 | if (cap.length) { |
| 249 | 1 | i += copyliteral(ebuf + i, " ("); | |
| 250 | 1 | i += make_printable_ctlseq(cap, ebuf + i, sizeof(ebuf) - i); | |
| 251 |
1/2✗ Branch 0 (33→34) not taken.
✓ Branch 1 (33→36) taken 1 times.
|
1 | if (val.length) { |
| 252 | ✗ | ebuf[i++] = '='; | |
| 253 | ✗ | i += make_printable_ctlseq(val, ebuf + i, sizeof(ebuf) - i); | |
| 254 | } | ||
| 255 | 1 | ebuf[i++] = ')'; | |
| 256 | } | ||
| 257 | |||
| 258 | 2 | LOG_INFO ( | |
| 259 | "unhandled XTGETTCAP reply: %.*s%.*s", | ||
| 260 | (int)len, data, // Original, hex-encoded string (no control chars) | ||
| 261 | (int)i, ebuf // Decoded string (with control chars escaped) | ||
| 262 | ); | ||
| 263 | |||
| 264 | 2 | return KEY_IGNORE; | |
| 265 | } | ||
| 266 | |||
| 267 | 3 | static KeyCode handle_decrqss_sgr_reply(const char *data, size_t len) | |
| 268 | { | ||
| 269 | 3 | LOG_DEBUG("DECRQSS SGR reply: %.*s", (int)len, data); | |
| 270 | |||
| 271 | 3 | TermFeatureFlags flags = 0; | |
| 272 |
2/2✓ Branch 0 (21→4) taken 8 times.
✓ Branch 1 (21→22) taken 3 times.
|
11 | for (size_t pos = 0; pos < len; ) { |
| 273 | // These SGR params correspond to the ones in term_put_level_3_queries() | ||
| 274 | 8 | StringView s = get_delim(data, &pos, len, ';'); | |
| 275 |
2/2✓ Branch 0 (6→7) taken 2 times.
✓ Branch 1 (6→8) taken 6 times.
|
8 | if (strview_equal_cstring(s, "48:5:255")) { |
| 276 | 2 | flags |= TFLAG_256_COLOR; | |
| 277 | 7 | continue; | |
| 278 | } | ||
| 279 | |||
| 280 |
2/2✓ Branch 0 (9→10) taken 5 times.
✓ Branch 1 (9→12) taken 1 times.
|
6 | if ( |
| 281 | 6 | strview_equal_cstring(s, "38:2::60:70:80") // xterm, foot | |
| 282 |
2/2✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 4 times.
|
5 | || strview_equal_cstring(s, "38:2:60:70:80") // kitty |
| 283 | ) { | ||
| 284 | 2 | flags |= TFLAG_TRUE_COLOR; | |
| 285 | 2 | continue; | |
| 286 | } | ||
| 287 | |||
| 288 |
3/4✓ Branch 0 (14→15) taken 3 times.
✓ Branch 1 (14→17) taken 1 times.
✓ Branch 2 (15→16) taken 3 times.
✗ Branch 3 (15→17) not taken.
|
4 | if (strview_equal_cstring(s, "0") && pos <= 2) { |
| 289 | 3 | continue; | |
| 290 | } | ||
| 291 | |||
| 292 | 1 | LOG_WARNING ( | |
| 293 | "unrecognized parameter substring in DECRQSS SGR reply: %.*s", | ||
| 294 | (int)s.length, s.data | ||
| 295 | ); | ||
| 296 | } | ||
| 297 | |||
| 298 |
2/2✓ Branch 0 (22→23) taken 2 times.
✓ Branch 1 (22→24) taken 1 times.
|
3 | return flags ? tflag(flags) : KEY_IGNORE; |
| 299 | } | ||
| 300 | |||
| 301 | 5 | static KeyCode parse_xtversion_reply(StringView reply) | |
| 302 | { | ||
| 303 | 5 | LOG_INFO("XTVERSION reply: %.*s", (int)reply.length, reply.data); | |
| 304 | |||
| 305 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 5 times.
|
5 | if (strview_has_prefix(reply, "XTerm(")) { |
| 306 | ✗ | return tflag ( | |
| 307 | TFLAG_QUERY_L3 | TFLAG_ECMA48_REPEAT | TFLAG_MODIFY_OTHER_KEYS | ||
| 308 | ); | ||
| 309 | } | ||
| 310 | |||
| 311 |
2/2✓ Branch 0 (7→8) taken 4 times.
✓ Branch 1 (7→9) taken 1 times.
|
5 | if (strview_has_prefix(reply, "tmux ")) { |
| 312 | // tmux gained support for XTVERSION in release 3.2, so any response | ||
| 313 | // starting with "tmux " implies support for all tmux 3.2 features. | ||
| 314 | // It also has bugs related to DECRQSS queries, including crashing | ||
| 315 | // and spuriously printing "SIXEL IMAGE (1x1)", so we also set the | ||
| 316 | // TFLAG_NO_QUERY_L3 flag here. | ||
| 317 | // See also: | ||
| 318 | // • https://github.com/tmux/tmux/wiki/FAQ#how-often-is-tmux-released-what-is-the-version-number-scheme | ||
| 319 | // • https://github.com/tmux/tmux/commit/9dd58470e41bfb (XTVERSION; v3.2) | ||
| 320 | // • https://github.com/tmux/tmux/commit/5fc0be50450e75 (REP; v2.6) | ||
| 321 | // TODO: Set TFLAG_MODIFY_OTHER_KEYS conditionally, for tmux 3.5a+ (or 3.6+?) | ||
| 322 | // TODO: Set TFLAG_SYNC for tmux 3.4+ (tmux doesn't support DECRQM) | ||
| 323 | 4 | return tflag ( | |
| 324 | TFLAG_NO_QUERY_L3 | TFLAG_ECMA48_REPEAT | TFLAG_MODIFY_OTHER_KEYS | ||
| 325 | | TFLAG_BS_CTRL_BACKSPACE | ||
| 326 | ); | ||
| 327 | } | ||
| 328 | |||
| 329 | 1 | return tflag(TFLAG_QUERY_L3); | |
| 330 | } | ||
| 331 | |||
| 332 | 15 | KeyCode parse_dcs_query_reply(const char *data, size_t len, bool truncated) | |
| 333 | { | ||
| 334 | 15 | const char *note = ""; | |
| 335 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 15 times.
|
15 | if (unlikely(len == 0 || truncated)) { |
| 336 | ✗ | note = truncated ? " (truncated)" : " (empty)"; | |
| 337 | ✗ | goto unhandled; | |
| 338 | } | ||
| 339 | |||
| 340 | 15 | StringView seq = string_view(data, len); | |
| 341 |
2/2✓ Branch 0 (7→8) taken 5 times.
✓ Branch 1 (7→9) taken 10 times.
|
15 | if (strview_remove_matching_prefix(&seq, ">|")) { |
| 342 | 5 | return parse_xtversion_reply(seq); | |
| 343 | } | ||
| 344 | |||
| 345 | // https://vt100.net/docs/vt510-rm/DA3.html | ||
| 346 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Tertiary%20DA,-) | ||
| 347 |
2/2✓ Branch 0 (10→11) taken 2 times.
✓ Branch 1 (10→13) taken 8 times.
|
10 | if (strview_remove_matching_prefix(&seq, "!|")) { |
| 348 | // Note that DA3 is no longer sent by term_put_level_2_queries(), | ||
| 349 | // because several terminals (including Termux) have buggy handling | ||
| 350 | // of CSI sequences containing '=', which causes "c" to be rendered | ||
| 351 | // (but not inserted into the buffer) at startup. | ||
| 352 | // See also: https://github.com/fish-shell/fish-shell/commit/e49dde87cc0f1dcedd424d5ed7a520a3f011ee29 | ||
| 353 | 2 | LOG_INFO("DA3 reply: %.*s", (int)seq.length, seq.data); | |
| 354 | 2 | return tflag(TFLAG_QUERY_L3); | |
| 355 | } | ||
| 356 | |||
| 357 |
4/4✓ Branch 0 (14→15) taken 6 times.
✓ Branch 1 (14→17) taken 2 times.
✓ Branch 2 (16→17) taken 2 times.
✓ Branch 3 (16→18) taken 4 times.
|
8 | if (strview_has_prefix(seq, "1+r") || strview_has_prefix(seq, "0+r")) { |
| 358 | 4 | return parse_xtgettcap_reply(data, len); | |
| 359 | } | ||
| 360 | |||
| 361 |
1/2✓ Branch 0 (19→20) taken 4 times.
✗ Branch 1 (19→35) not taken.
|
4 | if (strview_remove_matching_prefix(&seq, "1$r")) { |
| 362 |
2/2✓ Branch 0 (21→22) taken 1 times.
✓ Branch 1 (21→30) taken 3 times.
|
4 | if (strview_has_suffix(seq, " q")) { |
| 363 | 1 | size_t n = seq.length - 2; | |
| 364 | 1 | unsigned int x; | |
| 365 |
2/4✓ Branch 0 (22→23) taken 1 times.
✗ Branch 1 (22→29) not taken.
✓ Branch 2 (24→25) taken 1 times.
✗ Branch 3 (24→29) not taken.
|
1 | if (n >= 1 && n == buf_parse_uint(seq.data, n, &x)) { |
| 366 |
1/2✓ Branch 0 (25→26) taken 1 times.
✗ Branch 1 (25→27) not taken.
|
1 | const char *str = (x <= CURSOR_STEADY_BAR) ? cursor_type_to_str(x) : "??"; |
| 367 | 1 | LOG_DEBUG("DECRQSS DECSCUSR (cursor style) reply: %u (%s)", x, str); | |
| 368 | 1 | return KEY_IGNORE; | |
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 |
1/2✓ Branch 0 (31→32) taken 3 times.
✗ Branch 1 (31→33) not taken.
|
3 | if (strview_remove_matching_suffix(&seq, "m")) { |
| 373 | 3 | return handle_decrqss_sgr_reply(seq.data, seq.length); | |
| 374 | } | ||
| 375 | |||
| 376 | ✗ | LOG_INFO("unhandled DECRQSS reply: %.*s", (int)len, data); | |
| 377 | ✗ | return KEY_IGNORE; | |
| 378 | } | ||
| 379 | |||
| 380 | ✗ | if (strview_equal_cstring(seq, "0$r")) { | |
| 381 | ✗ | note = " (DECRQSS; invalid request)"; | |
| 382 | ✗ | goto unhandled; | |
| 383 | } | ||
| 384 | |||
| 385 | ✗ | unhandled: | |
| 386 | ✗ | LOG_INFO("unhandled DCS string%s: %.*s", note, (int)len, data); | |
| 387 | ✗ | return KEY_IGNORE; | |
| 388 | } | ||
| 389 | |||
| 390 | 4 | KeyCode parse_osc_query_reply(const char *data, size_t len, bool truncated) | |
| 391 | { | ||
| 392 |
1/2✓ Branch 0 (2→3) taken 4 times.
✗ Branch 1 (2→12) not taken.
|
4 | if (unlikely(len == 0)) { |
| 393 | return KEY_IGNORE; | ||
| 394 | } | ||
| 395 | |||
| 396 |
1/2✓ Branch 0 (3→4) taken 4 times.
✗ Branch 1 (3→5) not taken.
|
4 | const char *note = truncated ? " (truncated)" : ""; |
| 397 | 4 | char prefix = data[0]; | |
| 398 |
1/2✓ Branch 0 (5→6) taken 4 times.
✗ Branch 1 (5→10) not taken.
|
4 | if (prefix == 'L' || prefix == 'l') { |
| 399 |
2/2✓ Branch 0 (6→7) taken 2 times.
✓ Branch 1 (6→8) taken 2 times.
|
4 | const char *type = (prefix == 'l') ? "title" : "icon"; |
| 400 | 4 | LOG_DEBUG("window %s%s: %.*s", type, note, (int)len - 1, data + 1); | |
| 401 | 4 | return KEY_IGNORE; | |
| 402 | } | ||
| 403 | |||
| 404 | ✗ | LOG_WARNING("unhandled OSC string%s: %.*s", note, (int)len, data); | |
| 405 | ✗ | return KEY_IGNORE; | |
| 406 | } | ||
| 407 | |||
| 408 | ✗ | KeyCode parse_xtwinops_query_reply(const TermControlParams *csi) | |
| 409 | { | ||
| 410 | ✗ | if (unlikely(csi->nparams != 3)) { | |
| 411 | ✗ | LOG_INFO("XTWINOPS sequence with unrecognized number of params"); | |
| 412 | ✗ | return KEY_IGNORE; | |
| 413 | } | ||
| 414 | |||
| 415 | // CSI type ; height ; width t | ||
| 416 | ✗ | const char *typestr = "unknown"; | |
| 417 | ✗ | unsigned int type = csi->params[0][0]; | |
| 418 | ✗ | unsigned int height = csi->params[1][0]; | |
| 419 | ✗ | unsigned int width = csi->params[2][0]; | |
| 420 | |||
| 421 | ✗ | switch (type) { | |
| 422 | ✗ | case 4: typestr = "text area size in pixels"; break; | |
| 423 | ✗ | case 6: typestr = "cell size in pixels"; break; | |
| 424 | ✗ | case 8: typestr = "text area size in \"characters\""; break; | |
| 425 | } | ||
| 426 | |||
| 427 | ✗ | LOG_INFO("XTWINOPS %u (%s) reply: %ux%u", type, typestr, width, height); | |
| 428 | ✗ | return KEY_IGNORE; | |
| 429 | } | ||
| 430 |