{
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 .#<keyboard-name>"
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
'';
};
};
}
);
}