Stefan
d8e6a4f33a
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 =.
875 lines
40 KiB
Scheme
875 lines
40 KiB
Scheme
;;; 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␣ '(< 1 ␣))
|
||
(l4␣ '(< 4 ␣))
|
||
(l5␣ '(< 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>;};"
|
||
"< {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"))
|