| 1 | { |
| 2 | description = "automatic loudness and per-app volume control (pipewire)"; |
| 3 | |
| 4 | inputs = { |
| 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; |
| 6 | }; |
| 7 | |
| 8 | outputs = |
| 9 | { |
| 10 | self, |
| 11 | nixpkgs, |
| 12 | }: |
| 13 | let |
| 14 | systems = [ |
| 15 | "x86_64-linux" |
| 16 | "aarch64-linux" |
| 17 | ]; |
| 18 | forAllSystems = nixpkgs.lib.genAttrs systems; |
| 19 | |
| 20 | pkgsFor = |
| 21 | system: |
| 22 | import nixpkgs { |
| 23 | inherit system; |
| 24 | }; |
| 25 | |
| 26 | perSystem = |
| 27 | system: |
| 28 | let |
| 29 | pkgs = pkgsFor system; |
| 30 | |
| 31 | # native libs the audio crates link against |
| 32 | nativeAudioBuildInputs = with pkgs; [ |
| 33 | pipewire |
| 34 | pipewire.dev |
| 35 | ]; |
| 36 | |
| 37 | nativeBuildTools = with pkgs; [ |
| 38 | pkg-config |
| 39 | clang |
| 40 | ]; |
| 41 | |
| 42 | # system libs iced (wgpu/winit/cosmic-text) compiles + dlopens at runtime. |
| 43 | # wayland-only (x11 feature off); re-add libx11/libxcursor/libxi/libxrandr/libxcb if x11 re-enabled |
| 44 | guiLibs = with pkgs; [ |
| 45 | vulkan-loader |
| 46 | libxkbcommon |
| 47 | wayland |
| 48 | libGL |
| 49 | fontconfig |
| 50 | ]; |
| 51 | |
| 52 | commonEnv = { |
| 53 | LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; |
| 54 | PKG_CONFIG_PATH = "${pkgs.pipewire.dev}/lib/pkgconfig"; |
| 55 | }; |
| 56 | in |
| 57 | { |
| 58 | # nix develop — dev environment |
| 59 | devShell = pkgs.mkShell ( |
| 60 | { |
| 61 | name = "headroom-dev"; |
| 62 | |
| 63 | nativeBuildInputs = nativeBuildTools ++ [ |
| 64 | pkgs.cargo |
| 65 | pkgs.clippy |
| 66 | pkgs.rust-analyzer |
| 67 | pkgs.rustc |
| 68 | pkgs.rustfmt |
| 69 | ]; |
| 70 | |
| 71 | buildInputs = |
| 72 | nativeAudioBuildInputs |
| 73 | ++ guiLibs |
| 74 | ++ (with pkgs; [ |
| 75 | socat # poke the ipc socket |
| 76 | jq # pretty-print json |
| 77 | pipewire # pw-cli, pw-cat, etc. |
| 78 | wireplumber |
| 79 | ]); |
| 80 | |
| 81 | shellHook = '' |
| 82 | echo "headroom dev shell — rustc $(rustc --version | cut -d' ' -f2)" |
| 83 | echo " cargo build / cargo test for iteration." |
| 84 | echo " nix build .#headroom for the packaged binary." |
| 85 | echo " cargo run -p headroom-gui for the GUI (needs a display)." |
| 86 | export RUST_BACKTRACE=1 |
| 87 | export RUST_LOG=headroom=debug,info |
| 88 | # iced/wgpu dlopen vulkan/wayland/xkbcommon at runtime |
| 89 | export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath guiLibs}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" |
| 90 | ''; |
| 91 | } |
| 92 | // commonEnv |
| 93 | ); |
| 94 | |
| 95 | # nix build — packaged daemon + cli |
| 96 | headroom = pkgs.rustPlatform.buildRustPackage ( |
| 97 | { |
| 98 | pname = "headroom"; |
| 99 | # workspace version (per-crate manifests use version.workspace = true) |
| 100 | version = |
| 101 | (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version |
| 102 | + "-" |
| 103 | + (toString self.lastModifiedDate); |
| 104 | |
| 105 | src = ./.; |
| 106 | |
| 107 | cargoLock = { |
| 108 | lockFile = ./Cargo.lock; |
| 109 | # allowBuiltinFetchGit = true; |
| 110 | }; |
| 111 | |
| 112 | nativeBuildInputs = nativeBuildTools; |
| 113 | buildInputs = nativeAudioBuildInputs; |
| 114 | |
| 115 | # one binary: `headroom` (cli + daemon) |
| 116 | cargoBuildFlags = [ |
| 117 | "-p" |
| 118 | "headroom-cli" |
| 119 | ]; |
| 120 | doCheck = true; |
| 121 | # exclude headroom-gui from the test sweep — pulls iced's gpu stack, not part of the daemon |
| 122 | cargoTestFlags = [ |
| 123 | "--workspace" |
| 124 | "--exclude" |
| 125 | "headroom-gui" |
| 126 | ]; |
| 127 | |
| 128 | # systemd user unit (@bindir@ templated to this derivation's bin, not PATH) |
| 129 | # + canonical profiles under share/headroom/profiles (daemon finds via $XDG_DATA_DIRS) |
| 130 | postInstall = '' |
| 131 | install -Dm644 contrib/systemd/headroom.service \ |
| 132 | "$out/lib/systemd/user/headroom.service" |
| 133 | substituteInPlace "$out/lib/systemd/user/headroom.service" \ |
| 134 | --replace-fail '@bindir@' "$out/bin" |
| 135 | |
| 136 | mkdir -p "$out/share/headroom/profiles" |
| 137 | cp -r profiles/. "$out/share/headroom/profiles/" |
| 138 | ''; |
| 139 | |
| 140 | meta = with pkgs.lib; { |
| 141 | description = "automatic loudness and per-app volume control (pipewire)"; |
| 142 | license = licenses.gpl3Plus; |
| 143 | platforms = platforms.linux; |
| 144 | mainProgram = "headroom"; |
| 145 | }; |
| 146 | } |
| 147 | // commonEnv |
| 148 | ); |
| 149 | |
| 150 | # `nix build .#headroom-gui` — optional iced GUI monitor. |
| 151 | # separate package so the default build stays graphics-free. iced/wgpu |
| 152 | # dlopen vulkan/libGL/wayland/xkbcommon at runtime → wrap with LD_LIBRARY_PATH. |
| 153 | # also ships the .desktop entry. |
| 154 | headroom-gui = pkgs.rustPlatform.buildRustPackage ( |
| 155 | { |
| 156 | pname = "headroom-gui"; |
| 157 | version = |
| 158 | (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version |
| 159 | + "-" |
| 160 | + (toString self.lastModifiedDate); |
| 161 | |
| 162 | src = ./.; |
| 163 | |
| 164 | cargoLock = { |
| 165 | lockFile = ./Cargo.lock; |
| 166 | }; |
| 167 | |
| 168 | # makeWrapper for the LD_LIBRARY_PATH wrap. gui doesn't link pipewire |
| 169 | # (talks to the daemon over the socket via headroom-client) → no pipewire/clang |
| 170 | nativeBuildInputs = [ |
| 171 | pkgs.pkg-config |
| 172 | pkgs.makeWrapper |
| 173 | ]; |
| 174 | buildInputs = guiLibs; |
| 175 | |
| 176 | cargoBuildFlags = [ |
| 177 | "-p" |
| 178 | "headroom-gui" |
| 179 | ]; |
| 180 | # gui tests drive iced view/update; no display in the sandbox. |
| 181 | # run them via `cargo test -p headroom-gui` in the dev shell instead |
| 182 | doCheck = false; |
| 183 | |
| 184 | postInstall = '' |
| 185 | install -Dm644 contrib/desktop/headroom-gui.desktop \ |
| 186 | "$out/share/applications/headroom-gui.desktop" |
| 187 | substituteInPlace "$out/share/applications/headroom-gui.desktop" \ |
| 188 | --replace-fail '@bindir@' "$out/bin" |
| 189 | ''; |
| 190 | |
| 191 | postFixup = '' |
| 192 | wrapProgram "$out/bin/headroom-gui" \ |
| 193 | --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath guiLibs}" |
| 194 | ''; |
| 195 | |
| 196 | meta = with pkgs.lib; { |
| 197 | description = "automatic loudness and per-app volume control (pipewire)"; |
| 198 | license = licenses.eupl12; |
| 199 | platforms = platforms.linux; |
| 200 | mainProgram = "headroom-gui"; |
| 201 | }; |
| 202 | } |
| 203 | // commonEnv |
| 204 | ); |
| 205 | }; |
| 206 | in |
| 207 | { |
| 208 | devShells = forAllSystems (system: { |
| 209 | default = (perSystem system).devShell; |
| 210 | }); |
| 211 | |
| 212 | packages = forAllSystems ( |
| 213 | system: |
| 214 | let |
| 215 | ps = perSystem system; |
| 216 | in |
| 217 | rec { |
| 218 | default = headroom; |
| 219 | headroom = ps.headroom; |
| 220 | headroom-gui = ps.headroom-gui; |
| 221 | } |
| 222 | ); |
| 223 | |
| 224 | formatter = forAllSystems (system: (pkgsFor system).nixpkgs-fmt); |
| 225 | |
| 226 | # System-independent outputs — modules. |
| 227 | nixosModules.default = import ./nix/nixos-module.nix self; |
| 228 | }; |
| 229 | } |