atagen

headroom

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