| #define _XOPEN_SOURCE 500 // strdup |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #define streq(a,b) (strcmp(a,b)==0) |
| #define TRUE 1 |
| #define FALSE 0 |
| |
| #include <termios.h> |
| |
| static char *getvalue(int *argip, int argc, char *argv[]) |
| { |
| if(*argip >= argc) { |
| fprintf(stderr, "Expected an option value\n"); |
| exit(1); |
| } |
| |
| return argv[(*argip)++]; |
| } |
| |
| static int getchoice(int *argip, int argc, char *argv[], const char *options[]) |
| { |
| const char *arg = getvalue(argip, argc, argv); |
| |
| int value = -1; |
| while(options[++value]) |
| if(streq(arg, options[value])) |
| return value; |
| |
| fprintf(stderr, "Unrecognised option value %s\n", arg); |
| exit(1); |
| } |
| |
| typedef enum { |
| OFF, |
| ON, |
| QUERY, |
| } BoolQuery; |
| |
| static BoolQuery getboolq(int *argip, int argc, char *argv[]) |
| { |
| const char *choices[] = {"off", "on", "query", NULL}; |
| return getchoice(argip, argc, argv, choices); |
| } |
| |
| static char *helptext[] = { |
| "reset", |
| "s8c1t [off|on]", |
| "keypad [app|num]", |
| "screen [off|on|query]", |
| "cursor [off|on|query]", |
| "curblink [off|on|query]", |
| "curshape [block|under|bar|query]", |
| "mouse [off|click|clickdrag|motion]", |
| "reportfocus [off|on|query]", |
| "altscreen [off|on|query]", |
| "bracketpaste [off|on|query]", |
| "icontitle [STR]", |
| "icon [STR]", |
| "title [STR]", |
| NULL |
| }; |
| |
| static int seticanon(int icanon, int echo) |
| { |
| struct termios termios; |
| int ret; |
| |
| tcgetattr(0, &termios); |
| |
| ret = (termios.c_lflag & ICANON); |
| |
| if(icanon) termios.c_lflag |= ICANON; |
| else termios.c_lflag &= ~ICANON; |
| |
| if(echo) termios.c_lflag |= ECHO; |
| else termios.c_lflag &= ~ECHO; |
| |
| tcsetattr(0, TCSANOW, &termios); |
| |
| return ret; |
| } |
| |
| static void await_c1(unsigned char c1) |
| { |
| unsigned char c; |
| |
| // await CSI - 8bit or 2byte 7bit form |
| int in_esc = FALSE; |
| while((c = getchar())) { |
| if(c == c1) |
| break; |
| if(in_esc && c == (char)(c1 - 0x40)) |
| break; |
| if(!in_esc && c == 0x1b) |
| in_esc = TRUE; |
| else |
| in_esc = FALSE; |
| } |
| } |
| |
| static char *read_csi() |
| { |
| unsigned char csi[32]; |
| int i = 0; |
| |
| await_c1(0x9B); // CSI |
| |
| // TODO: This really should be a more robust CSI parser |
| for(; i < sizeof(csi)-1; i++) { |
| int c = csi[i] = getchar(); |
| if(c >= 0x40 && c <= 0x7e) |
| break; |
| } |
| csi[++i] = 0; |
| |
| // TODO: returns longer than 32? |
| |
| return strdup((char *)csi); |
| } |
| |
| static char *read_dcs() |
| { |
| unsigned char dcs[32]; |
| int in_esc = FALSE; |
| int i; |
| |
| await_c1(0x90); |
| |
| for(i = 0; i < sizeof(dcs)-1; ) { |
| char c = getchar(); |
| if(c == 0x9c) // ST |
| break; |
| if(in_esc && c == 0x5c) |
| break; |
| if(!in_esc && c == 0x1b) |
| in_esc = TRUE; |
| else { |
| dcs[i++] = c; |
| in_esc = FALSE; |
| } |
| } |
| dcs[++i] = 0; |
| |
| return strdup((char *)dcs); |
| } |
| |
| static void usage(int exitcode) |
| { |
| char **p; |
| |
| fprintf(stderr, "Control a libvterm-based terminal\n" |
| "\n" |
| "Options:\n"); |
| |
| for(p = helptext; *p; p++) |
| fprintf(stderr, " %s\n", *p); |
| |
| exit(exitcode); |
| } |
| |
| static int query_dec_mode(int mode) |
| { |
| char *s = NULL; |
| |
| printf("\x1b[?%d$p", mode); |
| |
| do { |
| int reply_mode, reply_value; |
| char reply_cmd; |
| |
| if(s) |
| free(s); |
| s = read_csi(); |
| |
| // expect "?" mode ";" value "$y" |
| |
| // If the sscanf format string ends in a literal, we can't tell from |
| // its return value if it matches. Hence we'll %c the cmd and check it |
| // explicitly |
| if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3) |
| continue; |
| if(reply_cmd != 'y') |
| continue; |
| |
| if(reply_mode != mode) |
| continue; |
| |
| free(s); |
| |
| if(reply_value == 1 || reply_value == 3) |
| return TRUE; |
| if(reply_value == 2 || reply_value == 4) |
| return FALSE; |
| |
| printf("Unrecognised reply to DECRQM: %d\n", reply_value); |
| return FALSE; |
| } while(1); |
| } |
| |
| static void do_dec_mode(int mode, BoolQuery val, const char *name) |
| { |
| switch(val) { |
| case OFF: |
| case ON: |
| printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l'); |
| break; |
| |
| case QUERY: |
| if(query_dec_mode(mode)) |
| printf("%s on\n", name); |
| else |
| printf("%s off\n", name); |
| break; |
| } |
| } |
| |
| static int query_rqss_numeric(char *cmd) |
| { |
| char *s = NULL; |
| |
| printf("\x1bP$q%s\x1b\\", cmd); |
| |
| do { |
| int num; |
| |
| if(s) |
| free(s); |
| s = read_dcs(); |
| |
| if(!s) |
| return -1; |
| if(strlen(s) < strlen(cmd)) |
| return -1; |
| if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) { |
| printf("No match\n"); |
| continue; |
| } |
| |
| if(s[0] != '1' || s[1] != '$' || s[2] != 'r') |
| return -1; |
| |
| if(sscanf(s + 3, "%d", &num) != 1) |
| return -1; |
| |
| return num; |
| } while(1); |
| } |
| |
| int wasicanon; |
| |
| void restoreicanon(void) |
| { |
| seticanon(wasicanon, TRUE); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int argi = 1; |
| |
| if(argc == 1) |
| usage(0); |
| |
| wasicanon = seticanon(FALSE, FALSE); |
| atexit(restoreicanon); |
| |
| while(argi < argc) { |
| const char *arg = argv[argi++]; |
| |
| if(streq(arg, "reset")) { |
| printf("\x1b" "c"); |
| } |
| else if(streq(arg, "s8c1t")) { |
| const char *choices[] = {"off", "on", NULL}; |
| switch(getchoice(&argi, argc, argv, choices)) { |
| case 0: |
| printf("\x1b F"); break; |
| case 1: |
| printf("\x1b G"); break; |
| } |
| } |
| else if(streq(arg, "keypad")) { |
| const char *choices[] = {"app", "num", NULL}; |
| switch(getchoice(&argi, argc, argv, choices)) { |
| case 0: |
| printf("\x1b="); break; |
| case 1: |
| printf("\x1b>"); break; |
| } |
| } |
| else if(streq(arg, "screen")) { |
| do_dec_mode(5, getboolq(&argi, argc, argv), "screen"); |
| } |
| else if(streq(arg, "cursor")) { |
| do_dec_mode(25, getboolq(&argi, argc, argv), "cursor"); |
| } |
| else if(streq(arg, "curblink")) { |
| do_dec_mode(12, getboolq(&argi, argc, argv), "curblink"); |
| } |
| else if(streq(arg, "curshape")) { |
| // TODO: This ought to query the current value of DECSCUSR because it |
| // may need blinking on or off |
| const char *choices[] = {"block", "under", "bar", "query", NULL}; |
| int shape = getchoice(&argi, argc, argv, choices); |
| switch(shape) { |
| case 3: // query |
| shape = query_rqss_numeric(" q"); |
| switch(shape) { |
| case 1: case 2: |
| printf("curshape block\n"); |
| break; |
| case 3: case 4: |
| printf("curshape under\n"); |
| break; |
| case 5: case 6: |
| printf("curshape bar\n"); |
| break; |
| } |
| break; |
| |
| case 0: |
| case 1: |
| case 2: |
| printf("\x1b[%d q", 1 + (shape * 2)); |
| break; |
| } |
| } |
| else if(streq(arg, "mouse")) { |
| const char *choices[] = {"off", "click", "clickdrag", "motion", NULL}; |
| switch(getchoice(&argi, argc, argv, choices)) { |
| case 0: |
| printf("\x1b[?1000l"); break; |
| case 1: |
| printf("\x1b[?1000h"); break; |
| case 2: |
| printf("\x1b[?1002h"); break; |
| case 3: |
| printf("\x1b[?1003h"); break; |
| } |
| } |
| else if(streq(arg, "reportfocus")) { |
| do_dec_mode(1004, getboolq(&argi, argc, argv), "reportfocus"); |
| } |
| else if(streq(arg, "altscreen")) { |
| do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen"); |
| } |
| else if(streq(arg, "bracketpaste")) { |
| do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste"); |
| } |
| else if(streq(arg, "icontitle")) { |
| printf("\x1b]0;%s\a", getvalue(&argi, argc, argv)); |
| } |
| else if(streq(arg, "icon")) { |
| printf("\x1b]1;%s\a", getvalue(&argi, argc, argv)); |
| } |
| else if(streq(arg, "title")) { |
| printf("\x1b]2;%s\a", getvalue(&argi, argc, argv)); |
| } |
| else { |
| fprintf(stderr, "Unrecognised command %s\n", arg); |
| exit(1); |
| } |
| } |
| return 0; |
| } |