diff --git a/include/config.h b/include/config.h index cfedfdb8..17ec5391 100644 --- a/include/config.h +++ b/include/config.h @@ -79,6 +79,18 @@ struct Config { * */ void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload); + +/** + * Ungrabs all keys, to be called before re-grabbing the keys because of a + * mapping_notify event or a configuration file reload + * + */ +void ungrab_all_keys(xcb_connection_t *conn); + +/** + * Grab the bound keys (tell X to send us keypress events for those keycodes) + * + */ void grab_all_keys(xcb_connection_t *conn); #endif diff --git a/include/data.h b/include/data.h index c2d83783..ad9f7a05 100644 --- a/include/data.h +++ b/include/data.h @@ -230,12 +230,29 @@ struct Workspace { * */ struct Binding { + /** Symbol the user specified in configfile, if any. This needs to be + * stored with the binding to be able to re-convert it into a keycode + * if the keyboard mapping changes (using Xmodmap for example) */ + char *symbol; + + /** Only in use if symbol != NULL. Gets set to the value to which the + * symbol got translated when binding. Useful for unbinding and + * checking which binding was used when a key press event comes in. + * + * This is an array of number_keycodes size. */ + xcb_keycode_t *translated_to; + + uint32_t number_keycodes; + /** Keycode to bind */ uint32_t keycode; + /** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */ uint32_t mods; + /** Command, like in command mode */ char *command; + TAILQ_ENTRY(Binding) bindings; }; diff --git a/include/handlers.h b/include/handlers.h index 5b776de7..a56ed282 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -44,6 +44,14 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event); +/** + * Called when the keyboard mapping changes (for example by using Xmodmap), + * we need to update our key bindings then (re-translate symbols). + * + */ +int handle_mapping_notify(void *ignored, xcb_connection_t *conn, + xcb_mapping_notify_event_t *event); + /** * Checks if the button press was on a stack window, handles focus setting and * returns true if so, or false otherwise. diff --git a/include/i3.h b/include/i3.h index 46caa415..935d8141 100644 --- a/include/i3.h +++ b/include/i3.h @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -23,6 +24,7 @@ #define NUM_ATOMS 18 extern xcb_connection_t *global_conn; +extern xcb_key_symbols_t *keysyms; extern char **start_argv; extern Display *xkbdpy; extern TAILQ_HEAD(bindings_head, Binding) bindings; diff --git a/src/config.c b/src/config.c index 2f78dec9..de859564 100644 --- a/src/config.c +++ b/src/config.c @@ -14,6 +14,11 @@ #include #include +/* We need Xlib for XStringToKeysym */ +#include + +#include + #include "i3.h" #include "util.h" #include "config.h" @@ -59,15 +64,28 @@ static void replace_variable(char *buffer, const char *key, const char *value) { } } -/* - * Ungrab the bound keys +/** + * Ungrabs all keys, to be called before re-grabbing the keys because of a + * mapping_notify event or a configuration file reload * */ void ungrab_all_keys(xcb_connection_t *conn) { - Binding *bind; - TAILQ_FOREACH(bind, &bindings, bindings) { - LOG("Ungrabbing %d\n", bind->keycode); - xcb_ungrab_key(conn, bind->keycode, root, bind->keycode); + LOG("Ungrabbing all keys\n"); + xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY); +} + +static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) { + LOG("Grabbing %d\n", keycode); + if ((bind->mods & BIND_MODE_SWITCH) != 0) + xcb_grab_key(conn, 0, root, 0, keycode, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); + else { + /* Grab the key in all combinations */ + #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, keycode, \ + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) + GRAB_KEY(bind->mods); + GRAB_KEY(bind->mods | xcb_numlock_mask); + GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); } } @@ -78,18 +96,41 @@ void ungrab_all_keys(xcb_connection_t *conn) { void grab_all_keys(xcb_connection_t *conn) { Binding *bind; TAILQ_FOREACH(bind, &bindings, bindings) { - LOG("Grabbing %d\n", bind->keycode); - if ((bind->mods & BIND_MODE_SWITCH) != 0) - xcb_grab_key(conn, 0, root, 0, bind->keycode, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); - else { - /* Grab the key in all combinations */ - #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \ - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) - GRAB_KEY(bind->mods); - GRAB_KEY(bind->mods | xcb_numlock_mask); - GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + /* The easy case: the user specified a keycode directly. */ + if (bind->keycode > 0) { + grab_keycode_for_binding(conn, bind, bind->keycode); + continue; } + + /* We need to translate the symbol to a keycode */ + LOG("Translating symbol to keycode (\"%s\")\n", bind->symbol); + xcb_keysym_t keysym = XStringToKeysym(bind->symbol); + if (keysym == NoSymbol) { + LOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol); + continue; + } + + xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym); + if (keycodes == NULL) { + LOG("Could not translate symbol \"%s\"\n", bind->symbol); + continue; + } + + uint32_t last_keycode; + bind->number_keycodes = 0; + for (xcb_keycode_t *walk = keycodes; *walk != 0; walk++) { + /* We hope duplicate keycodes will be returned in order + * and skip them */ + if (last_keycode == *walk) + continue; + grab_keycode_for_binding(conn, bind, *walk); + last_keycode = *walk; + bind->number_keycodes++; + } + LOG("Got %d different keycodes\n", bind->number_keycodes); + bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t)); + memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t)); + free(keycodes); } } @@ -234,7 +275,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, } /* key bindings */ - if (strcasecmp(key, "bind") == 0) { + if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) { #define CHECK_MODIFIER(name) \ if (strncasecmp(walk, #name, strlen(#name)) == 0) { \ modifiers |= BIND_##name; \ @@ -259,14 +300,25 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, break; } - /* Now check for the keycode */ - int keycode = strtol(walk, &rest, 10); - if (!rest || *rest != ' ') - die("Invalid binding\n"); + Binding *new = scalloc(sizeof(Binding)); + + /* Now check for the keycode or copy the symbol */ + if (strcasecmp(key, "bind") == 0) { + int keycode = strtol(walk, &rest, 10); + if (!rest || *rest != ' ') + die("Invalid binding (keycode)\n"); + new->keycode = keycode; + } else { + rest = walk; + char *sym = rest; + while (*rest != '\0' && *rest != ' ') + rest++; + if (*rest != ' ') + die("Invalid binding (keysym)\n"); + new->symbol = strndup(sym, (rest - sym)); + } rest++; - LOG("keycode = %d, modifiers = %d, command = *%s*\n", keycode, modifiers, rest); - Binding *new = smalloc(sizeof(Binding)); - new->keycode = keycode; + LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest); new->mods = modifiers; new->command = sstrdup(rest); TAILQ_INSERT_TAIL(&bindings, new, bindings); diff --git a/src/handlers.c b/src/handlers.c index ffbde9fa..d5a1e8fd 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -119,9 +119,24 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ /* Find the binding */ Binding *bind; - TAILQ_FOREACH(bind, &bindings, bindings) - if (bind->keycode == event->detail && bind->mods == state_filtered) - break; + TAILQ_FOREACH(bind, &bindings, bindings) { + /* First compare the modifiers */ + if (bind->mods != state_filtered) + continue; + + /* If a symbol was specified by the user, we need to look in + * the array of translated keycodes for the event’s keycode */ + if (bind->symbol != NULL) { + if (memmem(bind->translated_to, + bind->number_keycodes * sizeof(xcb_keycode_t), + &(event->detail), sizeof(xcb_keycode_t)) != NULL) + break; + } else { + /* This case is easier: The user specified a keycode */ + if (bind->keycode == event->detail) + break; + } + } /* No match? Then it was an actively grabbed key, that is with Mode_switch, and the user did not press Mode_switch, so just pass it… */ @@ -239,6 +254,30 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notif return 1; } +/* + * Called when the keyboard mapping changes (for example by using Xmodmap), + * we need to update our key bindings then (re-translate symbols). + * + */ +int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) { + LOG("\n\nmapping notify\n\n"); + + if (event->request != XCB_MAPPING_KEYBOARD && + event->request != XCB_MAPPING_MODIFIER) + return 0; + + xcb_refresh_keyboard_mapping(keysyms, event); + + xcb_get_numlock_mask(conn); + + ungrab_all_keys(conn); + LOG("Re-grabbing...\n"); + grab_all_keys(conn); + LOG("Done\n"); + + return 0; +} + /* * Checks if the button press was on a stack window, handles focus setting and returns true * if so, or false otherwise. diff --git a/src/mainx.c b/src/mainx.c index a26a957f..3ae04857 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,8 @@ char **start_argv; /* This is our connection to X11 for use with XKB */ Display *xkbdpy; +xcb_key_symbols_t *keysyms; + /* The list of key bindings */ struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings); @@ -109,6 +112,34 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { } } +/* + * When using xmodmap to change the keyboard mapping, this event + * is only sent via XKB. Therefore, we need this special handler. + * + */ +static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { + LOG("got xkb event, yay\n"); + XEvent ev; + /* When using xmodmap, every change (!) gets an own event. + * Therefore, we just read all events and only handle the + * mapping_notify once (we do not receive any other XKB + * events anyway). */ + while (XPending(xkbdpy)) + XNextEvent(xkbdpy, &ev); + + xcb_key_symbols_free(keysyms); + keysyms = xcb_key_symbols_alloc(global_conn); + + xcb_get_numlock_mask(global_conn); + + ungrab_all_keys(global_conn); + LOG("Re-grabbing...\n"); + grab_all_keys(global_conn); + LOG("Done\n"); + +} + + int main(int argc, char *argv[], char *env[]) { int i, screens, opt; char *override_configpath = NULL; @@ -193,6 +224,11 @@ int main(int argc, char *argv[], char *env[]) { return 1; } + if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { + fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); + return 1; + } + int i1; if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) { fprintf(stderr, "XKB not supported by X-server\n"); @@ -200,18 +236,30 @@ int main(int argc, char *argv[], char *env[]) { } /* end of ugliness */ + if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) { + fprintf(stderr, "Could not set XKB event mask\n"); + return 1; + } + /* Initialize event loop using libev */ struct ev_loop *loop = ev_default_loop(0); if (loop == NULL) die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); + struct ev_io *xkb = scalloc(sizeof(struct ev_io)); struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); ev_io_start(loop, xcb_watcher); + ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); + ev_io_start(loop, xkb); + + /* Flush the buffer so that libev can properly get new events */ + XFlush(xkbdpy); + ev_check_init(xcb_check, xcb_check_cb); ev_check_start(loop, xcb_check); @@ -261,6 +309,9 @@ int main(int argc, char *argv[], char *env[]) { * cross virtual screen boundaries doing that) */ xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL); + /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ + xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL); + /* Client message are sent to the root window. The only interesting client message for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */ xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL); @@ -341,6 +392,8 @@ int main(int argc, char *argv[], char *env[]) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3"); + keysyms = xcb_key_symbols_alloc(conn); + xcb_get_numlock_mask(conn); grab_all_keys(conn);