Use libxkbcommon for translating keysyms, support all XKB groups.

fixes #1835

This commit improves the translation of keysyms to keycodes by loading
keymaps using libxkbcommon-x11 and using libxkbcommon for figuring out
the keymap, depending on each keybinding’s modifiers. This way, the
upper layers of complex layouts are now usable with i3’s bindsym
directive, such as de_neo’s layer 3 and higher.

Furthermore, the commit generalizes the handling of different XKB
groups. We formerly had support only for two separate groups, the
default group 1, and group 2. While Mode_switch is only one way to
switch to group 2, we called the binding option Mode_switch. With this
commit, the new names Group1, Group2 (an alias for Mode_switch), Group3
and Group4 are introduced for configuring bindings. This is only useful
for advanced keyboard layouts, such as people loading two keyboard
layouts and switching between them (us, ru seems to be a popular
combination).

When grabbing keys, one can only specify the modifier mask, but not an
XKB state mask (or value), so we still dynamically unbind and re-bind
keys whenever the XKB group changes.

The commit was manually tested using the following i3 config:

    bindsym Group4+n nop heya from group 4
    bindsym Group3+n nop heya from group 3
    bindsym Group2+n nop heya from group 2
    bindsym n nop heya
    bindsym shift+N nop explicit shift binding
    bindsym shift+r nop implicit shift binding
    bindcode Group2+38 nop fallback overwritten in group 2 only
    bindcode 38 nop fallback

…with the following layout:

    setxkbmap -layout "us,ua,ru,de" -variant ",winkeys,,neo" \
      -option "grp:shift_caps_toggle,grp_led:scroll" \
      -model pc104 -rules evdev

By default (xkb group 1, us layout), pressing “n” will result in the
“heya” message appearing. Pressing “a” will result in the “fallback”
message appearing. “j” is not triggered.

By pressing Shift+CapsLock you switch to the next group (xkb group 2, ua
layout). Pressing “a” will result in the “fallback overwritten in group
2 only” message, pressing “n” will still result in “heya”. “j” is not
triggered.

In the next group (xkb group 3, ru layout), pressing “a” will result in
the “fallback” message again, pressing “n” will result in “heya”,
“j” is not triggered.

In the last group (xkb group 4, de_neo layout), pressing “a” will still
result in “fallback”, pressing “n” will result in “heya”, pressing “j”
will result in “heya from group 4”.

Pressing shift+n results in “explicit shift binding”, pressing shift+r
results in “implicit shift binding”. This ensures that keysym
translation falls back to looking at non-shift keys (“r” can be used
instead of ”R”) and that the order of keybindings doesn’t play a role
(“bindsym n” does not override “bindsym shift+n”, even though it’s
specified earlier in the config).

The fallback behavior ensures use-cases such as ticket #1775 are still
covered.

Only binding keys when the X server is in the corresponding XKB group
ensures use-cases such as ticket #585 are still covered.
next
Michael Stapelberg 2015-08-23 22:49:32 +02:00
parent 1a9a9cc68d
commit bf3cd41b5d
16 changed files with 462 additions and 182 deletions

View File

