grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / config / pulseaudio.nix
blob6e6e577e6e20466b223c77895611ac4020e2594e
1 { config, lib, pkgs, ... }:
2 let
4   cfg = config.hardware.pulseaudio;
6   hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
8   overriddenPackage = cfg.package.override
9     (lib.optionalAttrs hasZeroconf { zeroconfSupport = true; });
10   binary = "${lib.getBin overriddenPackage}/bin/pulseaudio";
11   binaryNoDaemon = "${binary} --daemonize=no";
13   # Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps
14   # using 32bit alsa on 64bit linux.
15   enable32BitAlsaPlugins = cfg.support32Bit && pkgs.stdenv.hostPlatform.isx86_64 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
18   myConfigFile =
19     let
20       addModuleIf = cond: mod: lib.optionalString cond "load-module ${mod}";
21       allAnon = lib.optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
22       ipAnon =  let a = cfg.tcp.anonymousClients.allowedIpRanges;
23                 in lib.optional (a != []) ''auth-ip-acl=${lib.concatStringsSep ";" a}'';
24     in pkgs.writeTextFile {
25       name = "default.pa";
26         text = ''
27         .include ${cfg.configFile}
28         ${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
29         ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
30         ${addModuleIf cfg.tcp.enable (lib.concatStringsSep " "
31            ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon))}
32         ${addModuleIf config.services.jack.jackd.enable "module-jack-sink"}
33         ${addModuleIf config.services.jack.jackd.enable "module-jack-source"}
34         ${cfg.extraConfig}
35       '';
36     };
38   ids = config.ids;
40   uid = ids.uids.pulseaudio;
41   gid = ids.gids.pulseaudio;
43   stateDir = "/run/pulse";
45   # Create pulse/client.conf even if PulseAudio is disabled so
46   # that we can disable the autospawn feature in programs that
47   # are built with PulseAudio support (like KDE).
48   clientConf = pkgs.writeText "client.conf" ''
49     autospawn=no
50     ${cfg.extraClientConf}
51   '';
53   # Write an /etc/asound.conf that causes all ALSA applications to
54   # be re-routed to the PulseAudio server through ALSA's Pulse
55   # plugin.
56   alsaConf = ''
57     pcm_type.pulse {
58       libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
59       ${lib.optionalString enable32BitAlsaPlugins
60      "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
61     }
62     pcm.!default {
63       type pulse
64       hint.description "Default Audio Device (via PulseAudio)"
65     }
66     ctl_type.pulse {
67       libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
68       ${lib.optionalString enable32BitAlsaPlugins
69      "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
70     }
71     ctl.!default {
72       type pulse
73     }
74   '';
76 in {
78   options = {
80     hardware.pulseaudio = {
81       enable = lib.mkOption {
82         type = lib.types.bool;
83         default = false;
84         description = ''
85           Whether to enable the PulseAudio sound server.
86         '';
87       };
89       systemWide = lib.mkOption {
90         type = lib.types.bool;
91         default = false;
92         description = ''
93           If false, a PulseAudio server is launched automatically for
94           each user that tries to use the sound system. The server runs
95           with user privileges. If true, one system-wide PulseAudio
96           server is launched on boot, running as the user "pulse", and
97           only users in the "pulse-access" group will have access to the server.
98           Please read the PulseAudio documentation for more details.
100           Don't enable this option unless you know what you are doing.
101         '';
102       };
104       support32Bit = lib.mkOption {
105         type = lib.types.bool;
106         default = false;
107         description = ''
108           Whether to include the 32-bit pulseaudio libraries in the system or not.
109           This is only useful on 64-bit systems and currently limited to x86_64-linux.
110         '';
111       };
113       configFile = lib.mkOption {
114         type = lib.types.nullOr lib.types.path;
115         description = ''
116           The path to the default configuration options the PulseAudio server
117           should use. By default, the "default.pa" configuration
118           from the PulseAudio distribution is used.
119         '';
120       };
122       extraConfig = lib.mkOption {
123         type = lib.types.lines;
124         default = "";
125         description = ''
126           Literal string to append to `configFile`
127           and the config file generated by the pulseaudio module.
128         '';
129       };
131       extraClientConf = lib.mkOption {
132         type = lib.types.lines;
133         default = "";
134         description = ''
135           Extra configuration appended to pulse/client.conf file.
136         '';
137       };
139       package = lib.mkOption {
140         type = lib.types.package;
141         default = if config.services.jack.jackd.enable
142                   then pkgs.pulseaudioFull
143                   else pkgs.pulseaudio;
144         defaultText = lib.literalExpression "pkgs.pulseaudio";
145         example = lib.literalExpression "pkgs.pulseaudioFull";
146         description = ''
147           The PulseAudio derivation to use.  This can be used to enable
148           features (such as JACK support, Bluetooth) via the
149           `pulseaudioFull` package.
150         '';
151       };
153       extraModules = lib.mkOption {
154         type = lib.types.listOf lib.types.package;
155         default = [];
156         example = lib.literalExpression "[ pkgs.pulseaudio-modules-bt ]";
157         description = ''
158           Extra pulseaudio modules to use. This is intended for out-of-tree
159           pulseaudio modules like extra bluetooth codecs.
161           Extra modules take precedence over built-in pulseaudio modules.
162         '';
163       };
165       daemon = {
166         logLevel = lib.mkOption {
167           type = lib.types.str;
168           default = "notice";
169           description = ''
170             The log level that the system-wide pulseaudio daemon should use,
171             if activated.
172           '';
173         };
175         config = lib.mkOption {
176           type = lib.types.attrsOf lib.types.unspecified;
177           default = {};
178           description = "Config of the pulse daemon. See `man pulse-daemon.conf`.";
179           example = lib.literalExpression ''{ realtime-scheduling = "yes"; }'';
180         };
181       };
183       zeroconf = {
184         discovery.enable =
185           lib.mkEnableOption "discovery of pulseaudio sinks in the local network";
186         publish.enable =
187           lib.mkEnableOption "publishing the pulseaudio sink in the local network";
188       };
190       # TODO: enable by default?
191       tcp = {
192         enable = lib.mkEnableOption "tcp streaming support";
194         anonymousClients = {
195           allowAll = lib.mkEnableOption "all anonymous clients to stream to the server";
196           allowedIpRanges = lib.mkOption {
197             type = lib.types.listOf lib.types.str;
198             default = [];
199             example = lib.literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
200             description = ''
201               A list of IP subnets that are allowed to stream to the server.
202             '';
203           };
204         };
205       };
207     };
209   };
212   config = lib.mkIf cfg.enable (lib.mkMerge [
213     {
214       environment.etc."pulse/client.conf".source = clientConf;
216       environment.systemPackages = [ overriddenPackage ];
218       environment.etc = {
219         "alsa/conf.d/99-pulseaudio.conf".text = alsaConf;
221         "pulse/daemon.conf".source = pkgs.writeText "daemon.conf"
222           (lib.generators.toKeyValue {} cfg.daemon.config);
224         "openal/alsoft.conf".source = pkgs.writeText "alsoft.conf" "drivers=pulse";
226         "libao.conf".source = pkgs.writeText "libao.conf" "default_driver=pulse";
227       };
229       hardware.pulseaudio.configFile = lib.mkDefault "${lib.getBin overriddenPackage}/etc/pulse/default.pa";
231       # Disable flat volumes to enable relative ones
232       hardware.pulseaudio.daemon.config.flat-volumes = lib.mkDefault "no";
234       # Upstream defaults to speex-float-1 which results in audible artifacts
235       hardware.pulseaudio.daemon.config.resample-method = lib.mkDefault "speex-float-5";
237       # Allow PulseAudio to get realtime priority using rtkit.
238       security.rtkit.enable = true;
240       systemd.packages = [ overriddenPackage ];
242       # PulseAudio is packaged with udev rules to handle various audio device quirks
243       services.udev.packages = [ overriddenPackage ];
244     }
246     (lib.mkIf (cfg.extraModules != []) {
247       hardware.pulseaudio.daemon.config.dl-search-path = let
248         overriddenModules = builtins.map
249           (drv: drv.override { pulseaudio = overriddenPackage; })
250           cfg.extraModules;
251         modulePaths = builtins.map
252           (drv: "${drv}/lib/pulseaudio/modules")
253           # User-provided extra modules take precedence
254           (overriddenModules ++ [ overriddenPackage ]);
255       in lib.concatStringsSep ":" modulePaths;
256     })
258     (lib.mkIf hasZeroconf {
259       services.avahi.enable = true;
260     })
261     (lib.mkIf cfg.zeroconf.publish.enable {
262       services.avahi.publish.enable = true;
263       services.avahi.publish.userServices = true;
264     })
266     (lib.mkIf (!cfg.systemWide) {
267       environment.etc = {
268         "pulse/default.pa".source = myConfigFile;
269       };
270       systemd.user = {
271         services.pulseaudio = {
272           restartIfChanged = true;
273           serviceConfig = {
274             RestartSec = "500ms";
275             PassEnvironment = "DISPLAY";
276           };
277         } // lib.optionalAttrs config.services.jack.jackd.enable {
278           environment.JACK_PROMISCUOUS_SERVER = "jackaudio";
279         };
280         sockets.pulseaudio = {
281           wantedBy = [ "sockets.target" ];
282         };
283       };
284     })
286     (lib.mkIf cfg.systemWide {
287       users.users.pulse = {
288         # For some reason, PulseAudio wants UID == GID.
289         uid = assert uid == gid; uid;
290         group = "pulse";
291         extraGroups = [ "audio" ];
292         description = "PulseAudio system service user";
293         home = stateDir;
294         homeMode = "755";
295         createHome = true;
296         isSystemUser = true;
297       };
299       users.groups.pulse.gid = gid;
300       users.groups.pulse-access = {};
302       systemd.services.pulseaudio = {
303         description = "PulseAudio System-Wide Server";
304         wantedBy = [ "sound.target" ];
305         before = [ "sound.target" ];
306         environment.PULSE_RUNTIME_PATH = stateDir;
307         serviceConfig = {
308           Type = "notify";
309           ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
310           Restart = "on-failure";
311           RestartSec = "500ms";
312         };
313       };
315       environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";
316     })
317   ]);