headroom/nix/home-module.nix

141 lines
4.4 KiB
Nix

# 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 automatic loudness and per-app volume control for PipeWire";
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 (automatic loudness and per-app volume control for PipeWire)";
Documentation = "https://github.com/atagen/headroom";
After = [
"pipewire.service"
"pipewire-pulse.service"
"wireplumber.service"
"graphical-session.target"
];
Requires = [ "pipewire.service" ];
Wants = [ "wireplumber.service" ];
# Tie our lifecycle to the graphical session rather than the
# socket-activated pipewire.service (which has no stable
# "started for the session" lifecycle to WantedBy=).
PartOf = [ "graphical-session.target" ];
};
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 = [ "graphical-session.target" ];
};
};
};
}