@ -773,8 +773,8 @@ The +binding (object)+ field contains details about the binding that was run:
command (string)::
The i3 command that is configured to run for this binding.
mods (array of strings)::
The modifier keys that were configured with this binding.
event_state_mask (array of strings)::
The group and modifier keys that were configured with this binding.
input_code (integer)::
If the binding was configured with +bindcode+, this will be the key code
that was given for the binding. If the binding is a mouse binding, it will be
@ -792,7 +792,7 @@ input_type (string)::
"change": "run",
"binding": {
"command": "nop",
"mods": [
"event_state_mask": [
"shift",
"ctrl"
],

View File

@ -368,8 +368,8 @@ after the keys have been released.
*Syntax*:
----------------------------------
bindsym [--release] [<Modifiers>+]<keysym> command
bindcode [--release] [<Modifiers>+]<keycode> command
bindsym [--release] [<Group>+][<Modifiers>+]<keysym> command
bindcode [--release] [<Group>+][<Modifiers>+]<keycode> command
----------------------------------
*Examples*:
@ -395,12 +395,13 @@ Available Modifiers:
Mod1-Mod5, Shift, Control::
Standard modifiers, see +xmodmap(1)+
Mode_switch::
Unlike other window managers, i3 can use Mode_switch as a modifier. This allows
you to remap capslock (for example) to Mode_switch and use it for both: typing
umlauts or special characters 'and' having some comfortably reachable key
bindings. For example, when typing, capslock+1 or capslock+2 for switching
workspaces is totally convenient. Try it :-).
Group1, Group2, Group3, Group4::
When using multiple keyboard layouts (e.g. with `setxkbmap -layout us,ru`), you
can specify in which XKB group (also called “layout”) a keybinding should be
active. By default, keybindings are translated in Group1 and are active in all
groups. If you want to override keybindings in one of your layouts, specify the
corresponding group. For backwards compatibility, the group “Mode_switch” is an
alias for Group2.
[[mousebindings]]

View File

@ -31,7 +31,7 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
* 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);
void grab_all_keys(xcb_connection_t *conn);
/**
* Returns a pointer to the Binding that matches the given xcb event or NULL if
@ -52,6 +52,21 @@ void translate_keysyms(void);
*/
void switch_mode(const char *new_mode);
/**
* Reorders bindings by event_state_mask descendingly so that get_binding()
* correctly matches more specific bindings before more generic bindings. Take
* the following binding configuration as an example:
*
* bindsym n nop lower-case n pressed
* bindsym Shift+n nop upper-case n pressed
*
* Without reordering, the first bindings event_state_mask of 0x0 would match
* the actual event_stat_mask of 0x1 and hence trigger instead of the second
* keybinding.
*
*/
void reorder_bindings(void);
/**
* Checks for duplicate key bindings (the same keycode or keysym is configured
* more than once). If a duplicate binding is found, a message is printed to
@ -74,3 +89,9 @@ void binding_free(Binding *bind);
*
*/
CommandResult *run_binding(Binding *bind, Con *con);
/**
* Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
*
*/
bool load_keymap(void);

View File

@ -12,10 +12,10 @@
#include "config_parser.h"
/**
* A utility function to convert a string of modifiers to the corresponding bit
* mask.
* A utility function to convert a string containing the group and modifiers to
* the corresponding bit mask.
*/
uint32_t modifiers_from_str(const char *str);
i3_event_state_mask_t event_state_from_str(const char *str);
/** The beginning of the prototype for every cfg_ function. */
#define I3_CFG Match *current_match, struct ConfigResultIR *result

View File

