| 1 | # NixOS module — system-wide install. headroom is a user-scope daemon |
| 2 | # (talks to the user's pipewire session), so this module just: |
| 3 | # 1. binary on every login's PATH |
| 4 | # 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 enabled |
| 7 | self: |
| 8 | { |
| 9 | config, |
| 10 | lib, |
| 11 | pkgs, |
| 12 | ... |
| 13 | }: |
| 14 | |
| 15 | let |
| 16 | inherit (lib) |
| 17 | mkEnableOption |
| 18 | mkOption |
| 19 | mkIf |
| 20 | types |
| 21 | literalExpression |
| 22 | ; |
| 23 | |
| 24 | cfg = config.programs.headroom; |
| 25 | in |
| 26 | { |
| 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 it |
| 49 | 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's |
| 59 | `default.target` so it autostarts without `systemctl |
| 60 | --user enable headroom`. |
| 61 | |
| 62 | `systemd.packages` installs the unit but doesn't process its |
| 63 | `[Install]` section, so the `default.target.wants` |
| 64 | symlink is never created (unit sits `linked-runtime`). this |
| 65 | materialises that symlink at the system level. false = unit |
| 66 | install-only, manage activation yourself. |
| 67 | ''; |
| 68 | }; |
| 69 | }; |
| 70 | |
| 71 | config = mkIf cfg.enable { |
| 72 | # binary on PATH; gui package too when enabled (drops a |
| 73 | # 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`; materialises |
| 77 | # /etc/systemd/user/headroom.service → package's lib/systemd/user/headroom.service |
| 78 | systemd.packages = [ cfg.package ]; |
| 79 | |
| 80 | # systemd.packages doesn't process [Install] → add a `wantedBy` so NixOS |
| 81 | # emits the default.target.wants symlink. unit body comes from the |
| 82 | # 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 only |
| 91 | # links an allowlist of share/ subdirs into the system profile |
| 92 | # (environment.pathsToLink), so opt share/headroom in or the shipped |
| 93 | # 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't |
| 97 | 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 | } |