diff --git a/README.md b/README.md index 1835319..c9bfa5a 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,13 @@ Minimal building instructions $ git clone https://github.com/alols/xcape.git . $ make +Usage +----- + $ xcape -e 'Shift_L=Shift_L|parenleft;Shift_R=Shift_R|parenright;Control_L=Escape' + +Will make Left Shift pressed alone generate (, Right shift to generate ) and +Left Control generate Escape. + Contact ------- diff --git a/xcape.c b/xcape.c index 5508806..187ed2b 100644 --- a/xcape.c +++ b/xcape.c @@ -35,19 +35,35 @@ /************************************************************************ * Internal data types ***********************************************************************/ +typedef struct _Key_t +{ + KeyCode key; + struct _Key_t *next; +} Key_t; + +typedef struct _KeyMap_t +{ + KeySym from; + Key_t *to_keys; + Bool used; + Bool pressed; + Bool mouse; + int fake; + struct timeval down_at; + struct _KeyMap_t *next; +} KeyMap_t; + typedef struct _XCape_t { Display *data_conn; Display *ctrl_conn; XRecordContext record_ctx; - KeyCode escape_key; pthread_t sigwait_thread; sigset_t sigset; Bool debug; - struct timeval down_at; + KeyMap_t *map; } XCape_t; - /************************************************************************ * Internal function declarations ***********************************************************************/ @@ -55,6 +71,7 @@ void *sig_handler (void *user_data); void intercept (XPointer user_data, XRecordInterceptData *data); +void parse_mapping (XCape_t *self, char *mapping); /************************************************************************ * Main function @@ -62,34 +79,32 @@ void intercept (XPointer user_data, XRecordInterceptData *data); int main (int argc, char **argv) { XCape_t *self = malloc (sizeof (XCape_t)); - int dummy; - - if (argc > 1) - { - if (0 == strncmp(argv[1], "--debug", 7)) - { - self->debug = True; - } - else - { - fprintf (stdout, "Usage: %s [--debug]\n", argv[0]); - fprintf (stdout, - "Runs as a daemon unless --debug flag is set\n"); - return EXIT_SUCCESS; + int dummy, ch; + static char default_mapping[] = "Control_L=Escape"; + char *mapping = default_mapping; + + self->debug = False; + while ((ch = getopt(argc, argv, "de:")) != -1) { + switch (ch) { + case 'd': + self->debug = True; + break; + case 'e': + mapping = optarg; + break; + default: + fprintf (stdout, "Usage: %s [-d] [-e ]\n", argv[0]); + fprintf (stdout, + "Runs as a daemon unless -d flag is set\n"); + return EXIT_SUCCESS; } } - else - { - self->debug = False; + if (self->debug != True) daemon (0, 0); - } self->data_conn = XOpenDisplay (NULL); self->ctrl_conn = XOpenDisplay (NULL); - self->escape_key = XKeysymToKeycode (self->ctrl_conn, - XK_Escape); - if (!XQueryExtension (self->ctrl_conn, "XTEST", &dummy, &dummy, &dummy)) { @@ -108,6 +123,8 @@ int main (int argc, char **argv) exit (EXIT_FAILURE); } + parse_mapping(self, mapping); + sigemptyset (&self->sigset); sigaddset (&self->sigset, SIGINT); sigaddset (&self->sigset, SIGTERM); @@ -181,13 +198,69 @@ void *sig_handler (void *user_data) return NULL; } +static +void handle_key(XCape_t *self, KeyMap_t *key, Bool mouse_pressed, int key_event) +{ + Key_t *k; + KeyCode kk; + + if (key->fake) { + key->fake--; + return; + } + + kk = XKeysymToKeycode(self->ctrl_conn, key->from); + if (key_event == KeyPress) + { + if (self->debug) fprintf (stdout, "Key pressed!\n"); + key->pressed = True; + gettimeofday (&key->down_at, NULL); + + if (mouse_pressed) + { + key->used = True; + } + } + else + { + if (self->debug) fprintf (stdout, "Key released!\n"); + if (key->used == False) + { + struct timeval timev, ms650 = { + .tv_sec = 0, + .tv_usec = 650000 + }; + gettimeofday (&timev, NULL); + timersub (&timev, &key->down_at, &timev); + + if (timercmp (&timev, &ms650, <)) + { + if (self->debug) fprintf (stdout, + "Generating ESC!\n"); + + for (k = key->to_keys; k != NULL; k = k->next) { + XTestFakeKeyEvent (self->ctrl_conn, + k->key, True, 0); + if (kk == k->key) + key->fake += 2; + } + for (k = key->to_keys; k != NULL; k = k->next) { + XTestFakeKeyEvent (self->ctrl_conn, + k->key, False, 0); + } + XFlush (self->ctrl_conn); + } + } + key->used = False; + key->pressed = False; + } +} void intercept (XPointer user_data, XRecordInterceptData *data) { XCape_t *self = (XCape_t*)user_data; - static Bool ctrl_pressed = False; - static Bool ctrl_used = False; static Bool mouse_pressed = False; + KeyMap_t *km; if (data->category == XRecordFromServer) { @@ -199,64 +272,81 @@ void intercept (XPointer user_data, XRecordInterceptData *data) key_event, key_code); if (key_event == ButtonPress) - { - ctrl_used = ctrl_pressed; mouse_pressed = True; - } else if (key_event == ButtonRelease) - { mouse_pressed = False; - } else { - if (XkbKeycodeToKeysym (self->ctrl_conn, key_code, 0, 0) - == XK_Control_L) - { - if (key_event == KeyPress) - { - if (self->debug) fprintf (stdout, "Control pressed!\n"); - ctrl_pressed = True; - gettimeofday (&self->down_at, NULL); - - if (mouse_pressed) - { - ctrl_used = True; - } - } - else - { - if (self->debug) fprintf (stdout, "Control released!\n"); - if (ctrl_used == False) - { - struct timeval timev, ms650 = { - .tv_sec = 0, - .tv_usec = 650000 - }; - gettimeofday (&timev, NULL); - timersub (&timev, &self->down_at, &timev); - - if (timercmp (&timev, &ms650, <)) - { - if (self->debug) fprintf (stdout, - "Generating ESC!\n"); - - XTestFakeKeyEvent (self->ctrl_conn, - self->escape_key, True, 0); - XTestFakeKeyEvent (self->ctrl_conn, - self->escape_key, False, 0); - XFlush (self->ctrl_conn); - } - } - ctrl_pressed = False; - ctrl_used = False; + for (km = self->map; km != NULL; km = km->next) { + if (XkbKeycodeToKeysym (self->ctrl_conn, key_code, 0, 0) + == km->from) { + handle_key(self, km, mouse_pressed, key_event); + } else if (km->pressed && key_event == KeyPress) { + km->used = True; } } - else if (ctrl_pressed && key_event == KeyPress) - { - ctrl_used = True; - } } } XRecordFreeData (data); } + +static +KeyMap_t* parse_token(Display *dpy, char *token) { + KeyMap_t *km = NULL; + Key_t *k, *nk; + KeySym ks; + char *from, *to, *key; + + to = token; + from = strsep(&to, "="); + if (from != NULL) { + km = calloc(1, sizeof(KeyMap_t)); + if ((ks = XStringToKeysym(from)) == NoSymbol) { + fprintf(stderr, "Cannot parse %s\n", token); + return NULL; + } + km->from = ks; + km->to_keys = k = NULL; + for(;;) { + key = strsep(&to, "|"); + if (key == NULL) + break; + if ((ks = XStringToKeysym(key)) == NoSymbol) { + fprintf(stderr, "Cannot parse %s\n", to); + return NULL; + } + nk = calloc(1, sizeof(Key_t)); + nk->key = XKeysymToKeycode(dpy, ks); + if (k == NULL) { + km->to_keys = k = nk; + } else { + k->next = nk; + k = nk; + } + } + } + return km; +} + +void parse_mapping (XCape_t *self, char *mapping) +{ + char *token; + KeyMap_t *km, *nkm; + + km = self->map = NULL; + for(;;) { + token = strsep(&mapping, ";"); + if (token == NULL) + break; + nkm = parse_token(self->ctrl_conn, token); + if (nkm != NULL) { + if (km == NULL) + self->map = km = nkm; + else { + km->next = nkm; + km = nkm; + } + } + } +}