blob: 14e35cda5262f39fdc487d534f530538a594512f [file] [log] [blame]
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001#define _XOPEN_SOURCE 500 /* strdup */
2
3#include <stdbool.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#define streq(a,b) (strcmp(a,b)==0)
8
9#include <termios.h>
10
11static char *getvalue(int *argip, int argc, char *argv[])
12{
13 if(*argip >= argc) {
14 fprintf(stderr, "Expected an option value\n");
15 exit(1);
16 }
17
18 return argv[(*argip)++];
19}
20
21static int getchoice(int *argip, int argc, char *argv[], const char *options[])
22{
23 const char *arg = getvalue(argip, argc, argv);
24
25 int value = -1;
26 while(options[++value])
27 if(streq(arg, options[value]))
28 return value;
29
30 fprintf(stderr, "Unrecognised option value %s\n", arg);
31 exit(1);
32}
33
34typedef enum {
35 OFF,
36 ON,
37 QUERY
38} BoolQuery;
39
40static BoolQuery getboolq(int *argip, int argc, char *argv[])
41{
42 const char *choices[] = {"off", "on", "query", NULL};
43 return getchoice(argip, argc, argv, choices);
44}
45
46static char *helptext[] = {
47 "reset",
48 "s8c1t [off|on]",
49 "keypad [app|num]",
50 "screen [off|on|query]",
51 "cursor [off|on|query]",
52 "curblink [off|on|query]",
53 "curshape [block|under|bar|query]",
54 "mouse [off|click|clickdrag|motion]",
55 "altscreen [off|on|query]",
56 "bracketpaste [off|on|query]",
57 "icontitle [STR]",
58 "icon [STR]",
59 "title [STR]",
60 NULL
61};
62
63static bool seticanon(bool icanon, bool echo)
64{
65 struct termios termios;
66
67 tcgetattr(0, &termios);
68
69 bool ret = (termios.c_lflag & ICANON);
70
71 if(icanon) termios.c_lflag |= ICANON;
72 else termios.c_lflag &= ~ICANON;
73
74 if(echo) termios.c_lflag |= ECHO;
75 else termios.c_lflag &= ~ECHO;
76
77 tcsetattr(0, TCSANOW, &termios);
78
79 return ret;
80}
81
82static void await_c1(int c1)
83{
84 int c;
85
86 /* await CSI - 8bit or 2byte 7bit form */
87 bool in_esc = false;
88 while((c = getchar())) {
89 if(c == c1)
90 break;
91 if(in_esc && c == (char)(c1 - 0x40))
92 break;
93 if(!in_esc && c == 0x1b)
94 in_esc = true;
95 else
96 in_esc = false;
97 }
98}
99
100static char *read_csi()
101{
102 unsigned char csi[32];
103 int i = 0;
104
105 await_c1(0x9B); /* CSI */
106
107 /* TODO: This really should be a more robust CSI parser
108 */
109 for(; i < sizeof(csi)-1; i++) {
110 int c = csi[i] = getchar();
111 if(c >= 0x40 && c <= 0x7e)
112 break;
113 }
114 csi[++i] = 0;
115
116 /* TODO: returns longer than 32? */
117
118 return strdup((char *)csi);
119}
120
121static char *read_dcs()
122{
123 unsigned char dcs[32];
124 bool in_esc = false;
125 int i;
126
127 await_c1(0x90);
128
129 for(i = 0; i < sizeof(dcs)-1; ) {
130 char c = getchar();
131 if(c == 0x9c) /* ST */
132 break;
133 if(in_esc && c == 0x5c)
134 break;
135 if(!in_esc && c == 0x1b)
136 in_esc = true;
137 else {
138 dcs[i++] = c;
139 in_esc = false;
140 }
141 }
142 dcs[++i] = 0;
143
144 return strdup((char *)dcs);
145}
146
147static void usage(int exitcode)
148{
149 char **p;
150
151 fprintf(stderr, "Control a libvterm-based terminal\n"
152 "\n"
153 "Options:\n");
154
155 for(p = helptext; *p; p++)
156 fprintf(stderr, " %s\n", *p);
157
158 exit(exitcode);
159}
160
161static bool query_dec_mode(int mode)
162{
163 char *s = NULL;
164
165 printf("\x1b[?%d$p", mode);
166
167 do {
168 int reply_mode, reply_value;
169 char reply_cmd;
170
171 if(s)
172 free(s);
173 s = read_csi();
174
175 /* expect "?" mode ";" value "$y" */
176
177 /* If the sscanf format string ends in a literal, we can't tell from
178 * its return value if it matches. Hence we'll %c the cmd and check it
179 * explicitly
180 */
181 if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
182 continue;
183 if(reply_cmd != 'y')
184 continue;
185
186 if(reply_mode != mode)
187 continue;
188
189 free(s);
190
191 if(reply_value == 1 || reply_value == 3)
192 return true;
193 if(reply_value == 2 || reply_value == 4)
194 return false;
195
196 printf("Unrecognised reply to DECRQM: %d\n", reply_value);
197 return false;
198 } while(1);
199}
200
201static void do_dec_mode(int mode, BoolQuery val, const char *name)
202{
203 switch(val) {
204 case OFF:
205 case ON:
206 printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
207 break;
208
209 case QUERY:
210 if(query_dec_mode(mode))
211 printf("%s on\n", name);
212 else
213 printf("%s off\n", name);
214 break;
215 }
216}
217
218static int query_rqss_numeric(char *cmd)
219{
220 char *s = NULL;
221
222 printf("\x1bP$q%s\x1b\\", cmd);
223
224 do {
225 int num;
226
227 if(s)
228 free(s);
229 s = read_dcs();
230
231 if(!s)
232 return -1;
233 if(strlen(s) < strlen(cmd))
234 return -1;
235 if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
236 printf("No match\n");
237 continue;
238 }
239
240 if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
241 return -1;
242
243 if(sscanf(s + 3, "%d", &num) != 1)
244 return -1;
245
246 return num;
247 } while(1);
248}
249
250bool wasicanon;
251
252void restoreicanon(void)
253{
254 seticanon(wasicanon, true);
255}
256
257int main(int argc, char *argv[])
258{
259 int argi = 1;
260
261 if(argc == 1)
262 usage(0);
263
264 wasicanon = seticanon(false, false);
265 atexit(restoreicanon);
266
267 while(argi < argc) {
268 const char *arg = argv[argi++];
269
270 if(streq(arg, "reset")) {
271 printf("\x1b" "c");
272 }
273 else if(streq(arg, "s8c1t")) {
274 const char *choices[] = {"off", "on", NULL};
275 switch(getchoice(&argi, argc, argv, choices)) {
276 case 0:
277 printf("\x1b F"); break;
278 case 1:
279 printf("\x1b G"); break;
280 }
281 }
282 else if(streq(arg, "keypad")) {
283 const char *choices[] = {"app", "num", NULL};
284 switch(getchoice(&argi, argc, argv, choices)) {
285 case 0:
286 printf("\x1b="); break;
287 case 1:
288 printf("\x1b>"); break;
289 }
290 }
291 else if(streq(arg, "screen")) {
292 do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
293 }
294 else if(streq(arg, "cursor")) {
295 do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
296 }
297 else if(streq(arg, "curblink")) {
298 do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
299 }
300 else if(streq(arg, "curshape")) {
301 /* TODO: This ought to query the current value of DECSCUSR because it */
302 /* may need blinking on or off */
303 const char *choices[] = {"block", "under", "bar", "query", NULL};
304 int shape = getchoice(&argi, argc, argv, choices);
305 switch(shape) {
306 case 3: /* query */
307 shape = query_rqss_numeric(" q");
308 switch(shape) {
309 case 1: case 2:
310 printf("curshape block\n");
311 break;
312 case 3: case 4:
313 printf("curshape under\n");
314 break;
315 case 5: case 6:
316 printf("curshape bar\n");
317 break;
318 }
319 break;
320
321 case 0:
322 case 1:
323 case 2:
324 printf("\x1b[%d q", 1 + (shape * 2));
325 break;
326 }
327 }
328 else if(streq(arg, "mouse")) {
329 const char *choices[] = {"off", "click", "clickdrag", "motion", NULL};
330 switch(getchoice(&argi, argc, argv, choices)) {
331 case 0:
332 printf("\x1b[?1000l"); break;
333 case 1:
334 printf("\x1b[?1000h"); break;
335 case 2:
336 printf("\x1b[?1002h"); break;
337 case 3:
338 printf("\x1b[?1003h"); break;
339 }
340 }
341 else if(streq(arg, "altscreen")) {
342 do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
343 }
344 else if(streq(arg, "bracketpaste")) {
345 do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
346 }
347 else if(streq(arg, "icontitle")) {
348 printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
349 }
350 else if(streq(arg, "icon")) {
351 printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
352 }
353 else if(streq(arg, "title")) {
354 printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
355 }
356 else {
357 fprintf(stderr, "Unrecognised command %s\n", arg);
358 exit(1);
359 }
360 }
361 return 0;
362}