blob: e43297c9f31d28bc6b9b720b0d3632e06092a29b [file] [log] [blame]
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001#define _XOPEN_SOURCE 500 /* strdup */
2
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,
38 QUERY
39} 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
89 /* 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
108 await_c1(0x9B); /* CSI */
109
110 /* TODO: This really should be a more robust CSI parser
111 */
112 for(; i < sizeof(csi)-1; i++) {
113 int c = csi[i] = getchar();
114 if(c >= 0x40 && c <= 0x7e)
115 break;
116 }
117 csi[++i] = 0;
118
119 /* TODO: returns longer than 32? */
120
121 return strdup((char *)csi);
122}
123
124static char *read_dcs()
125{
126 unsigned char dcs[32];
Bram Moolenaar8a773062017-07-24 22:29:21 +0200127 int in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200128 int i;
129
130 await_c1(0x90);
131
132 for(i = 0; i < sizeof(dcs)-1; ) {
133 char c = getchar();
134 if(c == 0x9c) /* ST */
135 break;
136 if(in_esc && c == 0x5c)
137 break;
138 if(!in_esc && c == 0x1b)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200139 in_esc = TRUE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200140 else {
141 dcs[i++] = c;
Bram Moolenaar8a773062017-07-24 22:29:21 +0200142 in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200143 }
144 }
145 dcs[++i] = 0;
146
147 return strdup((char *)dcs);
148}
149
150static void usage(int exitcode)
151{
152 char **p;
153
154 fprintf(stderr, "Control a libvterm-based terminal\n"
155 "\n"
156 "Options:\n");
157
158 for(p = helptext; *p; p++)
159 fprintf(stderr, " %s\n", *p);
160
161 exit(exitcode);
162}
163
Bram Moolenaar8a773062017-07-24 22:29:21 +0200164static int query_dec_mode(int mode)
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200165{
166 char *s = NULL;
167
168 printf("\x1b[?%d$p", mode);
169
170 do {
171 int reply_mode, reply_value;
172 char reply_cmd;
173
174 if(s)
175 free(s);
176 s = read_csi();
177
178 /* expect "?" mode ";" value "$y" */
179
180 /* If the sscanf format string ends in a literal, we can't tell from
181 * its return value if it matches. Hence we'll %c the cmd and check it
182 * explicitly
183 */
184 if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
185 continue;
186 if(reply_cmd != 'y')
187 continue;
188
189 if(reply_mode != mode)
190 continue;
191
192 free(s);
193
194 if(reply_value == 1 || reply_value == 3)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200195 return TRUE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200196 if(reply_value == 2 || reply_value == 4)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200197 return FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200198
199 printf("Unrecognised reply to DECRQM: %d\n", reply_value);
Bram Moolenaar8a773062017-07-24 22:29:21 +0200200 return FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200201 } while(1);
202}
203
204static void do_dec_mode(int mode, BoolQuery val, const char *name)
205{
206 switch(val) {
207 case OFF:
208 case ON:
209 printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
210 break;
211
212 case QUERY:
213 if(query_dec_mode(mode))
214 printf("%s on\n", name);
215 else
216 printf("%s off\n", name);
217 break;
218 }
219}
220
221static int query_rqss_numeric(char *cmd)
222{
223 char *s = NULL;
224
225 printf("\x1bP$q%s\x1b\\", cmd);
226
227 do {
228 int num;
229
230 if(s)
231 free(s);
232 s = read_dcs();
233
234 if(!s)
235 return -1;
236 if(strlen(s) < strlen(cmd))
237 return -1;
238 if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
239 printf("No match\n");
240 continue;
241 }
242
243 if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
244 return -1;
245
246 if(sscanf(s + 3, "%d", &num) != 1)
247 return -1;
248
249 return num;
250 } while(1);
251}
252
Bram Moolenaar8a773062017-07-24 22:29:21 +0200253int wasicanon;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200254
255void restoreicanon(void)
256{
Bram Moolenaar8a773062017-07-24 22:29:21 +0200257 seticanon(wasicanon, TRUE);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200258}
259
260int main(int argc, char *argv[])
261{
262 int argi = 1;
263
264 if(argc == 1)
265 usage(0);
266
Bram Moolenaar8a773062017-07-24 22:29:21 +0200267 wasicanon = seticanon(FALSE, FALSE);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200268 atexit(restoreicanon);
269
270 while(argi < argc) {
271 const char *arg = argv[argi++];
272
273 if(streq(arg, "reset")) {
274 printf("\x1b" "c");
275 }
276 else if(streq(arg, "s8c1t")) {
277 const char *choices[] = {"off", "on", NULL};
278 switch(getchoice(&argi, argc, argv, choices)) {
279 case 0:
280 printf("\x1b F"); break;
281 case 1:
282 printf("\x1b G"); break;
283 }
284 }
285 else if(streq(arg, "keypad")) {
286 const char *choices[] = {"app", "num", NULL};
287 switch(getchoice(&argi, argc, argv, choices)) {
288 case 0:
289 printf("\x1b="); break;
290 case 1:
291 printf("\x1b>"); break;
292 }
293 }
294 else if(streq(arg, "screen")) {
295 do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
296 }
297 else if(streq(arg, "cursor")) {
298 do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
299 }
300 else if(streq(arg, "curblink")) {
301 do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
302 }
303 else if(streq(arg, "curshape")) {
304 /* TODO: This ought to query the current value of DECSCUSR because it */
305 /* may need blinking on or off */
306 const char *choices[] = {"block", "under", "bar", "query", NULL};
307 int shape = getchoice(&argi, argc, argv, choices);
308 switch(shape) {
309 case 3: /* query */
310 shape = query_rqss_numeric(" q");
311 switch(shape) {
312 case 1: case 2:
313 printf("curshape block\n");
314 break;
315 case 3: case 4:
316 printf("curshape under\n");
317 break;
318 case 5: case 6:
319 printf("curshape bar\n");
320 break;
321 }
322 break;
323
324 case 0:
325 case 1:
326 case 2:
327 printf("\x1b[%d q", 1 + (shape * 2));
328 break;
329 }
330 }
331 else if(streq(arg, "mouse")) {
332 const char *choices[] = {"off", "click", "clickdrag", "motion", NULL};
333 switch(getchoice(&argi, argc, argv, choices)) {
334 case 0:
335 printf("\x1b[?1000l"); break;
336 case 1:
337 printf("\x1b[?1000h"); break;
338 case 2:
339 printf("\x1b[?1002h"); break;
340 case 3:
341 printf("\x1b[?1003h"); break;
342 }
343 }
Bram Moolenaarb5b49a32018-03-25 16:20:37 +0200344 else if(streq(arg, "reportfocus")) {
345 do_dec_mode(1004, getboolq(&argi, argc, argv), "reportfocus");
346 }
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200347 else if(streq(arg, "altscreen")) {
348 do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
349 }
350 else if(streq(arg, "bracketpaste")) {
351 do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
352 }
353 else if(streq(arg, "icontitle")) {
354 printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
355 }
356 else if(streq(arg, "icon")) {
357 printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
358 }
359 else if(streq(arg, "title")) {
360 printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
361 }
362 else {
363 fprintf(stderr, "Unrecognised command %s\n", arg);
364 exit(1);
365 }
366 }
367 return 0;
368}