{ 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; }; # 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 }: 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 ])) # 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" {} '' cp -r ${qmkSrc} $out chmod -R +w $out # Link keyboards from userspace if [ -d ${self}/keyboards ]; then for kb in ${self}/keyboards/*; do kbname=$(basename $kb) if [ -d "$out/keyboards/$kbname" ]; then # Merge with existing keyboard (copy keymaps) cp -r $kb/* $out/keyboards/$kbname/ 2>/dev/null || true else # New keyboard, copy entirely cp -r $kb $out/keyboards/ fi done fi # Link users directory if [ -d ${self}/users ]; then cp -r ${self}/users/* $out/users/ 2>/dev/null || true fi # Link layouts if [ -d ${self}/layouts ]; then for layout in ${self}/layouts/*; do layoutname=$(basename $layout) mkdir -p $out/layouts/$layoutname cp -r $layout/* $out/layouts/$layoutname/ 2>/dev/null || true done fi ''; # QMK sources with userspace integrated qmkUpstream = setupUserspace qmk-firmware; qmkZsa = setupUserspace zsa-qmk-firmware; qmkKeyball = setupUserspace keyball-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} 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; }; # 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 " 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 .#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 ''; }; }; } ); }