Contextual application QMK layers with a bit of raw HID magic

After experimenting with my ErgoDox configuration for a couple of years, I’ve found that it’s really nice to have a few application-specific layers. For reference, these are what my layers currently look like:

In order to access any of the application layers, I first have switch to the launcher layer, then press a key which corresponds to the layer I want to get to. In practice, this can take up to 4 key presses each time, so it can get to be rather annoying when frequently switching between multiple programs.

My solution to this is pretty simple: have a program listen to window focus change events, and turn them into raw HID messages sent to the keyboard, instructing it to switch to a specific layer.

Setting up the keyboard

QMK supports raw HID nearly out of the box. All that’s required to enable this feature is to set the following in your keyboard firmware’s


You’ll also want to look in your config.h to find the values for RAW_USAGE_PAGE and RAW_USAGE_ID (we’ll need these later). Here’s what mine looked like:

#define RAW_USAGE_PAGE 0xFF60
#define RAW_USAGE_ID 0x61

Then, in your keymap.c, add the following:

#include "raw_hid.h"

/* ... */

void raw_hid_receive(uint8_t *data, uint8_t length) {
    if (length == 3) {
        uint8_t layer      = data[1];
        uint8_t layer_mask = data[2] | (1 << layer);
        if ((layer_state & layer_mask) == 0)

The raw HID events sent to the device will contain both a destination layer and a “protected layer mask,” which specifies certain layers to which the keyboard should give priority. In this way, you can tell the keyboard “switch to layer 5, unless layers 2, 3, or 4 are enabled” without requiring any sort of state or bidirectional communication.

Finally, flash your device with the newly compiled firmware.

Appeasing udev

If you’re using udev, you’ll need a rule to allow you to access raw HID as a user. Here’s an example for an ErgoDox, in /etc/udev/rules.d/50-ergodox.rules:

KERNEL=="hidraw*", ATTRS{busnum}=="3", ATTRS{idVendor}=="3297", ATTRS{idProduct}=="4976", MODE="0666"

Reload udev. If this doesn’t work, try plugging your device in again.

Setting up the software

Next, we need to set up the software on the computer. Currently, this requires the usage of X11, though a Wayland port of this software would not be too difficult.

A few dependencies are required:

Clone the repository, and build it using Cargo (see the README for more instructions).

Before you run the resultant layerizer binary, you’ll need to set up a config. You can either put it in a known place like ~/.config/layerizer/config.toml, or specify the path with the --config argument.

Here’s an example for an ErgoDox, using the HID usage page and ID we found before:

# Vendor and product IDs of the device.
vendor_id = 0x3297
product_id = 0x4976

# HID usage page and ID of the device.
usage_page = 0xFF60
usage_id = 0x61

# The layer to switch to by default.
default_layer = 0

# Layers which have higher priority over application rules.
protected_layers = [1, 2, 3]

# Application rules.
# Can be selected by class and/or name, with regex support for both.
# You may also specify additional protected layers for each entry.
rules = [
    { class = "gzdoom", layer = 5 },
    { class = "csgo_linux64", layer = 5 },

Final thoughts

Hopefully you were able to make it this far! If not, please do open an ticket on the repository and let me know.

This project isn’t quite complete yet, so I’d love to have help. Future work might include:

To discuss this post, send an email to my public mailing list: ~cassaundra/