#define _XOPEN_SOURCE 500 /* strdup */ #include #include #include #define streq(a,b) (strcmp(a,b)==0) #define TRUE 1 #define FALSE 0 #include 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; }