@ -74,18 +74,6 @@ typedef enum { ADJ_NONE = 0,
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
enum {
BIND_NONE = 0,
BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */
BIND_CONTROL = XCB_MOD_MASK_CONTROL, /* (1 << 2) */
BIND_MOD1 = XCB_MOD_MASK_1, /* (1 << 3) */
BIND_MOD2 = XCB_MOD_MASK_2, /* (1 << 4) */
BIND_MOD3 = XCB_MOD_MASK_3, /* (1 << 5) */
BIND_MOD4 = XCB_MOD_MASK_4, /* (1 << 6) */
BIND_MOD5 = XCB_MOD_MASK_5, /* (1 << 7) */
BIND_MODE_SWITCH = (1 << 8)
};
/**
* Container layouts. See Con::layout.
*/
@ -107,6 +95,25 @@ typedef enum {
B_MOUSE = 1
} input_type_t;
/**
* Bitmask for matching XCB_XKB_GROUP_1 to XCB_XKB_GROUP_4.
*/
typedef enum {
I3_XKB_GROUP_MASK_ANY = 0,
I3_XKB_GROUP_MASK_1 = (1 << 0),
I3_XKB_GROUP_MASK_2 = (1 << 1),
I3_XKB_GROUP_MASK_3 = (1 << 2),
I3_XKB_GROUP_MASK_4 = (1 << 3)
} i3_xkb_group_mask_t;
/**
* The lower 16 bits contain a xcb_key_but_mask_t, the higher 16 bits contain
* an i3_xkb_group_mask_t. This type is necessary for the fallback logic to
* work when handling XKB groups (see ticket #1775) and makes the code which
* locates keybindings upon KeyPress/KeyRelease events simpler.
*/
typedef uint32_t i3_event_state_mask_t;
/**
* Mouse pointer warping modes.
*/
@ -269,8 +276,10 @@ struct Binding {
/** Keycode to bind */
uint32_t keycode;
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
uint32_t mods;
/** Bitmask which is applied against event->state for KeyPress and
* KeyRelease events to determine whether this binding applies to the
* current state. */
i3_event_state_mask_t event_state_mask;
/** 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

View File

@ -316,7 +316,7 @@ state BINDING:
->
whole_window = '--whole-window'
->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
->
'+'
->
@ -369,7 +369,7 @@ state MODE_BINDING:
->
whole_window = '--whole-window'
->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
->
'+'
->

View File

@ -9,6 +9,10 @@
#include "all.h"
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
static struct xkb_context *xkb_context;
static struct xkb_keymap *xkb_keymap;
pid_t command_error_nagbar_pid = -1;
@ -73,8 +77,19 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
return NULL;
}
}
new_binding->mods = modifiers_from_str(modifiers);
new_binding->command = sstrdup(command);
new_binding->event_state_mask = event_state_from_str(modifiers);
int group_bits_set = 0;
if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_1)
group_bits_set++;
if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_2)
group_bits_set++;
if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_3)
group_bits_set++;
if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_4)
group_bits_set++;
if (group_bits_set > 1)
ELOG("Keybinding has more than one Group specified, but your X server is always in precisely one group. The keybinding can never trigger.\n");
struct Mode *mode = mode_from_name(modename);
TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings);
@ -86,18 +101,23 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
if (bind->input_type != B_KEYBOARD)
return;
DLOG("Grabbing %d with modifiers %d (with mod_mask_lock %d)\n", keycode, bind->mods, bind->mods | 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;
}
int mods = bind->event_state_mask;
if (((mods >> 16) & I3_XKB_GROUP_MASK_1) && xkb_current_group != XCB_XKB_GROUP_1)
return;
if (((mods >> 16) & I3_XKB_GROUP_MASK_2) && xkb_current_group != XCB_XKB_GROUP_2)
return;
if (((mods >> 16) & I3_XKB_GROUP_MASK_3) && xkb_current_group != XCB_XKB_GROUP_3)
return;
if (((mods >> 16) & I3_XKB_GROUP_MASK_4) && xkb_current_group != XCB_XKB_GROUP_4)
return;
mods &= 0xFFFF;
DLOG("Grabbing keycode %d with event state mask 0x%x (mods 0x%x)\n",
keycode, bind->event_state_mask, mods);
GRAB_KEY(mods);
GRAB_KEY(mods | xcb_numlock_mask);
GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
@ -108,7 +128,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
* 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) {
void grab_all_keys(xcb_connection_t *conn) {
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
if (bind->input_type != B_KEYBOARD)
@ -120,9 +140,8 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
continue;
}
xcb_keycode_t *walk = bind->translated_to;
for (uint32_t i = 0; i < bind->number_keycodes; i++)
grab_keycode_for_binding(conn, bind, *walk++);
grab_keycode_for_binding(conn, bind, bind->translated_to[i]);
}
}
@ -131,7 +150,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
* keycode or NULL if no such binding exists.
*
*/
static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_code, input_type_t input_type) {
static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_release, uint16_t input_code, input_type_t input_type) {
Binding *bind;
if (!is_release) {
@ -146,12 +165,15 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_
}
TAILQ_FOREACH(bind, bindings, bindings) {
/* First compare the modifiers (unless this is a
DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n",
bind->event_state_mask, state_filtered,
((state_filtered & bind->event_state_mask) == bind->event_state_mask) ? "yes" : "no");
/* First compare the state_filtered (unless this is a
* B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
* event) */
if (bind->input_type != input_type)
continue;
if (bind->mods != modifiers &&
if ((state_filtered & bind->event_state_mask) != bind->event_state_mask &&
(bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
!is_release))
continue;
@ -195,49 +217,89 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_
*
*/
Binding *get_binding_from_xcb_event(xcb_generic_event_t *event) {
bool is_release = (event->response_type == XCB_KEY_RELEASE || event->response_type == XCB_BUTTON_RELEASE);
const bool is_release = (event->response_type == XCB_KEY_RELEASE ||
event->response_type == XCB_BUTTON_RELEASE);
input_type_t input_type = ((event->response_type == XCB_BUTTON_RELEASE || event->response_type == XCB_BUTTON_PRESS)
? B_MOUSE
: B_KEYBOARD);
const input_type_t input_type = ((event->response_type == XCB_BUTTON_RELEASE ||
event->response_type == XCB_BUTTON_PRESS)
? B_MOUSE
: B_KEYBOARD);
uint16_t event_state = ((xcb_key_press_event_t *)event)->state;
uint16_t event_detail = ((xcb_key_press_event_t *)event)->detail;
const uint16_t event_state = ((xcb_key_press_event_t *)event)->state;
const uint16_t event_detail = ((xcb_key_press_event_t *)event)->detail;
/* Remove the numlock bit, all other bits are modifiers we can bind to */
uint16_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
DLOG("(removed numlock, state = %d)\n", state_filtered);
/* Only use the lower 8 bits of the state (modifier masks) so that mouse
* button masks are filtered out */
state_filtered &= 0xFF;
DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
if (xkb_current_group == XCB_XKB_GROUP_2)
state_filtered |= BIND_MODE_SWITCH;
DLOG("(checked mode_switch, state %d)\n", state_filtered);
/* Find the binding */
Binding *bind = get_binding(state_filtered, is_release, event_detail, input_type);
/* 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} or {dvorak, us}, see e.g. ticket #1775). */
if (bind == NULL) {
state_filtered &= ~(BIND_MODE_SWITCH);
DLOG("no match, new state_filtered = %d\n", state_filtered);
if ((bind = get_binding(state_filtered, is_release, event_detail, input_type)) == NULL) {
/* This is not a real error since we can have release and
* non-release bindings. On a press event for which there is only a
* !release-binding, but no release-binding, the corresponding
* release event will trigger this. No problem, though. */
DLOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
state_filtered, event_detail);
}
/* Remove the numlock bit */
i3_event_state_mask_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
DLOG("(removed numlock, state = 0x%x)\n", state_filtered);
/* Transform the keyboard_group from bit 13 and bit 14 into an
* i3_xkb_group_mask_t, so that get_binding() can just bitwise AND the
* configured bindings against |state_filtered|.
*
* These bits are only set because we set the XKB client flags
* XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
* XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED. See also doc/kbproto
* section 2.2.2:
* http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Computing_A_State_Field_from_an_XKB_State */
switch ((event_state & 0x6000) >> 13) {
case XCB_XKB_GROUP_1:
state_filtered |= (I3_XKB_GROUP_MASK_1 << 16);
break;
case XCB_XKB_GROUP_2:
state_filtered |= (I3_XKB_GROUP_MASK_2 << 16);
break;
case XCB_XKB_GROUP_3:
state_filtered |= (I3_XKB_GROUP_MASK_3 << 16);
break;
case XCB_XKB_GROUP_4:
state_filtered |= (I3_XKB_GROUP_MASK_4 << 16);
break;
}
state_filtered &= ~0x6000;
DLOG("(transformed keyboard group, state = 0x%x)\n", state_filtered);
return get_binding(state_filtered, is_release, event_detail, input_type);
}
return bind;
struct resolve {
/* The binding which we are resolving. */
Binding *bind;
/* |bind|s keysym (translated to xkb_keysym_t), e.g. XKB_KEY_R. */
xkb_keysym_t keysym;
/* The xkb state built from the user-provided modifiers and group. */
struct xkb_state *xkb_state;
/* Like |xkb_state|, just without the shift modifier, if shift was specified. */
struct xkb_state *xkb_state_no_shift;
};
/*
* add_keycode_if_matches is called for each keycode in the keymap and will add
* the keycode to |data->bind| if the keycode can result in the keysym
* |data->resolving|.
*
*/
static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) {
const struct resolve *resolving = data;
xkb_keysym_t sym = xkb_state_key_get_one_sym(resolving->xkb_state, key);
if (sym != resolving->keysym) {
/* Check if Shift was specified, and try resolving the symbol without
* shift, so that bindsym $mod+Shift+a nop actually works. */
const xkb_layout_index_t layout = xkb_state_key_get_layout(resolving->xkb_state, key);
if (layout == XKB_LAYOUT_INVALID)
return;
if (xkb_state_key_get_level(resolving->xkb_state, key, layout) != 1)
return;
sym = xkb_state_key_get_one_sym(resolving->xkb_state_no_shift, key);
if (sym != resolving->keysym)
return;
}
Binding *bind = resolving->bind;
bind->number_keycodes++;
bind->translated_to = srealloc(bind->translated_to,
(sizeof(xcb_keycode_t) *
bind->number_keycodes));
bind->translated_to[bind->number_keycodes - 1] = key;
}
/*
@ -245,16 +307,19 @@ Binding *get_binding_from_xcb_event(xcb_generic_event_t *event) {
*
*/
void translate_keysyms(void) {
struct xkb_state *dummy_state = xkb_state_new(xkb_keymap);
if (dummy_state == NULL) {
ELOG("Could not create XKB state, cannot translate keysyms.\n");
return;
}
struct xkb_state *dummy_state_no_shift = xkb_state_new(xkb_keymap);
if (dummy_state_no_shift == NULL) {
ELOG("Could not create XKB state, cannot translate keysyms.\n");
return;
}
Binding *bind;
xcb_keysym_t keysym;
int col;
xcb_keycode_t i, min_keycode, max_keycode;
const bool mode_switch = (xkb_current_group == XCB_XKB_GROUP_2);
min_keycode = xcb_get_setup(conn)->min_keycode;
max_keycode = xcb_get_setup(conn)->max_keycode;
TAILQ_FOREACH(bind, bindings, bindings) {
if (bind->input_type == B_MOUSE) {
char *endptr;
@ -271,36 +336,66 @@ void translate_keysyms(void) {
continue;
/* We need to translate the symbol to a keycode */
keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
if (keysym == XKB_KEY_NoSymbol) {
ELOG("Could not translate string to key symbol: \"%s\"\n",
bind->symbol);
continue;
}
/* Base column we use for looking up key symbols. We always consider
* the base column and the corresponding shift column, so without
* mode_switch, we look in 0 and 1, with mode_switch we look in 2 and
* 3. */
col = (bind->mods & BIND_MODE_SWITCH || mode_switch ? 2 : 0);
xkb_layout_index_t group = XCB_XKB_GROUP_1;
if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2)
group = XCB_XKB_GROUP_2;
else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3)
group = XCB_XKB_GROUP_3;
else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4)
group = XCB_XKB_GROUP_4;
DLOG("group = %d, event_state_mask = %d, &2 = %s, &3 = %s, &4 = %s\n", group,
bind->event_state_mask,
(bind->event_state_mask & I3_XKB_GROUP_MASK_2) ? "yes" : "no",
(bind->event_state_mask & I3_XKB_GROUP_MASK_3) ? "yes" : "no",
(bind->event_state_mask & I3_XKB_GROUP_MASK_4) ? "yes" : "no");
(void)xkb_state_update_mask(
dummy_state,
(bind->event_state_mask & 0x1FFF) /* xkb_mod_mask_t base_mods, */,
0 /* xkb_mod_mask_t latched_mods, */,
0 /* xkb_mod_mask_t locked_mods, */,
0 /* xkb_layout_index_t base_group, */,
0 /* xkb_layout_index_t latched_group, */,
group /* xkb_layout_index_t locked_group, */);
(void)xkb_state_update_mask(
dummy_state_no_shift,
(bind->event_state_mask & 0x1FFF) & ~XCB_KEY_BUT_MASK_SHIFT /* xkb_mod_mask_t base_mods, */,
0 /* xkb_mod_mask_t latched_mods, */,
0 /* xkb_mod_mask_t locked_mods, */,
0 /* xkb_layout_index_t base_group, */,
0 /* xkb_layout_index_t latched_group, */,
group /* xkb_layout_index_t locked_group, */);
struct resolve resolving = {
.bind = bind,
.keysym = keysym,
.xkb_state = dummy_state,
.xkb_state_no_shift = dummy_state_no_shift,
};
FREE(bind->translated_to);
bind->number_keycodes = 0;
for (i = min_keycode; i && i <= max_keycode; i++) {
if ((xcb_key_symbols_get_keysym(keysyms, i, col) != keysym) &&
(xcb_key_symbols_get_keysym(keysyms, i, col + 1) != keysym))
continue;
bind->number_keycodes++;
bind->translated_to = srealloc(bind->translated_to,
(sizeof(xcb_keycode_t) *
bind->number_keycodes));
bind->translated_to[bind->number_keycodes - 1] = i;
xkb_keymap_key_for_each(xkb_keymap, add_keycode_if_matches, &resolving);
char *keycodes = sstrdup("");
for (uint32_t n = 0; n < bind->number_keycodes; n++) {
char *tmp;
sasprintf(&tmp, "%s %d", keycodes, bind->translated_to[n]);
free(keycodes);
keycodes = tmp;
}
DLOG("Translated symbol \"%s\" to %d keycode (mods %d)\n", bind->symbol,
bind->number_keycodes, bind->mods);
DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
bind->event_state_mask, bind->symbol, keysym, keycodes, bind->number_keycodes);
free(keycodes);
}
xkb_state_unref(dummy_state);
}
/*
@ -319,7 +414,7 @@ void switch_mode(const char *new_mode) {
ungrab_all_keys(conn);
bindings = mode->bindings;
translate_keysyms();
grab_all_keys(conn, false);
grab_all_keys(conn);
char *event_msg;
sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
@ -333,6 +428,61 @@ void switch_mode(const char *new_mode) {
ELOG("ERROR: Mode not found\n");
}
static void reorder_bindings_of_mode(struct Mode *mode) {
struct bindings_head *reordered = scalloc(1, sizeof(struct bindings_head));
TAILQ_INIT(reordered);
/* 20 bits are in use in an i3_event_state_mask_t. */
for (int n = 19; n >= 0; n--) {
Binding *current;
for (current = TAILQ_FIRST(mode->bindings); current != TAILQ_END(mode->bindings); ) {
/* Advance |current| so that we can use TAILQ_REMOVE safely. */
Binding *bind = current;
current = TAILQ_NEXT(current, bindings);
if ((bind->event_state_mask & (1 << n)) == 0)
continue;
TAILQ_REMOVE(mode->bindings, bind, bindings);
TAILQ_INSERT_TAIL(reordered, bind, bindings);
}
}
/* Move over the bindings with event_state_mask == 0x0. */
Binding *current;
for (current = TAILQ_FIRST(mode->bindings); current != TAILQ_END(mode->bindings); ) {
/* Advance |current| so that we can use TAILQ_REMOVE safely. */
Binding *bind = current;
current = TAILQ_NEXT(current, bindings);
assert(bind->event_state_mask == 0);
TAILQ_REMOVE(mode->bindings, bind, bindings);
TAILQ_INSERT_TAIL(reordered, bind, bindings);
}
assert(TAILQ_EMPTY(mode->bindings));
/* Free the old bindings_head, which is now empty. */
free(mode->bindings);
mode->bindings = reordered;
}
/*
* Reorders bindings by event_state_mask descendingly so that get_binding()
* correctly matches more specific bindings before more generic bindings. Take
* the following binding configuration as an example:
*
* bindsym n nop lower-case n pressed
* bindsym Shift+n nop upper-case n pressed
*
* Without reordering, the first bindings event_state_mask of 0x0 would match
* the actual event_stat_mask of 0x1 and hence trigger instead of the second
* keybinding.
*
*/
void reorder_bindings(void) {
struct Mode *mode;
SLIST_FOREACH(mode, &modes, modes) {
const bool current_mode = (mode->bindings == bindings);
reorder_bindings_of_mode(mode);
if (current_mode)
bindings = mode->bindings;
}
}
/*
* Checks for duplicate key bindings (the same keycode or keysym is configured
* more than once). If a duplicate binding is found, a message is printed to
@ -370,17 +520,17 @@ void check_for_duplicate_bindings(struct context *context) {
/* Check if the keycodes or modifiers are different. If so, they
* can't be duplicate */
if (bind->keycode != current->keycode ||
bind->mods != current->mods ||
bind->event_state_mask != current->event_state_mask ||
bind->release != current->release)
continue;
context->has_errors = true;
if (current->keycode != 0) {
ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
current->mods, current->keycode, current->command);
ELOG("Duplicate keybinding in config file:\n state mask 0x%x with keycode %d, command \"%s\"\n",
current->event_state_mask, current->keycode, current->command);
} else {
ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
current->mods, current->symbol, current->command);
ELOG("Duplicate keybinding in config file:\n state mask 0x%x with keysym %s, command \"%s\"\n",
current->event_state_mask, current->symbol, current->command);
}
}
}
@ -466,3 +616,28 @@ CommandResult *run_binding(Binding *bind, Con *con) {
return result;
}
/*
* Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
*
*/
bool load_keymap(void) {
if (xkb_context == NULL) {
if ((xkb_context = xkb_context_new(0)) == NULL) {
ELOG("Could not create xkbcommon context\n");
return false;
}
}
struct xkb_keymap *new_keymap;
const int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
DLOG("device_id = %d\n", device_id);
if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
ELOG("xkb_x11_keymap_new_from_device failed\n");
return false;
}
xkb_keymap_unref(xkb_keymap);
xkb_keymap = new_keymap;
return true;
}

