dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 77.4% 175 / 0 / 226
Functions: 92.9% 13 / 0 / 14
Branches: 68.8% 130 / 2 / 191

src/terminal/query.c
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 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 9 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 2 → 7 taken 1 time.
✓ Branch 2 → 8 taken 1 time.
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 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 2 → 7 taken 1 time.
✓ Branch 2 → 8 taken 1 time.
✓ Branch 2 → 9 taken 1 time.
✓ Branch 2 → 10 taken 6 times.
✓ Branch 2 → 11 taken 1 time.
✓ Branch 2 → 12 taken 1 time.
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 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 2 → 9 not taken.
✗ Branch 2 → 10 not taken.
✗ Branch 2 → 11 not taken.
✗ Branch 2 → 12 not taken.
✓ Branch 2 → 13 taken 1 time.
✓ Branch 2 → 14 taken 1 time.
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 7 → 3 taken 2 times.
✓ Branch 7 → 8 taken 1 time.
3 for (size_t i = 1, n = csi->nparams; i < n; i++) {
80
2/3
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 5 not taken.
✓ Branch 3 → 6 taken 1 time.
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 2 → 3 taken 18 times.
✓ Branch 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 4 → 5 taken 37 times.
✓ Branch 4 → 6 taken 1 time.
✗ Branch 5 → 6 not taken.
✓ Branch 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 7 → 8 taken 3 times.
✓ Branch 7 → 17 taken 34 times.
✓ Branch 8 → 9 taken 3 times.
✗ Branch 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 9 → 10 taken 1 time.
✓ Branch 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 12 → 13 taken 1 time.
✓ Branch 12 → 15 taken 1 time.
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 17 → 18 taken 3 times.
✓ Branch 17 → 23 taken 31 times.
✓ Branch 18 → 19 taken 3 times.
✗ Branch 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 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 2 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 51 taken 1 time.
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 23 → 24 taken 18 times.
✓ Branch 23 → 33 taken 13 times.
✓ Branch 24 → 25 taken 15 times.
✓ Branch 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 26 → 27 taken 1 time.
✓ Branch 26 → 28 taken 14 times.
15 if (mode == 1036 && status == DECRPM_RESET) {
153 1 return tflag(TFLAG_META_ESC);
154 }
155
2/2
✓ Branch 28 → 29 taken 1 time.
✓ Branch 28 → 30 taken 13 times.
14 if (mode == 1039 && status == DECRPM_RESET) {
156 1 return tflag(TFLAG_ALT_ESC);
157 }
158
4/4
✓ Branch 30 → 31 taken 6 times.
✓ Branch 30 → 51 taken 7 times.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 51 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 33 → 34 taken 6 times.
✓ Branch 33 → 37 taken 10 times.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 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 37 → 38 taken 7 times.
✓ Branch 37 → 48 taken 6 times.
✗ Branch 38 → 39 not taken.
✓ Branch 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 40 → 41 taken 6 times.
✓ Branch 40 → 46 taken 1 time.
7 if (code == 4 && nparams <= 2) {
175 // XTMODKEYS 4 reply to XTQMODKEYS 4 query (CSI > 4 ; Pv m)
176
2/2
✓ Branch 41 → 42 taken 4 times.
✓ Branch 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 44 → 45 taken 5 times.
✓ Branch 44 → 51 taken 1 time.
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 2 → 3 taken 4 times.
✓ Branch 2 → 5 taken 4 times.
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 9 taken 4 times.
8 if (n == 0 || n & 1 || n / 2 > bufsize) {
195 4 return empty;
196 }
197
198
2/2
✓ Branch 9 → 6 taken 13 times.
✓ Branch 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 6 → 7 not taken.
✓ Branch 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 // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=capabilities.-,xterm%20responds%20with,-DCS%201%20%2B%20r
211 4 static KeyCode parse_xtgettcap_reply(StringView seq, bool valid_request)
212 {
213 4 size_t pos = 0;
214 4 size_t len = seq.length;
215 4 StringView empty = STRING_VIEW_INIT;
216
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 1 time.
4 StringView cap_hex = (pos < len) ? get_delim(seq.data, &pos, len, '=') : empty;
217
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 3 times.
4 StringView val_hex = (pos < len) ? strview_from_slice(seq.data, pos, len) : empty;
218
219 4 char cbuf[16], vbuf[64];
220 4 StringView cap = hex_decode_str(cap_hex, cbuf, sizeof(cbuf));
221 4 StringView val = hex_decode_str(val_hex, vbuf, sizeof(vbuf));
222
223
3/4
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 34 taken 2 times.
✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 34 not taken.
4 if (valid_request && cap.length >= 2) {
224
3/4
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 16 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 16 not taken.
2 if (strview_equal_cstring(cap, "bce") && val.length == 0) {
225 1 return tflag(TFLAG_BACK_COLOR_ERASE);
226 }
227
2/4
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 21 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
1 if (strview_equal_cstring(cap, "tsl") && strview_equal_cstring(val, "\033]2;")) {
228 1 return tflag(TFLAG_SET_WINDOW_TITLE);
229 }
230 if (strview_equal_cstring(cap, "rep") && strview_has_suffix(val, "b")) {
231 return tflag(TFLAG_ECMA48_REPEAT);
232 }
233 if (strview_equal_cstring(cap, "Ms") && val.length >= 6) {
234 // All 71 entries with this cap in the ncurses terminfo database
235 // use OSC 52, with only slight differences (BEL vs. ST), so
236 // there's really no reason to check the value.
237 // Source: https://gitlab.com/craigbarnes/lua-terminfo-parser/-/blob/master/examples/output/cap-values-Ms.txt#:~:text=Totals,-%2D
238 return tflag(TFLAG_OSC52_COPY);
239 }
240 if (strview_equal_cstring(cap, "query-os-name")) {
241 // Terminal-side operating system query; see:
242 // • https://codeberg.org/dnkl/foot/issues/2209
243 // • https://codeberg.org/dnkl/foot/pulls/2217/
244 // • https://github.com/kovidgoyal/kitty/issues/9217
245 // • https://github.com/fish-shell/fish-shell/blob/7a05ea0f938a8483/doc_src/terminal-compatibility.rst#:~:text=query%2Dos%2Dname
246 LOG_DEBUG("XTGETTCAP \"query-os-name\" reply: %.*s", (int)val.length, val.data);
247 return KEY_IGNORE;
248 }
249 }
250
251
1/2
✓ Branch 35 → 36 taken 2 times.
✗ Branch 35 → 47 not taken.
2 if (!log_level_enabled(LOG_LEVEL_INFO)) {
252 return KEY_IGNORE;
253 }
254
255 2 char ebuf[8 + (U_SET_CHAR_MAXLEN * (sizeof(cbuf) + sizeof(vbuf)))];
256 2 size_t i = 0;
257
2/2
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 43 taken 1 time.
2 if (cap.length) {
258 1 MakePrintableFlags mpflags = MPF_C0_SYMBOLS;
259 1 i += copyliteral(ebuf + i, " (");
260 1 i += u_make_printable(cap, ebuf + i, sizeof(ebuf) - i, mpflags);
261
1/2
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 42 taken 1 time.
1 if (val.length) {
262 ebuf[i++] = '=';
263 i += u_make_printable(val, ebuf + i, sizeof(ebuf) - i, mpflags);
264 }
265 1 ebuf[i++] = ')';
266 }
267
268
1/2
✓ Branch 43 → 44 taken 2 times.
✗ Branch 43 → 45 not taken.
2 LOG_INFO (
269 "unhandled XTGETTCAP reply: %c+r%.*s%.*s",
270 valid_request ? '1' : '0', // For "1+r" or "0+r" prefix (removed by caller)
271 (int)seq.length, seq.data, // Original, hex-encoded string (no control chars)
272 (int)i, ebuf // Decoded string (with control chars escaped)
273 );
274
275 2 return KEY_IGNORE;
276 }
277
278 3 static KeyCode handle_decrqss_sgr_reply(const char *data, size_t len)
279 {
280 3 LOG_DEBUG("DECRQSS SGR reply: %.*s", (int)len, data);
281
282 3 TermFeatureFlags flags = 0;
283
2/2
✓ Branch 21 → 4 taken 8 times.
✓ Branch 21 → 22 taken 3 times.
11 for (size_t pos = 0; pos < len; ) {
284 // These SGR params correspond to the ones in term_put_level_3_queries()
285 8 StringView s = get_delim(data, &pos, len, ';');
286
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 6 times.
8 if (strview_equal_cstring(s, "48:5:255")) {
287 2 flags |= TFLAG_256_COLOR;
288 7 continue;
289 }
290
291
2/2
✓ Branch 9 → 10 taken 5 times.
✓ Branch 9 → 12 taken 1 time.
6 if (
292 6 strview_equal_cstring(s, "38:2::60:70:80") // xterm, foot
293
2/2
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 4 times.
5 || strview_equal_cstring(s, "38:2:60:70:80") // kitty
294 ) {
295 2 flags |= TFLAG_TRUE_COLOR;
296 2 continue;
297 }
298
299
3/4
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 17 taken 1 time.
✓ Branch 15 → 16 taken 3 times.
✗ Branch 15 → 17 not taken.
4 if (strview_equal_cstring(s, "0") && pos <= 2) {
300 3 continue;
301 }
302
303 1 LOG_WARNING (
304 "unrecognized parameter substring in DECRQSS SGR reply: %.*s",
305 (int)s.length, s.data
306 );
307 }
308
309
2/2
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 24 taken 1 time.
3 return flags ? tflag(flags) : KEY_IGNORE;
310 }
311
312 5 static KeyCode parse_xtversion_reply(StringView reply)
313 {
314 5 LOG_INFO("XTVERSION reply: %.*s", (int)reply.length, reply.data);
315
316
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 5 times.
5 if (strview_has_prefix(reply, "XTerm(")) {
317 return tflag (
318 TFLAG_QUERY_L3 | TFLAG_ECMA48_REPEAT | TFLAG_MODIFY_OTHER_KEYS
319 );
320 }
321
322
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 5 times.
5 if (strview_has_prefix(reply, "Konsole ")) {
323 return tflag(TFLAG_BS_CTRL_BACKSPACE);
324 }
325
326 // WezTerm sets TERM=xterm-256color by default, so it makes sense
327 // to set these flags here, in addition to the (typically not used)
328 // terms[] entry
329
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 5 times.
5 if (strview_has_prefix(reply, "WezTerm ")) {
330 return tflag (
331 TFLAG_TRUE_COLOR | TFLAG_BACK_COLOR_ERASE | TFLAG_ECMA48_REPEAT
332 | TFLAG_SET_WINDOW_TITLE | TFLAG_OSC52_COPY | TFLAG_SYNC
333 | TFLAG_BS_CTRL_BACKSPACE
334 );
335 }
336
337
2/2
✓ Branch 13 → 14 taken 4 times.
✓ Branch 13 → 15 taken 1 time.
5 if (strview_has_prefix(reply, "tmux ")) {
338 // tmux gained support for XTVERSION in release 3.2, so any response
339 // starting with "tmux " implies support for all tmux 3.2 features.
340 // It also has bugs related to DECRQSS queries, including crashing
341 // and spuriously printing "SIXEL IMAGE (1x1)", so we also set the
342 // TFLAG_NO_QUERY_L3 flag here.
343 // See also:
344 // • https://github.com/tmux/tmux/wiki/FAQ#how-often-is-tmux-released-what-is-the-version-number-scheme
345 // • https://github.com/tmux/tmux/commit/9dd58470e41bfb (XTVERSION; v3.2)
346 // • https://github.com/tmux/tmux/commit/5fc0be50450e75 (REP; v2.6)
347 // TODO: Set TFLAG_MODIFY_OTHER_KEYS conditionally, for tmux 3.5a+ (or 3.6+?)
348 // TODO: Set TFLAG_SYNC for tmux 3.4+ (tmux doesn't support DECRQM)
349 4 return tflag (
350 TFLAG_NO_QUERY_L3 | TFLAG_ECMA48_REPEAT | TFLAG_MODIFY_OTHER_KEYS
351 | TFLAG_BS_CTRL_BACKSPACE
352 );
353 }
354
355 1 return tflag(TFLAG_QUERY_L3);
356 }
357
358 15 KeyCode parse_dcs_query_reply(StringView seq, bool truncated)
359 {
360 15 const char *note = "";
361
2/4
✓ Branch 2 → 3 taken 15 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 6 not taken.
✓ Branch 3 → 7 taken 15 times.
15 if (unlikely(seq.length == 0 || truncated)) {
362 note = truncated ? " (truncated)" : " (empty)";
363 goto unhandled;
364 }
365
366
2/2
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 10 taken 10 times.
15 if (strview_remove_matching_prefix(&seq, ">|")) {
367 5 return parse_xtversion_reply(seq);
368 }
369
370 // https://vt100.net/docs/vt510-rm/DA3.html
371 // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Tertiary%20DA,-)
372
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 14 taken 8 times.
10 if (strview_remove_matching_prefix(&seq, "!|")) {
373 // Note that DA3 is no longer sent by term_put_level_2_queries(),
374 // because several terminals (including Termux) have buggy handling
375 // of CSI sequences containing '=', which causes "c" to be rendered
376 // (but not inserted into the buffer) at startup.
377 // See also: https://github.com/fish-shell/fish-shell/commit/e49dde87cc0f1dcedd424d5ed7a520a3f011ee29
378 2 LOG_INFO("DA3 reply: %.*s", (int)seq.length, seq.data);
379 2 return tflag(TFLAG_QUERY_L3);
380 }
381
382 8 bool one_plus_r = strview_remove_matching_prefix(&seq, "1+r");
383
4/4
✓ Branch 15 → 16 taken 6 times.
✓ Branch 15 → 18 taken 2 times.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 4 times.
8 if (one_plus_r || strview_remove_matching_prefix(&seq, "0+r")) {
384 4 return parse_xtgettcap_reply(seq, one_plus_r);
385 }
386
387
1/2
✓ Branch 20 → 21 taken 4 times.
✗ Branch 20 → 36 not taken.
4 if (strview_remove_matching_prefix(&seq, "1$r")) {
388
2/2
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 31 taken 3 times.
4 if (strview_has_suffix(seq, " q")) {
389 1 size_t n = seq.length - 2;
390 1 unsigned int x;
391
2/4
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 30 not taken.
✓ Branch 25 → 26 taken 1 time.
✗ Branch 25 → 30 not taken.
1 if (n >= 1 && n == buf_parse_uint(seq.data, n, &x)) {
392
1/2
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 28 not taken.
1 const char *str = (x <= CURSOR_STEADY_BAR) ? cursor_type_to_str(x) : "??";
393 1 LOG_DEBUG("DECRQSS DECSCUSR (cursor style) reply: %u (%s)", x, str);
394 1 return KEY_IGNORE;
395 }
396 }
397
398
1/2
✓ Branch 32 → 33 taken 3 times.
✗ Branch 32 → 34 not taken.
3 if (strview_remove_matching_suffix(&seq, "m")) {
399 3 return handle_decrqss_sgr_reply(seq.data, seq.length);
400 }
401
402 LOG_INFO("unhandled DECRQSS reply: %.*s", (int)seq.length, seq.data);
403 return KEY_IGNORE;
404 }
405
406 if (strview_equal_cstring(seq, "0$r")) {
407 note = " (DECRQSS; invalid request)";
408 goto unhandled;
409 }
410
411 unhandled:
412 LOG_INFO("unhandled DCS string%s: %.*s", note, (int)seq.length, seq.data);
413 return KEY_IGNORE;
414 }
415
416 4 KeyCode parse_osc_query_reply(StringView seq, bool truncated)
417 {
418
1/2
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 13 not taken.
4 if (unlikely(seq.length == 0)) {
419 return KEY_IGNORE;
420 }
421
422
1/2
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 5 not taken.
4 const char *note = truncated ? " (truncated)" : "";
423 4 bool l_prefix = strview_remove_matching_prefix(&seq, "l");
424
3/4
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 9 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 11 not taken.
4 if (l_prefix || strview_remove_matching_prefix(&seq, "L")) {
425 const char *type = l_prefix ? "title" : "icon";
426 4 LOG_DEBUG("window %s%s: %.*s", type, note, (int)seq.length, seq.data);
427 4 return KEY_IGNORE;
428 }
429
430 LOG_WARNING("unhandled OSC string%s: %.*s", note, (int)seq.length, seq.data);
431 return KEY_IGNORE;
432 }
433
434 KeyCode parse_xtwinops_query_reply(const TermControlParams *csi)
435 {
436 if (unlikely(csi->nparams != 3)) {
437 LOG_INFO("XTWINOPS sequence with unrecognized number of params");
438 return KEY_IGNORE;
439 }
440
441 // CSI type ; height ; width t
442 const char *typestr = "unknown";
443 unsigned int type = csi->params[0][0];
444 unsigned int height = csi->params[1][0];
445 unsigned int width = csi->params[2][0];
446
447 switch (type) {
448 case 4: typestr = "text area size in pixels"; break;
449 case 6: typestr = "cell size in pixels"; break;
450 case 8: typestr = "text area size in \"characters\""; break;
451 }
452
453 LOG_INFO("XTWINOPS %u (%s) reply: %ux%u", type, typestr, width, height);
454 return KEY_IGNORE;
455 }
456