main
1#! /usr/bin/env nix-shell
2#! nix-shell -i bash -p qmk -p keymap-drawer -p librsvg
3# shellcheck shell=bash
4
5# Generate keymap SVGs for keyboards using keymap-drawer
6# Usage: ./generate-keymaps.sh [eyelash|moonlander|all]
7
8set -euo pipefail
9
10SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11KEYMAP_DRAWER_DIR="$SCRIPT_DIR/keymap-drawer"
12OUTPUT_DIR="${OUTPUT_DIR:-$SCRIPT_DIR}"
13DEPS_DIR="$SCRIPT_DIR/.deps"
14
15# Colors for output
16GREEN='\033[0;32m'
17BLUE='\033[0;34m'
18YELLOW='\033[1;33m'
19NC='\033[0m' # No Color
20
21log_info() {
22 echo -e "${BLUE}==>${NC} $1"
23}
24
25log_success() {
26 echo -e "${GREEN}==>${NC} $1"
27}
28
29log_warn() {
30 echo -e "${YELLOW}==>${NC} $1"
31}
32
33# Create output directory
34mkdir -p "$OUTPUT_DIR"
35
36# Ensure zmk-helpers is cloned at the correct revision from west.yml
37ensure_zmk_helpers() {
38 local west_yml="$SCRIPT_DIR/eyelash_corne/config/west.yml"
39 local target_dir="$DEPS_DIR/zmk-helpers"
40
41 if [[ ! -f "$west_yml" ]]; then
42 log_warn "west.yml not found at $west_yml, skipping zmk-helpers setup"
43 return 1
44 fi
45
46 # Parse revision from west.yml (the revision line after "name: zmk-helpers")
47 local revision
48 revision=$(awk '/name: zmk-helpers/{found=1} found && /revision:/{print $2; exit}' "$west_yml")
49 if [[ -z "$revision" ]]; then
50 log_warn "Could not parse zmk-helpers revision from west.yml"
51 return 1
52 fi
53
54 # Parse remote URL from west.yml
55 local remote_name
56 remote_name=$(awk '/name: zmk-helpers/{found=1} found && /remote:/{print $2; exit}' "$west_yml")
57 local url_base
58 url_base=$(awk -v name="$remote_name" '$0 ~ "name: " name {found=1} found && /url-base:/{print $2; exit}' "$west_yml")
59 local repo_url="${url_base}/zmk-helpers.git"
60
61 if [[ ! -d "$target_dir" ]]; then
62 log_info "Cloning zmk-helpers ($revision) to $target_dir..."
63 mkdir -p "$DEPS_DIR"
64 git clone --depth 1 --branch "$revision" "$repo_url" "$target_dir" 2>/dev/null
65 log_success "zmk-helpers cloned at $revision"
66 fi
67}
68
69# Generate resolved config with absolute deps path
70resolve_config() {
71 local config_template="$KEYMAP_DRAWER_DIR/config.yaml"
72 local resolved_config
73 resolved_config=$(mktemp "${TMPDIR:-/tmp}/keymap-drawer-config.XXXXXX.yaml")
74 sed "s|__DEPS_DIR__|$DEPS_DIR|g" "$config_template" >"$resolved_config"
75 echo "$resolved_config"
76}
77
78# Convert SVG to PNG
79svg_to_png() {
80 local svg="$1"
81 local png="${svg%.svg}.png"
82 rsvg-convert -o "$png" "$svg"
83 log_success "Generated: $png"
84}
85
86# Setup deps and config
87ensure_zmk_helpers
88RESOLVED_CONFIG=$(resolve_config)
89trap 'rm -f "$RESOLVED_CONFIG"' EXIT
90
91generate_eyelash_corne() {
92 log_info "Generating keymap for eyelash_corne (ZMK)..."
93
94 local zmk_config="$SCRIPT_DIR/eyelash_corne/config/eyelash_corne.keymap"
95 local output_svg="$OUTPUT_DIR/eyelash_corne.svg"
96
97 if [[ ! -f "$zmk_config" ]]; then
98 log_warn "ZMK config not found at $zmk_config"
99 return 1
100 fi
101
102 # Parse ZMK keymap and draw SVG
103 keymap -c "$RESOLVED_CONFIG" parse -z "$zmk_config" |
104 keymap -c "$RESOLVED_CONFIG" draw - >"$output_svg"
105
106 log_success "Generated: $output_svg"
107 svg_to_png "$output_svg"
108}
109
110generate_moonlander() {
111 log_info "Generating keymap for moonlander (QMK)..."
112
113 local qmk_firmware_dir="$SCRIPT_DIR/moonlander/build/qmk_firmware"
114 local keyboard="zsa/moonlander"
115 local keymap_name="vincent"
116 local qmk_json="/tmp/moonlander_keymap.json"
117 local keymap_yaml="/tmp/moonlander_keymap.yaml"
118 local output_svg="$OUTPUT_DIR/moonlander.svg"
119
120 # Check if QMK firmware is checked out
121 if [[ ! -d "$qmk_firmware_dir" ]]; then
122 log_warn "QMK firmware not found. Running checkout..."
123 (cd "$SCRIPT_DIR/moonlander" && ./go.sh checkout)
124 fi
125
126 # Ensure symlink exists
127 if [[ ! -L "$qmk_firmware_dir/keyboards/$keyboard/keymaps/$keymap_name" ]]; then
128 log_info "Creating symlink for keymap..."
129 mkdir -p "$qmk_firmware_dir/keyboards/$keyboard/keymaps"
130 ln -rvsf "$SCRIPT_DIR/moonlander/config" "$qmk_firmware_dir/keyboards/$keyboard/keymaps/$keymap_name"
131 fi
132
133 # Convert keymap to JSON using QMK CLI from the firmware directory
134 log_info "Converting QMK keymap to JSON..."
135 if ! (cd "$qmk_firmware_dir" && qmk c2json --no-cpp -kb "$keyboard" -km "$keymap_name" >"$qmk_json" 2>/dev/null); then
136 log_warn "QMK conversion failed."
137 log_warn "Alternative: Use QMK Configurator to export JSON or manually create YAML."
138 return 1
139 fi
140
141 # Parse QMK JSON to YAML
142 log_info "Parsing keymap to YAML..."
143 keymap -c "$RESOLVED_CONFIG" parse -c 14 -q "$qmk_json" >"$keymap_yaml"
144
145 # Add manual combo definitions to the YAML
146 log_info "Adding manual combo definitions..."
147 cat >>"$keymap_yaml" <<'EOF'
148combos:
149 # Layer switching combos (L0=Bépo, L1=ErgoL, L2=QWERTY)
150 - { p: [67, 70], k: "→ Bépo", l: [L1, L2], draw_separate: true }
151 - { p: [66, 71], k: "→ ErgoL", l: [L0, L2], draw_separate: true }
152 - { p: [58, 61], k: "→ QWERTY", l: [L0, L1], draw_separate: true }
153 - { p: [17, 18], k: "⇄ Mouse", draw_separate: true }
154
155 # Escape combos (layer-specific)
156 - { p: [39, 40], k: ESC, l: [L0] }
157 - { p: [39, 40], k: ESC, l: [L2] }
158
159 # Special character combos (available on all layers)
160 - { p: [15, 16], k: "|" }
161 - { p: [16, 17], k: "@" }
162 - { p: [17, 18], k: "#" }
163 - { p: [18, 19], k: "&" }
164 - { p: [18, 32], k: "$" }
165 - { p: [17, 31], k: "/" }
166 - { p: [31, 45], k: "\\" }
167 - { p: [16, 30], k: "-" }
168 - { p: [32, 46], k: "_" }
169 - { p: [30, 44], k: "=" }
170
171 # Bracket combos (available on all layers)
172 - { p: [23, 38], k: "(" }
173 - { p: [38, 50], k: ")" }
174 - { p: [22, 37], k: "{" }
175 - { p: [37, 49], k: "}" }
176 - { p: [24, 39], k: "[" }
177 - { p: [39, 51], k: "]" }
178 - { p: [23, 24], k: "<" }
179 - { p: [24, 25], k: ">" }
180
181 # Additional character combos
182 - { p: [22, 36], k: '"' }
183 - { p: [19, 33], k: "~" }
184 - { p: [33, 47], k: "%" }
185 - { p: [36, 48], k: '`' }
186 - { p: [26, 40], k: "*" }
187 - { p: [40, 52], k: "+" }
188
189 # Leader key combo (available on all layers)
190 - { p: [31, 32], k: LEADER }
191EOF
192
193 # Draw SVG from YAML with combos
194 log_info "Drawing SVG with combos..."
195 if keymap -c "$RESOLVED_CONFIG" draw "$keymap_yaml" >"$output_svg" 2>&1; then
196 log_success "Generated: $output_svg"
197 svg_to_png "$output_svg"
198 # Clean up temp files
199 rm -f "$qmk_json" "$keymap_yaml"
200 else
201 log_warn "Failed to draw SVG. YAML file saved at: $keymap_yaml"
202 log_info "You can inspect it for debugging"
203 return 1
204 fi
205}
206
207generate_keyball44() {
208 log_info "Generating keymap for keyball44 (ZMK)..."
209
210 local zmk_config="$SCRIPT_DIR/keyball44/config/keyball44.keymap"
211 local layout_json="$SCRIPT_DIR/keyball44/config/keyball44.json"
212 local output_svg="$OUTPUT_DIR/keyball44.svg"
213 local keymap_yaml
214 keymap_yaml=$(mktemp "${TMPDIR:-/tmp}/keyball44-keymap.XXXXXX.yaml")
215
216 if [[ ! -f "$zmk_config" ]]; then
217 log_warn "ZMK config not found at $zmk_config"
218 return 1
219 fi
220
221 # Parse ZMK keymap to YAML, then replace layout with JSON reference
222 keymap -c "$RESOLVED_CONFIG" parse -z "$zmk_config" >"$keymap_yaml"
223
224 # Replace the layout line with the JSON physical layout
225 sed -i "s|^layout:.*|layout: {qmk_info_json: $layout_json}|" "$keymap_yaml"
226
227 # Draw SVG from YAML
228 keymap -c "$RESOLVED_CONFIG" draw "$keymap_yaml" >"$output_svg"
229 rm -f "$keymap_yaml"
230
231 log_success "Generated: $output_svg"
232 svg_to_png "$output_svg"
233}
234
235# Main logic
236case "${1:-all}" in
237eyelash | eyelash_corne)
238 generate_eyelash_corne
239 ;;
240moonlander)
241 generate_moonlander
242 ;;
243keyball | keyball44)
244 generate_keyball44
245 ;;
246all)
247 generate_eyelash_corne || true
248 generate_moonlander || true
249 generate_keyball44 || true
250 ;;
251*)
252 echo "Usage: $0 [eyelash|moonlander|keyball44|all]"
253 echo ""
254 echo "Options:"
255 echo " eyelash Generate only eyelash_corne keymap"
256 echo " moonlander Generate only moonlander keymap"
257 echo " keyball44 Generate only keyball44 keymap"
258 echo " all Generate all keymaps (default)"
259 echo ""
260 echo "Output directory: $OUTPUT_DIR"
261 exit 1
262 ;;
263esac
264
265log_success "Done! SVGs saved to: $OUTPUT_DIR"