Skip to main content

A module and CLI tool to help parse and draw keyboard layouts.

Project description

keymap-drawer

Parse QMK & ZMK keymaps and draw them in vector graphics (SVG) format, with support for visualizing hold-taps and combos that are commonly used with smaller keyboards.

Available as a command-line tool or a web application.

Example keymap

Features

  • Draw keymap representations consisting of multiple layers, hold-tap keys and combos
    • Uses a human-editable YAML format for specifying the keymap
    • Non-adjacent or 3+ key combos can be visualized by specifying its positioning relative to the keys, with automatically drawn dendrons to keys
  • Bootstrap the YAML representation by automatically parsing QMK or ZMK keymap files
  • Arbitrary physical keyboard layouts (with rotated keys!) supported, along with parametrized ortho layouts
  • Both parsing and drawing are customizable with a config file, see "Customization" section

See examples in the live web demo for example inputs and outputs.

Compared to to visual editors like KLE, keymap-drawer takes a more programmatic approach. It also decouples the physical keyboard layout from the keymap (i.e., layer and combo definitions) and provides the tooling to bootstrap it quickly from existing firmware configuration.

Usage

Try it as a web application

You can try the keymap parsing and drawing functionalities with a Streamlit web application available at https://caksoylar.github.io/keymap-drawer. Below instructions mostly apply for the web interface, where subcommands and option flags are mapped to different widgets in the UX.

Command-line tool installation

The recommended way to install keymap-drawer is through pipx, which sets up an isolated environment and installs the application with a single command:

pipx install keymap-drawer

This will make the keymap command available in your PATH to use:

keymap --help

Alternatively, you can pip install keymap-drawer in a virtual environment or install into your user install directory with pip install --user keymap-drawer. See the development section for instructions to install from source.

Bootstrapping your keymap representation

keymap parse command helps to parse an existing QMK or ZMK keymap file into the keymap YAML representation the draw command uses to generate SVGs. -c/--columns is an optional parameter that specifies the total number of columns in the keymap to better reorganize output layers.

  • QMK: Only json-format keymaps are supported, which can be exported from QMK Configurator, converted from keymap.c via qmk c2json, or from a VIA backup json via qmk via2json:

    qmk c2json ~/qmk_firmware/keyboards/ferris/keymaps/username/keymap.c | keymap parse -c 10 -q - >sweep_keymap.yaml
    

    Due to current limitations of the keymap.json format, combos and #define'd layer names will not be present in the parsing output.

  • ZMK: .keymap files are used for parsing. These will be preprocessed similar to the ZMK build system, so #define's and #includes will be expanded.

    keymap parse -c 10 -z ~/zmk-config/config/cradio.keymap >sweep_keymap.yaml
    

    Currently combos, hold-taps (including custom ones), layer names and sticky keys (&sk/&sl) can be determined via parsing. For layer names, the value of the label property will take precedence over the layer's node name if provided.

    Warning

    Parsing rules currently require that your keymap have nodes named keymap and combos that are nested one level-deep from the root. (These conditions hold for most keymaps by convention.)

As an alternative to parsing, you can also check out the examples to find a layout similar to yours to use as a starting point.

Tweaking the produced keymap representation

