While writing documentation for Evdoublebind, I realized how difficult it was to setup. It was designed to be as simple as possible but since it works at a low-level, users needed to do so as well. Additionally, complex configuration of XKB was required for the full feature set.
The main improvements to ease of use was: Introducing a unified configuration system which use accurate higher level representations of key values; Automating as much of the configuration as possible; and adding tool, evdoublebind-inspector, quickly get the information needed for the setup. But before I discuss the improvements why it hard to use in the first place.
Although, source configuration is not too much of burden, finding the perfect setup may require experimentation; users will trying many configurations thus needing to rebuild it each time.
The previous version used the labels from linux/input-event-codes.h
for configuration so you could write {.key = KEY_LEFTSHIFT, .tap_key = KEY_BACKSLASH}
, this lead to a number of problems:
Misleading Labels: Although, it was simple to implement and on the surface easy to use, using these labels can be misleading. The labels correspond to evdev key codes, key codes which once processed and translated to the keyboard layout, may correspond to keys other than their label. Meaning labels can be wrong, ex. KEY_BACKSLASH
produces a #
when using the gb
layout.
Narrow Responsibility: To setup a advanced configurations manually configuration of XKB was required. Evdoublebind did not help make these configs and the user would need to manually do the XKB translation and the two configs in sync. Further the user needed to be aware of 4 different identify for a key press to competently configure the software, now you only need to be aware of the bottom two.
Also, users may get confused if these don't realize they need external configuration.
Encouraging Bad Configuration: By not handling the external configuration explicitly, the easy way to implement simple bindings where to map the tap key directly the key code of another key already on keyboard with the desired key, ex. {.key = KEY_LEFTSHIFT, .tap_key = KEY_BACKSLASH}
. However, this is problematic since if tapping the left shift well holding the backslash key would be put cause backslash key to be reset even though it is still held.
When manually configuring XKB or using a non-US layout figuring what keys to bind was a lot of grepping around /usr/share/X11/xkb/
and using xev
.
Broken configurations can result in broken input. This is mainly a problem when initially configuring a complex setup. For me this usually happens when the XKB settings are used but evdoublebind is not running. Since the XKB remaps my space-bar to control without evdoublebind running I have no space-bar. For me I solved this by binding a key in my window manager to toggle the setup, both XKB settings and evdoublebind. Importantly the binding is accessible from any combination of configurations.
The original program was tiny, fast and simple but was hard to setup. Well making it easier I did not want to compromise on it's simplicity which is why most effort was in making extra programs to help with configuration.
As the original program was just a signal 100 line C file which required source configuration, I felt OK leaving installation details unspecified and only a spartan one line build script was provided. But since the project has grown and I eliminated the need for source configuration, a makefile
is now provided, which can install the binaries. The makefile
will by default compile all the binary with the system gcc
but optionally can, using make musl-static
, build static musl binaries instead. Currently, when installing, the group sticky bit is set on evdoublebind
so user can run the process directly.
Additionally, a shell script, install_xkb_rule.sh
, is provided to install a custom xkb_rule that adds the evdoublebind:mapping
option.
Since the key labels from linux/input-event-codes.h
can be wrong, Evdoublebind should be configured from the keycode numbers directly. Using numbers the mapping can be easily specified via command line argument removing the need for source configuration. Thus the settings can be pass as like this,
evdoublebind "/dev/input/by-id/path-kbd" "58:189,42:190,54:191"
which is hardly an improvement in terms easy of use if your write the mapping by hand.
Introducing, evdoublebind-make-config
which has two purposes, generating the XKB configs and the evdoublebind arguments. The config generating uses xkbcommon to accommodate different keyboard layouts. Further the configuring allows for a direct description of the entire desired behavior. Writing, <CAPS> : Hyper_L | Escape
, says caps-lock should be the hyper
key when held and escape
when tapped. The output keys are specified in XKB keysyms which are the resulting representation of key press after translation.
unused : <FK19> <FK20> <FK21> <FK22> <FK23> <FK24> <I159> <I160>
kbd : /dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-event-kbd
kbd : /dev/input/by-id/some-other-keyboard
<CAPS> : Hyper_L | Escape #Tap Caps -> Escape
#Hold Caps -> Hyper
<LFSH> : Shift_L | backslash #Tap Left Shift -> \
<RTSH> : Shift_R | ampersand #Tap Right Shift -> &
<LALT> : Alt_L | asciicirum #Tap Left alt -> ^
<SPCE> : Control_L | space #Hold Space -> control
<AC10> : Super_L | semicolon colon #Hld Semicolon -> Super
Notice the unused list which defines which keys to use as intermediate key-codes for tap keys. Running evdoublebind-make-config mysetup.conf
, generates the XKB config, default locataion ~/.xkb/symbols/evdoublebind
,
partial modifier_keys
xkb_symbols "mapping" {
replace key <CAPS> { [ Hyper_L, Hyper_L, Hyper_L, Hyper_L ] };
replace key <FK19> { [ Escape, Escape, Escape, Escape ] };
modifier_map Mod3 { <CAPS> };
replace key <LFSH> { [ Shift_L, Shift_L, Shift_L, Shift_L ] };
replace key <FK20> { [ backslash, backslash, backslash, backslash ] };
modifier_map Shift { <LFSH> };
...
and outputs to STDOUT the arguments for running the config for evdoublebind. Although, I would save the arguments to a file instead regenerating them each time, you can run,
evdoublebind-make-config mysetup.conf | while read args; do
evdoublebind $args &
done
or, if you only have one keyboard, (one kbd
specified),
evdoublebind $(evdoublebind-make-config mysetup.conf)
If your only want to generate the evdoublebind arguments and not make the XKB config pass the -a
flag to evdoublebind-make-config.
Since evdoublebind-make-config
generates the configuration for you all you have to do is tell XKB to use it.
Currently there is no way to add an option to XKB without redefining the entire rule set. Running install_xkb_rule.sh
installs a modified rule set to /.xkb/rules/evdev-doublebind
. Then for configuration on X11 you can usually,depending on your desktop environment/window manager, run
setxkbmap -I$HOME/.xkb -rules 'evdev-doublebind' -option evdoublebind:mapping\
-print | xkbcomp -w 2 -I$HOME/.xkb - $DISPLAY
some desktop environment/window manager have there own way of configuring XKB. Sway: Add the following to your sway config, man sway-input
for my details or Input configuration wiki.
input * {
xkb_rules evdev-doublebind
xkb_options evdoublebind:mapping
}
For gnome and cinnamon you can use gsettings set org.gnome.desktop.input-sources
to configure XKB although I have tried run something like this in a terminal,
gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us')]"
gsettings set org.gnome.desktop.input-sources xkb-rules "['evdev-doublebind']"
gsettings set org.gnome.desktop.input-sources xkb-options "['evdoublebind:mapping']"
Before to do anything with evdoublebind you needed to find your keyboard dev input node, which previously was done by searching around /dev/input/by-id/
and /dev/input/by-path
with a bit of trial and error. Now, just run sudo evdoublebind-inspector
. You will get a list of inputs evdoublebind-inspector thinks might be your keyboards and it outputs events from those keyboards so you can make sure.
Found 2 Possible Keyboards.
---
/dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-if01-event-kbd
/dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-event-kbd
---
[/dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-event-kbd]{
EVDEV: keycode:28 name:KEY_ENTER;
XKB: key[36]:<RTRN> keysym:Return;
}
[/dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-event-kbd]{
EVDEV: keycode:31 name:KEY_S;
XKB: key[39]:<AC02> keysym:s;
}
...
Besides helping you identify your keyboard it also gives you lots of the information needed to make the configuration file for evdoublebind-make-config. As the <KEY_NAME>
in the XKB line allows you to identify the key your want to rebind. The keysym is also the same keysyms used in the config.
There are only two things evdoublebind inspector can't directly help you find, both of which pertain to keys not on your keyboard and should be mostly edge cases.
You need to specify enough unused keys, one for each tap key you have, I used /usr/share/X11/xkb/keycodes/evdev
to find keys. However, I suspect most people can just use the default I use in my configs which is the functions keys passed F19.
Secondly, you may want to output keysyms not already on your keyboard. The keysym name scheme is pretty simple but the capitalization is all over the place. Here is a, list of key-syms I found.
Previously you had to do some fiddling around, finding you keyboard and tweak values, figuring out XKB before you could just see evdoublebind work properly.
But now you can run the following on X11, (and assuming you desktop environment doesn't get in the way like gnome does), after installation.
#!/bin/sh
#install XKB rule set: evdev-doublebind
./install_xkb_rule.sh
echo 'unused : <FK19> <FK20> <FK21> <FK22> <FK23> <FK24>' > basic.conf
# find keyboards, THIS MAY FIND TO MANY KEYBOARDS!!! for real setup please
# run `evdoublebind-inspector` to identify your actual keyboards
echo "sudo evdoublebind-inspector -k : to get keyboards"
sudo evdoublebind-inspector -k | awk '{print "kbd:" $1;}' >> basic.conf
echo '<CAPS> : Control_L | Escape' >> basic.conf
# Generate XKB_option will go in `~/.xkb/symbols/evdoublebind` and `evdb.in`.
evdoublebind-make-config -c evdb.args basic.conf || exit #abort on failure
#make sure no other instances are running
killall evdoublebind
#Start evdouble-bind
cat evdb.args | while read args; do
evdoublebind $args &
done
# Alternatively you could generate the arguments from the config
# and pass the directly
# evdoublebind-make-config basic.conf | while read args; do
# sudo ../build/evdoublebind $args &
# done
#xkb to use the generated option
setxkbmap -I$HOME/.xkb -rules 'evdev-doublebind' -option evdoublebind:mapping\
-print | xkbcomp -w 2 -I$HOME/.xkb - $DISPLAY
echo "it is 'normal' for xkbcomp to output some warnings."
This shell scripts setups and starts a CAPS as Control and tap for escape setup and requires no extra configuration to work.
This is what default setting for the old evdoublebind did as well however it did not configure XKB and so did not fully work.
The naive setup is for Evdoublebind to emit keycode already associated with keycodes on the keyboards. This was previously, done by write a config like {.key = KEY_LEFTSHIFT, .tap_key = KEY_BACKSLASH}
. The naive approach has may problems as discussed above and limits functionally since can only add tap keys,which are already defined for keyboard layout, to keys, that are already modifiers. However, this limited amount of functionality may be enough.
Additionally, some desktop environments (e.g. gnome) provide configuration which make easy to map Capslock to control. These limited configurations, although in a manual fashion can be used broaden the naive setup as in this case Capslock could now be overload by Evdoublebind as well.
Previously, it was easy to use Evdoublebind in a naive way that did not require configuring XKB. However, since configuring XKB layouts,rules and options is inconsistent and sometimes not well supported across different desktop environments, using the Naive approach may still be desirable.
The updated Evdoublebind can still be used in the naive way.
Using evdouble-make-config
, the only thing that matters is the unused line and the key definition at the beginning of each line in the mapping, unused keys are used in order.
unused : <ESC>
kbd : /dev/input/by-id/some-other-keyboard
<CAPS> : void | void
This would generate the mapping 58:1
.
Or instead you could write the mapping manually, if you run sudo evdoublebind-inspector
you can see the EVDEV keycodes, these are what evdoublebind uses for the mapping.
[/dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-event-kbd]{
EVDEV: keycode:31 name:KEY_S;
XKB: key[39]:<AC02> keysym:s;
}
The mapping argument is just a comma sperated list of BASE_KEYCODE:TAP_KEYCODE,...
.
Once you have your mapping you can just run evdoublebind
as shown below.
evdoublebind "/dev/input/by-id/path-kbd" "58:189,42:190,54:191"
I think I have made Evdoublebind a lot easier to use without sacrificing any of the efficiency or simplicity of the original program. Most importantly I limited the scope of details the user most know and consolidated the bulk of configuration to one file. So, if after reading this you still have troubles configuring evdoublebind
please open a issue.
To processing and translating a key event on a modern linux system there 4 important identifies shown below in the order they used to process the key event.
`linux/input-event-codes.h`
#define KEY_X 45
#define KEY_C 46
#define KEY_V 47
const KeyBind KEYMAP[] = {
{.key = KEY_SPACE, .tap_key = 183},
{.key = KEY_CAPSLOCK, .tap_key = KEY_ESC},
};
int evdev_offset = 8; //Usally 8 for most keyboards/layouts.
xkb_keycode_t xkbcode = ev_keycode + evdev_offset;
int evdev_offset = 8; //Usally 8 for most keyboards/layouts.
xkb_keycode_t xkbcode = ev_keycode + evdev_offset;
// <XKB_KEY_NAME> = XKB_KEY_CODE;
// from: /usr/share/X11/xkb/keycodes/digital_vndr/pc
<LCTL> = 17;
<LALT> = 25;
<SPCE> = 41;
<RALT> = 57;
KeyRelease event, serial 28, synthetic NO, window 0x2400001, ... state 0x0, keycode 9 (keysym 0xff1b, Escape), same_screen YES, ...
// key <XKB_KEY_NAME> { [ XKB_KEY_SYM ... ] };
key <FK14> { [ semicolon, colon ] };
key <SPCE> { [ Control_L ] };
key <CAPS> { [ Hyper_L ] };
// key <XKB_KEY_NAME> { [ XKB_KEY_SYM ... ] };
key <FK14> { [ semicolon, colon ] };
key <SPCE> { [ Control_L ] };
key <CAPS> { [ Hyper_L ] };
KeyRelease event, serial 28, synthetic NO, window 0x2400001, ... state 0x0, keycode 9 (keysym 0xff1b, Escape), same_screen YES, ...