# Home Manager module — installs the headroom binary, the systemd # user service, and (optionally) a default set of profiles into the # user's XDG_CONFIG_HOME. # # Headroom is a per-user daemon that talks to PipeWire over the user # session, so the Home Manager scope is its natural install point. A # separate NixOS module (./nixos-module.nix) covers the case where the # user wants `headroom` on every account's PATH or wants to enable the # service at the system level via systemd-user; that module simply # delegates the heavy lifting to `services.headroom` (this file) when # Home Manager is in use. self: { config, lib, pkgs, ... }: let inherit (lib) mkEnableOption mkOption mkIf types literalExpression; cfg = config.services.headroom; package = cfg.package; # Profiles shipped by the package, suitable for symlinking into the # user's XDG_CONFIG_HOME so they show up in `headroom profile list` # without the user having to copy them by hand. shippedProfilesDir = "${package}/share/headroom/profiles"; in { options.services.headroom = { enable = mkEnableOption "Headroom — PipeWire AGC + compressor + true-peak limiter daemon"; package = mkOption { type = types.package; default = self.packages.${pkgs.system}.headroom; defaultText = literalExpression "headroom.packages.\${pkgs.system}.headroom"; description = '' The headroom package to install. Override to pin a local build (e.g. `path:/home/me/code/headroom`) when iterating. ''; }; installDefaultProfiles = mkOption { type = types.bool; default = true; description = '' Symlink the profiles shipped with the package into `$XDG_CONFIG_HOME/headroom/profiles/`. Disable if you maintain your own profile set and don't want the shipped ones cluttering `headroom profile list`. ''; }; extraProfiles = mkOption { type = types.attrsOf types.path; default = { }; example = literalExpression '' { "studio.toml" = ./profiles/studio.toml; } ''; description = '' Additional profile TOML files to drop into the user's profile directory, keyed by filename. Overrides any identically-named shipped profile. ''; }; }; config = mkIf cfg.enable { home.packages = [ package ]; # Symlink shipped profiles + any user-provided extras into the # user's XDG_CONFIG_HOME. The daemon's profile watcher # (notify-debouncer-mini) treats symlinks identically to # regular files, so this is transparent. xdg.configFile = lib.mkMerge [ (mkIf cfg.installDefaultProfiles ( lib.mapAttrs' (name: _: lib.nameValuePair "headroom/profiles/${name}" { source = "${shippedProfilesDir}/${name}"; }) (builtins.readDir shippedProfilesDir) )) (lib.mapAttrs' (name: path: lib.nameValuePair "headroom/profiles/${name}" { source = path; }) cfg.extraProfiles) ]; # systemd user unit. The unit shipped by the package already # carries the right ExecStart with an absolute path baked in, # so we just symlink it into the user's services directory and # let Home Manager start it via its systemd-user machinery. systemd.user.services.headroom = { Unit = { Description = "Headroom audio daemon (PipeWire AGC + compressor + true-peak limiter)"; Documentation = "https://github.com/amaanq/headroom"; After = [ "pipewire.service" "pipewire-pulse.service" "wireplumber.service" ]; Requires = [ "pipewire.service" ]; Wants = [ "wireplumber.service" ]; }; Service = { Type = "simple"; ExecStart = "${package}/bin/headroom daemon"; Restart = "on-failure"; RestartSec = "2s"; StandardOutput = "journal"; StandardError = "journal"; SyslogIdentifier = "headroom"; LimitRTPRIO = 20; LimitRTTIME = 200000; LimitNICE = -11; }; Install = { WantedBy = [ "pipewire.service" ]; }; }; }; }