*
*/
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
*
*/
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;
};
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.
#include <xcb/xcb.h>
#include <xcb/xcb_property.h>
#include <xcb/xcb_event.h>
+#include <xcb/xcb_keysyms.h>
#include <X11/XKBlib.h>
#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;
#include <stdlib.h>
#include <glob.h>
+/* We need Xlib for XStringToKeysym */
+#include <X11/Xlib.h>
+
+#include <xcb/xcb_keysyms.h>
+
#include "i3.h"
#include "util.h"
#include "config.h"
}
}
-/*
- * 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);
}
}
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);
}
}
}
/* 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; \
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);
/* 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… */
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.
#include <assert.h>
#include <limits.h>
#include <locale.h>
+#include <fcntl.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
/* 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);
}
}
+/*
+ * 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;
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");
}
/* 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);
* 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);
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);