Skip to content

How to set up keycodes

Bakkeby edited this page Apr 14, 2024 · 2 revisions

By default dusk, as well as dwm, use keysyms (symbolic keys) when it comes to configuring keybindings.

In the below example the symbolic key of XK_d is used to run the dmenu command.

static Key keys[] = {
	/* type      modifier  key    function  argument */
	{ KeyPress,  MODKEY,   XK_d,  spawn,    {.v = dmenucmd } }, // spawn dmenu for launching other programs
	...

The benefit of this is twofold. The first is that it is readable in that it is easy to understand that the keybinding is in relation to the letter d.

The second benefit is that that Super+d will always trigger the dmenu command regardless of where on the keyboard the letter d happens to be.

In other words the keybinding is not dependent on the layout that the end user may use.

For example the first row of letters on a US keyboard reads out "QUERTY" and on a German keyboard it may read "QUERTZ" while a Fench keyboard will typically use the "AZERTY" layout.

Using keysyms for keybindings is great for default configuration (as it will be the same for everyone) and it is generally sufficient for most people.

That said, the benefit outlined above becomes an annoying problem for users that speak multiple languages and tend to switch between different keyboard layouts depending on what they are doing.

A simple example is that when switching from an English to a French keyboard layout the numeric keybindings to move between workspaces (e.g. Super+7, Super+1, etc.) stops working because on a French keyboard layout will use the following keysyms rather than XK_1 through XK_0:

  • XK_ampersand, XK_eacute, XK_quotedbl, XK_apostrophe, XK_parenleft, XK_minus, XK_egrave, XK_underscore, XK_ccedilla, XK_agrave

In principle one could work around this by duplicating a lot of keybindings, but a better way is to use key codes rather than the symbolic keys.

A key code maps to a specific key on your keyboard regardless of the keyboard layout.

If we consider the dwm patch on the suckless site then the example keybinding above would be written as:

static Key keys[] = {
	/* type      modifier  key    function  argument */
	{ KeyPress,  MODKEY,   40,    spawn,    {.v = dmenucmd } }, // d
	...

The key of 40 is not particularly readable so one would end up adding a comment to say that it refers to the letter d. As such this has an impact on readability as well as maintenance - as if you change the keybinding you also have to remember to update the comment.

There is a better way.

We can set up our own symbolic keys that are hardcoded to our primary keyboard layout. This allows us to retain the same readability as when using keysyms.

One way to do this would be to use the xev (event tester) tool to generate them.

xev -rv -event keyboard | awk '/KeyPress/ { SKIP=0 } /KeyRelease/ { SKIP=1 } SKIP { next } /state/ { gsub("),",""); print "#define KC_" $7 " " $4 }'

Output:

#define KC_q 24
#define KC_w 25
#define KC_e 26
#define KC_r 27
#define KC_t 28
#define KC_y 29
...

The above method is recommended if you are unsure about certain keys. Also note that some keys can refer to the same symbolic key which means there could be conflicts.

The other way to do this is to use a small program to just list all the key codes that link to a symbolic key.

Consider the list_keycodes.c program.

It can be compiled in a terminal by running the file as a shell script:

sh list_keycodes.c

Example output when run:

$ ./list_keycodes | tee config.keycodes.h
#define KC_Escape 9
#define KC_1 10
#define KC_2 11
#define KC_3 12
#define KC_4 13
#define KC_5 14
...
#define KC_XF86MonBrightnessCycle 251
#define KC_XF86BrightnessAuto 252
#define KC_XF86DisplayOff 253
#define KC_XF86WWAN 254
#define KC_XF86RFKill 255
$

We store the output of that in a file config.keycodes.h which we then include in our config.h configuration file:

#include "config.keycodes.h"

Now for the existing keybindings we are going to rename e.g. XK_d to KC_d. Because we are lazy we are going to do an in-place search and replace using sed.

sed -i 's/XK_/KC_/' config.h

In hindsight it may have been a good idea to take a backup of config.h before doing this.

Note that XF86 keysyms, if used, will need to be prefixed with KC_ manually as these are not prefixed with XK_.

In config.mk there is a compile time option to use key codes instead of keysyms for keybindings. We need to change that to 1 before compiling.

# Optionally set to 1 to use key codes rather than keysyms for keybindings
USE_KEYCODES = 0

When compiling you are likely going to get warnings on this form:

config.keycodes.h:183: warning: "KC_XF86AudioPlay" redefined
  183 | #define KC_XF86AudioPlay 208
      |
config.keycodes.h:156: note: this is the location of the previous definition
  156 | #define KC_XF86AudioPlay 172
      |
config.keycodes.h:189: warning: "KC_XF86Close" redefined
  189 | #define KC_XF86Close 214
      |
config.keycodes.h:165: note: this is the location of the previous definition
  165 | #define KC_XF86Close 182
      |
config.keycodes.h:190: warning: "KC_XF86AudioPlay" redefined
  190 | #define KC_XF86AudioPlay 215
      |
config.keycodes.h:183: note: this is the location of the previous definition
  183 | #define KC_XF86AudioPlay 208
      |
config.keycodes.h:192: warning: "KC_Print" redefined
  192 | #define KC_Print 218
      |
config.keycodes.h:96: note: this is the location of the previous definition
   96 | #define KC_Print 107
      |
config.keycodes.h:195: warning: "KC_XF86Mail" redefined
  195 | #define KC_XF86Mail 223
      |
config.keycodes.h:148: note: this is the location of the previous definition
  148 | #define KC_XF86Mail 163
      |
config.keycodes.h:202: warning: "KC_Cancel" redefined
  202 | #define KC_Cancel 231
      |
config.keycodes.h:123: note: this is the location of the previous definition
  123 | #define KC_Cancel 136
      |

The reason for this is that some keysyms map to more than one key code.

I would recommend commenting out those that are duplicates in the first instance and cross-check with e.g. xev if you happen to need to add a keybinding that involves one of the duplicates.

If everything has gone according to plan then you should now be using key codes rather than keysyms for your keybindings, which should all work as-is regardless of which keyboard layout you switch to.


Two questions that may be lingering:

Q: Why use the KC_ prefix rather than just redefining XK_? - that way the default config would not need to change.

A: This would cause a lot of warnings about constants being redefined when compiling, which one would likely want to silence using compilation flags. Having done that it could easily lead to confusion due to some keysyms being redefined twice and later on it may be misleading given that all the keybindings look like they are using keysyms when they are not. Using the KC_ prefix it makes it clear that keybindings map directly to key codes rather than symbolic keys.

Q: Why #define a lot of macros when an enum could be used instead?

A: Using macros is just practical for demonstration purposes. If you prefer then you could of course define these using enums; just make sure that you define the index values as not all key codes have an associated symbolic key.


Back to Guides.

Clone this wiki locally