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 | // 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 (6→3) taken 2 times.
✓ Branch 1 (6→7) 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 (3→4) taken 1 times.
✓ Branch 1 (3→5) 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 (2→3) taken 18 times.
✓ Branch 1 (2→4) 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 (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)) { |
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 (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) { |
105 | 3 | unsigned int code = csi->params[0][0]; | |
106 |
2/2✓ Branch 0 (9→10) taken 1 times.
✓ Branch 1 (9→12) 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 (12→13) taken 1 times.
✓ Branch 1 (12→15) 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 (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) { |
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 (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) { |
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 (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) { |
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 (26→27) taken 1 times.
✓ Branch 1 (26→28) taken 14 times.
|
15 | if (mode == 1036 && status == DECRPM_RESET) { |
143 | 1 | return tflag(TFLAG_META_ESC); | |
144 | } | ||
145 |
2/2✓ Branch 0 (28→29) taken 1 times.
✓ Branch 1 (28→30) taken 13 times.
|
14 | if (mode == 1039 && status == DECRPM_RESET) { |
146 | 1 | return tflag(TFLAG_ALT_ESC); | |
147 | } | ||
148 |
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)) { |
149 | 2 | return tflag(TFLAG_SYNC); | |
150 | } | ||
151 | return KEY_IGNORE; | ||
152 | } | ||
153 | |||
154 |
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) { |
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 (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) { |
163 | 7 | unsigned int code = csi->params[0][0]; | |
164 |
2/2✓ Branch 0 (40→41) taken 6 times.
✓ Branch 1 (40→47) 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 (41→42) taken 4 times.
✓ Branch 1 (41→43) 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 (44→45) taken 5 times.
✓ Branch 1 (44→46) 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 (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) { |
185 | 4 | return empty; | |
186 | } | ||
187 | |||
188 |
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; ) { |
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 (6→7) not taken.
✓ Branch 1 (6→8) 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 (2→3) taken 3 times.
✓ Branch 1 (2→4) taken 1 times.
|
4 | StringView cap_hex = (pos < len) ? get_delim(data, &pos, len, '=') : empty; |
211 |
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; |
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 (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) { |
218 |
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) { |
219 | 1 | return tflag(TFLAG_BACK_COLOR_ERASE); | |
220 | } | ||
221 |
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;")) { |
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 (30→31) taken 1 times.
✓ Branch 1 (30→37) 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 (33→34) not taken.
✓ Branch 1 (33→36) 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 (21→4) taken 8 times.
✓ Branch 1 (21→22) 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 (6→7) taken 2 times.
✓ Branch 1 (6→8) 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 (9→10) taken 5 times.
✓ Branch 1 (9→12) taken 1 times.
|
6 | if ( |
270 | 6 | strview_equal_cstring(&s, "38:2::60:70:80") // xterm, foot | |
271 |
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 |
272 | ) { | ||
273 | 2 | flags |= TFLAG_TRUE_COLOR; | |
274 | 2 | continue; | |
275 | } | ||
276 | |||
277 |
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) { |
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 (22→23) taken 2 times.
✓ Branch 1 (22→24) 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 (2→3) not taken.
✓ Branch 1 (2→6) 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 (7→8) taken 5 times.
✓ Branch 1 (7→16) 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 (10→11) not taken.
✓ Branch 1 (10→12) 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 (13→14) taken 4 times.
✓ Branch 1 (13→15) 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 | | TFLAG_BS_CTRL_BACKSPACE | ||
320 | ); | ||
321 | } | ||
322 | 1 | return tflag(TFLAG_QUERY_L3); | |
323 | } | ||
324 | |||
325 | // https://vt100.net/docs/vt510-rm/DA3.html | ||
326 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#:~:text=(-,Tertiary%20DA,-) | ||
327 |
2/2✓ Branch 0 (17→18) taken 2 times.
✓ Branch 1 (17→20) taken 8 times.
|
10 | if (strview_remove_matching_prefix(&seq, "!|")) { |
328 | // Note that DA3 is no longer sent by term_put_level_2_queries(), | ||
329 | // because several terminals (including Termux) have buggy handling | ||
330 | // of CSI sequences containing '=', which causes "c" to be rendered | ||
331 | // (but not inserted into the buffer) at startup. | ||
332 | // See also: https://github.com/fish-shell/fish-shell/commit/e49dde87cc0f1dcedd424d5ed7a520a3f011ee29 | ||
333 | 2 | LOG_INFO("DA3 reply: %.*s", (int)seq.length, seq.data); | |
334 | 2 | return tflag(TFLAG_QUERY_L3); | |
335 | } | ||
336 | |||
337 |
4/4✓ Branch 0 (21→22) taken 6 times.
✓ Branch 1 (21→24) taken 2 times.
✓ Branch 2 (23→24) taken 2 times.
✓ Branch 3 (23→25) taken 4 times.
|
8 | if (strview_has_prefix(&seq, "1+r") || strview_has_prefix(&seq, "0+r")) { |
338 | 4 | return parse_xtgettcap_reply(data, len); | |
339 | } | ||
340 | |||
341 |
1/2✓ Branch 0 (26→27) taken 4 times.
✗ Branch 1 (26→42) not taken.
|
4 | if (strview_remove_matching_prefix(&seq, "1$r")) { |
342 |
2/2✓ Branch 0 (28→29) taken 1 times.
✓ Branch 1 (28→37) taken 3 times.
|
4 | if (strview_has_suffix(&seq, " q")) { |
343 | 1 | size_t n = seq.length - 2; | |
344 | 1 | unsigned int x; | |
345 |
2/4✓ Branch 0 (29→30) taken 1 times.
✗ Branch 1 (29→36) not taken.
✓ Branch 2 (31→32) taken 1 times.
✗ Branch 3 (31→36) not taken.
|
1 | if (n >= 1 && n == buf_parse_uint(seq.data, n, &x)) { |
346 |
1/2✓ Branch 0 (32→33) taken 1 times.
✗ Branch 1 (32→34) not taken.
|
1 | const char *str = (x <= CURSOR_STEADY_BAR) ? cursor_type_to_str(x) : "??"; |
347 | 1 | LOG_DEBUG("DECRQSS DECSCUSR (cursor style) reply: %u (%s)", x, str); | |
348 | 1 | return KEY_IGNORE; | |
349 | } | ||
350 | } | ||
351 | |||
352 |
1/2✓ Branch 0 (38→39) taken 3 times.
✗ Branch 1 (38→40) not taken.
|
3 | if (strview_remove_matching_suffix(&seq, "m")) { |
353 | 3 | return handle_decrqss_sgr_reply(seq.data, seq.length); | |
354 | } | ||
355 | |||
356 | ✗ | LOG_INFO("unhandled DECRQSS reply: %.*s", (int)len, data); | |
357 | ✗ | return KEY_IGNORE; | |
358 | } | ||
359 | |||
360 | ✗ | if (strview_equal_cstring(&seq, "0$r")) { | |
361 | ✗ | note = " (DECRQSS; invalid request)"; | |
362 | ✗ | goto unhandled; | |
363 | } | ||
364 | |||
365 | ✗ | unhandled: | |
366 | ✗ | LOG_INFO("unhandled DCS string%s: %.*s", note, (int)len, data); | |
367 | ✗ | return KEY_IGNORE; | |
368 | } | ||
369 | |||
370 | 4 | KeyCode parse_osc_query_reply(const char *data, size_t len, bool truncated) | |
371 | { | ||
372 |
1/2✓ Branch 0 (2→3) taken 4 times.
✗ Branch 1 (2→12) not taken.
|
4 | if (unlikely(len == 0)) { |
373 | return KEY_IGNORE; | ||
374 | } | ||
375 | |||
376 |
1/2✓ Branch 0 (3→4) taken 4 times.
✗ Branch 1 (3→5) not taken.
|
4 | const char *note = truncated ? " (truncated)" : ""; |
377 | 4 | char prefix = data[0]; | |
378 |
1/2✓ Branch 0 (5→6) taken 4 times.
✗ Branch 1 (5→10) not taken.
|
4 | if (prefix == 'L' || prefix == 'l') { |
379 |
2/2✓ Branch 0 (6→7) taken 2 times.
✓ Branch 1 (6→8) taken 2 times.
|
4 | const char *type = (prefix == 'l') ? "title" : "icon"; |
380 | 4 | LOG_DEBUG("window %s%s: %.*s", type, note, (int)len - 1, data + 1); | |
381 | 4 | return KEY_IGNORE; | |
382 | } | ||
383 | |||
384 | ✗ | LOG_WARNING("unhandled OSC string%s: %.*s", note, (int)len, data); | |
385 | ✗ | return KEY_IGNORE; | |
386 | } | ||
387 | |||
388 | ✗ | KeyCode parse_xtwinops_query_reply(const TermControlParams *csi) | |
389 | { | ||
390 | ✗ | if (unlikely(csi->nparams != 3)) { | |
391 | ✗ | LOG_INFO("XTWINOPS sequence with unrecognized number of params"); | |
392 | ✗ | return KEY_IGNORE; | |
393 | } | ||
394 | |||
395 | // CSI type ; height ; width t | ||
396 | ✗ | const char *typestr = "unknown"; | |
397 | ✗ | unsigned int type = csi->params[0][0]; | |
398 | ✗ | unsigned int height = csi->params[1][0]; | |
399 | ✗ | unsigned int width = csi->params[2][0]; | |
400 | |||
401 | ✗ | switch (type) { | |
402 | ✗ | case 4: typestr = "text area size in pixels"; break; | |
403 | ✗ | case 6: typestr = "cell size in pixels"; break; | |
404 | ✗ | case 8: typestr = "text area size in \"characters\""; break; | |
405 | } | ||
406 | |||
407 | ✗ | LOG_INFO("XTWINOPS %u (%s) reply: %ux%u", type, typestr, width, height); | |
408 | ✗ | return KEY_IGNORE; | |
409 | } | ||
410 |