Skip to main content

Community Spotlight Series #3: ZMK Tools and ZMK Locale Generator

· 7 min read
Cem Aksoylar

This blog continues our series of posts where we highlight projects within the ZMK ecosystem that we think are interesting and that the users might benefit from knowing about them.

In this installment, we are highlighting two projects (and a bonus one!) from Joel Spadin, a member of the core ZMK team. The first one is ZMK Tools, a handy Visual Studio Code extension to ease working with ZMK configurations, and the second is ZMK Locale Generator, a tool to help users that use non-US English keyboard locales in their operating systems.

In the rest of the post we leave it to Joel to introduce and explain the motivations of his ZMK-related projects. Stay tuned for future installments in the series!

ZMK Tools

ZMK Tools is an extension for Visual Studio Code that helps with editing a ZMK user config repo or a fork of ZMK. I originally created it to add some code completion in .keymap files, but then I realized that with the web version of VS Code, I could also let you set up a user config repo and build firmware, much like the user setup script, except without downloading a single thing.

User Config Setup in Browser

Here is how you can use ZMK Tools to get started with writing a ZMK keymap entirely within your browser. More detailed instructions can be found on the ZMK Tools README.

  1. Open the ZMK config template repo on GitHub.
  2. Click the Use this template button and follow the instructions to create your own repo.
    • If you don't see this button, make sure you're signed in to GitHub first.
    • You can name the repo anything you want, but "zmk-config" is the conventional name.
  3. From the GitHub page for your new repo, press . (period) and it will re-open the repo in github.dev.
  4. Press Ctrl + P and enter the following to install the ZMK Tools extension:
    ext install spadin.zmk-tools
  5. Press Ctrl + Shift + P and run the ZMK: Add Keyboard command.
  6. Follow the prompts to select a keyboard. ZMK Tools will copy the default keymap for that keyboard if you don't already have one, and it will automatically add it to your build.yaml file so GitHub will build it for you.

You can then edit your .keymap and .conf files. Once you're done:

  1. Click the Source Control tab on the side bar.
  2. Hover over the header for the Changes list and click the + (Stage All Changes) button.
  3. Write a commit message and click Commit & Push to push your changes to GitHub.

GitHub will start building the new firmware. To check the results:

  1. Use your browser's back button to go back to your repo's GitHub page.
  2. Click the Actions tab at the top of the page.
  3. Click the latest build (it should show the commit message you entered earlier). If it's still in progress, wait for it to finish.
  4. If the build was successful, go to the Artifacts section and click firmware to download the firmware. If it failed, check the error and go back to github.dev to fix it.

Keymap Code Completion

ZMK Tools also provides some basic code completion in .keymap files. It will suggest any of ZMK's built-in behaviors inside bindings and sensor-bindings properties, and it will automatically add the necessary headers.

For example, with the cursor at the end of line 6 in the following keymap...

/ {
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&
>;
};
};
};

...it will suggest things such as &kp, &mo, etc., and upon entering one, it will recognize that #include <behaviors.dtsi> is missing and add it to the top of the keymap:

#include <behaviors.dtsi>
/ {
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp
>;
};
};
};

Press space after &kp, and it will suggest all of ZMK's key codes. Upon entering one, it will again recognize that #include <dt-bindings/zmk/keys.h> is missing and add it too:

#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
/ {
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp A
>;
};
};
};

This can be very helpful for making sure you spelled key codes correctly and included all the correct headers.

Future Work

Unfortunately, all the code completion info currently comes from a config file baked into the extension, so it won't pick up any custom behaviors or key code aliases you've defined. I'd like to make that work eventually, but it's a much more difficult problem to solve.

ZMK Tools will discover all the boards/shields from both ZMK and your user config repo. With some recent changes in ZMK to allow pulling in features from other Zephyr modules, it's now possible to use board/shields defined in other repos, but ZMK Tools doesn't know about this yet. I'd like to support this too, but making it work in the web version of the extension will be challenging.

ZMK Locale Generator

ZMK's key codes follow the HID specification, and many key codes indicate the position of a key on US keyboard layout, not the key's function. If your operating system is set to a different keyboard locale, then the character each key types won't necessarily line up with the key code name. For example, on a German "QWERTZ" layout, &kp Y will type Z and &kp Z will type Y, so you have to write your layout as if it were QWERTY instead. Other layouts can be even more confusing!

ZMK Locale Generator is another tool I made to help with this. It reads CLDR keyboard layouts and generates #defines to alias key codes to names that make sense in other locales. To use it, first go to the latest release and download the header that matches the locale you use. Next, copy it into the same folder as your keymap and #include it:

#include "keys_de.h"

/ {
...
};

If you open the header file in a text editor, you'll see that it contains many of the standard ZMK key codes, except they are prefixed by the locale code. Depending on the locale, it may also define key codes for special characters specific to that locale, e.g. DE_A_UMLAUT for "ä" and DE_SZ for "ß". If you use these in your keymap, then ZMK will send the correct key codes to type those characters.

#include "keys_de.h"

/ {
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp DE_Q &kp DE_W &kp DE_E &kp DE_R &kp DE_T &kp DE_Z ...
>;
};
}
};

I should note that, as a native English speaker and typer, I don't use any of this myself! I just saw that many people were asking for help with this, and I realized I could automate a solution. If you find something that isn't generated correctly, please file an issue or PR a fix on GitHub.

Keyboard Latency Testing

The last project I want to mention is a tool for testing keyboard latency. It requires only a Rasbperry Pi, an optocoupler IC, a resistor, and some wire. If you've ever wondered how ZMK's latency compares to other keyboards, you can check the results here!

I don't have a very large collection of keyboards though, so the data is pretty limited so far. If you want to try it on your own keyboard, see the instructions on the keyboard latency tester README, and please send me a PR with your results!

About Me

I got a degree in electrical engineering but promptly became a software engineer instead. I still like tinkering with electronics though, so I discovered ZMK when I was making wireless macropad with a nice!nano, and I became a regular contributor after that. I use mostly larger keyboards with standard layouts and rarely use anything more complicated than momentary layers, so I've mostly focused on improving core features and tooling.

The keyboards I regularly use are a Ducky One 2 TKL that I leave at work, a Freebird TKL[^1], a custom wireless numpad, and a Yamaha CP4.

[^1] Running QMK, but I have designs to make a wireless PCB for it someday...