dte test coverage


Directory: ./
File: src/terminal/query.c
Date: 2025-09-07 23:01:39
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 "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