blob: 9e06011d176966723a5d450a6393ff88b3fe455e [file] [log] [blame]
Bram Moolenaardb1085a2019-08-18 20:41:38 +02001#define _XOPEN_SOURCE 500 // strdup
Bram Moolenaare4f25e42017-07-07 11:54:15 +02002
Bram Moolenaare4f25e42017-07-07 11:54:15 +02003#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#define streq(a,b) (strcmp(a,b)==0)
Bram Moolenaar8a773062017-07-24 22:29:21 +02007#define TRUE 1
8#define FALSE 0
Bram Moolenaare4f25e42017-07-07 11:54:15 +02009
10#include <termios.h>
11
12static char *getvalue(int *argip, int argc, char *argv[])
13{
14 if(*argip >= argc) {
15 fprintf(stderr, "Expected an option value\n");
16 exit(1);
17 }
18
19 return argv[(*argip)++];
20}
21
22static int getchoice(int *argip, int argc, char *argv[], const char *options[])
23{
24 const char *arg = getvalue(argip, argc, argv);
25
26 int value = -1;
27 while(options[++value])
28 if(streq(arg, options[value]))
29 return value;
30
31 fprintf(stderr, "Unrecognised option value %s\n", arg);
32 exit(1);
33}
34
35typedef enum {
36 OFF,
37 ON,
Bram Moolenaarb691de02018-04-24 18:39:14 +020038 QUERY,
Bram Moolenaare4f25e42017-07-07 11:54:15 +020039} BoolQuery;
40
41static BoolQuery getboolq(int *argip, int argc, char *argv[])
42{
43 const char *choices[] = {"off", "on", "query", NULL};
44 return getchoice(argip, argc, argv, choices);
45}
46
47static char *helptext[] = {
48 "reset",
49 "s8c1t [off|on]",
50 "keypad [app|num]",
51 "screen [off|on|query]",
52 "cursor [off|on|query]",
53 "curblink [off|on|query]",
54 "curshape [block|under|bar|query]",
55 "mouse [off|click|clickdrag|motion]",
Bram Moolenaarb5b49a32018-03-25 16:20:37 +020056 "reportfocus [off|on|query]",
Bram Moolenaare4f25e42017-07-07 11:54:15 +020057 "altscreen [off|on|query]",
58 "bracketpaste [off|on|query]",
59 "icontitle [STR]",
60 "icon [STR]",
61 "title [STR]",
62 NULL
63};
64
Bram Moolenaar8a773062017-07-24 22:29:21 +020065static int seticanon(int icanon, int echo)
Bram Moolenaare4f25e42017-07-07 11:54:15 +020066{
67 struct termios termios;
Bram Moolenaar8a773062017-07-24 22:29:21 +020068 int ret;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020069
70 tcgetattr(0, &termios);
71
Bram Moolenaar8a773062017-07-24 22:29:21 +020072 ret = (termios.c_lflag & ICANON);
Bram Moolenaare4f25e42017-07-07 11:54:15 +020073
74 if(icanon) termios.c_lflag |= ICANON;
75 else termios.c_lflag &= ~ICANON;
76
77 if(echo) termios.c_lflag |= ECHO;
78 else termios.c_lflag &= ~ECHO;
79
80 tcsetattr(0, TCSANOW, &termios);
81
82 return ret;
83}
84
Bram Moolenaarb5b49a32018-03-25 16:20:37 +020085static void await_c1(unsigned char c1)
Bram Moolenaare4f25e42017-07-07 11:54:15 +020086{
Bram Moolenaarb5b49a32018-03-25 16:20:37 +020087 unsigned char c;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020088
Bram Moolenaardb1085a2019-08-18 20:41:38 +020089 // await CSI - 8bit or 2byte 7bit form
Bram Moolenaar8a773062017-07-24 22:29:21 +020090 int in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020091 while((c = getchar())) {
92 if(c == c1)
93 break;
94 if(in_esc && c == (char)(c1 - 0x40))
95 break;
96 if(!in_esc && c == 0x1b)
Bram Moolenaar8a773062017-07-24 22:29:21 +020097 in_esc = TRUE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020098 else
Bram Moolenaar8a773062017-07-24 22:29:21 +020099 in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200100 }
101}
102
103static char *read_csi()
104{
105 unsigned char csi[32];
106 int i = 0;
107
Bram Moolenaarb691de02018-04-24 18:39:14 +0200108 await_c1(0x9B); // CSI
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200109
Bram Moolenaardb1085a2019-08-18 20:41:38 +0200110 // TODO: This really should be a more robust CSI parser
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200111 for(; i < sizeof(csi)-1; i++) {
112 int c = csi[i] = getchar();
113 if(c >= 0x40 && c <= 0x7e)
114 break;
115 }
116 csi[++i] = 0;
117
Bram Moolenaarb691de02018-04-24 18:39:14 +0200118 // TODO: returns longer than 32?
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200119
120 return strdup((char *)csi);
121}
122
123static char *read_dcs()
124{
125 unsigned char dcs[32];
Bram Moolenaar8a773062017-07-24 22:29:21 +0200126 int in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200127 int i;
128
129 await_c1(0x90);
130
131 for(i = 0; i < sizeof(dcs)-1; ) {
132 char c = getchar();
Bram Moolenaarb691de02018-04-24 18:39:14 +0200133 if(c == 0x9c) // ST
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200134 break;
135 if(in_esc && c == 0x5c)
136 break;
137 if(!in_esc && c == 0x1b)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200138 in_esc = TRUE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200139 else {
140 dcs[i++] = c;
Bram Moolenaar8a773062017-07-24 22:29:21 +0200141 in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200142 }
143 }
144 dcs[++i] = 0;
145
146 return strdup((char *)dcs);
147}
148
149static void usage(int exitcode)
150{
151 char **p;
152
153 fprintf(stderr, "Control a libvterm-based terminal\n"
154 "\n"
155 "Options:\n");
156
157 for(p = helptext; *p; p++)
158 fprintf(stderr, " %s\n", *p);
159
160 exit(exitcode);
161}
162
Bram Moolenaar8a773062017-07-24 22:29:21 +0200163static int query_dec_mode(int mode)
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200164{
165 char *s = NULL;
166
167 printf("\x1b[?%d$p", mode);
168
169 do {
170 int reply_mode, reply_value;
171 char reply_cmd;
172
173 if(s)
174 free(s);
175 s = read_csi();
176
Bram Moolenaardb1085a2019-08-18 20:41:38 +0200177 // expect "?" mode ";" value "$y"
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200178
Bram Moolenaardb1085a2019-08-18 20:41:38 +0200179 // If the sscanf format string ends in a literal, we can't tell from
180 // its return value if it matches. Hence we'll %c the cmd and check it
181 // explicitly
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200182 if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
183 continue;
184 if(reply_cmd != 'y')
185 continue;
186
187 if(reply_mode != mode)
188 continue;
189
190 free(s);
191
192 if(reply_value == 1 || reply_value == 3)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200193 return TRUE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200194 if(reply_value == 2 || reply_value == 4)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200195 return FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200196
197 printf("Unrecognised reply to DECRQM: %d\n", reply_value);
Bram Moolenaar8a773062017-07-24 22:29:21 +0200198 return FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200199 } while(1);
200}
201
202static void do_dec_mode(int mode, BoolQuery val, const char *name)
203{
204 switch(val) {
205 case OFF:
206 case ON:
207 printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
208 break;
209
210 case QUERY:
211 if(query_dec_mode(mode))
212 printf("%s on\n", name);
213 else
214 printf("%s off\n", name);
215 break;
216 }
217}
218
219static int query_rqss_numeric(char *cmd)
220{
221 char *s = NULL;
222
223 printf("\x1bP$q%s\x1b\\", cmd);
224
225 do {
226 int num;
227
228 if(s)
229 free(s);
230 s = read_dcs();
231
232 if(!s)
233 return -1;
234 if(strlen(s) < strlen(cmd))
235 return -1;
236 if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
237 printf("No match\n");
238 continue;
239 }
240
241 if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
242 return -1;
243
244 if(sscanf(s + 3, "%d", &num) != 1)
245 return -1;
246
247 return num;
248 } while(1);
249}
250
Bram Moolenaar8a773062017-07-24 22:29:21 +0200251int wasicanon;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200252
253void restoreicanon(void)
254{
Bram Moolenaar8a773062017-07-24 22:29:21 +0200255 seticanon(wasicanon, TRUE);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200256}
257
258int main(int argc, char *argv[])
259{
260 int argi = 1;
261
262 if(argc == 1)
263 usage(0);
264
Bram Moolenaar8a773062017-07-24 22:29:21 +0200265 wasicanon = seticanon(FALSE, FALSE);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200266 atexit(restoreicanon);
267
268 while(argi < argc) {
269 const char *arg = argv[argi++];
270
271 if(streq(arg, "reset")) {
272 printf("\x1b" "c");
273 }
274 else if(streq(arg, "s8c1t")) {
275 const char *choices[] = {"off", "on", NULL};
276 switch(getchoice(&argi, argc, argv, choices)) {
277 case 0:
278 printf("\x1b F"); break;
279 case 1:
280 printf("\x1b G"); break;
281 }
282 }
283 else if(streq(arg, "keypad")) {
284 const char *choices[] = {"app", "num", NULL};
285 switch(getchoice(&argi, argc, argv, choices)) {
286 case 0:
287 printf("\x1b="); break;
288 case 1:
289 printf("\x1b>"); break;
290 }
291 }
292 else if(streq(arg, "screen")) {
293 do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
294 }
295 else if(streq(arg, "cursor")) {
296 do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
297 }
298 else if(streq(arg, "curblink")) {
299 do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
300 }
301 else if(streq(arg, "curshape")) {
Bram Moolenaarb691de02018-04-24 18:39:14 +0200302 // TODO: This ought to query the current value of DECSCUSR because it
303 // may need blinking on or off
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200304 const char *choices[] = {"block", "under", "bar", "query", NULL};
305 int shape = getchoice(&argi, argc, argv, choices);
306 switch(shape) {
Bram Moolenaarb691de02018-04-24 18:39:14 +0200307 case 3: // query
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200308 shape = query_rqss_numeric(" q");
309 switch(shape) {
310 case 1: case 2:
311 printf("curshape block\n");
312 break;
313 case 3: case 4:
314 printf("curshape under\n");
315 break;
316 case 5: case 6:
317 printf("curshape bar\n");
318 break;
319 }
320 break;
321
322 case 0:
323 case 1:
324 case 2:
325 printf("\x1b[%d q", 1 + (shape * 2));
326 break;
327 }
328 }
329 else if(streq(arg, "mouse")) {
330 const char *choices[] = {"off", "click", "clickdrag", "motion", NULL};
331 switch(getchoice(&argi, argc, argv, choices)) {
332 case 0:
333 printf("\x1b[?1000l"); break;
334 case 1:
335 printf("\x1b[?1000h"); break;
336 case 2:
337 printf("\x1b[?1002h"); break;
338 case 3:
339 printf("\x1b[?1003h"); break;
340 }
341 }
Bram Moolenaarb5b49a32018-03-25 16:20:37 +0200342 else if(streq(arg, "reportfocus")) {
343 do_dec_mode(1004, getboolq(&argi, argc, argv), "reportfocus");
344 }
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200345 else if(streq(arg, "altscreen")) {
346 do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
347 }
348 else if(streq(arg, "bracketpaste")) {
349 do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
350 }
351 else if(streq(arg, "icontitle")) {
352 printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
353 }
354 else if(streq(arg, "icon")) {
355 printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
356 }
357 else if(streq(arg, "title")) {
358 printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
359 }
360 else {
361 fprintf(stderr, "Unrecognised command %s\n", arg);
362 exit(1);
363 }
364 }
365 return 0;
366}