summaryrefslogtreecommitdiffstats
path: root/src/libvterm/bin/vterm-ctrl.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libvterm/bin/vterm-ctrl.c368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/libvterm/bin/vterm-ctrl.c b/src/libvterm/bin/vterm-ctrl.c
new file mode 100644
index 0000000..7c08fe1
--- /dev/null
+++ b/src/libvterm/bin/vterm-ctrl.c
@@ -0,0 +1,368 @@
+#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;
+}