Package: keymap-drawer
keymap-drawerParse 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 acommand-line toolor aweb application.FeaturesDraw keymap representations consisting of multiple layers, hold-tap keys and combosUses a human-editable YAML format for specifying the keymapNon-adjacent or 3+ key combos can be visualized by specifying its positioning relative to the keys, with automatically drawn dendrons to keysAlternatively, output a separate diagram per combo if you have tricky key position combinationsBootstrap the YAML representation by automatically parsing QMK or ZMK keymap filesArbitrary physical keyboard layouts (with rotated keys!) supported, along with parametrized ortho layoutsBoth parsing and drawing are customizable with a config file, see"Customization" sectionCustom glyph support: render custom svg icons and not just unicode textSee examples inthe live web demofor example inputs and outputs.Compared to to visual editors likeKLE,keymap-drawertakes 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.UsageTry it as a web applicationYou can try the keymap parsing and drawing functionalities with aStreamlitweb application available athttps://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 installationThe recommended way to installkeymap-draweris throughpipx, which sets up an isolated environment and installs the application with a single command:pipxinstallkeymap-drawerThis will make thekeymapcommand available in yourPATHto use:keymap--helpAlternatively, you canpip install keymap-drawerin a virtual environment or install into your user install directory withpip install --user keymap-drawer.
Seethe development sectionfor instructions to install from source.Bootstrapping your keymap representationkeymap parsecommand helps to parse an existing QMK or ZMK keymap file into the keymap YAML representation thedrawcommand uses to generate SVGs.-c/--columnsis 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 fromQMK Configurator, converted fromkeymap.cviaqmk c2json, or from a VIA backup json viaqmk via2json:# from keymap.cqmkc2json~/qmk_firmware/keyboards/ferris/keymaps/username/keymap.c|keymapparse-c10-q->sweep_keymap.yaml# from VIA backupqmkvia2json-kbferris/sweepsweep_via_backup.json|keymapparse-c10-q->sweep_keymap.yamlDue to current limitations of thekeymap.jsonformat, combos and#define'd layer names will not be present in the parsing output.
However you can manually specify layer names using the layer names parameter, e.g.keymap parse --layer-names Base Sym Nav ....ZMK:.keymapfiles are used for parsing. These will be preprocessed similar to the ZMK build system, so#define's and#includes will be expanded.keymapparse-c10-z~/zmk-config/config/cradio.keymap>sweep_keymap.yamlCurrently combos, hold-taps, mod-morphs, sticky keys and layer names can be determined via parsing.
For layer names, the value of thelabelproperty will take precedence over the layer's node name if provided.As an alternative to parsing, you can also check out theexamplesto find a layout similar to yours to use as a starting point.Tweaking the produced keymap representationWhile 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 tothe keymap schema specificationwhile making changes:(If starting from a QMK keymap) Add combo definitions using key position indices.Tweak the display form of parsed keys, e.g., replacing&bootloaderwithBOOT. (Seethe customization sectionto modify parser's behavior.)If you have combos between non-adjacent keys or 3+ key positions, addalignand/oroffsetproperties in order to position them betterAdd or modifytypespecifiers for certain keys, like"ghost"for keys optional to the layoutIt might be beneficial to start bydraw'ing the current representation and iterate over these changes, especially for tweaking combo positioning.ℹ️ Preserving manual modificationsIf you need to re-parse a firmware file after it was changed, you can provide the previous parse output that you tweaked to the
parse command viakeymap parse -b old_keymap.yaml ... >new_keymap.yamland the tool will try to preserve your manual tweaks.Producing the SVGFinal step is to produce the SVG representation using thekeymap drawcommand.
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.If you produced your keymap YAML throughkeymap parse, it will have tried to guess the proper layout in thelayoutfield of your keymap.
If you like you can tweak the field value according to thespec, then finally call the draw command:keymapdrawsweep_keymap.yaml>sweep_keymap.ortho.svgAnd you are done! You can render the SVG on your browser or use a tool likeCairoSVGorInkscapeto export to a different format.ℹ️ Specifying layouts in the CLIIf you like you can override the layout specification on the command line.
For instance you can provide a QMK keyboard name with-q/--qmk-keyboardand layout with-l/--qmk-layout,
or an ortho layout with-o/--ortho-layout(using YAML syntax for the value). Seekeymap draw --helpfor details.CustomizationBoth parsing and drawing can be customized using a configuration file passed to thekeymapexecutable.
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:keymapdump-config>my_config.yamlThen, edit the file to change the settings, referring toCONFIGURATION.md.
You can then pass this file to eitherdrawandparsesubcommands with the-c/--configargument (note the location before the subcommand):keymap-cmy_config.yamlparse[...]>my_keymap.yaml
keymap-cmy_config.yamldraw[...]my_keymap.yaml>my_keymap.svgSince configuration classes arePydantic settingsthey can also be overridden by environment variables with aKEYMAP_prefix:KEYMAP_raw_binding_map='{"&bootloader": "BOOT"}'keymapparse-zzmk-config/config/cradio.keymap>cradio.yamlDrawing parameters that are specified in thedraw_configfield can also be overridden inthe keymap YAML.Custom GlyphsCustom glyphs can be defined in thedraw_configblock of the keymap config.
After a glyph is defined it can be used in key fields via the glyph name surrounded by$$, e.g.$$vol_up$$.
The provided svg must specify aviewBox, positional or dimensional properties will be calculated bykeymap-drawer.
The height of the svg is bound by the config propertiesglyph_{tap,hold,shifted}_sizeand width will maintain the aspect ratio.
To allow for customization, glyphs are assigned CSS classesglyphand<glyph_name>.Example:draw_config:# specify the size to bound the vertical dimension of your glyph, below are defaultsglyph_tap_size:14glyph_hold_size:12glyph_shifted_size:10glyphs:# mapping of glyph name to be used to svg definitionvol_up:|<svg viewBox="2 3 34 33"><path style="stroke: black; fill: black;" d="M23.41,25.25a1,1,0,0,1-.54-1.85,6.21,6.21,0,0,0-.19-10.65,1,1,0,1,1,1-1.73,8.21,8.21,0,0,1,.24,14.06A1,1,0,0,1,23.41,25.25Z"/><path style="stroke: black; fill: black;" d="M25.62,31.18a1,1,0,0,1-.45-1.89A12.44,12.44,0,0,0,25,6.89a1,1,0,1,1,.87-1.8,14.44,14.44,0,0,1,.24,26A1,1,0,0,1,25.62,31.18Z"/><path style="stroke: black; fill: black;" d="M18.33,4,9.07,12h-6a1,1,0,0,0-1,1v9.92a1,1,0,0,0,1,1H8.88l9.46,8.24A1,1,0,0,0,20,31.43V4.72A1,1,0,0,0,18.33,4Z"/></svg>layers:Media:-["","$$vol_up$$","","",""]You can also use the$$source:id$$notation forcertain sourcesto automatically fetch
the SVGs without having to define them manually in theglyphsfield, e.g.$$tabler:volume$$.
The followingsourcevalues are currently supported:tabler:Tabler Icons(icon name asid)mdi:Pictogrammers Material Design Icons(icon name asid)mdil:Pictogrammers Material Design Icons Light(icon name asid)material:Google Material Symbols(use value in "Android" tab asid)Fetched SVGs will becached by defaultto speed up future runs.Setting up an automated drawing workflowIf you use aZMK config repo, you can set up an automated workflow that parses and draws your keymaps, then commits the YAML parse outputs and produced SVGs to your repo.
To do that you can add a new workflow to your repo at.github/workflows/draw-keymaps.ymlthat refers to the reusablekeymap-drawerworkflow:# Example for using the keymap-drawer ZMK user config workflowname:Draw ZMK keymapson:workflow_dispatch:# can be triggered manuallypush:# automatically run on changes to following pathspaths:-"config/*.keymap"-"config/*.dtsi"-"keymap_drawer.config.yaml"# - 'config/boards/*/*/*.keymap'jobs:draw:uses:caksoylar/keymap-drawer/.github/workflows/draw-zmk.yml@mainpermissions:contents:write# allow workflow to commit to the repowith:keymap_patterns:"config/*.keymap"# path to the keymaps to parseconfig_path:"keymap_drawer.config.yaml"# config file, ignored if not existsoutput_folder:"keymap-drawer"# path to save produced SVG and keymap YAML filesparse_args:""# map of extra args to pass to `keymap parse`, e.g. "corne:'-l Def Lwr Rse' cradio:''"draw_args:""# map of extra args to pass to `keymap draw`, e.g. "corne:'-k corne_rotated' cradio:'-k paroxysm'"Modifying the workflow-generated commitThe workflow will add the generated SVG and keymap representation YAML files to theoutput_folder, and generate a new commit with commit message "keymap-drawer render" by default. You can modify this commit message with thecommit_messageinput param, e.g.:jobs:draw:uses:caksoylar/keymap-drawer/.github/workflows/draw-zmk.yml@mainwith:# Use the triggering commit's message, prepending the "[Draw]" tagcommit_message:"[Draw]${{github.event.head_commit.message}}"# …other inputsAlternatively, you can choose to amend the triggering commit instead of generating a new one by using theamend_commit: trueoption. In this case the triggering commit's message will be used by default, and thecommit_messageinput will be ignored. E.g.:jobs:draw:uses:caksoylar/keymap-drawer/.github/workflows/draw-zmk.yml@mainwith:amend_commit:true# …other inputs⚠️ Rewriting historyYou should understand the implications of rewriting history if you amend a commit that has already been published. Seeremarksingit-rebasedocumentation.CommunityBelow are a few example usages from the community that might be inspirational, whether they are doing unique things with styling, configuration or legends used, or integratekeymap-drawerinto other workflows.minusfive's ZMK config: Uses anextensive config filefor great results out of the automated drawing workflow, with plenty of SVG glyphsSethMilliken's Swept Corne config: Another config using the automated workflow with anice configurationand SVG glyphsjbarr21'skeymap-display: Uses aconverter scriptto convert QMKkeymap.cto a keymap YAMLcasuanoob's keymap: Many useful unicode and SVG glyphs in thekeymap YAMLpossumvibes's keymap: Separate layer and combo diagramsinfused-kim's ZMK config:Uses the--base-keymapfeatureto pre-assign held keys and combo positionscrides's Fissure write-up: Custom physical layout with non-square keys and unique SVG stylingIf you usekeymap-drawer, tag your Github repo with thekeymap-drawertopicand it will show up for anyone else searching for it!DevelopmentThis project requires Python 3.10+ and usesPoetryfor packaging.To get started,install Poetry, clone this repo, then install dependencies with thepoetrycommand:gitclonehttps://github.com/caksoylar/keymap-drawer.gitcdkeymap-drawer
poetryinstall# --with dev,lsp optional dependenciespoetry shellwill activate a virtual environment with thekeymap_drawermodule in Python path andkeymapexecutable 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 withpip install --editable .inside thekeymap-drawerfolder.The source code for the Streamlit app lives in thekeymap-drawer-webrepo.Related projects@nickcoutsos's ZMK keymap editorThe originalkeymap@jbarr21's keymap parser@leiserfg's ZMK parserKeymapvizKeymap YAML specificationThis page documents the YAML-format keymap representation that is output bykeymap parseand used bykeymap draw.At the root, four fields can be specified which are detailed in respective sections. A typical keymap will have the following structure:layout:# physical layout specs, optional if used in CLI...layers:# ordered mapping of layer name to contentslayer_1:# list of (lists of) key specs-[Q,W,...]...layer_2:...combos:# list of combo specs, optional-...draw_config:# config overrides for drawing, optional-...layoutThis field provides information about the physical layout of the keyboard, i.e., the location and sizes of individual keys.keymap-drawerunderstands two types of physical layout descriptions:QMKinfo.jsonlayoutspecification:
This is theofficial QMK formatfor physical key descriptions
that everyinfo.jsonfile in the QMK firmware repository uses.keymap-draweronly uses thex,y,r,rxandryfields.
Note thatkeymap-editorutilizesthe same formatforinfo.json.QMK spec also lets you specify multiple "layouts" per keyboard corresponding to different layout macros to support physical variations.You can create your own physical layout definitions in QMK format to use withkeymap-drawer, which accepts JSONs with the official schema that
has layouts listed under thelayoutkey, or one that directly consists of a list of key specs as a shortcut. The best way to generate one is to use
the interactiveKeymap Layout Helper tooltool by @nickcoutsos. This web app is useful to
visualize a given JSON definition, re-order keys using the "Re-order" tool and generate one from scratch from various formats such as KLE or Kicad
PCBs using the "Import" tool.Note that the behavior of the layout helper andkeymap-drawerdiffers for rotated keys when omittingrx,ryparameters --keymap-drawerassumes rotation around the key center and layout helper assumes rotation around the top left of the key.
For this reason it is recommended to explicitly specifyrx,ryfields ifris specified. You might also want to omit the fields
besidesx,y,r,rxandryin your final JSON since they won't be used bykeymap-drawer.Parametrized ortholinear layouts:
This lets you specify parameters to automatically generate a split or non-split ortholinear layout.Following physical layout parameters can be specified either in the command line or under this field definition as key-value pairs:qmk_keyboard(equivalent to-k/--qmk-keyboardon the command line):
Specifies the keyboard name to use with QMKinfo.jsonformat layout definition, retrieved from following sources in order of preference:<keyboard>.json(with/'s in<keyboard>replaced by@) underresources/qmk_layouts, if it existsQMK keyboard metadata APIthatQMK Configuratoralso usesExample:layout: {qmk_keyboard: crkbd/rev1}qmk_info_json(equivalent to-j/--qmk-info-jsonon the command line):
Specifies the path to a local QMK formatinfo.jsonfile to useExample:layout: {qmk_info_json: my_special_layout.json}qmk_layout(equivalent to-l/--qmk-layouton the command line):
Specifies the layout macro to be used for the QMK keyboard, defaults to first one specified if not used --
should be used alongside one of the above two optionsExample:layout: {qmk_keyboard: crkbd/rev1, qmk_layout: LAYOUT_split_3x5_3}ortho_layout(equivalent to-o/--ortho-layouton the command line):
Specifies a mapping of parameters to values to generate an ortholinear physical layout, with schema:field nametypedefault valuedescriptionsplitboolFalsewhether the layout is a split keyboard or not, affects a few other options belowrowsintrequiredhow many rows are in the keyboard, excluding the thumb row if splitcolumnsintrequiredhow many columns are in the keyboard, only applies to one half if splitthumbsint | "MIT" | "2x2u"0the number thumb keys per half if split; for non-splits can only take special valuesMITor2x2u[^1]drop_pinkyboolFalsewhether the pinky (outermost) columns have one fewer key, N/A for non-splitsdrop_innerboolFalsewhether the inner index (innermost) columns have one fewer key, N/A for non-splitsExample:layout: {ortho_layout: {split: true, rows: 3, columns: 5, thumbs: 3}}[^1]: Corresponding to bottom row arrangements of a single2ukey, or two neighboring2ukeys, respectively.Hint: You can use theQMK Configuratorto search forqmk_keyboardandqmk_layoutvalues, and preview the physical layout.ℹ️ CLI+keymap YAML specificationIf these parameters are specified in both command line and under thelayoutsection, the former will take precedence.layersThis field is an ordered mapping of layer names to a list ofLayoutKeyspecs that represent the keys on that layer.
ALayoutKeycan be defined with either a string value or with a mapping with the following fields:field name (alias)typedefault valuedescriptiontap (t)str""the tap action of a key, drawn on the center of the key; spaces will be converted to line breaks[^2]hold (h)str""the hold action of a key, drawn on the bottom of the keyshifted (s)str""the "shifted" action of a key, drawn on the top of the keytypestr""the styling of the key that corresponds to theSVG class[^3]. predefined types areheld(a red shading to denote held down keys),ghost(dashed outline to denote optional keys in a layout),trans(lighter text for transparent keys)[^2]: You can prevent line breaks by using double spaces"  "to denote a single non-breaking space.
[^3]: Text styling can be overridden in the SVG config using the"tap","hold"and"shifted"classes if desired.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 needholdortypefields.You can use the special$$..$$syntax to refer to custom SVG glyphs intap/hold/shiftedfields, however note that they cannot be used with other text or glyphs inside the same field value.
See thecustom glyphs sectionfor more information.layersfield 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}combosThis is an optional field that contains a list ofComboSpecs, each of which is a mapping that can have the following fields:field name (alias)typedefault valuedescriptionkey_positions (p)list[int]requiredlist of key indices that trigger the combo[^4]key (k)LayoutKey[^5]requiredkey produced by the combo when triggered,typefield will be ignoredlayers (l)list[str][][^6]list of layers the combo can trigger on, specified using layer names inlayersfieldalign (a)"mid" | "top" | "bottom" | "left" | "right""mid"where to draw the combo:middraws on the mid-point of triggering keys' center coordinates, or to thetop/bottom/left/rightof the triggering keysoffset (o)float0.0additional offset totop/bottom/left/rightpositioning, specified in units of key width/height: useful for combos that would otherwise overlapdendron (d)null | boolnullwhether to draw dendrons going from combo to triggering key coordinates, default is to draw for non-midalignments and draw formidif key coordinates are far from the comboslide (s)null | float (-1 <= val <= 1)nullslide the combo box along an axis between keys -- can be used for movingtop/bottomcombo boxes left/right,left/rightboxes up/down, ormidcombos between two keysarc_scalefloat1.0scale the arcs going left/right fortop/bottomor up/down forleft/rightaligned combostypestr""the styling of the key that corresponds to theSVG class, seeLayoutKeydefinition abovewidth (w)floatnullthe width of the combo box (in pixels), defaults todraw_config.combo_wif nullheight (h)floatnullthe height of the combo box (in pixels), defaults todraw_config.combo_hif nullrotation (r)float0.0the rotation of the combo box in degrees -- only applies to the box itself and not any dendronsdraw_separatenull | boolnullwhether to draw the combo separate from layers, using a dedicated diagram. defaults todraw_config.separate_combo_diagramsif nullAll fields exceptkey_positions,keyandtypeare ignored when combo is drawn in a separate diagram usingdraw_separateordraw_config.separate_combo_diagrams.[^4]: Key indices start from0on the first key position and increase by columns and then rows, corresponding to their ordering in thelayersfield. This matches thekey-positionsproperty in ZMK combo definitions.
[^5]: Just like for keys in a layer under thelayersfield,keyfield can be specified with a string value as a shortcut, or a mapping (where thetypefield will be ignored).
[^6]: The default value of empty list corresponds to all layers in the keymap, similar to thelayersproperty in ZMK.Example:combos:-{p:[0,1],k:Tab,l:[Qwerty]}-{p:[1,2],k:Esc,l:[Qwerty]}draw_configThis optional field lets you overrideconfig parametersfor 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 inDrawConfigclassto values.Example:draw_config:key_h:60combo_h:22combo_w:24