blob: 2568ee6090dd07aeb67e3668b655bf30cefeccf8 [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]",
56 "altscreen [off|on|query]",
57 "bracketpaste [off|on|query]",
58 "icontitle [STR]",
59 "icon [STR]",
60 "title [STR]",
61 NULL
62};
63
Bram Moolenaar8a773062017-07-24 22:29:21 +020064static int seticanon(int icanon, int echo)
Bram Moolenaare4f25e42017-07-07 11:54:15 +020065{
66 struct termios termios;
Bram Moolenaar8a773062017-07-24 22:29:21 +020067 int ret;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020068
69 tcgetattr(0, &termios);
70
Bram Moolenaar8a773062017-07-24 22:29:21 +020071 ret = (termios.c_lflag & ICANON);
Bram Moolenaare4f25e42017-07-07 11:54:15 +020072
73 if(icanon) termios.c_lflag |= ICANON;
74 else termios.c_lflag &= ~ICANON;
75
76 if(echo) termios.c_lflag |= ECHO;
77 else termios.c_lflag &= ~ECHO;
78
79 tcsetattr(0, TCSANOW, &termios);
80
81 return ret;
82}
83
84static void await_c1(int c1)
85{
86 int c;
87
88 /* await CSI - 8bit or 2byte 7bit form */
Bram Moolenaar8a773062017-07-24 22:29:21 +020089 int in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020090 while((c = getchar())) {
91 if(c == c1)
92 break;
93 if(in_esc && c == (char)(c1 - 0x40))
94 break;
95 if(!in_esc && c == 0x1b)
Bram Moolenaar8a773062017-07-24 22:29:21 +020096 in_esc = TRUE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020097 else
Bram Moolenaar8a773062017-07-24 22:29:21 +020098 in_esc = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020099 }
100}
101
102static char *read_csi()
103{
104 unsigned char csi[32];
105 int i = 0;
106
107 await_c1(0x9B); /* CSI */
108
109 /* TODO: This really should be a more robust CSI parser
110 */
111 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
118 /* TODO: returns longer than 32? */
119
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();
133 if(c == 0x9c) /* ST */
134 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
177 /* expect "?" mode ";" value "$y" */
178
179 /* 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
182 */
183 if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
184 continue;
185 if(reply_cmd != 'y')
186 continue;
187
188 if(reply_mode != mode)
189 continue;
190
191 free(s);
192
193 if(reply_value == 1 || reply_value == 3)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200194 return TRUE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200195 if(reply_value == 2 || reply_value == 4)
Bram Moolenaar8a773062017-07-24 22:29:21 +0200196 return FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200197
198 printf("Unrecognised reply to DECRQM: %d\n", reply_value);
Bram Moolenaar8a773062017-07-24 22:29:21 +0200199 return FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200200 } while(1);
201}
202
203static void do_dec_mode(int mode, BoolQuery val, const char *name)
204{
205 switch(val) {
206 case OFF:
207 case ON:
208 printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
209 break;
210
211 case QUERY:
212 if(query_dec_mode(mode))
213 printf("%s on\n", name);
214 else
215 printf("%s off\n", name);
216 break;
217 }
218}
219
220static int query_rqss_numeric(char *cmd)
221{
222 char *s = NULL;
223
224 printf("\x1bP$q%s\x1b\\", cmd);
225
226 do {
227 int num;
228
229 if(s)
230 free(s);
231 s = read_dcs();
232
233 if(!s)
234 return -1;
235 if(strlen(s) < strlen(cmd))
236 return -1;
237 if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
238 printf("No match\n");
239 continue;
240 }
241
242 if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
243 return -1;
244
245 if(sscanf(s + 3, "%d", &num) != 1)
246 return -1;
247
248 return num;
249 } while(1);
250}
251
Bram Moolenaar8a773062017-07-24 22:29:21 +0200252int wasicanon;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200253
254void restoreicanon(void)
255{
Bram Moolenaar8a773062017-07-24 22:29:21 +0200256 seticanon(wasicanon, TRUE);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200257}
258
259int main(int argc, char *argv[])
260{
261 int argi = 1;
262
263 if(argc == 1)
264 usage(0);
265
Bram Moolenaar8a773062017-07-24 22:29:21 +0200266 wasicanon = seticanon(FALSE, FALSE);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200267 atexit(restoreicanon);
268
269 while(argi < argc) {
270 const char *arg = argv[argi++];
271
272 if(streq(arg, "reset")) {
273 printf("\x1b" "c");
274 }
275 else if(streq(arg, "s8c1t")) {
276 const char *choices[] = {"off", "on", NULL};
277 switch(getchoice(&argi, argc, argv, choices)) {
278 case 0:
279 printf("\x1b F"); break;
280 case 1:
281 printf("\x1b G"); break;
282 }
283 }
284 else if(streq(arg, "keypad")) {
285 const char *choices[] = {"app", "num", NULL};
286 switch(getchoice(&argi, argc, argv, choices)) {
287 case 0:
288 printf("\x1b="); break;
289 case 1:
290 printf("\x1b>"); break;
291 }
292 }
293 else if(streq(arg, "screen")) {
294 do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
295 }
296 else if(streq(arg, "cursor")) {
297 do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
298 }
299 else if(streq(arg, "curblink")) {
300 do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
301 }
302 else if(streq(arg, "curshape")) {
303 /* TODO: This ought to query the current value of DECSCUSR because it */
304 /* may need blinking on or off */
305 const char *choices[] = {"block", "under", "bar", "query", NULL};
306 int shape = getchoice(&argi, argc, argv, choices);
307 switch(shape) {
308 case 3: /* query */
309 shape = query_rqss_numeric(" q");
310 switch(shape) {
311 case 1: case 2:
312 printf("curshape block\n");
313 break;
314 case 3: case 4:
315 printf("curshape under\n");
316 break;
317 case 5: case 6:
318 printf("curshape bar\n");
319 break;
320 }
321 break;
322
323 case 0:
324 case 1:
325 case 2:
326 printf("\x1b[%d q", 1 + (shape * 2));
327 break;
328 }
329 }
330 else if(streq(arg, "mouse")) {
331 const char *choices[] = {"off", "click", "clickdrag", "motion", NULL};
332 switch(getchoice(&argi, argc, argv, choices)) {
333 case 0:
334 printf("\x1b[?1000l"); break;
335 case 1:
336 printf("\x1b[?1000h"); break;
337 case 2:
338 printf("\x1b[?1002h"); break;
339 case 3:
340 printf("\x1b[?1003h"); break;
341 }
342 }
343 else if(streq(arg, "altscreen")) {
344 do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
345 }
346 else if(streq(arg, "bracketpaste")) {
347 do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
348 }
349 else if(streq(arg, "icontitle")) {
350 printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
351 }
352 else if(streq(arg, "icon")) {
353 printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
354 }
355 else if(streq(arg, "title")) {
356 printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
357 }
358 else {
359 fprintf(stderr, "Unrecognised command %s\n", arg);
360 exit(1);
361 }
362 }
363 return 0;
364}