Commit c454b1085466
tarball3 files changed+156-0
@@ -0,0 +1,13 @@
1
+[Desktop Entry]2
+Type=Application3
+Name=Headroom4
+GenericName=Audio Loudness Control5
+Comment=automatic loudness and per-app volume control (pipewire)6
+# @bindir@ rewritten to this build's bin dir at install time (headroom-gui nix package)7
+Exec=@bindir@/headroom-gui8
+# no icon shipped yet; stock freedesktop name9
+Icon=multimedia-volume-control10
+Terminal=false11
+Categories=AudioVideo;Audio;Mixer;12
+Keywords=audio;volume;loudness;pipewire;normalization;compressor;limiter;13
+StartupNotify=true@@ -0,0 +1,36 @@
1
+[Unit]2
+Description=automatic loudness and per-app volume control (pipewire)3
+Documentation=https://github.com/manic-systems/headroom4
+# pipewire is a hard dep — can't start before pw-mainloop. ConditionUser pins user-scope5
+After=pipewire.service pipewire-pulse.service wireplumber.service6
+Requires=pipewire.service7
+Wants=wireplumber.service8
+# start with the user's service manager, but do not stop just because9
+# graphical-session.target is restarted during idle/session churn.10
+ConditionUser=!@system11
+12
+[Service]13
+Type=simple14
+ExecStart=@bindir@/headroom daemon15
+# restart after unexpected daemon exits, including clean main-loop exits.16
+Restart=always17
+RestartSec=2s18
+# sigterm is the clean shutdown path; explicit for clarity19
+KillMode=control-group20
+TimeoutStopSec=5s21
+# logs to journald (journalctl --user -u headroom)22
+StandardOutput=journal23
+StandardError=journal24
+SyslogIdentifier=headroom25
+# Allow realtime/nice elevation where the user manager has permission26
+# to grant it. Do not request CPUSchedulingPolicy= here: on systems27
+# without CAP_SYS_NICE / realtime user limits, systemd fails the unit28
+# before headroom can start. Users who have configured rt privileges can29
+# add a drop-in with CPUSchedulingPolicy=rr, CPUSchedulingPriority=20,30
+# and Nice=-11.31
+LimitRTPRIO=2032
+LimitRTTIME=20000033
+LimitNICE=-1134
+35
+[Install]36
+WantedBy=default.target@@ -0,0 +1,107 @@
1
+# NixOS module — system-wide install. headroom is a user-scope daemon2
+# (talks to the user's pipewire session), so this module just:3
+# 1. binary on every login's PATH4
+# 2. systemd user unit in the system-wide location (`systemctl --user enable --now headroom`)5
+# 3. bundled profiles into the system profile's share/ (daemon finds via $XDG_DATA_DIRS)6
+# 4. assert pipewire+wireplumber are enabled7
+self:8
+{9
+ config,10
+ lib,11
+ pkgs,12
+ ...13
+}:14
+15
+let16
+ inherit (lib)17
+ mkEnableOption18
+ mkOption19
+ mkIf20
+ types21
+ literalExpression22
+ ;23
+24
+ cfg = config.programs.headroom;25
+in26
+{27
+ options.programs.headroom = {28
+ enable = mkEnableOption "Headroom — automatic loudness and per-app volume control (pipewire)";29
+30
+ package = mkOption {31
+ type = types.package;32
+ default = self.packages.${pkgs.stdenv.hostPlatform.system}.headroom;33
+ defaultText = literalExpression "headroom.packages.\${pkgs.system}.headroom";34
+ description = ''35
+ The headroom package to install system-wide.36
+ '';37
+ };38
+39
+ gui = {40
+ enable = mkEnableOption "the Headroom GUI (iced monitor + control) and its application-launcher entry";41
+42
+ package = mkOption {43
+ type = types.package;44
+ default = self.packages.${pkgs.stdenv.hostPlatform.system}.headroom-gui;45
+ defaultText = literalExpression "headroom.packages.\${pkgs.system}.headroom-gui";46
+ description = ''47
+ headroom-gui package installed when `programs.headroom.gui.enable`48
+ is set. ships the `headroom-gui` binary + `.desktop` entry, so it49
+ appears in application launchers automatically.50
+ '';51
+ };52
+ };53
+54
+ autoStart = mkOption {55
+ type = types.bool;56
+ default = true;57
+ description = ''58
+ pull the headroom user service into every login's59
+ `default.target` so it autostarts without `systemctl60
+ --user enable headroom`.61
+62
+ `systemd.packages` installs the unit but doesn't process its63
+ `[Install]` section, so the `default.target.wants`64
+ symlink is never created (unit sits `linked-runtime`). this65
+ materialises that symlink at the system level. false = unit66
+ install-only, manage activation yourself.67
+ '';68
+ };69
+ };70
+71
+ config = mkIf cfg.enable {72
+ # binary on PATH; gui package too when enabled (drops a73
+ # share/applications/.desktop entry NixOS aggregates into the menu)74
+ environment.systemPackages = [ cfg.package ] ++ lib.optional cfg.gui.enable cfg.gui.package;75
+76
+ # make the user unit discoverable by `systemctl --user`; materialises77
+ # /etc/systemd/user/headroom.service → package's lib/systemd/user/headroom.service78
+ systemd.packages = [ cfg.package ];79
+80
+ # systemd.packages doesn't process [Install] → add a `wantedBy` so NixOS81
+ # emits the default.target.wants symlink. unit body comes from the82
+ # package, so we must NOT author headroom.service here (duplicate-unit error);83
+ # overrideStrategy="asDropin" makes this a drop-in (empty bar the wants link).84
+ # not via environment.etc: /etc/systemd/user is a read-only store symlink.85
+ systemd.user.services.headroom = mkIf cfg.autoStart {86
+ overrideStrategy = "asDropin";87
+ wantedBy = [ "default.target" ];88
+ };89
+90
+ # daemon scans each $XDG_DATA_DIRS entry's headroom/profiles/. NixOS only91
+ # links an allowlist of share/ subdirs into the system profile92
+ # (environment.pathsToLink), so opt share/headroom in or the shipped93
+ # profiles get dropped and the daemon sees only the built-in `default`.94
+ environment.pathsToLink = [ "/share/headroom" ];95
+96
+ # fail eval (not a confusing runtime error) if headroom is on but pipewire isn't97
+ assertions = [98
+ {99
+ assertion = config.services.pipewire.enable;100
+ message = ''101
+ programs.headroom.enable requires services.pipewire.enable = true;102
+ headroom is a PipeWire-only daemon.103
+ '';104
+ }105
+ ];106
+ };107
+}