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