dte test coverage


Directory: ./
File: src/terminal/query.c
Date: 2025-07-03 15:44:24
Exec Total Coverage
Lines: 174 220 79.1%
Functions: 14 15 93.3%
Branches: 125 177 70.6%

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