View File

@ -229,7 +229,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
/* get the floating con */
Con *floatingcon = con_inside_floating(con);
const bool proportional = (event->state & BIND_SHIFT);
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */

View File

@ -214,7 +214,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
if (reload) {
translate_keysyms();
grab_all_keys(conn, false);
grab_all_keys(conn);
}
if (config.font.type == FONT_TYPE_NONE) {

View File

@ -161,32 +161,40 @@ static bool eval_boolstr(const char *str) {
}
/*
* A utility function to convert a string of modifiers to the corresponding bit
* mask.
* A utility function to convert a string containing the group and modifiers to
* the corresponding bit mask.
*/
uint32_t modifiers_from_str(const char *str) {
i3_event_state_mask_t event_state_from_str(const char *str) {
/* It might be better to use strtok() here, but the simpler strstr() should
* do for now. */
uint32_t result = 0;
i3_event_state_mask_t result = 0;
if (str == NULL)
return result;
if (strstr(str, "Mod1") != NULL)
result |= BIND_MOD1;
result |= XCB_KEY_BUT_MASK_MOD_1;
if (strstr(str, "Mod2") != NULL)
result |= BIND_MOD2;
result |= XCB_KEY_BUT_MASK_MOD_2;
if (strstr(str, "Mod3") != NULL)
result |= BIND_MOD3;
result |= XCB_KEY_BUT_MASK_MOD_3;
if (strstr(str, "Mod4") != NULL)
result |= BIND_MOD4;
result |= XCB_KEY_BUT_MASK_MOD_4;
if (strstr(str, "Mod5") != NULL)
result |= BIND_MOD5;
result |= XCB_KEY_BUT_MASK_MOD_5;
if (strstr(str, "Control") != NULL ||
strstr(str, "Ctrl") != NULL)
result |= BIND_CONTROL;
result |= XCB_KEY_BUT_MASK_CONTROL;
if (strstr(str, "Shift") != NULL)
result |= BIND_SHIFT;
if (strstr(str, "Mode_switch") != NULL)
result |= BIND_MODE_SWITCH;
result |= XCB_KEY_BUT_MASK_SHIFT;
if (strstr(str, "Group1") != NULL)
result |= (I3_XKB_GROUP_MASK_1 << 16);
if (strstr(str, "Group2") != NULL ||
strstr(str, "Mode_switch") != NULL)
result |= (I3_XKB_GROUP_MASK_2 << 16);
if (strstr(str, "Group3") != NULL)
result |= (I3_XKB_GROUP_MASK_3 << 16);
if (strstr(str, "Group4") != NULL)
result |= (I3_XKB_GROUP_MASK_4 << 16);
return result;
}
@ -260,7 +268,7 @@ CFGFUN(floating_maximum_size, const long width, const long height) {
}
CFGFUN(floating_modifier, const char *modifiers) {
config.floating_modifier = modifiers_from_str(modifiers);
config.floating_modifier = event_state_from_str(modifiers);
}
CFGFUN(default_orientation, const char *orientation) {

View File

@ -997,6 +997,7 @@ bool parse_file(const char *f, bool use_nagbar) {
yajl_gen_free(config_output->json_gen);
check_for_duplicate_bindings(context);
reorder_bindings();
if (use_nagbar && (context->has_errors || context->has_warnings)) {
ELOG("FYI: You are using i3 version %s\n", i3_version);

View File

@ -16,7 +16,6 @@
#include <float.h>
#include <sys/time.h>
#include <xcb/randr.h>
#include <X11/XKBlib.h>
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-monitor.h>
@ -264,7 +263,7 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
ungrab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
grab_all_keys(conn);
return;
}
@ -1338,7 +1337,9 @@ void handle_event(int type, xcb_generic_event_t *event) {
keysyms = xcb_key_symbols_alloc(conn);
ungrab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
grab_all_keys(conn);
if (((xcb_xkb_new_keyboard_notify_event_t *)event)->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
(void)load_keymap();
} else if (state->xkbType == XCB_XKB_MAP_NOTIFY) {
if (event_is_ignored(event->sequence, type)) {
DLOG("Ignoring map notify event for sequence %d.\n", state->sequence);
@ -1349,22 +1350,16 @@ void handle_event(int type, xcb_generic_event_t *event) {
keysyms = xcb_key_symbols_alloc(conn);
ungrab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
grab_all_keys(conn);
(void)load_keymap();
}
} else if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
DLOG("xkb state group = %d\n", state->group);
/* 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 XCB_XKB_GROUP_2 */
if (xkb_current_group == state->group)
return;
xkb_current_group = state->group;
DLOG("Mode_switch %s\n", (xkb_current_group == XCB_XKB_GROUP_1 ? "disabled" : "enabled"));
ungrab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, (xkb_current_group == XCB_XKB_GROUP_2));
grab_all_keys(conn);
}
return;

View File

@ -5,8 +5,8 @@ CLEAN_TARGETS += clean-i3
i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
i3_CFLAGS = $(XKB_COMMON_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS)
i3_LIBS = $(XKB_COMMON_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread
i3_CFLAGS = $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS)
i3_LIBS = $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread
# When using clang, we use pre-compiled headers to speed up the build. With
# gcc, this actually makes the build slower.

100
src/ipc.c
View File

@ -121,6 +121,68 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
y(map_close);
}
static void dump_event_state_mask(yajl_gen gen, Binding *bind) {
y(array_open);
for (int i = 0; i < 20; i++) {
if (bind->event_state_mask & (1 << i)) {
switch (1 << i) {
case XCB_KEY_BUT_MASK_SHIFT:
ystr("shift");
break;
case XCB_KEY_BUT_MASK_LOCK:
ystr("lock");
break;
case XCB_KEY_BUT_MASK_CONTROL:
ystr("ctrl");
break;
case XCB_KEY_BUT_MASK_MOD_1:
ystr("Mod1");
break;
case XCB_KEY_BUT_MASK_MOD_2:
ystr("Mod2");
break;
case XCB_KEY_BUT_MASK_MOD_3:
ystr("Mod3");
break;
case XCB_KEY_BUT_MASK_MOD_4:
ystr("Mod4");
break;
case XCB_KEY_BUT_MASK_MOD_5:
ystr("Mod5");
break;
case XCB_KEY_BUT_MASK_BUTTON_1:
ystr("Button1");
break;
case XCB_KEY_BUT_MASK_BUTTON_2:
ystr("Button2");
break;
case XCB_KEY_BUT_MASK_BUTTON_3:
ystr("Button3");
break;
case XCB_KEY_BUT_MASK_BUTTON_4:
ystr("Button4");
break;
case XCB_KEY_BUT_MASK_BUTTON_5:
ystr("Button5");
break;
case (I3_XKB_GROUP_MASK_1 << 16):
ystr("Group1");
break;
case (I3_XKB_GROUP_MASK_2 << 16):
ystr("Group2");
break;
case (I3_XKB_GROUP_MASK_3 << 16):
ystr("Group3");
break;
case (I3_XKB_GROUP_MASK_4 << 16):
ystr("Group4");
break;
}
}
}
y(array_close);
}
static void dump_binding(yajl_gen gen, Binding *bind) {
y(map_open);
ystr("input_code");
@ -138,39 +200,13 @@ static void dump_binding(yajl_gen gen, Binding *bind) {
ystr("command");
ystr(bind->command);
// This key is only provided for compatibility, new programs should use
// event_state_mask instead.
ystr("mods");
y(array_open);
for (int i = 0; i < 8; i++) {
if (bind->mods & (1 << i)) {
switch (1 << i) {
case XCB_MOD_MASK_SHIFT:
ystr("shift");
break;
case XCB_MOD_MASK_LOCK:
ystr("lock");
break;
case XCB_MOD_MASK_CONTROL:
ystr("ctrl");
break;
case XCB_MOD_MASK_1:
ystr("Mod1");
break;
case XCB_MOD_MASK_2:
ystr("Mod2");
break;
case XCB_MOD_MASK_3:
ystr("Mod3");
break;
case XCB_MOD_MASK_4:
ystr("Mod4");
break;
case XCB_MOD_MASK_5:
ystr("Mod5");
break;
}
}
}
y(array_close);
dump_event_state_mask(gen, bind);
ystr("event_state_mask");
dump_event_state_mask(gen, bind);
y(map_close);
}

View File

@ -18,11 +18,11 @@
*
*/
void handle_key_press(xcb_key_press_event_t *event) {
bool key_release = (event->response_type == XCB_KEY_RELEASE);
const bool key_release = (event->response_type == XCB_KEY_RELEASE);
last_timestamp = event->time;
DLOG("%s %d, state raw = %d\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state);
DLOG("%s %d, state raw = 0x%x\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state);
Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);

View File

@ -556,6 +556,37 @@ int main(int argc, char *argv[]) {
0xff,
0xff,
NULL);
/* Setting both, XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
* XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, will lead to the
* X server sending us the full XKB state in KeyPress and KeyRelease:
* https://sources.debian.net/src/xorg-server/2:1.17.2-1.1/xkb/xkbEvents.c/?hl=927#L927
*/
xcb_xkb_per_client_flags_reply_t *pcf_reply;
/* The last three parameters are unset because they are only relevant
* when using a feature called automatic reset of boolean controls:
* http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Automatic_Reset_of_Boolean_Controls
* */
pcf_reply = xcb_xkb_per_client_flags_reply(
conn,
xcb_xkb_per_client_flags(
conn,
XCB_XKB_ID_USE_CORE_KBD,
XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
0 /* uint32_t ctrlsToChange */,
0 /* uint32_t autoCtrls */,
0 /* uint32_t autoCtrlsValues */),
NULL);
if (pcf_reply == NULL ||
!(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE)) {
ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE\n");
}
if (pcf_reply == NULL ||
!(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED)) {
ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED\n");
}
free(pcf_reply);
xkb_base = extreply->first_event;
}
@ -569,8 +600,11 @@ int main(int argc, char *argv[]) {
xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
if (!load_keymap())
die("Could not load keymap\n");
translate_keysyms();
grab_all_keys(conn, false);
grab_all_keys(conn);
bool needs_tree_init = true;
if (layout_path) {