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 taken 2 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 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 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 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 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 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 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=features%20the%20terminal%20supports | ||
78 | 1 | static TermFeatureFlags da1_params_to_features(const TermControlParams *csi) | |
79 | { | ||
80 | 1 | TermFeatureFlags flags = 0; | |
81 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | for (size_t i = 1, n = csi->nparams; i < n; i++) { |
82 | 2 | unsigned int p = csi->params[i][0]; | |
83 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (p == 22) { |
84 | 1 | flags |= TFLAG_8_COLOR; | |
85 | } | ||
86 | } | ||
87 | 1 | return flags; | |
88 | } | ||
89 | |||
90 | 38 | KeyCode parse_csi_query_reply(const TermControlParams *csi, uint8_t prefix) | |
91 | { | ||
92 | // NOTE: The main conditions below must check ALL of these values, in | ||
93 | // addition to the prefix byte | ||
94 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 20 times.
|
38 | uint8_t intermediate = csi->nr_intermediate ? csi->intermediate[0] : 0; |
95 | 38 | uint8_t final = csi->final_byte; | |
96 | 38 | unsigned int nparams = csi->nparams; | |
97 | |||
98 |
3/4✓ Branch 0 taken 37 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
|
38 | if (unlikely(csi->have_subparams || csi->nr_intermediate > 1)) { |
99 | 1 | goto ignore; | |
100 | } | ||
101 | |||
102 | // https://vt100.net/docs/vt510-rm/DA1.html | ||
103 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Primary%20DA,-) | ||
104 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
37 | if (prefix == '?' && final == 'c' && intermediate == 0 && nparams >= 1) { |
105 | 3 | unsigned int code = csi->params[0][0]; | |
106 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | if (code >= 61 && code <= 65) { |
107 | 1 | LOG_INFO("DA1 reply with P=%u (device level %u)", code, code - 60); | |
108 | 1 | return tflag(TFLAG_QUERY_L2 | TFLAG_QUERY_L3 | da1_params_to_features(csi)); | |
109 | } | ||
110 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (code >= 1 && code <= 12) { |
111 | 1 | LOG_INFO("DA1 reply with P=%u (VT100 series)", code); | |
112 | 1 | return tflag(TFLAG_QUERY_L2); | |
113 | } | ||
114 | 1 | LOG_INFO("DA1 reply with P=%u (unknown)", code); | |
115 | 1 | return KEY_IGNORE; | |
116 | } | ||
117 | |||
118 | // https://vt100.net/docs/vt510-rm/DA2.html | ||
119 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Secondary%20DA,-) | ||
120 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
34 | if (prefix == '>' && final == 'c' && intermediate == 0 && nparams == 3) { |
121 | 3 | unsigned int type = csi->params[0][0]; | |
122 | 3 | unsigned int firmware = csi->params[1][0]; | |
123 | 3 | unsigned int pc = csi->params[2][0]; | |
124 | 3 | const char *name = da2_param_to_name(type); | |
125 | 3 | LOG_INFO("DA2 reply: %u (%s); %u; %u", type, name, firmware, pc); | |
126 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
3 | if (type == 0 && firmware == 136 && pc == 0) { |
127 | // This is the DA2 response sent by PuTTY, which doesn't parse | ||
128 | // DCS sequences in accordance with ECMA-48 and so shouldn't | ||
129 | // be sent the queries in term_put_level_3_queries() | ||
130 | return KEY_IGNORE; | ||
131 | } | ||
132 | 2 | return tflag(TFLAG_QUERY_L3); | |
133 | } | ||
134 | |||
135 |
4/4✓ Branch 0 taken 18 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 3 times.
|
31 | if (prefix == '?' && final == 'y' && intermediate == '$' && nparams == 2) { |
136 | // DECRPM reply to DECRQM query (CSI ? Ps; Pm $ y) | ||
137 | 15 | unsigned int mode = csi->params[0][0]; | |
138 | 15 | unsigned int status = csi->params[1][0]; | |
139 | 15 | const char *mstr = decrpm_mode_to_str(mode); | |
140 | 15 | const char *sstr = decrpm_status_to_str(status); | |
141 | 15 | LOG_DEBUG("DECRPM %u (%s) reply: %u (%s)", mode, mstr, status, sstr); | |
142 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 14 times.
|
15 | if (mode == 1036 && status == DECRPM_RESET) { |
143 | 1 | return tflag(TFLAG_META_ESC); | |
144 | } | ||
145 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
|
14 | if (mode == 1039 && status == DECRPM_RESET) { |
146 | 1 | return tflag(TFLAG_ALT_ESC); | |
147 | } | ||
148 |
4/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
|
13 | if (mode == 2026 && decrpm_is_set_or_reset(status)) { |
149 | 2 | return tflag(TFLAG_SYNC); | |
150 | } | ||
151 | return KEY_IGNORE; | ||
152 | } | ||
153 | |||
154 |
4/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
|
16 | if (prefix == '?' && final == 'u' && intermediate == 0 && nparams == 1) { |
155 | // Kitty keyboard protocol flags (CSI ? flags u) | ||
156 | 3 | unsigned int flags = csi->params[0][0]; | |
157 | 3 | LOG_DEBUG("query reply for kittykbd flags: 0x%x", flags); | |
158 | // Interpret reply with any flags to mean "supported" | ||
159 | 3 | return tflag(TFLAG_KITTY_KEYBOARD); | |
160 | } | ||
161 | |||
162 |
3/4✓ Branch 0 taken 7 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
|
13 | if (prefix == '>' && final == 'm' && intermediate == 0 && nparams >= 1) { |
163 | 7 | unsigned int code = csi->params[0][0]; | |
164 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
|
7 | if (code == 4 && nparams <= 2) { |
165 | // XTMODKEYS 4 reply to XTQMODKEYS 4 query (CSI > 4 ; Pv m) | ||
166 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
|
6 | unsigned int val = (nparams == 1) ? 0 : csi->params[1][0]; |
167 | 6 | LOG_DEBUG("XTMODKEYS 4 reply: modifyOtherKeys=%u", val); | |
168 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
|
6 | return (val <= 2) ? tflag(TFLAG_MODIFY_OTHER_KEYS) : KEY_IGNORE; |
169 | } | ||
170 | 1 | LOG_DEBUG("XTMODKEYS %u reply with %u params", code, nparams); | |
171 | 1 | return KEY_IGNORE; | |
172 | } | ||
173 | |||
174 | 6 | ignore: | |
175 | // TODO: Also log intermediate and nparams | ||
176 | 7 | LOG_INFO("unhandled CSI with '%c' param prefix and final byte '%c'", prefix, (char)final); | |
177 | 7 | return KEY_IGNORE; | |
178 | } | ||
179 | |||
180 | 8 | static StringView hex_decode_str(StringView input, char *outbuf, size_t bufsize) | |
181 | { | ||
182 | 8 | StringView empty = STRING_VIEW_INIT; | |
183 | 8 | size_t n = input.length; | |
184 |
4/6✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
|
8 | if (n == 0 || n & 1 || n / 2 > bufsize) { |
185 | 4 | return empty; | |
186 | } | ||
187 | |||
188 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 4 times.
|
17 | for (size_t i = 0, j = 0; i < n; ) { |
189 | 13 | unsigned int a = hex_decode(input.data[i++]); | |
190 | 13 | unsigned int b = hex_decode(input.data[i++]); | |
191 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (unlikely((a | b) > 0xF)) { |
192 | ✗ | return empty; | |
193 | } | ||
194 | 13 | outbuf[j++] = (unsigned char)((a << 4) | b); | |
195 | } | ||
196 | |||
197 | 4 | return string_view(outbuf, n / 2); | |
198 | } | ||
199 | |||
200 | 1 | static size_t make_printable_ctlseq(const StringView *seq, char *buf, size_t buflen) | |
201 | { | ||
202 | 1 | MakePrintableFlags flags = MPF_C0_SYMBOLS; | |
203 | 1 | return u_make_printable(seq->data, seq->length, buf, buflen, flags); | |
204 | } | ||
205 | |||
206 | 4 | static KeyCode parse_xtgettcap_reply(const char *data, size_t len) | |
207 | { | ||
208 | 4 | size_t pos = 3; | |
209 | 4 | StringView empty = STRING_VIEW_INIT; | |
210 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | StringView cap_hex = (pos < len) ? get_delim(data, &pos, len, '=') : empty; |
211 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
|
4 | StringView val_hex = (pos < len) ? string_view(data + pos, len - pos) : empty; |
212 | |||
213 | 4 | char cbuf[8], vbuf[64]; | |
214 | 4 | StringView cap = hex_decode_str(cap_hex, cbuf, sizeof(cbuf)); | |
215 | 4 | StringView val = hex_decode_str(val_hex, vbuf, sizeof(vbuf)); | |
216 | |||
217 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 | if (data[0] == '1' && cap.length >= 2) { |
218 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | if (strview_equal_cstring(&cap, "bce") && val.length == 0) { |
219 | 1 | return tflag(TFLAG_BACK_COLOR_ERASE); | |
220 | } | ||
221 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | if (strview_equal_cstring(&cap, "tsl") && strview_equal_cstring(&val, "\033]2;")) { |
222 | 1 | return tflag(TFLAG_SET_WINDOW_TITLE); | |
223 | } | ||
224 | ✗ | if (strview_equal_cstring(&cap, "rep") && strview_has_suffix(&val, "b")) { | |
225 | ✗ | return tflag(TFLAG_ECMA48_REPEAT); | |
226 | } | ||
227 | ✗ | if (strview_equal_cstring(&cap, "Ms") && val.length >= 6) { | |
228 | // All 65 entries with this cap in the ncurses terminfo database | ||
229 | // use OSC 52, with only slight differences (BEL vs. ST), so | ||
230 | // there's really no reason to check the value | ||
231 | ✗ | return tflag(TFLAG_OSC52_COPY); | |
232 | } | ||
233 | } | ||
234 | |||
235 | 2 | char ebuf[8 + (U_SET_CHAR_MAXLEN * (sizeof(cbuf) + sizeof(vbuf)))]; | |
236 | 2 | size_t i = 0; | |
237 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (cap.length) { |
238 | 1 | i += copyliteral(ebuf + i, " ("); | |
239 | 1 | i += make_printable_ctlseq(&cap, ebuf + i, sizeof(ebuf) - i); | |
240 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (val.length) { |
241 | ✗ | ebuf[i++] = '='; | |
242 | ✗ | i += make_printable_ctlseq(&val, ebuf + i, sizeof(ebuf) - i); | |
243 | } | ||
244 | 1 | ebuf[i++] = ')'; | |
245 | } | ||
246 | |||
247 | 2 | LOG_INFO ( | |
248 | "unhandled XTGETTCAP reply: %.*s%.*s", | ||
249 | (int)len, data, // Original, hex-encoded string (no control chars) | ||
250 | (int)i, ebuf // Decoded string (with control chars escaped) | ||
251 | ); | ||
252 | |||
253 | 2 | return KEY_IGNORE; | |
254 | } | ||
255 | |||
256 | 3 | static KeyCode handle_decrqss_sgr_reply(const char *data, size_t len) | |
257 | { | ||
258 | 3 | LOG_DEBUG("DECRQSS SGR reply: %.*s", (int)len, data); | |
259 | |||
260 | 3 | TermFeatureFlags flags = 0; | |
261 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
|
11 | for (size_t pos = 0; pos < len; ) { |
262 | // These SGR params correspond to the ones in term_put_level_3_queries() | ||
263 | 8 | StringView s = get_delim(data, &pos, len, ';'); | |
264 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | if (strview_equal_cstring(&s, "48:5:255")) { |
265 | 2 | flags |= TFLAG_256_COLOR; | |
266 | 7 | continue; | |
267 | } | ||
268 | |||
269 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
|
6 | if ( |
270 | 6 | strview_equal_cstring(&s, "38:2::60:70:80") // xterm, foot | |
271 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | || strview_equal_cstring(&s, "38:2:60:70:80") // kitty |
272 | ) { | ||
273 | 2 | flags |= TFLAG_TRUE_COLOR; | |
274 | 2 | continue; | |
275 | } | ||
276 | |||
277 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
4 | if (strview_equal_cstring(&s, "0") && pos <= 2) { |
278 | 3 | continue; | |
279 | } | ||
280 | |||
281 | 1 | LOG_WARNING ( | |
282 | "unrecognized parameter substring in DECRQSS SGR reply: %.*s", | ||
283 | (int)s.length, s.data | ||
284 | ); | ||
285 | } | ||
286 | |||
287 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | return flags ? tflag(flags) : KEY_IGNORE; |
288 | } | ||
289 | |||
290 | 15 | KeyCode parse_dcs_query_reply(const char *data, size_t len, bool truncated) | |
291 | { | ||
292 | 15 | const char *note = ""; | |
293 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | if (unlikely(len == 0 || truncated)) { |
294 | ✗ | note = truncated ? " (truncated)" : " (empty)"; | |
295 | ✗ | goto unhandled; | |
296 | } | ||
297 | |||
298 | 15 | StringView seq = string_view(data, len); | |
299 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 10 times.
|
15 | if (strview_remove_matching_prefix(&seq, ">|")) { |
300 | 5 | LOG_INFO("XTVERSION reply: %.*s", (int)seq.length, seq.data); | |
301 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (strview_has_prefix(&seq, "XTerm(")) { |
302 | ✗ | return tflag ( | |
303 | TFLAG_QUERY_L3 | TFLAG_ECMA48_REPEAT | TFLAG_MODIFY_OTHER_KEYS | ||
304 | ); | ||
305 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | } else if (strview_has_prefix(&seq, "tmux ")) { |
306 | // tmux gained support for XTVERSION in release 3.2, so any response | ||
307 | // starting with "tmux " implies support for all tmux 3.2 features. | ||
308 | // It also has bugs related to DECRQSS queries, including crashing | ||
309 | // and spuriously printing "SIXEL IMAGE (1x1)", so we also set the | ||
310 | // TFLAG_NO_QUERY_L3 flag here. | ||
311 | // See also: | ||
312 | // • https://github.com/tmux/tmux/wiki/FAQ#how-often-is-tmux-released-what-is-the-version-number-scheme | ||
313 | // • https://github.com/tmux/tmux/commit/9dd58470e41bfb (XTVERSION; v3.2) | ||
314 | // • https://github.com/tmux/tmux/commit/5fc0be50450e75 (REP; v2.6) | ||
315 | // TODO: Set TFLAG_MODIFY_OTHER_KEYS conditionally, for tmux 3.5a+ (or 3.6+?) | ||
316 | // TODO: Set TFLAG_SYNC for tmux 3.4+ (tmux doesn't support DECRQM) | ||
317 | 4 | return tflag ( | |
318 | TFLAG_NO_QUERY_L3 | TFLAG_ECMA48_REPEAT | TFLAG_MODIFY_OTHER_KEYS | ||
319 | ); | ||
320 | } | ||
321 | 1 | return tflag(TFLAG_QUERY_L3); | |
322 | } | ||
323 | |||
324 | // https://vt100.net/docs/vt510-rm/DA3.html | ||
325 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Tertiary%20DA,-) | ||
326 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
|
10 | if (strview_remove_matching_prefix(&seq, "!|")) { |
327 | 2 | LOG_INFO("DA3 reply: %.*s", (int)seq.length, seq.data); | |
328 | 2 | return tflag(TFLAG_QUERY_L3); | |
329 | } | ||
330 | |||
331 |
4/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
|
8 | if (strview_has_prefix(&seq, "1+r") || strview_has_prefix(&seq, "0+r")) { |
332 | 4 | return parse_xtgettcap_reply(data, len); | |
333 | } | ||
334 | |||
335 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (strview_remove_matching_prefix(&seq, "1$r")) { |
336 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
|
4 | if (strview_has_suffix(&seq, " q")) { |
337 | 1 | size_t n = seq.length - 2; | |
338 | 1 | unsigned int x; | |
339 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | if (n >= 1 && n == buf_parse_uint(seq.data, n, &x)) { |
340 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | const char *str = (x <= CURSOR_STEADY_BAR) ? cursor_type_to_str(x) : "??"; |
341 | 1 | LOG_DEBUG("DECRQSS DECSCUSR (cursor style) reply: %u (%s)", x, str); | |
342 | 1 | return KEY_IGNORE; | |
343 | } | ||
344 | } | ||
345 | |||
346 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (strview_remove_matching_suffix(&seq, "m")) { |
347 | 3 | return handle_decrqss_sgr_reply(seq.data, seq.length); | |
348 | } | ||
349 | |||
350 | ✗ | LOG_INFO("unhandled DECRQSS reply: %.*s", (int)len, data); | |
351 | ✗ | return KEY_IGNORE; | |
352 | } | ||
353 | |||
354 | ✗ | if (strview_equal_cstring(&seq, "0$r")) { | |
355 | ✗ | note = " (DECRQSS; invalid request)"; | |
356 | ✗ | goto unhandled; | |
357 | } | ||
358 | |||
359 | ✗ | unhandled: | |
360 | ✗ | LOG_INFO("unhandled DCS string%s: %.*s", note, (int)len, data); | |
361 | ✗ | return KEY_IGNORE; | |
362 | } | ||
363 | |||
364 | 4 | KeyCode parse_osc_query_reply(const char *data, size_t len, bool truncated) | |
365 | { | ||
366 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (unlikely(len == 0)) { |
367 | return KEY_IGNORE; | ||
368 | } | ||
369 | |||
370 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | const char *note = truncated ? " (truncated)" : ""; |
371 | 4 | char prefix = data[0]; | |
372 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (prefix == 'L' || prefix == 'l') { |
373 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | const char *type = (prefix == 'l') ? "title" : "icon"; |
374 | 4 | LOG_DEBUG("window %s%s: %.*s", type, note, (int)len - 1, data + 1); | |
375 | 4 | return KEY_IGNORE; | |
376 | } | ||
377 | |||
378 | ✗ | LOG_WARNING("unhandled OSC string%s: %.*s", note, (int)len, data); | |
379 | ✗ | return KEY_IGNORE; | |
380 | } | ||
381 | |||
382 | ✗ | KeyCode parse_xtwinops_query_reply(const TermControlParams *csi) | |
383 | { | ||
384 | ✗ | if (unlikely(csi->nparams != 3)) { | |
385 | ✗ | LOG_INFO("XTWINOPS sequence with unrecognized number of params"); | |
386 | ✗ | return KEY_IGNORE; | |
387 | } | ||
388 | |||
389 | // CSI type ; height ; width t | ||
390 | ✗ | const char *typestr = "unknown"; | |
391 | ✗ | unsigned int type = csi->params[0][0]; | |
392 | ✗ | unsigned int height = csi->params[1][0]; | |
393 | ✗ | unsigned int width = csi->params[2][0]; | |
394 | |||
395 | ✗ | switch (type) { | |
396 | ✗ | case 4: typestr = "text area size in pixels"; break; | |
397 | ✗ | case 6: typestr = "cell size in pixels"; break; | |
398 | ✗ | case 8: typestr = "text area size in \"characters\""; break; | |
399 | } | ||
400 | |||
401 | ✗ | LOG_INFO("XTWINOPS %u (%s) reply: %ux%u", type, typestr, width, height); | |
402 | ✗ | return KEY_IGNORE; | |
403 | } | ||
404 |