While the parsing step aims to create a decent starting point, you will likely want to make certain tweaks to the produced keymap representation. Please refer to the keymap schema specification while making changes:

  1. (If starting from a QMK keymap) Add combo definitions using key position indices.
  2. Tweak the display form of parsed keys, e.g., replacing &bootloader with BOOT. (See the customization section to modify parser's behavior.)
  3. If you have combos between non-adjacent keys or 3+ key positions, add align and/or offset properties in order to position them better
  4. Add type specifiers to certain keys, such as held for layer keys used to enter the current layer or ghost for optional keys

It might be beneficial to start by draw'ing the current representation and iterate over these changes, especially for tweaking combo positioning.

Producing the SVG

Final step is to produce the SVG representation using the keymap draw command. However to do that, we need to specify the physical layout of the keyboard, i.e., how many keys there are, where each key is positioned etc. You can provide this information to keymap-drawer in two ways:

  • QMK info.json specification: Each keyboard in the QMK repo has a info.json file which specifies physical key locations. Using the keyboard name in the QMK repo, we can fetch this information from the keyboard metadata API:

    keymap draw -k ferris/sweep sweep_keymap.yaml >sweep_keymap.svg
    

    You can also specify a layout macro to use alongside the keyboard name if you don't want to use the default one:

    keymap draw -k crkbd/rev1 -l LAYOUT_split_3x5_3 corne_5col_keymap.yaml >corne_5col_keymap.svg
    

    -j flag also allows you to pass a local info.json file instead of the keyboard name.

    Note

    If you parsed a QMK keymap, keyboard and layout information will be populated in the keymap YAML already, so you don't need to specify it in the command line.

    Hint: You can use the QMK Configurator to search for keyboard and layout names, and preview the physical layout.

  • Parametrized ortholinear layouts: You can also specify parameters to automatically generate a split or non-split ortholinear layout, for example:

    keymap draw -o '{split: true, rows: 3, columns: 5, thumbs: 2}' sweep_keymap.yaml >sweep_keymap.ortho.svg
    

    See the keymap specification for parameter definitions.

Note

If you prefer, you can specify physical layouts in the keymap YAML file rather than the command line. This is also necessary for the web interface.

Once you produced the SVG representation, you can render it on your browser or use a tool like CairoSVG or Inkscape to export to a different format.

Customization

Both parsing and drawing can be customized using a configuration file passed to the keymap executable. This allows you to, for instance, change the default keycode-to-symbol mappings while parsing, or change font sizes, colors etc. while drawing the SVG.

Start by dumping the default configuration settings to a file:

keymap dump-config >my_config.yaml

Then, edit the file to change the settings, referring to comments in config.py. You can then pass this file to either draw and parse subcommands with the -c/--config argument (note the location before the subcommand):

keymap -c my_config.yaml parse [...] >my_keymap.yaml
keymap -c my_config.yaml draw [...] my_keymap.yaml >my_keymap.svg

Since configuration classes are Pydantic settings they can also be overridden by environment variables with a KEYMAP_ prefix:

KEYMAP_raw_binding_map='{"&bootloader": "BOOT"}' keymap parse -z zmk-config/config/cradio.keymap >cradio.yaml

Drawing parameters that are specified in the draw_config field can also be overridden in the keymap YAML.

Development

This project requires Python 3.10+ and uses Poetry for packaging.

To get started, install Poetry, clone this repo, then install dependencies with the poetry command:

git clone https://github.com/caksoylar/keymap-drawer.git
cd keymap-drawer
poetry install  # --with dev,lsp,streamlit optional dependencies

poetry shell will activate a virtual environment with the keymap_drawer module in Python path and keymap executable available. Changes you make in the source code will be reflected when using the module or the command.

If you prefer not to use Poetry, You can get an editable install with pip install --editable . inside the keymap-drawer folder.

Related projects

Keymap YAML specification

This page documents the YAML-format keymap representation that is output by keymap parse and used by keymap draw.

At the root, three key values are expected, which are detailed in respective sections. A typical keymap will have the following structure:

layout:      # physical layout specs, optional
  ...
layers:      # ordered mapping of layer name to contents
  layer_1:   # list of (lists of) key specs
    - [Q, W, ...]
    ...
  layer_2:
    ...
combos:      # list of combo specs, optional
  - ...
draw_config: # config overrides for drawing, optional
  - ...

layout

This field provides information about the physical layout of the keyboard, i.e., the location and sizes of individual keys. Following physical layout parameters (mentioned in the README) can be specified either in the command line or under this field definition as key-value pairs:

  • qmk_keyboard (equivalent to -k/--qmk-keyboard on the command line): Specifies the path of the keyboard to retrieve from, in the QMK repository

  • qmk_layout (equivalent to -l/--qmk-layout on the command line): Specifies the layout macro to be used for the QMK keyboard, defaults to first one specified if not used

  • ortho_layout (equivalent to -o/--ortho-layout on the command line): Specifies a mapping of parameters to values to generate an ortholinear physical layout, with schema:

    field name type required? default value description
    split bool yes whether the layout is a split keyboard or not, affects a few other options below
    rows int yes how many rows are in the keyboard, excluding the thumb row if split
    columns int yes how many columns are in the keyboard, only applies to one half if split
    thumbs int | "MIT" | "2x2u" no 0 the number thumb keys per half if split; for non-splits can only take special values MIT or 2x2u[^1]
    drop_pinky bool no False whether the pinky (outermost) columns have one fewer key, N/A for non-splits
    drop_inner bool no False whether the inner index (innermost) columns have one fewer key, N/A for non-splits

[^1]: Corresponding to bottom row arrangements of a single 2u key, or two neighboring 2u keys, respectively.

Note

If these parameters are specified in both command line and under the layout section, the former will take precedence.

layers

This field is an ordered mapping of layer names to a list of LayoutKey specs that represent the keys on that layer. A LayoutKey can be defined with either a string value or with a mapping with the following fields:

field name (alias) type required? default value description
tap (t) str yes the tap action of a key, drawn on the center of the key; spaces will be converted to line breaks
hold (h) str no "" the hold action of a key, drawn on the bottom of the key
type null | "held" | "ghost" no null the styling of the key: held adds a red shading to denote held down keys, ghost adds a gray shading to denote optional keys in a layout

Using a string value such as "A" for a key spec is equivalent to defining a mapping with only the tap field, i.e., {tap: "A"}. It is meant to be used as a shortcut for keys that do not need hold or type fields.

layers field also flattens any lists that are contained in its value: This allows you to semantically divide keys to "rows," if you prefer to do so. The two layers in the following example are functionally identical:

layers:
  flat_layer: ["7", "8", "9", "4", "5", "6", "1", "2", "3", { t: "0", h: Fn }]
  nested_layer:
    - ["7", "8", "9"]
    - ["4", "5", "6"]
    - ["1", "2", "3"]
    - { t: "0", h: Fn }

combos

This is an optional field that contains a list of combo specs, each of which is a mapping that can have the following fields:

field name (alias) type required? default value description
key_positions (p) list[int] yes list of key indices that trigger the combo[^2]
key (k) LayoutKey[^3] yes key produced by the combo when triggered
layers (l) list[str] no [][^4] list of layers the combo can trigger on, specified using layer names in layers field
align (a) "mid" | "top" | "bottom" | "left" | "right" no "mid" where to draw the combo: mid draws on the mid-point of triggering keys' center coordinates, or to the top/bottom/left/right of the triggering keys
offset (o) float no 0.0 additional offset to top/bottom/left/right positioning, specified in units of key width/height: useful for combos that would otherwise overlap
dendron (d) null | bool no null whether to draw dendrons going from combo to triggering key coordinates, default is to draw for non-mid alignments and draw for mid if key coordinates are far from the combo

[^2]: Key indices start from 0 on the first key position and increase by columns and then rows, corresponding to their ordering in the layers field. This matches the key-positions property in ZMK combo definitions. [^3]: Just like for keys in a layer under the layers field, key field can be specified with a string value as a shortcut, or a mapping (where the type field will be ignored). [^4]: The default value of empty list corresponds to all layers in the keymap, similar to the layers property in ZMK.

draw_config

This optional field lets you override config parameters for SVG drawing. This way you can specify drawing configuration for a specific layout and store in the keymap specification. It is a mapping from field names in DrawConfig class to values.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

keymap_drawer-0.1.0-py3-none-any.whl (7.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page