ZKeymap (WIP)

March 31, 2025 · View on GitHub

Python DSL for ZMK Keymaps definitions and files generation.

This tool can generate keymaps, transoforms and layouts in json and svg formats.

Motivation

While I still prefer text-based layout definitions over graphical editors, devicetree syntax seems overly complicated. As a result, I created this small language to enable easy and pleasant keymap definitions for ZMK, eliminating the need for graphical editors.

Big Note

You can use unicode chars directly as aliases, it looks good and works well but it is totally optional. All aliases are user defined or can be overridden by the user.

Usage

1. Install zkeymap

pip install zkeymap

2. Add a python file to your config directory, for example mykeymap.py

3. Write your keymap in zkeymap language, here is an example splitkb.py:

src/zkeymap/demo/splitkb.py

4. Generate your devicetree files:

python3 mykeymap.py

That will generate four files as per the example:

FileContentFormat
splitkb_keymap.dtsiKeymap, macros, dances, encodersdevicetree
splitkb_transform.dtsizmk,matrix-transformdevicetree
splitkb_layout.dtsizmk,physical-layoutdevicetree
splitkb_info.jsonphysical layoutQMK info.json
splitkb_layout.svgGraphical layout (built-in generator)svg
splitkb_drawer.svgGraphical layout (keymap-drawer generator)svg
splitkb_switches_layout.svgGraphical layout for switches holessvg

5. Then in your zmk keymap file remove the keymap node and include the generated file example:

#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/bt.h>
#include <dt-bindings/zmk/ext_power.h>
#include <dt-bindings/zmk/outputs.h>
#include <dt-bindings/zmk/matrix_transform.h>

// +------------------------------------+
// | Include the generated keymap here: |
// +------------------------------------+
#include "splitkb_keymap.dtsi"
#include "splitkb_transform.dtsi"
#include "splitkb_layout.dtsi"

/ {

    chosen {
        zmk,kscan = &kscan0;
        zmk,matrix-transform = &marz_split_3x6_4_transform;
        zmk,physical-layout = &marz_split_3x6_4;
    };

    kscan0: kscan {
        compatible = "zmk,kscan-gpio-matrix";
        label = "KSCAN";

        diode-direction = "col2row";
        wakeup-source;

        row-gpios
            = <&pro_micro 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            , <&pro_micro 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            , <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            , <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            ;

    };


};

Commit and push your changes as usual to built the firmware.

Basic language rules

Everything is based around aliases, you define an alias mapping any char (even unicode chars) to ZML Keys or macros or whatever.

Depending on how you decorate the alias, it will be translated into a specific behavior (&lt, &mo, &to, &kp, etc...)

Aliases definitions

To define an alias just express it like this:

alias / "alias" / "translation"

Example: define symbol as an alias of LGUI:

alias / "⌘" / "LGUI"

Then you can use in the keymap as [ ⌘ ] it will be translated to &kp LGUI

Key press (&kp, &sk)

Square brackets syntax [ alias ].

Example: Where a is an alias and X is the alias resolution

syntaxcompiles toNotes
[ a ]&kp XSimple case
[ shift a ]&kp LS(X)With shift mods
[ ⎈ a ]&kp LC(X)With Ctrl mods
[ r⇧ ⎈ a ]&kp RS(LC(X))With RShift+LCtrl mods

For a sticky key variation, just add ~ at the end and &kp will be changed to &sk

syntaxcompiles toNotes
[ lshift ~]&sk LSHIFTOne shot/sticky Shift

Mod-Tap behavior (&mt)

Curly brackets syntax { mod alias }.

Example: Where a is an alias and X is the alias resolution.

syntaxcompiles toNotes
{ shift a }&mt LSHIFT Xhold=lshift, tap=X
{ ⎈ a }&mt LCTRL Xhold=lctrl, tap=X
{ r⇧ a }&mt RSHIFT Xhold=rshift, tap=X

Layer based behaviors (&lt, &mo, &sl, &to, &tog)

Parenthesis syntax ( layer alias ).

Example: Where LAY is a layer, a is an alias and X is the alias resolution.

syntaxcompiles toNotes
( LAY )&mo LAYmomentary layer
( LAY a )&lt LAY Xlayer tap LAY and X
( LAY / ~ )&lt LAY TILDElayer tap LAY and ~
( LAY ~)&sl LAYsticky layer LAY. See the difference with previous
( LAY !)&to LAYabsolute layer LAY
( LAY /)&tog LAYtoggle layer LAY

Raw ZMK stuff

Triangle brackets syntax < whatever >.

Content inside < and > is resolved to raw ZMK code.

Example:

syntaxcompiles toNotes
Araw zmk code
<&lt 1 A>&lt 1 Araw zmk code
<&caps_word>&caps_wordraw zmk code
<&kp LCTRL>&kp LCTRLraw zmk code

Macros

Definitions of macros is done using aliases:

alias / "hello" / macro("[shift h] e l l o")

Then it can be used in a layer:

layer / "def" / r""" hello """

Unicode

A special case for Unicode macros allows to define lower and upper variations:


alias / "∴" / uc(name="t3p", char="∴", shifted="△")

layer / "def" / r""" [ ∴ ] """

In this case [ ∴ ] will be translated to on tap and to on Shift tap.

Status

This project is quite new and experimental, testers and contributors are welcome.

Key areas of contribution:

  1. Documentation
  2. Aliases for different languages/layouts.
  3. Unit tests
  4. Reporting bugs

Common Unicode chars for keyboards

LEFTRIGHTUNICODEDESCRIPTION
r⌘u2318GUI/Command
r⎈u2388Ctrl
r⇧u21E7Shift
r⎇u2387Alt
u23CEEnter
u2423Space
u232bBackspace
u2190Arrow Left
u2191Arrow Up
u2192Arrow Right
u2193Arrow Down
u16D2Bluetooth
u21EACapsLock
🄰u1F130CapsLock
u2399PrintScreen
u21C4Tab
u2196Home
u2198End
u21DEPgUp
u21DFPgDn
u21B6Undo
u21B7Redo
u2702Cut
u2FFbCopy
u29c9Paste
u2699Bootloader

Working examples

AI Usage

Codeium AI was used to generate docstrings and basic unit tests.