/* xdo * * Compile: * gcc `pkg-config --cflags --libs x11 xtst` xdo.c -o xdo * * FWIW, pkg-config above outputs this: * -D_THREAD_SAFE -I/usr/local/include -L/usr/local/lib -lX11 -lXtst * * "do" things normally done from the keyboard and mouse * * Implemented: * type something_to_type * move xcoord ycoord [screennum] * click mouse_button * key key_sequence * sleep sleep_in_ms * * Commands a separated by semicolon and/or newlines. * * Example: * * Hello world using faked keyboard input * echo 'type hello world' | ./xdo * * Use this while firefox is focused to go to google: * echo 'key ctrl+l; sleep 1; type http://www.google.com/; key Return' | ./xdo * - This does: * 1) Ctrl+L to focus the URL bar * 2) Sleep to let the event propogate * 3) Type http://www.google.com/ * 4) Press return */ #include #include #include #include #include #include #include #include #include #include int main_loop(); void xdo(char *cmd); int get_keycode(char key); int get_shift(char key); void populate_charcode_map(); char char_from_keystring(char *keysym); /* Commands */ void cmd_type(char *args); void cmd_move(char *args); void cmd_key(char *args); void cmd_keydown(char *args); void cmd_keyup(char *args); void cmd_click(char *args); void cmd_mouseup(char *args); void cmd_mousedown(char *args); void cmd_sleep(char *args); typedef struct dispatch { char *name; void (*func)(char *args); } dispatch_t; static dispatch_t commands[] = { "type", cmd_type, "key", cmd_key, "sleep", cmd_sleep, "move", cmd_move, "click", cmd_click, NULL, NULL, }; static char *symbol_map[] = { "alt", "Alt_L", "ctrl", "Control_L", "control", "Control_L", "meta", "Meta_L", "super", "Super_L", "shift", "Shift_L", NULL, NULL, }; typedef struct charcodemap { char key; int code; int shift; } charcodemap_t; typedef struct keysymcharmap { char *keysym; char key; } keysymcharmap_t; static charcodemap_t *charcodes = NULL; static int keycode_lowest; static int keycode_highest; static Display *xdpy = NULL; keysymcharmap_t keysymcharmap[] = { "Return", '\n', "ampersand", '&', "apostrophe", '\'', "asciicircum", '^', "asciitilde", '~', "asterisk", '*', "at", '@', "backslash", '\\', "bar", '|', "braceleft", '{', "braceright", '}', "bracketleft", '[', "bracketright", ']', "colon", ':', "comma", ',', "dollar", '$', "equal", '=', "exclam", '!', "grave", '`', "greater", '>', "less", '<', "minus", '-', "numbersign", '#', "parenleft", '(', "parenright", ')', "percent", '%', "period", '.', "plus", '+', "question", '?', "quotedbl", '"', "semicolon", ';', "slash", '/', "space", ' ', "tab", '\t', "underscore", '_', NULL, 0, }; int main() { char *display_name = NULL; int dummy; if ( (display_name = getenv("DISPLAY")) == (void *)NULL) { fprintf(stderr, "Error: DISPLAY environment variable not set\n"); exit(1); } if ((xdpy = XOpenDisplay(display_name)) == NULL) { fprintf(stderr, "Error: Can't open display: %s", display_name); exit(1); } if (XTestQueryExtension(xdpy, &dummy, &dummy, &dummy, &dummy) != True) { fprintf(stderr, "Error: No XTEST extension available on %s", display_name); return 1; } populate_charcode_map(); return main_loop(); } void populate_charcode_map() { int key_low, key_high; int dummy; int i; int charcodes_len; XDisplayKeycodes(xdpy, &key_low, &key_high); /* Double in size because there's actually 2 characters per key * shifted and nonshifted. */ charcodes_len = (key_high - key_low) * 2; charcodes = malloc(charcodes_len * sizeof(charcodemap_t)); memset(charcodes, 0, charcodes_len * sizeof(charcodemap_t)); int shiftcode = XKeysymToKeycode(xdpy, XStringToKeysym("Shift_L")); keycode_lowest = key_low; keycode_highest = key_high; //printf("Low/High: %d/%d\n", key_low, key_high); for (i = key_low; i <= key_high; i++) { char *keybuf; int j; for (j = 0; j <= 1; j++) { int index = (i * 2 + j) - key_low; keybuf = XKeysymToString(XKeycodeToKeysym(xdpy, i, j)); if (keybuf != NULL) { //printf("%d => %s\n", i, keybuf); charcodes[index].key = char_from_keystring(keybuf); } else { //printf("No such key for code %d\n", i); charcodes[index].key = -1; } charcodes[index].code = i; charcodes[index].shift = j ? shiftcode : 0; } } //printf("end: %d\n", i); } int get_keycode(char key) { int i; int len = keycode_highest - keycode_lowest; for (i = 0; i < len; i++) { //if (charcodes[i].key != -1) //printf("%c vs %c\n", charcodes[i].key, key); if (charcodes[i].key == key) return charcodes[i].code; } return -1; } int get_shift(char key) { int i; int len = keycode_highest - keycode_lowest; for (i = 0; i < len; i++) { if (charcodes[i].key == key) return charcodes[i].shift; } return 0; } char char_from_keystring(char *keysym) { int i; for (i = 0; keysymcharmap[i].keysym; i++) { if (!strcmp(keysymcharmap[i].keysym, keysym)) return keysymcharmap[i].key; } if (strlen(keysym) == 1) return keysym[0]; return -1; } int main_loop() { #define READBUFSIZE (4096) #define COMMANDSEP (";\n") char buf[READBUFSIZE]; int bytes = 0; int rem_bytes = 0; char *tok; char *tokctx; memset(buf, 0, READBUFSIZE); while (!feof(stdin)) { bytes = fread(buf+rem_bytes, 1, READBUFSIZE - rem_bytes, stdin); rem_bytes = bytes; tok = strtok_r(buf, COMMANDSEP, &tokctx); while (tok != NULL) { xdo(tok); rem_bytes -= strlen(tok); tok = strtok_r(NULL, COMMANDSEP, &tokctx); } } return 0; } void xdo(char *cmd) { char *tok = NULL; char *tokctx = NULL; int i; tok = strtok_r(cmd, " ", &tokctx); for (i = 0; i < sizeof(commands) && commands[i].name != NULL; i++) { if (!strcmp(commands[i].name, tok)) commands[i].func(tokctx); } } void cmd_type(char *args) { int i = 0; int len = 0; char key = 0; int keycode = 0; int shift = 0; len = strlen(args); //printf("Typing: %s\n", args); for (i = 0; i < len; i++) { key = *(args + i); keycode = get_keycode(key); shift = get_shift(key); //printf("K: %d (%s)\n", keycode, (shift ? "shifted" : "")); if (shift) XTestFakeKeyEvent(xdpy, shift, True, CurrentTime); XTestFakeKeyEvent(xdpy, keycode, True, CurrentTime); XTestFakeKeyEvent(xdpy, keycode, False, CurrentTime); if (shift) XTestFakeKeyEvent(xdpy, shift, False, CurrentTime); XFlush(xdpy); } } void cmd_key(char *args) { char *tokctx; char *tok; int i; int *keys = NULL; int nkeys = 0; int keys_len = 10; char *strptr = NULL; keys = malloc(keys_len * sizeof(int)); if (strcspn(args, " \t\n.-[]{}\\|") != strlen(args)) { fprintf(stderr, "Error: Invalid key sequence '%s'\n", args); return; } strptr = args; while ((tok = strtok_r(strptr, "+", &tokctx)) != NULL) { int keysym; if (strptr != NULL) strptr = NULL; /* Check if 'tok' (string keysym) is an alias to another key */ for (i = 0; symbol_map[i] != NULL; i+=2) if (!strcasecmp(tok, symbol_map[i])) tok = symbol_map[i + 1]; keysym = XStringToKeysym(tok); if (keysym == NoSymbol) { fprintf(stderr, "(symbol) No such key name '%s'. Ignoring it.\n", tok); continue; } keys[nkeys] = XKeysymToKeycode(xdpy, keysym); if (keys[nkeys] == 0) { fprintf(stderr, "No such key '%s'. Ignoring it.\n", tok); continue; } nkeys++; if (nkeys == keys_len) { keys_len *= 2; keys = realloc(keys, keys_len); } } for (i = 0; i < nkeys; i++) XTestFakeKeyEvent(xdpy, keys[i], True, CurrentTime); for (i = nkeys - 1; i >= 0; i--) XTestFakeKeyEvent(xdpy, keys[i], False, CurrentTime); XFlush(xdpy); } void cmd_sleep(char *args) { unsigned long sleep_ms; struct timeval t; sleep_ms = strtoul(args, NULL, 0); t.tv_sec = sleep_ms / 1000; t.tv_usec = (sleep_ms % 1000) * 1000; /* milliseconds -> microseconds */ select(0, NULL, NULL, NULL, &t); } void cmd_move(char *args) { int x, y; int screen; sscanf(args, "%d %d %d", &x, &y, &screen); XTestFakeMotionEvent(xdpy, -1, x, y, CurrentTime); XFlush(xdpy); } void cmd_click(char *args) { int button; button = atoi(args); printf("Button: %d\n", button); XTestFakeButtonEvent(xdpy, button, True, CurrentTime); XTestFakeButtonEvent(xdpy, button, False, CurrentTime); XFlush(xdpy); } void strtolower(char *str) { int i; for (i = 0; str[i] != (char)NULL; i++) str[i] = tolower(str[i]); }