blob: 9e06011d176966723a5d450a6393ff88b3fe455e [file] [log] [blame]
#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;
}