{ description = "QMK Userspace - Personal keyboard firmware configurations"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; # Upstream QMK firmware (for Ploopyco and other standard keyboards) qmk-firmware = { url = "git+https://github.com/qmk/qmk_firmware?submodules=1"; flake = false; }; # BastardKB QMK fork (for Charybdis RP2040/Splinky UF2 builds) bastardkb-qmk-firmware = { url = "git+https://github.com/Bastardkb/bastardkb-qmk?ref=refs/heads/bkb-master&submodules=1"; flake = false; }; # ZSA fork of QMK firmware (for Voyager, Moonlander, Planck EZ, Ergodox EZ) zsa-qmk-firmware = { url = "git+https://github.com/zsa/qmk_firmware?submodules=1"; flake = false; }; # Holykeebs fork of QMK firmware (for Keyball keyboards) keyball-qmk-firmware = { url = "git+https://github.com/holykeebs/qmk_firmware?submodules=1"; flake = false; }; }; outputs = { self, nixpkgs, flake-utils, qmk-firmware, zsa-qmk-firmware, keyball-qmk-firmware, bastardkb-qmk-firmware }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; # Common QMK build dependencies qmkBuildInputs = with pkgs; [ # AVR toolchain (for ATmega-based keyboards like Ploopyco) pkgsCross.avr.buildPackages.gcc avrdude # ARM toolchain (for ARM-based keyboards like Voyager, Keyball) gcc-arm-embedded # Build tools gnumake git which diffutils util-linux # Python with QMK dependencies (python3.withPackages (ps: with ps; [ pip setuptools wheel pillow qmk hid pyusb hjson jsonschema milc pygments dotty-dict intelhex ])) # Additional dependencies libusb1 dfu-programmer dfu-util teensy-loader-cli qmk ]; # Helper to link userspace into QMK firmware setupUserspace = qmkSrc: pkgs.runCommand "qmk-with-userspace" {} '' mkdir -p "$out" # Dereference symlinks from source snapshot so overlay writes stay in $out. cp -rL ${qmkSrc}/. "$out" chmod -R u+w "$out" # Recreate overlay roots as writable real directories. for d in keyboards users layouts; do rm -rf "$out/$d" mkdir -p "$out/$d" if [ -d "${qmkSrc}/$d" ]; then cp -rL "${qmkSrc}/$d"/. "$out/$d/" fi done chmod -R u+w "$out/keyboards" "$out/users" "$out/layouts" # Link keyboards from userspace if [ -d "${self}/keyboards" ]; then for kb in "${self}"/keyboards/*; do [ -e "$kb" ] || continue kbname=$(basename "$kb") if [ -d "$out/keyboards/$kbname" ]; then # Merge with existing keyboard (copy keymaps) cp -r "$kb"/. "$out/keyboards/$kbname/" else # New keyboard, copy entirely cp -r "$kb" "$out/keyboards/" fi done fi # Link users directory if [ -d "${self}/users" ]; then mkdir -p "$out/users" cp -r "${self}/users"/. "$out/users/" fi # Link layouts if [ -d "${self}/layouts" ]; then for layout in "${self}"/layouts/*; do [ -e "$layout" ] || continue layoutname=$(basename "$layout") mkdir -p "$out/layouts/$layoutname" cp -r "$layout"/. "$out/layouts/$layoutname/" done fi ''; # QMK sources with userspace integrated qmkUpstream = setupUserspace qmk-firmware; qmkZsa = setupUserspace zsa-qmk-firmware; qmkKeyball = setupUserspace keyball-qmk-firmware; qmkBastard = setupUserspace bastardkb-qmk-firmware; # Build a QMK firmware buildFirmware = { name, keyboard, keymap, qmkSource }: pkgs.stdenv.mkDerivation { pname = "qmk-${name}"; version = "0.1.0"; src = qmkSource; nativeBuildInputs = qmkBuildInputs; buildPhase = '' runHook preBuild # QMK needs a writable home directory export HOME="$NIX_BUILD_TOP/home" mkdir -p $HOME # Build the firmware make ${keyboard}:${keymap} \ UF2CONV="${pkgs.python3}/bin/python3 util/uf2conv.py" runHook postBuild ''; installPhase = '' runHook preInstall mkdir -p $out # Copy all generated firmware files find . -maxdepth 1 \( -name "*.hex" -o -name "*.bin" -o -name "*.uf2" \) \ -exec cp {} $out/ \; runHook postInstall ''; # Don't try to strip firmware files dontStrip = true; dontPatchELF = true; dontFixup = true; }; # Define keyboard builds keyboards = { # ZSA Voyager - uses ZSA QMK fork voyager-pers = buildFirmware { name = "voyager-pers"; keyboard = "zsa/voyager"; keymap = "pers"; qmkSource = qmkZsa; }; voyager-ball = buildFirmware { name = "voyager-ball"; keyboard = "zsa/voyager"; keymap = "ball"; qmkSource = qmkZsa; }; voyager-mod = buildFirmware { name = "voyager-mod"; keyboard = "zsa/voyager"; keymap = "mod"; qmkSource = qmkZsa; }; # Keyball39 - uses holykeebs QMK fork keyball39-init = buildFirmware { name = "keyball39-init"; keyboard = "keyball/keyball39"; keymap = "init"; qmkSource = qmkKeyball; }; # BastardKB Charybdis (RP2040 Splinky) - uses BastardKB QMK fork charybdis-init = buildFirmware { name = "charybdis-init"; keyboard = "bastardkb/charybdis/4x6"; keymap = "init"; qmkSource = qmkBastard; }; # Ploopyco Trackball - uses upstream QMK ploopyco-trackball-rev1_005-pers = buildFirmware { name = "ploopyco-trackball-rev1_005-pers"; keyboard = "ploopyco/trackball/rev1_005"; keymap = "pers"; qmkSource = qmkUpstream; }; ploopyco-trackball-rev1_007-pers = buildFirmware { name = "ploopyco-trackball-rev1_007-pers"; keyboard = "ploopyco/trackball/rev1_007"; keymap = "pers"; qmkSource = qmkUpstream; }; }; in { # Individual keyboard packages packages = keyboards // { # Build all keyboards all = pkgs.symlinkJoin { name = "qmk-all-keyboards"; paths = builtins.attrValues keyboards; }; default = self.packages.${system}.all; }; # Development shell with QMK tooling devShells.default = pkgs.mkShell { buildInputs = qmkBuildInputs ++ (with pkgs; [ # Additional dev tools qmk ]); shellHook = '' echo "QMK Userspace Development Environment" echo "" echo "Available keyboards:" echo " ZSA (use zsa/qmk_firmware):" echo " - zsa/voyager:pers" echo " - zsa/voyager:ball" echo " - zsa/voyager:mod" echo "" echo " Keyball (use holykeebs/qmk_firmware):" echo " - keyball/keyball39:init" echo "" echo " BastardKB (use BastardKB qmk fork):" echo " - bastardkb/charybdis/4x6:init" echo "" echo " Ploopyco (use upstream qmk_firmware):" echo " - ploopyco/trackball/rev1_005:pers" echo " - ploopyco/trackball/rev1_007:pers" echo "" echo "Build with: nix build .#" echo " e.g.: nix build .#voyager-pers" echo " nix build .#keyball39-init" echo " nix build .#charybdis-init" echo " nix build .#all" echo "" ''; }; # Apps for flashing firmware apps = { flash-voyager = flake-utils.lib.mkApp { drv = pkgs.writeShellScriptBin "flash-voyager" '' ${pkgs.wally-cli}/bin/wally-cli ${self.packages.${system}.voyager-pers}/*.bin ''; }; }; } ); }