|
|
|
|
@ -1,7 +1,7 @@ |
|
|
|
|
/************************************************************************
|
|
|
|
|
* xcape.c |
|
|
|
|
* |
|
|
|
|
* Copyright 2015 Albin Olsson |
|
|
|
|
* Copyright 2012 Albin Olsson |
|
|
|
|
* |
|
|
|
|
* This program is free software: you can redistribute it and/or modify |
|
|
|
|
* it under the terms of the GNU General Public License as published by |
|
|
|
|
@ -44,7 +44,7 @@ typedef struct _Key_t |
|
|
|
|
|
|
|
|
|
typedef struct _KeyMap_t |
|
|
|
|
{ |
|
|
|
|
Bool UseKeyCode; /* (for from) instead of KeySym; ignore latter */ |
|
|
|
|
Bool UseKeyCode; // (for from) instead of KeySym; ignore latter
|
|
|
|
|
KeySym from_ks; |
|
|
|
|
KeyCode from_kc; |
|
|
|
|
Key_t *to_keys; |
|
|
|
|
@ -62,11 +62,11 @@ typedef struct _XCape_t |
|
|
|
|
XRecordContext record_ctx; |
|
|
|
|
pthread_t sigwait_thread; |
|
|
|
|
sigset_t sigset; |
|
|
|
|
Bool foreground; |
|
|
|
|
Bool debug; |
|
|
|
|
KeyMap_t *map; |
|
|
|
|
Key_t *generated; |
|
|
|
|
struct timeval timeout; |
|
|
|
|
Bool timeout_valid; |
|
|
|
|
} XCape_t; |
|
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
|
@ -78,47 +78,29 @@ void intercept (XPointer user_data, XRecordInterceptData *data); |
|
|
|
|
|
|
|
|
|
KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug); |
|
|
|
|
|
|
|
|
|
void delete_mapping (KeyMap_t *map); |
|
|
|
|
|
|
|
|
|
Key_t *key_add_key (Key_t *keys, KeyCode key); |
|
|
|
|
|
|
|
|
|
void delete_keys (Key_t *keys); |
|
|
|
|
|
|
|
|
|
void print_usage (const char *program_name); |
|
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
|
* Main function |
|
|
|
|
***********************************************************************/ |
|
|
|
|
int main (int argc, char **argv) |
|
|
|
|
{ |
|
|
|
|
XCape_t *self = malloc (sizeof (XCape_t)); |
|
|
|
|
|
|
|
|
|
int dummy, ch; |
|
|
|
|
|
|
|
|
|
static char default_mapping[] = "Control_L=Escape"; |
|
|
|
|
char *mapping = default_mapping; |
|
|
|
|
|
|
|
|
|
XRecordRange *rec_range = XRecordAllocRange(); |
|
|
|
|
XRecordClientSpec client_spec = XRecordAllClients; |
|
|
|
|
|
|
|
|
|
self->foreground = False; |
|
|
|
|
self->debug = False; |
|
|
|
|
self->timeout.tv_sec = 0; |
|
|
|
|
self->timeout.tv_usec = 500000; |
|
|
|
|
self->timeout_valid = True; |
|
|
|
|
self->generated = NULL; |
|
|
|
|
|
|
|
|
|
rec_range->device_events.first = KeyPress; |
|
|
|
|
rec_range->device_events.last = ButtonRelease; |
|
|
|
|
|
|
|
|
|
while ((ch = getopt (argc, argv, "dfe:t:")) != -1) |
|
|
|
|
while ((ch = getopt (argc, argv, "de:t:")) != -1) |
|
|
|
|
{ |
|
|
|
|
switch (ch) |
|
|
|
|
{ |
|
|
|
|
case 'd': |
|
|
|
|
self->debug = True; |
|
|
|
|
/* imply -f (no break) */ |
|
|
|
|
case 'f': |
|
|
|
|
self->foreground = True; |
|
|
|
|
break; |
|
|
|
|
case 'e': |
|
|
|
|
mapping = optarg; |
|
|
|
|
@ -128,36 +110,24 @@ int main (int argc, char **argv) |
|
|
|
|
int ms = atoi (optarg); |
|
|
|
|
if (ms > 0) |
|
|
|
|
{ |
|
|
|
|
self->timeout_valid = True; |
|
|
|
|
self->timeout.tv_sec = ms / 1000; |
|
|
|
|
self->timeout.tv_usec = (ms % 1000) * 1000; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "Invalid argument for '-t': %s.\n", optarg); |
|
|
|
|
print_usage (argv[0]); |
|
|
|
|
return EXIT_FAILURE; |
|
|
|
|
self->timeout_valid = False; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
print_usage (argv[0]); |
|
|
|
|
fprintf (stdout, "Usage: %s [-d] [-t timeout_ms] [-e <mapping>]\n", argv[0]); |
|
|
|
|
fprintf (stdout, |
|
|
|
|
"Runs as a daemon unless -d flag is set\n"); |
|
|
|
|
return EXIT_SUCCESS; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (optind < argc) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "Not a command line option: '%s'\n", argv[optind]); |
|
|
|
|
print_usage (argv[0]); |
|
|
|
|
return EXIT_SUCCESS; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!XInitThreads ()) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "Failed to initialize threads.\n"); |
|
|
|
|
exit (EXIT_FAILURE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self->data_conn = XOpenDisplay (NULL); |
|
|
|
|
self->ctrl_conn = XOpenDisplay (NULL); |
|
|
|
|
|
|
|
|
|
@ -187,12 +157,9 @@ int main (int argc, char **argv) |
|
|
|
|
self->map = parse_mapping (self->ctrl_conn, mapping, self->debug); |
|
|
|
|
|
|
|
|
|
if (self->map == NULL) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "Failed to parse_mapping\n"); |
|
|
|
|
exit (EXIT_FAILURE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (self->foreground != True) |
|
|
|
|
if (self->debug != True) |
|
|
|
|
daemon (0, 0); |
|
|
|
|
|
|
|
|
|
sigemptyset (&self->sigset); |
|
|
|
|
@ -203,6 +170,11 @@ int main (int argc, char **argv) |
|
|
|
|
pthread_create (&self->sigwait_thread, |
|
|
|
|
NULL, sig_handler, self); |
|
|
|
|
|
|
|
|
|
XRecordRange *rec_range = XRecordAllocRange(); |
|
|
|
|
rec_range->device_events.first = KeyPress; |
|
|
|
|
rec_range->device_events.last = ButtonRelease; |
|
|
|
|
XRecordClientSpec client_spec = XRecordAllClients; |
|
|
|
|
|
|
|
|
|
self->record_ctx = XRecordCreateContext (self->ctrl_conn, |
|
|
|
|
0, &client_spec, 1, &rec_range, 1); |
|
|
|
|
|
|
|
|
|
@ -221,23 +193,15 @@ int main (int argc, char **argv) |
|
|
|
|
exit (EXIT_FAILURE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pthread_join (self->sigwait_thread, NULL); |
|
|
|
|
|
|
|
|
|
if (!XRecordFreeContext (self->ctrl_conn, self->record_ctx)) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "Failed to free xrecord context\n"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (self->debug) fprintf (stdout, "main exiting\n"); |
|
|
|
|
|
|
|
|
|
XFree (rec_range); |
|
|
|
|
|
|
|
|
|
XCloseDisplay (self->ctrl_conn); |
|
|
|
|
XCloseDisplay (self->data_conn); |
|
|
|
|
|
|
|
|
|
delete_mapping (self->map); |
|
|
|
|
|
|
|
|
|
free (self); |
|
|
|
|
if (self->debug) fprintf (stdout, "main exiting\n"); |
|
|
|
|
|
|
|
|
|
return EXIT_SUCCESS; |
|
|
|
|
} |
|
|
|
|
@ -257,8 +221,6 @@ void *sig_handler (void *user_data) |
|
|
|
|
|
|
|
|
|
if (self->debug) fprintf (stdout, "Caught signal %d!\n", sig); |
|
|
|
|
|
|
|
|
|
XLockDisplay (self->ctrl_conn); |
|
|
|
|
|
|
|
|
|
if (!XRecordDisableContext (self->ctrl_conn, |
|
|
|
|
self->record_ctx)) |
|
|
|
|
{ |
|
|
|
|
@ -268,8 +230,6 @@ void *sig_handler (void *user_data) |
|
|
|
|
|
|
|
|
|
XSync (self->ctrl_conn, False); |
|
|
|
|
|
|
|
|
|
XUnlockDisplay (self->ctrl_conn); |
|
|
|
|
|
|
|
|
|
if (self->debug) fprintf (stdout, "sig_handler exiting...\n"); |
|
|
|
|
|
|
|
|
|
return NULL; |
|
|
|
|
@ -307,7 +267,8 @@ void handle_key (XCape_t *self, KeyMap_t *key, |
|
|
|
|
|
|
|
|
|
key->pressed = True; |
|
|
|
|
|
|
|
|
|
gettimeofday (&key->down_at, NULL); |
|
|
|
|
if (self->timeout_valid) |
|
|
|
|
gettimeofday (&key->down_at, NULL); |
|
|
|
|
|
|
|
|
|
if (mouse_pressed) |
|
|
|
|
{ |
|
|
|
|
@ -320,10 +281,13 @@ void handle_key (XCape_t *self, KeyMap_t *key, |
|
|
|
|
if (key->used == False) |
|
|
|
|
{ |
|
|
|
|
struct timeval timev = self->timeout; |
|
|
|
|
gettimeofday (&timev, NULL); |
|
|
|
|
timersub (&timev, &key->down_at, &timev); |
|
|
|
|
if (self->timeout_valid) |
|
|
|
|
{ |
|
|
|
|
gettimeofday (&timev, NULL); |
|
|
|
|
timersub (&timev, &key->down_at, &timev); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (timercmp (&timev, &self->timeout, <)) |
|
|
|
|
if (!self->timeout_valid || timercmp (&timev, &self->timeout, <)) |
|
|
|
|
{ |
|
|
|
|
for (k = key->to_keys; k != NULL; k = k->next) |
|
|
|
|
{ |
|
|
|
|
@ -355,8 +319,6 @@ void intercept (XPointer user_data, XRecordInterceptData *data) |
|
|
|
|
static Bool mouse_pressed = False; |
|
|
|
|
KeyMap_t *km; |
|
|
|
|
|
|
|
|
|
XLockDisplay (self->ctrl_conn); |
|
|
|
|
|
|
|
|
|
if (data->category == XRecordFromServer) |
|
|
|
|
{ |
|
|
|
|
int key_event = data->data[0]; |
|
|
|
|
@ -395,13 +357,9 @@ void intercept (XPointer user_data, XRecordInterceptData *data) |
|
|
|
|
{ |
|
|
|
|
mouse_pressed = False; |
|
|
|
|
} |
|
|
|
|
for (km = self->map; km != NULL; km = km->next) |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
if ((km->UseKeyCode == False |
|
|
|
|
&& XkbKeycodeToKeysym (self->ctrl_conn, key_code, 0, 0) |
|
|
|
|
== km->from_ks) |
|
|
|
|
|| (km->UseKeyCode == True |
|
|
|
|
&& key_code == km->from_kc)) |
|
|
|
|
for (km = self->map; km != NULL; km = km->next) |
|
|
|
|
{ |
|
|
|
|
if ((km->UseKeyCode == False |
|
|
|
|
&& XkbKeycodeToKeysym (self->ctrl_conn, key_code, 0, 0) |
|
|
|
|
@ -411,7 +369,7 @@ void intercept (XPointer user_data, XRecordInterceptData *data) |
|
|
|
|
{ |
|
|
|
|
handle_key (self, km, mouse_pressed, key_event); |
|
|
|
|
} |
|
|
|
|
else if (km->pressed && (key_event == KeyPress || key_event == ButtonPress)) |
|
|
|
|
else if (km->pressed && key_event == KeyPress)
|
|
|
|
|
{ |
|
|
|
|
/* We should check if the pressed key is a modifier before marking the key as used. */ |
|
|
|
|
if (key_code != 50) /* hack; try if it works */ |
|
|
|
|
@ -424,7 +382,6 @@ void intercept (XPointer user_data, XRecordInterceptData *data) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
exit: |
|
|
|
|
XUnlockDisplay (self->ctrl_conn); |
|
|
|
|
XRecordFreeData (data); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -434,8 +391,8 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug) |
|
|
|
|
KeyMap_t *km = NULL; |
|
|
|
|
KeySym ks; |
|
|
|
|
char *from, *to, *key; |
|
|
|
|
KeyCode code; /* keycode */ |
|
|
|
|
long parsed_code; /* parsed keycode value */ |
|
|
|
|
KeyCode code; // keycode (to)
|
|
|
|
|
long fromcode; // keycode (from)
|
|
|
|
|
|
|
|
|
|
to = token; |
|
|
|
|
from = strsep (&to, "="); |
|
|
|
|
@ -447,17 +404,17 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug) |
|
|
|
|
&& strsep (&from, "#") != NULL) |
|
|
|
|
{ |
|
|
|
|
errno = 0; |
|
|
|
|
parsed_code = strtoul (from, NULL, 0); /* dec, oct, hex automatically */ |
|
|
|
|
fromcode = strtoul (from, NULL, 0); // dec, oct, hex automatically
|
|
|
|
|
if (errno == 0 |
|
|
|
|
&& parsed_code <=255 |
|
|
|
|
&& XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0) != NoSymbol) |
|
|
|
|
&& fromcode <=255 |
|
|
|
|
&& XkbKeycodeToKeysym (dpy, (KeyCode) fromcode, 0, 0) != NoSymbol) |
|
|
|
|
{ |
|
|
|
|
km->UseKeyCode = True; |
|
|
|
|
km->from_kc = (KeyCode) parsed_code; |
|
|
|
|
km->from_kc = (KeyCode) fromcode; |
|
|
|
|
if (debug) |
|
|
|
|
{ |
|
|
|
|
KeySym ks_temp = XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0); |
|
|
|
|
fprintf(stderr, "Assigned mapping from \"%s\" ( keysym 0x%x, " |
|
|
|
|
KeySym ks_temp = XkbKeycodeToKeysym (dpy, (KeyCode) fromcode, 0, 0); |
|
|
|
|
fprintf(stderr, "Assigned mapping from from \"%s\" ( keysym 0x%x, " |
|
|
|
|
"key code %d)\n", |
|
|
|
|
XKeysymToString(ks_temp), |
|
|
|
|
(unsigned) ks_temp, |
|
|
|
|
@ -498,47 +455,28 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug) |
|
|
|
|
if (key == NULL) |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
if (!strncmp (key, "#", 1) |
|
|
|
|
&& strsep (&key, "#") != NULL) |
|
|
|
|
if ((ks = XStringToKeysym (key)) == NoSymbol) |
|
|
|
|
{ |
|
|
|
|
errno = 0; |
|
|
|
|
parsed_code = strtoul (key, NULL, 0); /* dec, oct, hex automatically */ |
|
|
|
|
if (!(errno == 0 |
|
|
|
|
&& parsed_code <=255 |
|
|
|
|
&& XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0) != NoSymbol)) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "Invalid keycode: %s\n", key); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
code = (KeyCode) parsed_code; |
|
|
|
|
fprintf (stderr, "Invalid key: %s\n", key); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
if ((ks = XStringToKeysym (key)) == NoSymbol) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "Invalid key: %s\n", key); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
code = XKeysymToKeycode (dpy, ks); |
|
|
|
|
if (code == 0) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "WARNING: No keycode found for keysym " |
|
|
|
|
"%s (0x%x) in mapping %s. Ignoring this " |
|
|
|
|
"mapping.\n", key, (unsigned int)ks, token); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
code = XKeysymToKeycode (dpy, ks); |
|
|
|
|
if (code == 0) |
|
|
|
|
{ |
|
|
|
|
fprintf (stderr, "WARNING: No keycode found for keysym " |
|
|
|
|
"%s (0x%x) in mapping %s. Ignoring this " |
|
|
|
|
"mapping.\n", key, (unsigned int)ks, token); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
km->to_keys = key_add_key (km->to_keys, code); |
|
|
|
|
|
|
|
|
|
if (debug) |
|
|
|
|
{ |
|
|
|
|
KeySym ks_temp = XkbKeycodeToKeysym (dpy, code, 0, 0); |
|
|
|
|
fprintf(stderr, "to \"%s\" (keysym 0x%x, key code %d)\n", |
|
|
|
|
XKeysymToString(ks_temp), |
|
|
|
|
(unsigned) ks_temp, |
|
|
|
|
(unsigned) code); |
|
|
|
|
key, |
|
|
|
|
(unsigned) XStringToKeysym (key), |
|
|
|
|
(unsigned) code); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -578,28 +516,3 @@ KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug) |
|
|
|
|
|
|
|
|
|
return rval; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void delete_mapping (KeyMap_t *map) |
|
|
|
|
{ |
|
|
|
|
while (map != NULL) { |
|
|
|
|
KeyMap_t *next = map->next; |
|
|
|
|
delete_keys (map->to_keys); |
|
|
|
|
free (map); |
|
|
|
|
map = next; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void delete_keys (Key_t *keys) |
|
|
|
|
{ |
|
|
|
|
while (keys != NULL) { |
|
|
|
|
Key_t *next = keys->next; |
|
|
|
|
free (keys); |
|
|
|
|
keys = next; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void print_usage (const char *program_name) |
|
|
|
|
{ |
|
|
|
|
fprintf (stdout, "Usage: %s [-d] [-f] [-t timeout_ms] [-e <mapping>]\n", program_name); |
|
|
|
|
fprintf (stdout, "Runs as a daemon unless -d or -f flag is set\n"); |
|
|
|
|
} |
|
|
|
|
|