dte test coverage


Directory: ./
File: src/terminal/query.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 171 214 79.9%
Functions: 13 14 92.9%
Branches: 125 176 71.0%

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