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.
This commit is contained in:
parent
1a9a9cc68d
commit
bf3cd41b5d
6
docs/ipc
6
docs/ipc
|
@ -773,8 +773,8 @@ The +binding (object)+ field contains details about the binding that was run:
|
||||||
|
|
||||||
command (string)::
|
command (string)::
|
||||||
The i3 command that is configured to run for this binding.
|
The i3 command that is configured to run for this binding.
|
||||||
mods (array of strings)::
|
event_state_mask (array of strings)::
|
||||||
The modifier keys that were configured with this binding.
|
The group and modifier keys that were configured with this binding.
|
||||||
input_code (integer)::
|
input_code (integer)::
|
||||||
If the binding was configured with +bindcode+, this will be the key code
|
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
|
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",
|
"change": "run",
|
||||||
"binding": {
|
"binding": {
|
||||||
"command": "nop",
|
"command": "nop",
|
||||||
"mods": [
|
"event_state_mask": [
|
||||||
"shift",
|
"shift",
|
||||||
"ctrl"
|
"ctrl"
|
||||||
],
|
],
|
||||||
|
|
|
@ -368,8 +368,8 @@ after the keys have been released.
|
||||||
|
|
||||||
*Syntax*:
|
*Syntax*:
|
||||||
----------------------------------
|
----------------------------------
|
||||||
bindsym [--release] [<Modifiers>+]<keysym> command
|
bindsym [--release] [<Group>+][<Modifiers>+]<keysym> command
|
||||||
bindcode [--release] [<Modifiers>+]<keycode> command
|
bindcode [--release] [<Group>+][<Modifiers>+]<keycode> command
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
*Examples*:
|
*Examples*:
|
||||||
|
@ -395,12 +395,13 @@ Available Modifiers:
|
||||||
Mod1-Mod5, Shift, Control::
|
Mod1-Mod5, Shift, Control::
|
||||||
Standard modifiers, see +xmodmap(1)+
|
Standard modifiers, see +xmodmap(1)+
|
||||||
|
|
||||||
Mode_switch::
|
Group1, Group2, Group3, Group4::
|
||||||
Unlike other window managers, i3 can use Mode_switch as a modifier. This allows
|
When using multiple keyboard layouts (e.g. with `setxkbmap -layout us,ru`), you
|
||||||
you to remap capslock (for example) to Mode_switch and use it for both: typing
|
can specify in which XKB group (also called “layout”) a keybinding should be
|
||||||
umlauts or special characters 'and' having some comfortably reachable key
|
active. By default, keybindings are translated in Group1 and are active in all
|
||||||
bindings. For example, when typing, capslock+1 or capslock+2 for switching
|
groups. If you want to override keybindings in one of your layouts, specify the
|
||||||
workspaces is totally convenient. Try it :-).
|
corresponding group. For backwards compatibility, the group “Mode_switch” is an
|
||||||
|
alias for Group2.
|
||||||
|
|
||||||
[[mousebindings]]
|
[[mousebindings]]
|
||||||
|
|
||||||
|
|
|
@ -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)
|
* 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
|
* 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);
|
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 binding’s 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
|
* 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
|
* 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);
|
CommandResult *run_binding(Binding *bind, Con *con);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool load_keymap(void);
|
||||||
|
|
|
@ -12,10 +12,10 @@
|
||||||
#include "config_parser.h"
|
#include "config_parser.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility function to convert a string of modifiers to the corresponding bit
|
* A utility function to convert a string containing the group and modifiers to
|
||||||
* mask.
|
* 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. */
|
/** The beginning of the prototype for every cfg_ function. */
|
||||||
#define I3_CFG Match *current_match, struct ConfigResultIR *result
|
#define I3_CFG Match *current_match, struct ConfigResultIR *result
|
||||||
|
|
|
@ -74,18 +74,6 @@ typedef enum { ADJ_NONE = 0,
|
||||||
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
|
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
|
||||||
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
|
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.
|
* Container layouts. See Con::layout.
|
||||||
*/
|
*/
|
||||||
|
@ -107,6 +95,25 @@ typedef enum {
|
||||||
B_MOUSE = 1
|
B_MOUSE = 1
|
||||||
} input_type_t;
|
} 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.
|
* Mouse pointer warping modes.
|
||||||
*/
|
*/
|
||||||
|
@ -269,8 +276,10 @@ struct Binding {
|
||||||
/** Keycode to bind */
|
/** Keycode to bind */
|
||||||
uint32_t keycode;
|
uint32_t keycode;
|
||||||
|
|
||||||
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
|
/** Bitmask which is applied against event->state for KeyPress and
|
||||||
uint32_t mods;
|
* 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
|
/** 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
|
* stored with the binding to be able to re-convert it into a keycode
|
||||||
|
|
|
@ -316,7 +316,7 @@ state BINDING:
|
||||||
->
|
->
|
||||||
whole_window = '--whole-window'
|
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'
|
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'
|
||||||
->
|
->
|
||||||
'+'
|
'+'
|
||||||
->
|
->
|
||||||
|
|
343
src/bindings.c
343
src/bindings.c
|
@ -9,6 +9,10 @@
|
||||||
#include "all.h"
|
#include "all.h"
|
||||||
|
|
||||||
#include <xkbcommon/xkbcommon.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;
|
pid_t command_error_nagbar_pid = -1;
|
||||||
|
|
||||||
|
@ -73,8 +77,19 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_binding->mods = modifiers_from_str(modifiers);
|
|
||||||
new_binding->command = sstrdup(command);
|
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);
|
struct Mode *mode = mode_from_name(modename);
|
||||||
TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings);
|
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)
|
if (bind->input_type != B_KEYBOARD)
|
||||||
return;
|
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 */
|
/* Grab the key in all combinations */
|
||||||
#define GRAB_KEY(modifier) \
|
#define GRAB_KEY(modifier) \
|
||||||
do { \
|
do { \
|
||||||
xcb_grab_key(conn, 0, root, modifier, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
|
xcb_grab_key(conn, 0, root, modifier, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
|
||||||
} while (0)
|
} while (0)
|
||||||
int mods = bind->mods;
|
int mods = bind->event_state_mask;
|
||||||
if ((bind->mods & BIND_MODE_SWITCH) != 0) {
|
if (((mods >> 16) & I3_XKB_GROUP_MASK_1) && xkb_current_group != XCB_XKB_GROUP_1)
|
||||||
mods &= ~BIND_MODE_SWITCH;
|
return;
|
||||||
if (mods == 0)
|
if (((mods >> 16) & I3_XKB_GROUP_MASK_2) && xkb_current_group != XCB_XKB_GROUP_2)
|
||||||
mods = XCB_MOD_MASK_ANY;
|
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);
|
||||||
GRAB_KEY(mods | xcb_numlock_mask);
|
GRAB_KEY(mods | xcb_numlock_mask);
|
||||||
GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
|
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)
|
* 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;
|
Binding *bind;
|
||||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||||
if (bind->input_type != B_KEYBOARD)
|
if (bind->input_type != B_KEYBOARD)
|
||||||
|
@ -120,9 +140,8 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_keycode_t *walk = bind->translated_to;
|
|
||||||
for (uint32_t i = 0; i < bind->number_keycodes; i++)
|
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.
|
* 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;
|
Binding *bind;
|
||||||
|
|
||||||
if (!is_release) {
|
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) {
|
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
|
* B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
|
||||||
* event) */
|
* event) */
|
||||||
if (bind->input_type != input_type)
|
if (bind->input_type != input_type)
|
||||||
continue;
|
continue;
|
||||||
if (bind->mods != modifiers &&
|
if ((state_filtered & bind->event_state_mask) != bind->event_state_mask &&
|
||||||
(bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
|
(bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
|
||||||
!is_release))
|
!is_release))
|
||||||
continue;
|
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) {
|
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)
|
const input_type_t input_type = ((event->response_type == XCB_BUTTON_RELEASE ||
|
||||||
|
event->response_type == XCB_BUTTON_PRESS)
|
||||||
? B_MOUSE
|
? B_MOUSE
|
||||||
: B_KEYBOARD);
|
: B_KEYBOARD);
|
||||||
|
|
||||||
uint16_t event_state = ((xcb_key_press_event_t *)event)->state;
|
const 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_detail = ((xcb_key_press_event_t *)event)->detail;
|
||||||
|
|
||||||
/* Remove the numlock bit, all other bits are modifiers we can bind to */
|
/* Remove the numlock bit */
|
||||||
uint16_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
|
i3_event_state_mask_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
|
||||||
DLOG("(removed numlock, state = %d)\n", state_filtered);
|
DLOG("(removed numlock, state = 0x%x)\n", state_filtered);
|
||||||
/* Only use the lower 8 bits of the state (modifier masks) so that mouse
|
/* Transform the keyboard_group from bit 13 and bit 14 into an
|
||||||
* button masks are filtered out */
|
* i3_xkb_group_mask_t, so that get_binding() can just bitwise AND the
|
||||||
state_filtered &= 0xFF;
|
* configured bindings against |state_filtered|.
|
||||||
DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
|
*
|
||||||
|
* These bits are only set because we set the XKB client flags
|
||||||
if (xkb_current_group == XCB_XKB_GROUP_2)
|
* XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
|
||||||
state_filtered |= BIND_MODE_SWITCH;
|
* XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED. See also doc/kbproto
|
||||||
|
* section 2.2.2:
|
||||||
DLOG("(checked mode_switch, state %d)\n", state_filtered);
|
* http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Computing_A_State_Field_from_an_XKB_State */
|
||||||
|
switch ((event_state & 0x6000) >> 13) {
|
||||||
/* Find the binding */
|
case XCB_XKB_GROUP_1:
|
||||||
Binding *bind = get_binding(state_filtered, is_release, event_detail, input_type);
|
state_filtered |= (I3_XKB_GROUP_MASK_1 << 16);
|
||||||
|
break;
|
||||||
/* No match? Then the user has Mode_switch enabled but does not have a
|
case XCB_XKB_GROUP_2:
|
||||||
* specific keybinding. Fall back to the default keybindings (without
|
state_filtered |= (I3_XKB_GROUP_MASK_2 << 16);
|
||||||
* Mode_switch). Makes it much more convenient for users of a hybrid
|
break;
|
||||||
* layout (like {us, ru} or {dvorak, us}, see e.g. ticket #1775). */
|
case XCB_XKB_GROUP_3:
|
||||||
if (bind == NULL) {
|
state_filtered |= (I3_XKB_GROUP_MASK_3 << 16);
|
||||||
state_filtered &= ~(BIND_MODE_SWITCH);
|
break;
|
||||||
DLOG("no match, new state_filtered = %d\n", state_filtered);
|
case XCB_XKB_GROUP_4:
|
||||||
if ((bind = get_binding(state_filtered, is_release, event_detail, input_type)) == NULL) {
|
state_filtered |= (I3_XKB_GROUP_MASK_4 << 16);
|
||||||
/* This is not a real error since we can have release and
|
break;
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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) {
|
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;
|
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) {
|
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||||
if (bind->input_type == B_MOUSE) {
|
if (bind->input_type == B_MOUSE) {
|
||||||
char *endptr;
|
char *endptr;
|
||||||
|
@ -271,36 +336,66 @@ void translate_keysyms(void) {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* We need to translate the symbol to a keycode */
|
/* 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) {
|
if (keysym == XKB_KEY_NoSymbol) {
|
||||||
ELOG("Could not translate string to key symbol: \"%s\"\n",
|
ELOG("Could not translate string to key symbol: \"%s\"\n",
|
||||||
bind->symbol);
|
bind->symbol);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base column we use for looking up key symbols. We always consider
|
xkb_layout_index_t group = XCB_XKB_GROUP_1;
|
||||||
* the base column and the corresponding shift column, so without
|
if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2)
|
||||||
* mode_switch, we look in 0 and 1, with mode_switch we look in 2 and
|
group = XCB_XKB_GROUP_2;
|
||||||
* 3. */
|
else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3)
|
||||||
col = (bind->mods & BIND_MODE_SWITCH || mode_switch ? 2 : 0);
|
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);
|
FREE(bind->translated_to);
|
||||||
bind->number_keycodes = 0;
|
bind->number_keycodes = 0;
|
||||||
|
xkb_keymap_key_for_each(xkb_keymap, add_keycode_if_matches, &resolving);
|
||||||
for (i = min_keycode; i && i <= max_keycode; i++) {
|
char *keycodes = sstrdup("");
|
||||||
if ((xcb_key_symbols_get_keysym(keysyms, i, col) != keysym) &&
|
for (uint32_t n = 0; n < bind->number_keycodes; n++) {
|
||||||
(xcb_key_symbols_get_keysym(keysyms, i, col + 1) != keysym))
|
char *tmp;
|
||||||
continue;
|
sasprintf(&tmp, "%s %d", keycodes, bind->translated_to[n]);
|
||||||
bind->number_keycodes++;
|
free(keycodes);
|
||||||
bind->translated_to = srealloc(bind->translated_to,
|
keycodes = tmp;
|
||||||
(sizeof(xcb_keycode_t) *
|
}
|
||||||
bind->number_keycodes));
|
DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
|
||||||
bind->translated_to[bind->number_keycodes - 1] = i;
|
bind->event_state_mask, bind->symbol, keysym, keycodes, bind->number_keycodes);
|
||||||
|
free(keycodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
DLOG("Translated symbol \"%s\" to %d keycode (mods %d)\n", bind->symbol,
|
xkb_state_unref(dummy_state);
|
||||||
bind->number_keycodes, bind->mods);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -319,7 +414,7 @@ void switch_mode(const char *new_mode) {
|
||||||
ungrab_all_keys(conn);
|
ungrab_all_keys(conn);
|
||||||
bindings = mode->bindings;
|
bindings = mode->bindings;
|
||||||
translate_keysyms();
|
translate_keysyms();
|
||||||
grab_all_keys(conn, false);
|
grab_all_keys(conn);
|
||||||
|
|
||||||
char *event_msg;
|
char *event_msg;
|
||||||
sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
|
sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
|
||||||
|
@ -333,6 +428,61 @@ void switch_mode(const char *new_mode) {
|
||||||
ELOG("ERROR: Mode not found\n");
|
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 binding’s 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
|
* 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
|
* 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
|
/* Check if the keycodes or modifiers are different. If so, they
|
||||||
* can't be duplicate */
|
* can't be duplicate */
|
||||||
if (bind->keycode != current->keycode ||
|
if (bind->keycode != current->keycode ||
|
||||||
bind->mods != current->mods ||
|
bind->event_state_mask != current->event_state_mask ||
|
||||||
bind->release != current->release)
|
bind->release != current->release)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
context->has_errors = true;
|
context->has_errors = true;
|
||||||
if (current->keycode != 0) {
|
if (current->keycode != 0) {
|
||||||
ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
|
ELOG("Duplicate keybinding in config file:\n state mask 0x%x with keycode %d, command \"%s\"\n",
|
||||||
current->mods, current->keycode, current->command);
|
current->event_state_mask, current->keycode, current->command);
|
||||||
} else {
|
} else {
|
||||||
ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
|
ELOG("Duplicate keybinding in config file:\n state mask 0x%x with keysym %s, command \"%s\"\n",
|
||||||
current->mods, current->symbol, current->command);
|
current->event_state_mask, current->symbol, current->command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -466,3 +616,28 @@ CommandResult *run_binding(Binding *bind, Con *con) {
|
||||||
|
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -229,7 +229,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
||||||
|
|
||||||
/* get the floating con */
|
/* get the floating con */
|
||||||
Con *floatingcon = con_inside_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);
|
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 */
|
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
|
||||||
|
|
|
@ -214,7 +214,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
|
||||||
|
|
||||||
if (reload) {
|
if (reload) {
|
||||||
translate_keysyms();
|
translate_keysyms();
|
||||||
grab_all_keys(conn, false);
|
grab_all_keys(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.font.type == FONT_TYPE_NONE) {
|
if (config.font.type == FONT_TYPE_NONE) {
|
||||||
|
|
|
@ -161,32 +161,40 @@ static bool eval_boolstr(const char *str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A utility function to convert a string of modifiers to the corresponding bit
|
* A utility function to convert a string containing the group and modifiers to
|
||||||
* mask.
|
* 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
|
/* It might be better to use strtok() here, but the simpler strstr() should
|
||||||
* do for now. */
|
* do for now. */
|
||||||
uint32_t result = 0;
|
i3_event_state_mask_t result = 0;
|
||||||
if (str == NULL)
|
if (str == NULL)
|
||||||
return result;
|
return result;
|
||||||
if (strstr(str, "Mod1") != NULL)
|
if (strstr(str, "Mod1") != NULL)
|
||||||
result |= BIND_MOD1;
|
result |= XCB_KEY_BUT_MASK_MOD_1;
|
||||||
if (strstr(str, "Mod2") != NULL)
|
if (strstr(str, "Mod2") != NULL)
|
||||||
result |= BIND_MOD2;
|
result |= XCB_KEY_BUT_MASK_MOD_2;
|
||||||
if (strstr(str, "Mod3") != NULL)
|
if (strstr(str, "Mod3") != NULL)
|
||||||
result |= BIND_MOD3;
|
result |= XCB_KEY_BUT_MASK_MOD_3;
|
||||||
if (strstr(str, "Mod4") != NULL)
|
if (strstr(str, "Mod4") != NULL)
|
||||||
result |= BIND_MOD4;
|
result |= XCB_KEY_BUT_MASK_MOD_4;
|
||||||
if (strstr(str, "Mod5") != NULL)
|
if (strstr(str, "Mod5") != NULL)
|
||||||
result |= BIND_MOD5;
|
result |= XCB_KEY_BUT_MASK_MOD_5;
|
||||||
if (strstr(str, "Control") != NULL ||
|
if (strstr(str, "Control") != NULL ||
|
||||||
strstr(str, "Ctrl") != NULL)
|
strstr(str, "Ctrl") != NULL)
|
||||||
result |= BIND_CONTROL;
|
result |= XCB_KEY_BUT_MASK_CONTROL;
|
||||||
if (strstr(str, "Shift") != NULL)
|
if (strstr(str, "Shift") != NULL)
|
||||||
result |= BIND_SHIFT;
|
result |= XCB_KEY_BUT_MASK_SHIFT;
|
||||||
if (strstr(str, "Mode_switch") != NULL)
|
|
||||||
result |= BIND_MODE_SWITCH;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +268,7 @@ CFGFUN(floating_maximum_size, const long width, const long height) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CFGFUN(floating_modifier, const char *modifiers) {
|
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) {
|
CFGFUN(default_orientation, const char *orientation) {
|
||||||
|
|
|
@ -997,6 +997,7 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||||
yajl_gen_free(config_output->json_gen);
|
yajl_gen_free(config_output->json_gen);
|
||||||
|
|
||||||
check_for_duplicate_bindings(context);
|
check_for_duplicate_bindings(context);
|
||||||
|
reorder_bindings();
|
||||||
|
|
||||||
if (use_nagbar && (context->has_errors || context->has_warnings)) {
|
if (use_nagbar && (context->has_errors || context->has_warnings)) {
|
||||||
ELOG("FYI: You are using i3 version %s\n", i3_version);
|
ELOG("FYI: You are using i3 version %s\n", i3_version);
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <xcb/randr.h>
|
#include <xcb/randr.h>
|
||||||
#include <X11/XKBlib.h>
|
|
||||||
#define SN_API_NOT_YET_FROZEN 1
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
#include <libsn/sn-monitor.h>
|
#include <libsn/sn-monitor.h>
|
||||||
|
|
||||||
|
@ -264,7 +263,7 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
|
||||||
|
|
||||||
ungrab_all_keys(conn);
|
ungrab_all_keys(conn);
|
||||||
translate_keysyms();
|
translate_keysyms();
|
||||||
grab_all_keys(conn, false);
|
grab_all_keys(conn);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1338,7 +1337,9 @@ void handle_event(int type, xcb_generic_event_t *event) {
|
||||||
keysyms = xcb_key_symbols_alloc(conn);
|
keysyms = xcb_key_symbols_alloc(conn);
|
||||||
ungrab_all_keys(conn);
|
ungrab_all_keys(conn);
|
||||||
translate_keysyms();
|
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) {
|
} else if (state->xkbType == XCB_XKB_MAP_NOTIFY) {
|
||||||
if (event_is_ignored(event->sequence, type)) {
|
if (event_is_ignored(event->sequence, type)) {
|
||||||
DLOG("Ignoring map notify event for sequence %d.\n", state->sequence);
|
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);
|
keysyms = xcb_key_symbols_alloc(conn);
|
||||||
ungrab_all_keys(conn);
|
ungrab_all_keys(conn);
|
||||||
translate_keysyms();
|
translate_keysyms();
|
||||||
grab_all_keys(conn, false);
|
grab_all_keys(conn);
|
||||||
|
(void)load_keymap();
|
||||||
}
|
}
|
||||||
} else if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
|
} else if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
|
||||||
DLOG("xkb state group = %d\n", state->group);
|
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)
|
if (xkb_current_group == state->group)
|
||||||
return;
|
return;
|
||||||
xkb_current_group = state->group;
|
xkb_current_group = state->group;
|
||||||
DLOG("Mode_switch %s\n", (xkb_current_group == XCB_XKB_GROUP_1 ? "disabled" : "enabled"));
|
|
||||||
ungrab_all_keys(conn);
|
ungrab_all_keys(conn);
|
||||||
translate_keysyms();
|
grab_all_keys(conn);
|
||||||
grab_all_keys(conn, (xkb_current_group == XCB_XKB_GROUP_2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -5,8 +5,8 @@ CLEAN_TARGETS += clean-i3
|
||||||
i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
|
i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
|
||||||
i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
|
i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
|
||||||
i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.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_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) $(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_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
|
# When using clang, we use pre-compiled headers to speed up the build. With
|
||||||
# gcc, this actually makes the build slower.
|
# gcc, this actually makes the build slower.
|
||||||
|
|
100
src/ipc.c
100
src/ipc.c
|
@ -121,6 +121,68 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
|
||||||
y(map_close);
|
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) {
|
static void dump_binding(yajl_gen gen, Binding *bind) {
|
||||||
y(map_open);
|
y(map_open);
|
||||||
ystr("input_code");
|
ystr("input_code");
|
||||||
|
@ -138,39 +200,13 @@ static void dump_binding(yajl_gen gen, Binding *bind) {
|
||||||
ystr("command");
|
ystr("command");
|
||||||
ystr(bind->command);
|
ystr(bind->command);
|
||||||
|
|
||||||
|
// This key is only provided for compatibility, new programs should use
|
||||||
|
// event_state_mask instead.
|
||||||
ystr("mods");
|
ystr("mods");
|
||||||
y(array_open);
|
dump_event_state_mask(gen, bind);
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
if (bind->mods & (1 << i)) {
|
ystr("event_state_mask");
|
||||||
switch (1 << i) {
|
dump_event_state_mask(gen, bind);
|
||||||
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);
|
|
||||||
|
|
||||||
y(map_close);
|
y(map_close);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void handle_key_press(xcb_key_press_event_t *event) {
|
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;
|
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);
|
Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
|
||||||
|
|
||||||
|
|
36
src/main.c
36
src/main.c
|
@ -556,6 +556,37 @@ int main(int argc, char *argv[]) {
|
||||||
0xff,
|
0xff,
|
||||||
0xff,
|
0xff,
|
||||||
NULL);
|
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;
|
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);
|
xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
|
||||||
|
|
||||||
|
if (!load_keymap())
|
||||||
|
die("Could not load keymap\n");
|
||||||
|
|
||||||
translate_keysyms();
|
translate_keysyms();
|
||||||
grab_all_keys(conn, false);
|
grab_all_keys(conn);
|
||||||
|
|
||||||
bool needs_tree_init = true;
|
bool needs_tree_init = true;
|
||||||
if (layout_path) {
|
if (layout_path) {
|
||||||
|
|
Loading…
Reference in New Issue