embedded-channel/zephyr/zmk.scm
Stefan d8e6a4f33a Improve redox-neo-keymap.
zephyr/zmk.scm: Formatting.
(/behaviors/hold-perferred-modifier-tap): New behavior.
(special-bindings->zmk-name): Change character for encoder.
(de->hid): Add = for P=.
(redox-neo-keymap): Use new behavior and =.
2024-11-23 15:04:45 +01:00

875 lines
40 KiB
Scheme
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; GNU Guix --- Functional package management for GNU
;;;
;;; Copyright © 2023 Stefan <stefan-guix@vodafonemail.de>
;;;
;;; This file is not part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (zephyr zmk)
#:use-module (guix build union)
#:use-module (guix build utils)
#:use-module (guix build-system trivial)
#:use-module (guix gexp)
#:use-module (guix git-download)
#:use-module ((guix licenses)
#:prefix license:)
#:use-module (guix packages)
#:use-module (ice-9 match)
#:use-module (ice-9 optargs)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:use-module (zephyr)
#:use-module (zephyr apps)
#:use-module (zephyr modules))
(define zmk-config
(package
(name "zmk-config")
(version "0")
(source #f)
(build-system trivial-build-system)
(arguments (list #:builder #~(mkdir #$output)))
(native-search-paths
(list (search-path-specification
(variable "ZMK_CONFIG")
(files '("zmk-config"))
(separator #f)
(file-type 'directory)
(file-pattern "^config$"))))
(home-page "https://zmk.dev/docs/config#config-file-locations")
(synopsis "ZMK firmware configuration")
(description
"This ZMK firmware configuration is a helper to set the ZMK_CONFIG
environment varibale during a ZMK firmware package build to its configuration
input. Add a file-like object like a file-union or a package containing a
zmk-config/config folder as build input to a ZMK firmare packege.")
(license license:expat)))
(define*-public (make-zmk board
#:key
(shield "")
(extra-inputs '())
(extra-name "")
(patches '())
snippet)
"Make a ZMK firmware package for a keyboard consisting of an Arm
microcontroller BOARD with a SHIELD PCB using the list of EXTRA-INPUTS. Add an
EXTRA-NAME with a trailing hyphen to customize the package name. Use PATCHES or
SNIPPET to modify the ZMK sources."
(make-zephyr-application-for-arm
(let* ((revision "1")
(commit "0d3a4b7bbb199103d151ee1cadde613101859054")
(underscore->hyphen (lambda (name)
(string-map (lambda (char)
(if (char=? char #\_)
#\-
char))
name)))
(board-name (underscore->hyphen board))
(shield-name (underscore->hyphen shield))
(shield (if (string-null? shield) #f shield)))
(package
(name (string-append shield-name (if shield "-" "")
extra-name board-name "-zmk"))
(version (git-version "2024.04.28" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/zmkfirmware/zmk")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "0i0656y83lwy2qspymy6fp7w4z68khxk9w1l7gqbv5c4ng8qjl0z"))
(patches patches)
(snippet snippet)))
(build-system #f)
(arguments
(list
#:out-of-source? #t
#:configure-flags
#~(cons*
"-S../source/app"
(string-append "-DBOARD=" #$board)
"-DCMAKE_BUILD_TYPE=RelMinSize"
(if #$shield
(list (string-append "-DSHIELD=" #$shield))
'()))))
(inputs (append extra-inputs
(list zephyr-module-cmsis
zephyr-module-lvgl-8.3.11
zephyr-module-tinycrypt
zmk-config)))
(home-page "https://zmk.dev")
(synopsis (if shield (format #f "ZMK Firmware for a ~a keyboard with ~a"
shield-name board-name)
(format #f "ZMK Firmware for a ~a keyboard"
board-name)))
(description
"ZMK Firmware is an open source (MIT) keyboard firmware built on the
Zephyr Project Real Time Operating System (RTOS).")
(license license:expat)))
#:zephyr zephyr-3.5+zmk-fixes
#:source-prefix "zmk"))
(define*-public (make-zmk-union zmk-packages #:key name synopsis)
"Make a union of several ZMK Firmware packages for left and right hand or
settings-reset firmware files."
(package
(inherit (car zmk-packages))
(name (or name (package-name (car zmk-packages))))
(source #f)
(build-system trivial-build-system)
(arguments
(list
#:modules '((guix build union))
#:builder
#~(begin
(use-modules ((guix build union)))
(union-build #$output (quote #$zmk-packages)))))
(synopsis (or synopsis (package-synopsis (car zmk-packages))))))
(define*-public (make-nrfmicro-13-zmk shield #:key zmk-config (extra-name ""))
"Make a ZMK firmware package for a keyboard consisting of the nrfmicro 1.3/1.4
board with a SHIELD PCB. Use the ZMK-CONFIG directory containing optional
boards/ or dts/ directories, or .conf, .keypad, .overlay files prefixed with
shield or board names."
(make-zmk
"nrfmicro_13"
#:shield shield
#:extra-name extra-name
#:extra-inputs (append (list zephyr-module-hal-nordic-3.1.0)
(if zmk-config (list zmk-config)
'()))
#:snippet
#~(begin
(use-modules (guix build utils))
(substitute* "app/CMakeLists.txt"
;; Move combo.c and behaviour_tap_dance.c above all other behaviors.
;; This fix is needed to get a working layer-tap-dance.
(("^ target_sources\\(app PRIVATE src/combo.c\\)\n")
"")
(("^ target_sources\\(app PRIVATE src/behaviors/behavior_tap_dance.c\\)\n")
"")
(("^ target_sources\\(app PRIVATE src/hid.c\\)\n" line)
(string-append
line
" target_sources(app PRIVATE src/combo.c)\n"
" target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c)\n"))))))
(define-public settings-reset-nrfmicro-13-zmk
(package
(inherit (make-nrfmicro-13-zmk "settings_reset"))
(synopsis "ZMK settings reset firmware for split-keyboards with nrfmicro
1.3/1.4 boards")
(description
"Pairing issues of ZMK firmware split-keyboard halves can be resolved by
flashing this settings reset firmware to both controllers.")))
(define-public redox-right-nrfmicro-13-zmk
(make-nrfmicro-13-zmk "redox_right"))
(define-public redox-nrfmicro-13-zmk
(make-zmk-union
(list (make-nrfmicro-13-zmk "redox_left")
redox-right-nrfmicro-13-zmk
settings-reset-nrfmicro-13-zmk
)
#:name "redox-nrfmicro-13-zmk"
#:synopsis "ZMK firmware for a Redox shield with nrfmicro-1.3/1.4 board"))
(define (hid-modifier modifier)
"Map a symbol for a MODIFIER key into a macro symbol for a ZMK keymap file.
An unknown MODIFIER symbol is just returned."
(define hid-modifier->zmk-macro
'(( . LS) ( . LC) ( . LA) ( . LG) ( . LG)
(R . RG) (R . RG) (R . RA) (R . RC) (R . RS)))
(or (assoc-ref hid-modifier->zmk-macro modifier) modifier))
(define-public (special-bindings key-label)
"Map a KEY-LABEL matching a special-binding into a binding symbol for a
ZMK keymap file. An unknown KEY-LABEL symbol is just returned."
(define special-bindings->zmk-name
'(;; A whole in the keyboard matrix without meaning to ZMK.
( . "")
;; No functionality.
( . &none)
;; Fall-through to the next active lower layer.
( . &trans)
;; Repeat the last key-press.
( . &key_repeat)
;; Rotation of sensor, requires two parameters for up and down keycodes.
( . &inc_dec_kp)
;; Reset and bootloader, on split keyboards this is side specific.
( . &sys_reset) ( . &bootloader)
;; Bluetooth, requires one or two parameters.
( . &bt)
;; Backlight, requires one parameter.
( . &bl)))
(or (assoc-ref special-bindings->zmk-name key-label) key-label))
(define-public (hid-layout key-label)
"Map a HID KEY-LABEL into a macro symbol for a ZMK keymap file. Any other
KEY-LABEL will be treated by 'special-bindings'."
(define hid->zmk-name
'(( . ESC) ( . PSCRN) ( . SLCK) ( . PAUSE_BREAK)
(^ . GRAVE) (- . MINUS)
(= . EQUAL) ( . BSPC)
( . TAB) ( . LBKT) ( . RBKT) ( . RET) ( . RET) ( . RET)
( . CAPS) ( . SEMI) ( . SQT) ( . NUHS)
( . LSHFT) (\ . NUBS)
( . COMMA) (· . DOT) (/ . SLASH) (R . RSHFT)
( . LCTRL) ( . LALT) ( . LGUI) ( . LGUI) ( . SPC)
(R . RGUI) (R . RGUI) (R . RALT) (R . RCTRL) ( . K_APP)
( . INS) ( . HOME) ( . HOME) ( . PG_UP)
( . DEL) ( . END) ( . END) ( . PG_DN)
(🌐 . GLOBE)
( . LEFT) ( . DOWN) ( . UP) ( . RIGHT)
( . LEFT) ( . DOWN) ( . UP) ( . RIGHT)
( . KP_NUMLOCK) (NUM . KP_NUMLOCK)
( . KP_CLEAR) ( . KP_LPAR) ( . KP_RPAR) (P= . KP_EQUAL)
(÷ . KP_DIVIDE) (* . KP_MULTIPLY) ( . KP_MINUS) (+ . KP_PLUS)
(P1 . KP_N1) (P2 . KP_N2) (P3 . KP_N3) (P4 . KP_N4) (P5 . KP_N5)
(P6 . KP_N6) (P7 . KP_N7) (P8 . KP_N8) (P9 . KP_N9) (P0 . KP_N0)
(P. . KP_DOT) (P, . KP_COMMA) ( . ENTER)
( . C_AC_CUT) ( . C_AC_COPY) ( . C_AC_PASTE)
( . C_AC_UNDO) ( . C_AC_REDO)
( . C_AL_KEYBOARD_LAYOUT)
( . C_BRIGHTNESS_DEC) ( . C_BRIGHTNESS_INC)
( . C_PREVIOUS) ( . C_PLAY_PAUSE) ( . C_NEXT)
(/ . C_MUTE) ( . C_VOLUME_DOWN) ( . C_VOLUME_UP)))
(special-bindings (or (assoc-ref hid->zmk-name key-label) key-label)))
(define-public (de-layout key-label)
"Map a german KEY-LABEL based on the QWERTZ-layout into an international HID
key-label, if needed, and return a symbol for a ZMK keymap file."
(define de->hid
'((ß . -) (´ . =)
(Z . Y) (Ü . ) (+ . )
(Ö . ) (Ä . )
(< . \) (Y . Z) (- . /)
(= . P=) (/ . ÷) (P+ . +) (P, . P.) (P. . P,)))
(hid-layout (or (assoc-ref de->hid key-label) key-label)))
(define-public (neo-layout key-label)
"Map a german KEY-LABEL based on the neo-layout into the international HID
key-label, if needed, and return a symbol as needed by a ZMK keymap file."
(define neo->de
'((T1 . ^) (¹ . ^)
(X . Q) (V . W) (L . E) (C . R) (W . T)
(M3 . ) (U . A) (I . S) (A . D) (E . F) (O . G)
(M4 . <) (Ü . Y) (Ö . X) (Ä . C) (P . V) (Z . B)
(- . ß) (T2 . ´) (² . ´)
(K . Z) (H . U) (G . I) (F . O) (Q . P) ( . Ü) (T3 . +) (³ . +)
(S . H) (N . J) (R . K) (T . L) (D . Ö) (Y . Ä) (RM3 . )
(B . N) (J . -) (RM4 . R)
(P . ) (+ . P+)))
(de-layout (or (assoc-ref neo->de key-label) key-label)))
(define-public (bindings layout keys)
"Transform a list of KEYS based on LAYOUT into ZMK behavior bindings enclosed
in \"<…>\". Any string in KEYS will be passed through."
(define (zmk-name->string zmk-name)
"Tansform a ZMK-NAME into a string."
(cond ((string? zmk-name) zmk-name)
((number? zmk-name) (number->string zmk-name))
(else (symbol->string zmk-name))))
(define (key-label->zmk key-label)
"Tansform a key-label based on a keyboard-layout into a ZMK string."
(zmk-name->string (layout key-label)))
(define (modified-key->zmk modified-key)
"Transform a possibly MODIFIED-KEY like '(⇧ ⌥ ⎋) into the \"LS(LA(ESC))\"
respresentation of ZMK."
(match modified-key
((modifier modifier-or-key . rest)
(string-append (zmk-name->string (hid-modifier modifier))
"("
(modified-key->zmk (cdr modified-key))
")"))
((unmodified-key)
(modified-key->zmk unmodified-key))
(key-label
(key-label->zmk key-label))))
(define (reference->zmk reference strings-of-layers-or-modified-keys)
"Join a reference symbol like '&mt with STRINGS-OF-LAYERS-OR-MODIFIED-KEYS
as parameters like '(\"LALT\" \"ESC\") into the \"&mt LALT ESC\" respresentation
of ZMK."
(string-join (cons (key-label->zmk reference)
strings-of-layers-or-modified-keys)))
(define (node->zmk node strings-of-layers-or-modified-keys)
"Join a BEHAVIOAR-NODE symbol like '/behaviors/hold-tap with
STRINGS-OF-LAYERS-OR-MODIFIED-KEYS as parameters like '(\"LALT\" \"ESC\")
into the proper node referecne \"&{/behaviors/hold-tap} LALT ESC\"
respresentation of ZMK."
(reference->zmk (string-append "&{" (key-label->zmk node) "}")
strings-of-layers-or-modified-keys))
(define (&-symbol? symbol)
"Predicate to identify a symbol as a ZMK behavior or macro reference
prefixed with &."
(string-prefix? "&" (key-label->zmk symbol)))
(define (/behaviors-or-macros-symbol? symbol)
"Predicate to identify a symbol as a ZMK behavior or macro node prefixed
with /behaviors/ or /macros/."
(any (cute string-prefix? <> (key-label->zmk symbol))
'("/behaviors/" "/macros/")))
(define (key-binding->zmk key-binding)
"Transform the KEY-BINDING, which could be a key-label, a modified key, or a
behavior with layer and modified key parameters, into the representation of a
ZMK behavior for a keymap layer."
(match key-binding
(((? &-symbol? label) . parameters)
;; A list starting with an &-symbol is a behavior or macro reference
;; with parameters. The parameters may be layers or modified keys.
(reference->zmk label (map modified-key->zmk parameters)))
(((? /behaviors-or-macros-symbol? node) . parameters)
;; A list starting with a /behaviors/… or /macros/… symbol is a node
;; with parameters. The parameters may be layers or modified keys.
(node->zmk node (map modified-key->zmk parameters)))
(modified-key
(let ((modified-key (modified-key->zmk modified-key)))
(if (or (&-symbol? modified-key)
(and (string? modified-key)
(string-null? (string-trim-both modified-key))))
;; There is a behavior present or only whitespace, just use it.
modified-key
;; Add a key-press behavior to the modified-key and start over.
(reference->zmk '&kp (list modified-key)))))))
(string-append
"<"
(string-trim-right
(string-join (map (lambda (zmk-behavior)
(string-pad-right
zmk-behavior
(max 23 (string-length zmk-behavior))))
(map key-binding->zmk keys))))
">"))
(define*-public (zmk-keymap #:key (c-defines '())
(behaviors '())
(macros '())
(layers '())
(conditional-layers '())
(combos '())
(properties '()))
"Generate the content of a keymap file for ZMK. Each layer in LAYERS has a
name, a layout and multiple rows, of which each contains the key-bindings. The
last row contains the bindings for sensors. The key-bindings use symbols from
the layout. The C-DEFINES contains a list of C pre-processor macros. The
BEHAVIORS, MACROS, CONDITIONAL-LAYERS and COMBOS contain lists of strings to
inject own appropiate definitions for ZMK. PROPERTIES may contain properties
for behaviors or other device-tree nodes."
(define (include file)
"Return an include statement for file"
(string-append "#include <" file ">"))
(define (include-binding file)
"Return an include statement for file defining bindings."
(include (string-append "dt-bindings/zmk/" file)))
(define (includes)
"Return all include statements offered by ZMK for keymap files."
(append (map include '("behaviors.dtsi"))
(map include-binding '("backlight.h" "bt.h" "ext_power.h"
"hid_usage.h" "hid_usage_pages.h" "keys.h"
"kscan_mock.h" "matrix_transform.h"
"modifiers.h" "mouse.h" "outputs.h" "reset.h"
"rgb.h"))))
(define* (keymap-layer name layer-number layout rows)
"Return a string with a keymap layer definition named NAME with a
LAYER-NUMBER suffix for a ZMK keymap file, consisting of ROWS of keys with their
labels based on LAYOUT. The last row contains the bindings for sensors."
(string-append name "-layer" (number->string layer-number) " {"
"\n bindings ="
"\n "
(string-join (map (cut bindings layout <>)
(drop-right rows 1))
",\n ")
#;(bindings layout (append-map (lambda (row)
(append '("\n ") row))
(drop-right rows 1)))
(if (null? (last rows))
""
(string-append
";\n sensor-bindings = "
(bindings layout (last rows))))
";\n};"))
(define (layer layer layer-number)
"Return a string for a ZMK keymap file containing a layer definition."
(match layer
((name layout . rows)
(keymap-layer name layer-number layout rows))))
(define* (indent lines #:optional (n 4))
"Indent a list of LINES by N spaces. A line containing a list of strings
will be concatenated and a line containing a newline will be split before the indentation."
(map (cute string-append (make-string n #\space) <>)
(append-map (lambda (line)
(string-split (if (string? line)
line
(string-join line ""))
#\newline))
lines)))
(string-join (append (includes)
c-defines
(list "/ {"
" behaviors {")
(indent behaviors)
(list " };"
" macros {")
(indent macros)
(list " };"
" keymap {"
" compatible = \"zmk,keymap\";")
(indent (map layer layers (iota (length layers))))
(list " };"
" conditional_layers {"
" compatible = \"zmk,conditional-layers\";")
(indent conditional-layers)
(list " };"
" combos {"
" compatible = \"zmk,combos\";")
(indent combos)
(list " };"
"};")
properties)
"\n"))
(define-public (positions layer keys)
"Return a string enclosed in \"<…>\" with the positional numbers of KEYS in
the LAYER. This is useful for combos or behaviors relying on the position of
keys in the keyboard matrix. If the layer contains a key multiple times, then
the first key position will be returned. LAYER has a name, a layout and
multiple rows, of which each contains the key-bindings. The last row contains
the bindings for sensors."
(let* ((rows (drop-right (drop layer 2) 1))
(whitespace? (lambda (key)
(and (string? key)
(string-null? (string-trim-both key)))))
(keys-in-matrix (filter
(lambda (key)
(not (or (whitespace? key)
(eq? key '))))
(apply append rows)))
(positions (map (lambda (key)
(list-index (cut equal? key <>) keys-in-matrix))
keys)))
(when (not (null? (remove number? positions)))
(error (format #f "some keys of ~s were not found in the layer rows ~s"
keys
rows)))
(string-append "<" (string-join (map number->string positions)) ">")))
(define*-public (combo name positions bindings #:key timeout-ms
require-prior-idle-ms
slow-release
layers)
"Return a combo configuration describing a combination of key POSITIONS to
be pressed simultaneous to produce e.g. the key press in BINDNGS. Omitted
optional parameters will not be part of the configuration and make use of ZMK
defaults."
(let ((combo-begin
(format #f "combo-~a {" name))
(key-positions
(format #f " key-positions = ~a;" positions))
(bindings
(format #f " bindings = ~a;" bindings))
(timeout-ms
(if timeout-ms
(format #f " timeout-ms = <~a>;" timeout-ms)
""))
(require-prior-idle-ms
(if require-prior-idle-ms
(format #f " require-prior-idle-ms = <~a>;" require-prior-idle-ms)
""))
(slow-release
(if slow-release
" slow-release;"
""))
(layers
(if layers
(format #f " layers = <~a>;"
(string-join (map number->string layers)))
""))
(combo-end "};"))
(string-join (remove string-null? (list combo-begin
key-positions
bindings
timeout-ms
require-prior-idle-ms
slow-release
layers
combo-end))
"\n")))
(define*-public (vertical-combo name positions bindings #:key
(timeout-ms 200)
require-prior-idle-ms
(slow-release #t)
layers)
"Return a combo configuration describing a combination of key POSITIONS to
be pressed simultaneous to produce e.g. the key press in BINDNGS. Omitted
optional parameters will not be part of the configuration and make use of ZMK
defaults. The defaults for TIMEOUT-MS and SLOW-RELEASE make the combo suited
best for vertically adjacent keys to be pressed with one finger."
(combo name positions bindings #:timeout-ms timeout-ms
#:require-prior-idle-ms require-prior-idle-ms
#:slow-release slow-release
#:layers layers))
;; This is a hold-tap behavior with a balanced flavor for modifiers.
(define-public /behaviors/balanced-modifier-tap
"/omit-if-no-ref/ balanced-modifier-tap {
compatible = \"zmk,behavior-hold-tap\";
#binding-cells = <2>;
flavor = \"balanced\";
tapping-term-ms = <200>;
quick-tap-ms = <200>;
bindings = <&kp &kp>;
};")
;; This is a hold-tap behavior with a hold-preferred flavor for modifiers.
(define-public /behaviors/hold-perferred-modifier-tap
"/omit-if-no-ref/ hold-perferred-modifier-tap {
compatible = \"zmk,behavior-hold-tap\";
#binding-cells = <2>;
flavor = \"hold-preferred\";
hold-while-undecided;
tapping-term-ms = <200>;
quick-tap-ms = <200>;
bindings = <&kp &kp>;
};")
;; This is a hold-tap behavior for modifiers and mouse-buttons.
(define-public /behaviors/modifier-mousebutton
"/omit-if-no-ref/ modifier-mousebutton {
compatible = \"zmk,behavior-hold-tap\";
#binding-cells = <2>;
flavor = \"hold-preferred\";
tapping-term-ms = <200>;
bindings = <&kp &mkp>;
};")
;; This is a double-tap tap-dance with two key-presses.
(define-public /behaviors/bluetooth-clear-or-clear-all
"/omit-if-no-ref/ bluetooth-clear-or-clear-all {
compatible = \"zmk,behavior-tap-dance\";
#binding-cells = <0>;
tapping-term-ms = <200>;
bindings = <&bt BT_CLR &bt BT_CLR_ALL>;
};")
;; This is a hold-tap behavior with tap-preferred flavor.
(define-public /behaviors/hold-tap
"/omit-if-no-ref/ hold-tap {
compatible = \"zmk,behavior-hold-tap\";
#binding-cells = <2>;
flavor = \"tap-preferred\";
tapping-term-ms = <400>;
require-prior-idle-ms = <400>;
quick-tap-ms = <200>;
bindings = <&kp &kp>;
};")
;; This is a hold-tap behavior for a key, which momentarily activates a
;; layer, if hold, or switches to that layer, if tapped.
(define-public /behaviors/layer-hold-tap
"/omit-if-no-ref/ layer-hold-tap {
compatible = \"zmk,behavior-hold-tap\";
#binding-cells = <2>;
flavor = \"balanced\";
tapping-term-ms = <200>;
bindings = <&mo &to>;
};")
(define*-public (/behaviors/4-layer-tap-dance n #:key (start-layer 0))
"Give a tap-dance behavior, which counts the taps for the layer number and
momentarily activates that layer on hold, or switches to that layer on tap. If
the parameter N is 0, then taps select the layers 1, 2, 3. If N is 1, taps
select the layers 0, 2, 3, and so on.
As it is not possible to momentarily activate a lower layer from an upper layer,
it is expected that the layers 0, 1, 2 are duplicated in the layers 4, 5, 6. In
total seven layers are needed instead of four.
The START-LAYER defines the range of layers: START-LAYER ≤ N < START-LAYER + 4.
The examples above were given for the START-LAYER 0."
(let* ((a start-layer)
(b (1+ a))
(c (1+ b))
(d (1+ c))
(a' (+ a 4))
(b' (+ b 4))
(c' (+ c 4))
(layers (lambda (mo to)
(string-join (map number->string (list mo to)))))
(layer-n-hold-tap-i (if (<= n a) (layers b b) (layers a' a)))
(layer-n-hold-tap-j (if (<= n b) (layers c c) (layers b' b)))
(layer-n-hold-tap-k (if (<= n c) (layers d d) (layers c' c))))
(when (or (< n a)
(> n d))
(error
(format
#f
"/behaviors/4-layer-tap-dance: N = ~a is out of range ~a ≤ N ≤ ~a"
n a d)))
(string-append
"/omit-if-no-ref/ 4-layer-tap-dance-" (number->string n) " {
compatible = \"zmk,behavior-tap-dance\";
#binding-cells = <0>;
tapping-term-ms = <200>;
bindings = <&{/behaviors/layer-hold-tap} " layer-n-hold-tap-i "
&{/behaviors/layer-hold-tap} " layer-n-hold-tap-j "
&{/behaviors/layer-hold-tap} " layer-n-hold-tap-k ">;
};")))
(define-public redox-neo-keymap
(let* ((M3Y '(/behaviors/balanced-modifier-tap RM3 Y))
(M3. '(/behaviors/balanced-modifier-tap RM3 ·))
(¹ '(/behaviors/hold-perferred-modifier-tap ¹))
( '(/behaviors/hold-perferred-modifier-tap ))
( '(/behaviors/hold-perferred-modifier-tap ))
( '(/behaviors/hold-perferred-modifier-tap ))
(R '(/behaviors/hold-perferred-modifier-tap R ))
(R² '(/behaviors/hold-perferred-modifier-tap R ²))
(R '(/behaviors/modifier-mousebutton R MB4))
(R '(/behaviors/modifier-mousebutton R MB5))
( '(/behaviors/hold-perferred-modifier-tap ))
(R³ '(/behaviors/hold-perferred-modifier-tap R ³))
( '(&mt ))
(l1 '(&lt 1 ))
(l4 '(&lt 4 ))
(l5 '(&lt 5 ))
(l0 '(/behaviors/4-layer-tap-dance-0))
(l1 '(/behaviors/4-layer-tap-dance-1))
(l2 '(/behaviors/4-layer-tap-dance-2))
(l3 '(/behaviors/4-layer-tap-dance-3))
(¹ '( BT_SEL 0))
(² '( BT_SEL 1))
(³ '( BT_SEL 2))
( '( BT_SEL 3))
( '( BT_SEL 4))
( '( BT_DISC 0))
( '( BT_DISC 1))
( '( BT_DISC 2))
( '( BT_DISC 3))
( '( BT_DISC 4))
( '( BT_NXT))
( '( BT_PRV))
( '(/behaviors/bluetooth-clear-or-clear-all))
(K '(/behaviors/hold-tap K))
(H '(/behaviors/hold-tap H))
(G '(/behaviors/hold-tap G))
(F '(/behaviors/hold-tap F))
(Q '(/behaviors/hold-tap Q))
(S '(/behaviors/hold-tap S))
(N '(/behaviors/hold-tap N))
(R '(/behaviors/hold-tap R))
(T '(/behaviors/hold-tap T))
(D '(/behaviors/hold-tap D))
(B '(/behaviors/hold-tap B))
(M '(/behaviors/hold-tap M))
( '(/behaviors/hold-tap ))
(· '(/behaviors/hold-tap ·))
(J '(/behaviors/hold-tap J))
( '(&mkp MB4))
( '(&mkp MB5))
(layer-0+4
`("default" ,neo-layout
( N1 N2 N3 N4 N5 N7 N8 N9 N0 - )
( X V L C W N6 ,K ,H ,G ,F ,Q )
( M3 U I A E O ,S ,N ,R ,T ,D ,M3Y)
(,¹ Ü Ö Ä P Z R R ,B ,M , ,· ,J ,R²)
(, ,l0 ,l1 M4 RM4 R ,l1 ,l0 , ,R³)
()))
(layer-1+5
`("cursor" ,neo-layout
( F1 F2 F3 F4 F5 F8 F9 F10 F11 F12 )
( F6 F7 )
( ,R)
(, R ,R)
( ,l1 , ,l4 ,l1 )
()))
(layer-2+6
`("keypad" ,neo-layout
( = P / * )
( / , P7 P8 P9 + )
( M3 , P4 P5 P6 P, ,M3.)
(, R P1 P2 P3 ,R)
( ,l2 ,l5 M4 RM4 P0 ,l2 )
()))
(layer-3
`("zmk" ,neo-layout
( ,¹ ,² ,³ , , )
( , , , , , )
( , , )
( )
( ,l3 , ,l3 ,l3 ,l3 )
()))
(keymap
(zmk-keymap
#:behaviors (list /behaviors/balanced-modifier-tap
/behaviors/hold-perferred-modifier-tap
/behaviors/modifier-mousebutton
/behaviors/bluetooth-clear-or-clear-all
/behaviors/hold-tap
/behaviors/layer-hold-tap
(/behaviors/4-layer-tap-dance 0)
(/behaviors/4-layer-tap-dance 1)
(/behaviors/4-layer-tap-dance 2)
(/behaviors/4-layer-tap-dance 3))
#:layers (list layer-0+4 layer-1+5 layer-2+6 layer-3
layer-0+4 layer-1+5 layer-2+6)
#:combos
(list (vertical-combo "lhs-shift-up"
(positions layer-1+5 '(F3 ))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "lhs-shift-home"
(positions layer-1+5 '( ))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "lhs-shift-left"
(positions layer-0+4 '(V I))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "lhs-shift-down"
(positions layer-1+5 '( ))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "lhs-shift-right"
(positions layer-1+5 '( ))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "lhs-shift-end"
(positions layer-1+5 '( ))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "rhs-shift-up"
(positions layer-0+4 `(N9 ,G))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "rhs-shift-home"
(positions layer-0+4 `(,K ,S))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "rhs-shift-left"
(positions layer-0+4 `(,H ,N))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "rhs-shift-down"
(positions layer-0+4 `(,G ,R))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "rhs-shift-right"
(positions layer-0+4 `(,F ,T))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "rhs-shift-end"
(positions layer-0+4 `(,Q ,D))
(bindings neo-layout '(( )))
#:layers '(1 5))
(vertical-combo "lhs-pageup"
(positions layer-1+5 '(F1 ))
(bindings neo-layout '()))
(vertical-combo "lhs-backspace"
(positions layer-0+4 '(N2 V))
(bindings neo-layout '()))
(vertical-combo "lhs-up"
(positions layer-1+5 '(F3 ))
(bindings neo-layout '()))
(vertical-combo "lhs-delete"
(positions layer-1+5 '(F4 ))
(bindings neo-layout '()))
(vertical-combo "lhs-pagedown"
(positions layer-1+5 '(F5 ))
(bindings neo-layout '()))
(vertical-combo "lhs-home"
(positions layer-1+5 '( ))
(bindings neo-layout '()))
(vertical-combo "lhs-left"
(positions layer-0+4 '(V I))
(bindings neo-layout '()))
(vertical-combo "lhs-down"
(positions layer-1+5 '( ))
(bindings neo-layout '()))
(vertical-combo "lhs-right"
(positions layer-1+5 '( ))
(bindings neo-layout '()))
(vertical-combo "lhs-end"
(positions layer-1+5 '( ))
(bindings neo-layout '()))
(vertical-combo "rhs-pageup"
(positions layer-0+4 `(N7 ,K))
(bindings neo-layout '()))
(vertical-combo "rhs-backspace"
(positions layer-0+4 `(N8 ,H))
(bindings neo-layout '()))
(vertical-combo "rhs-up"
(positions layer-0+4 `(N9 ,G))
(bindings neo-layout '()))
(vertical-combo "rhs-delete"
(positions layer-0+4 `(N0 ,F))
(bindings neo-layout '()))
(vertical-combo "rhs-pagedown"
(positions layer-0+4 `(- ,Q))
(bindings neo-layout '()))
(vertical-combo "rhs-home"
(positions layer-0+4 `(,K ,S))
(bindings neo-layout '()))
(vertical-combo "rhs-left"
(positions layer-0+4 `(,H ,N))
(bindings neo-layout '()))
(vertical-combo "rhs-down"
(positions layer-0+4 `(,G ,R))
(bindings neo-layout '()))
(vertical-combo "rhs-right"
(positions layer-0+4 `(,F ,T))
(bindings neo-layout '()))
(vertical-combo "rhs-end"
(positions layer-0+4 `(,Q ,D))
(bindings neo-layout '())))
#:properties (list "&mt {quick-tap-ms = <200>;};"
"&lt {quick-tap-ms = <200>;};"))))
(file-union "redox-config"
(list (list "zmk-config/config/redox.keymap"
(plain-file "redox-neo.keymap" keymap))
(list "zmk-config/config/redox.conf"
(plain-file "redox-neo.conf"
;; Left hand options to fetch and report the battery level of the right hand.
"CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY=y
CONFIG_ZMK_BLE_EXPERIMENTAL_CONN=y"))))))
(define-public redox-neo-nrfmicro-13-zmk
(make-zmk-union
(list (make-nrfmicro-13-zmk "redox_left"
#:zmk-config redox-neo-keymap
#:extra-name "neo-")
(make-nrfmicro-13-zmk "redox_right"
#:zmk-config redox-neo-keymap
#:extra-name "neo-")
settings-reset-nrfmicro-13-zmk
redox-neo-keymap)
#:name "redox-neo-nrfmicro-13-zmk"
#:synopsis
"Neo layout ZMK firmware for a Redox shield with nrfmicro-1.3/1.4 board"))