Introduction
This document contains a quick walk-through of the often-used parts of the library. We will employ a few use-cases to lead the examples:
- An evdev client. evdev is the Linux kernel’s input subsystem; it only reports to the client which keys are pressed and released.
- An X11 client, using the XCB library to communicate with the X server and the xcb-xkb library for using the XKB protocol.
- A Wayland client, using the standard protocol.
- A Wayland server, using the standard protocol.
The snippets are not complete, and some support code is omitted. You can find complete and more complex examples in the [source directory]:
- tools/interactive-evdev.c contains an interactive evdev client.
- tools/interactive-x11.c contains an interactive X11 client.
- tools/interactive-wayland.c contains an interactive Wayland client.
Also, the library contains many more functions for examining and using the library context, the keymap and the keyboard state. See the hyper-linked reference documentation or go through the header files in xkbcommon/ for more details.
Code for clients
Before we can do anything interesting, we need a library context:
if (!ctx) {
exit(EXIT_FAILURE);
}
The xkb_context contains the keymap include paths, the log level and functions, and other general customizable administrativia.
Next we need to create a keymap, xkb_keymap. This is an immutable object which contains all of the information about the keys, layouts, etc. There are different ways to do this.
If we are an evdev client, we have nothing to go by, so we need to ask the user for his/her keymap preferences (for example, an Icelandic keyboard with a Dvorak layout). The configuration format is commonly called RMLVO (Rules+Model+Layout+Variant+Options), the same format used by the X server. With it, we can fill a struct called xkb_rule_names; passing NULL chooses the system’s default.
.rules = NULL,
.model = "pc105",
.layout = "ca",
.variant = "fr-dvorak",
.options = "terminate:ctrl_alt_bksp"
};
);
if (!keymap) {
exit(EXIT_FAILURE);
}
If we are a Wayland client, the compositor gives us a string complete with a keymap. In this case, we can create the keymap object like this:
*keymap = xkb_keymap_new_from_buffer(ctx,
keymap_buffer,
keymap_buffer_size,
format,
if (!*keymap) {
exit(EXIT_FAILURE);
}
If we are an X11 client, we are better off getting the keymap from the X server directly. For this we need to choose the XInput device; here we will use the core keyboard device:
int ret = EXIT_SUCCESS;
xcb_connection_t *conn = xcb_connect(display, NULL);
if (!conn || xcb_connection_has_error(conn)) {
ret = EXIT_FAILURE;
goto connection_error;
}
NULL, NULL, NULL, NULL);
if (!ret) {
goto connection_error;
}
if (device_id == -1) {
ret = EXIT_FAILURE;
goto device_error;
}
);
if (!keymap) {
ret = EXIT_FAILURE;
goto keymap_error;
}
Now that we have the keymap, we are ready to handle the keyboard devices. For each device, we create an xkb_state, which remembers things like which keyboard modifiers and LEDs are active:
- Wayland
if (!state) {
switch (error) {
default:
exit(EXIT_FAILURE);
}
}
- X11/XCB
if (!state) {
ret = EXIT_FAILURE;
goto state_error;
}
- evdev
if (!state) {
exit(EXIT_FAILURE);
}
When we have an xkb_state for a device, we can start handling key events from it. Given a keycode for a key, we can get its keysym:
keysym = xkb_state_key_get_one_sym(state, keycode);
We can see which keysym we got, and get its name:
switch(keysym) {
break;
default:
assert(!"unhandled keysym");
}
char keysym_name[64];
xkbcommon also supports an extension to the classic XKB, whereby a single event can result in multiple keysyms. Here’s how to use it:
const int num_keysyms = xkb_state_key_get_syms(state, keycode, &keysyms);
for (int k = 0; k < num_keysyms; k++) {
handle_keysym(keysyms[k]);
}
We can also get a UTF-8 string representation for this key:
int size = xkb_state_key_get_utf8(state, keycode, NULL, 0) + 1;
if (size <= 1) {
assert(!"no UTF-8 representation");
} else {
char *utf8 = malloc((size_t)size);
if (!utf8) {
exit(EXIT_FAILURE);
}
xkb_state_key_get_utf8(state, keycode, utf8, size);
assert(*utf8 != '\0');
free(utf8);
}
Of course, we also need to keep the xkb_state up-to-date with the keyboard device, if we want to get the correct keysyms in the future.
If we are an evdev client, we must let the library know whether a key is pressed or released at any given time:
enum {
KEY_STATE_RELEASE = 0,
KEY_STATE_PRESS = 1,
KEY_STATE_REPEAT = 2,
};
switch (direction) {
case KEY_STATE_RELEASE:
break;
case KEY_STATE_PRESS:
break;
case KEY_STATE_REPEAT:
break;
default:
exit(EXIT_FAILURE);
}
The changed return value tells us exactly which parts of the state have changed.
If it is a key-repeat event, we can ask the keymap what to do with it:
if (direction == KEY_STATE_REPEAT &&
!xkb_keymap_key_repeats(keymap, keycode)) {
}
On the other hand, if we are an X or Wayland client, the server already does the hard work for us. It notifies us when the device’s state changes, and we can simply use what it tells us (the necessary information usually comes in a form of some “state changed” event):
state,
event->depressed_mods,
event->latched_mods,
event->locked_mods,
event->depressed_layout,
event->latched_layout,
event->locked_layout
);
Now that we have an always-up-to-date xkb_state, we can examine it. For example, we can check whether the Control modifier is active, or whether the Num Lock LED is active:
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
}
}
if (xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM) > 0) {
}
}
And that’s it! Eventually, we should free the objects we’ve created:
xkb_state_unref(state);
xkb_keymap_unref(keymap);
xkb_context_unref(ctx);
Code for a Wayland server
The code is very similar to the evdev client presented hereinabove. The main difference is the use of the xkb_machine API instead of the xkb_state API.
enum {
EVDEV_OFFSET = 8,
};
struct my_keyboard {
struct xkb_context *ctx;
struct xkb_keymap *keymap;
struct xkb_machine *machine;
struct xkb_events *events;
};
static int
new_keyboard(
struct my_keyboard *keyboard,
const struct xkb_rule_names *names)
{
assert(keyboard->ctx);
if (!keymap) {
exit(EXIT_FAILURE);
}
if (!machine_builder) {
switch (error) {
default:
exit(EXIT_FAILURE);
}
}
xkb_machine_builder_unref(machine_builder);
if (!machine) {
switch (error) {
default:
exit(EXIT_FAILURE);
}
}
if (!events) {
switch (error) {
default:
exit(EXIT_FAILURE);
}
}
char *keymap_string =
if (!keymap_string) {
exit(EXIT_FAILURE);
}
free(keymap_string);
keyboard->keymap = keymap;
keyboard->machine = machine;
keyboard->events = events;
return EXIT_SUCCESS;
}
static int
destroy_keyboard(struct my_keyboard *keyboard)
{
xkb_machine_unref(keyboard->machine);
xkb_keymap_unref(keyboard->keymap);
xkb_context_unref(keyboard->ctx);
return EXIT_SUCCESS;
}
static int
handle_key(struct my_keyboard *keyboard, uint32_t key, uint32_t state)
{
: WL_KEYBOARD_KEY_STATE_REPEATED
xkb_machine_process_key(keyboard->machine, keycode, direction,
keyboard->events);
exit(EXIT_FAILURE);
}
while ((event = xkb_events_next(keyboard->events)) != NULL) {
switch (event_type) {
exit(EXIT_FAILURE);
exit(EXIT_FAILURE);
}
break;
}
.size = sizeof(components)
};
error = xkb_event_get_components(event, &components);
exit(EXIT_FAILURE);
}
}
break;
}
.size = sizeof(motion)
};
error = xkb_event_get_pointer_motion(event, &motion);
exit(EXIT_FAILURE);
}
break;
}
};
error = xkb_event_get_pointer_button(event, &
button);
exit(EXIT_FAILURE);
}
break;
}
default:
exit(EXIT_FAILURE);
}
}
return EXIT_SUCCESS;
}
Synthetic updates are handled using xkb_machine::xkb_machine_process_synthetic().