*/
void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload);
+/**
+ * Translates keysymbols to keycodes for all bindings which use keysyms.
+ *
+ */
+void translate_keysyms();
+
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload
* Grab the bound keys (tell X to send us keypress events for those keycodes)
*
*/
-void grab_all_keys(xcb_connection_t *conn);
+void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
/**
* Switches the key bindings to the given mode, if the mode exists
*/
void switch_mode(xcb_connection_t *conn, const char *new_mode);
+/**
+ * Returns a pointer to the Binding with the specified modifiers and keycode
+ * or NULL if no such binding exists.
+ *
+ */
+Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode);
+
/* prototype for src/cfgparse.y */
void parse_file(const char *f);
#include <xcb/randr.h>
-/**
- * Due to bindings like Mode_switch + <a>, we need to bind some keys in
- * XCB_GRAB_MODE_SYNC. Therefore, we just replay all key presses.
- *
- */
-int handle_key_release(void *ignored, xcb_connection_t *conn,
- xcb_key_release_event_t *event);
-
/**
* There was a key press. We compare this key code with our bindings table and
* pass the bound action to parse_command().
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xkbdpy;
+extern int xkb_current_group;
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
extern TAILQ_HEAD(assignments_head, Assignment) assignments;
static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
DLOG("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);
+ /* Grab the key in all combinations */
+ #define GRAB_KEY(modifier) \
+ do { \
+ xcb_grab_key(conn, 0, root, modifier, keycode, \
+ XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
+ } while (0)
+ int mods = bind->mods;
+ if ((bind->mods & BIND_MODE_SWITCH) != 0) {
+ mods &= ~BIND_MODE_SWITCH;
+ if (mods == 0)
+ mods = XCB_MOD_MASK_ANY;
}
+ GRAB_KEY(mods);
+ GRAB_KEY(mods | xcb_numlock_mask);
+ GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
}
/*
- * Grab the bound keys (tell X to send us keypress events for those keycodes)
+ * Returns a pointer to the Binding with the specified modifiers and keycode
+ * or NULL if no such binding exists.
*
*/
-void grab_all_keys(xcb_connection_t *conn) {
+Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) {
Binding *bind;
+
TAILQ_FOREACH(bind, bindings, bindings) {
- /* The easy case: the user specified a keycode directly. */
- if (bind->keycode > 0) {
- grab_keycode_for_binding(conn, bind, bind->keycode);
+ /* First compare the modifiers */
+ if (bind->mods != modifiers)
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),
+ &keycode, sizeof(xcb_keycode_t)) != NULL)
+ break;
+ } else {
+ /* This case is easier: The user specified a keycode */
+ if (bind->keycode == keycode)
+ break;
}
+ }
+
+ return (bind == TAILQ_END(bindings) ? NULL : bind);
+}
+
+/*
+ * Translates keysymbols to keycodes for all bindings which use keysyms.
+ *
+ */
+void translate_keysyms() {
+ Binding *bind;
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ if (bind->keycode > 0)
+ continue;
/* We need to translate the symbol to a keycode */
xcb_keysym_t keysym = XStringToKeysym(bind->symbol);
* and skip them */
if (last_keycode == *walk)
continue;
- grab_keycode_for_binding(conn, bind, *walk);
last_keycode = *walk;
bind->number_keycodes++;
}
}
}
+/*
+ * Grab the bound keys (tell X to send us keypress events for those keycodes)
+ *
+ */
+void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
+ Binding *bind;
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
+ (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
+ continue;
+
+ /* The easy case: the user specified a keycode directly. */
+ if (bind->keycode > 0) {
+ grab_keycode_for_binding(conn, bind, bind->keycode);
+ continue;
+ }
+
+ xcb_keycode_t *walk = bind->translated_to;
+ for (int i = 0; i < bind->number_keycodes; i++)
+ grab_keycode_for_binding(conn, bind, *walk);
+ }
+}
+
/*
* Switches the key bindings to the given mode, if the mode exists
*
ungrab_all_keys(conn);
bindings = mode->bindings;
- grab_all_keys(conn);
+ grab_all_keys(conn, false);
return;
}
parse_configuration(override_configpath);
- if (reload)
- grab_all_keys(conn);
+ if (reload) {
+ translate_keysyms();
+ grab_all_keys(conn, false);
+ }
REQUIRED_OPTION(font);
return false;
}
-/*
- * Due to bindings like Mode_switch + <a>, we need to bind some keys in XCB_GRAB_MODE_SYNC.
- * Therefore, we just replay all key presses.
- *
- */
-int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
- xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time);
- xcb_flush(conn);
- return 1;
-}
-
/*
* There was a key press. We compare this key code with our bindings table and pass
* the bound action to parse_command().
state_filtered &= 0xFF;
DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
- if (xkb_supported) {
- /* We need to get the keysym group (There are group 1 to group 4, each holding
- two keysyms (without shift and with shift) using Xkb because X fails to
- provide them reliably (it works in Xephyr, it does not in real X) */
- XkbStateRec state;
- if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2)
- state_filtered |= BIND_MODE_SWITCH;
- }
+ if (xkb_current_group == XkbGroup2Index)
+ state_filtered |= BIND_MODE_SWITCH;
DLOG("(checked mode_switch, state %d)\n", state_filtered);
/* Find the binding */
- Binding *bind;
- 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;
+ Binding *bind = get_binding(state_filtered, event->detail);
+
+ /* No match? Then the user has Mode_switch enabled but does not have a
+ * specific keybinding. Fall back to the default keybindings (without
+ * Mode_switch). Makes it much more convenient for users of a hybrid
+ * layout (like us, ru). */
+ if (bind == NULL) {
+ state_filtered &= ~(BIND_MODE_SWITCH);
+ DLOG("no match, new state_filtered = %d\n", state_filtered);
+ if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
+ ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
+ state_filtered, event->detail);
+ return 1;
}
}
- /* 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… */
- if (bind == TAILQ_END(bindings)) {
- xcb_allow_events(conn, ReplayKeyboard, event->time);
- xcb_flush(conn);
- return 1;
- }
-
parse_command(conn, bind->command);
- if (state_filtered & BIND_MODE_SWITCH) {
- DLOG("Mode_switch -> allow_events(SyncKeyboard)\n");
- xcb_allow_events(conn, SyncKeyboard, event->time);
- xcb_flush(conn);
- }
return 1;
}
xcb_get_numlock_mask(conn);
ungrab_all_keys(conn);
- grab_all_keys(conn);
+ translate_keysyms();
+ grab_all_keys(conn, false);
return 0;
}
#include "log.h"
#include "sighandler.h"
+static int xkb_event_base;
+
+int xkb_current_group;
+
xcb_connection_t *global_conn;
/* This is the path to i3, copied from argv[0] when starting up */
*/
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
DLOG("Handling XKB event\n");
- XEvent ev;
+ XkbEvent 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);
+ * mapping_notify once. */
+ bool mapping_changed = false;
+ while (XPending(xkbdpy)) {
+ XNextEvent(xkbdpy, (XEvent*)&ev);
+ /* While we should never receive a non-XKB event,
+ * better do sanity checking */
+ if (ev.type != xkb_event_base)
+ continue;
+
+ if (ev.any.xkb_type == XkbMapNotify) {
+ mapping_changed = true;
+ continue;
+ }
+
+ if (ev.any.xkb_type != XkbStateNotify) {
+ ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
+ continue;
+ }
+
+ /* See The XKB Extension: Library Specification, section 14.1 */
+ /* We check if the current group (each group contains
+ * two levels) has been changed. Mode_switch activates
+ * group XkbGroup2Index */
+ if (xkb_current_group == ev.state.group)
+ continue;
+
+ xkb_current_group = ev.state.group;
+ if (ev.state.group == XkbGroup2Index) {
+ DLOG("Mode_switch enabled\n");
+ grab_all_keys(global_conn, true);
+ }
+
+ if (ev.state.group == XkbGroup1Index) {
+ DLOG("Mode_switch disabled\n");
+ ungrab_all_keys(global_conn);
+ grab_all_keys(global_conn, false);
+ }
+ }
+
+ if (!mapping_changed)
+ return;
+
+ DLOG("Keyboard mapping changed, updating keybindings\n");
xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(global_conn);
ungrab_all_keys(global_conn);
DLOG("Re-grabbing...\n");
- grab_all_keys(global_conn);
+ translate_keysyms();
+ grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index));
DLOG("Done\n");
-
}
major = XkbMajorVersion;
minor = XkbMinorVersion;
- int evBase, errBase;
+ int errBase;
- if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
+ if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) {
ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n");
xkb_supported = false;
}
}
int i1;
- if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
+ if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
return 1;
}
/* end of ugliness */
- if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) {
+ if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
+ XkbMapNotifyMask | XkbStateNotifyMask,
+ XkbMapNotifyMask | XkbStateNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
}
/* Expose = an Application should redraw itself, in this case it’s our titlebars. */
xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
- /* Key presses/releases are pretty obvious, I think */
+ /* Key presses are pretty obvious, I think */
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
- xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
/* Enter window = user moved his mouse over the window */
xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
xcb_get_numlock_mask(conn);
- grab_all_keys(conn);
+ translate_keysyms();
+ grab_all_keys(conn, false);
int randr_base;
if (force_xinerama) {