1{ config, lib, pkgs, pkgs_i686, ... }:
2
3with pkgs;
4with lib;
5
6let
7
8 cfg = config.hardware.pulseaudio;
9 alsaCfg = config.sound;
10
11 systemWide = cfg.enable && cfg.systemWide;
12 nonSystemWide = cfg.enable && !cfg.systemWide;
13 hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
14
15 overriddenPackage = cfg.package.override
16 (optionalAttrs hasZeroconf { zeroconfSupport = true; });
17 binary = "${getBin overriddenPackage}/bin/pulseaudio";
18 binaryNoDaemon = "${binary} --daemonize=no";
19
20 # Forces 32bit pulseaudio and alsaPlugins to be built/supported for apps
21 # using 32bit alsa on 64bit linux.
22 enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs_i686.alsaLib != null && pkgs_i686.libpulseaudio != null);
23
24
25 myConfigFile =
26 let
27 addModuleIf = cond: mod: optionalString cond "load-module ${mod}";
28 allAnon = optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
29 ipAnon = let a = cfg.tcp.anonymousClients.allowedIpRanges;
30 in optional (a != []) ''auth-ip-acl=${concatStringsSep ";" a}'';
31 in writeTextFile {
32 name = "default.pa";
33 text = ''
34 .include ${cfg.configFile}
35 ${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
36 ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
37 ${addModuleIf cfg.tcp.enable (concatStringsSep " "
38 ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon))}
39 ${cfg.extraConfig}
40 '';
41 };
42
43 ids = config.ids;
44
45 uid = ids.uids.pulseaudio;
46 gid = ids.gids.pulseaudio;
47
48 stateDir = "/var/run/pulse";
49
50 # Create pulse/client.conf even if PulseAudio is disabled so
51 # that we can disable the autospawn feature in programs that
52 # are built with PulseAudio support (like KDE).
53 clientConf = writeText "client.conf" ''
54 autospawn=${if nonSystemWide then "yes" else "no"}
55 ${optionalString nonSystemWide "daemon-binary=${binary}"}
56 ${cfg.extraClientConf}
57 '';
58
59 # Write an /etc/asound.conf that causes all ALSA applications to
60 # be re-routed to the PulseAudio server through ALSA's Pulse
61 # plugin.
62 alsaConf = writeText "asound.conf" (''
63 pcm_type.pulse {
64 libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
65 ${lib.optionalString enable32BitAlsaPlugins
66 "libs.32Bit = ${pkgs_i686.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
67 }
68 pcm.!default {
69 type pulse
70 hint.description "Default Audio Device (via PulseAudio)"
71 }
72 ctl_type.pulse {
73 libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
74 ${lib.optionalString enable32BitAlsaPlugins
75 "libs.32Bit = ${pkgs_i686.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
76 }
77 ctl.!default {
78 type pulse
79 }
80 ${alsaCfg.extraConfig}
81 '');
82
83in {
84
85 options = {
86
87 hardware.pulseaudio = {
88 enable = mkOption {
89 type = types.bool;
90 default = false;
91 description = ''
92 Whether to enable the PulseAudio sound server.
93 '';
94 };
95
96 systemWide = mkOption {
97 type = types.bool;
98 default = false;
99 description = ''
100 If false, a PulseAudio server is launched automatically for
101 each user that tries to use the sound system. The server runs
102 with user privileges. This is the recommended and most secure
103 way to use PulseAudio. If true, one system-wide PulseAudio
104 server is launched on boot, running as the user "pulse".
105 Please read the PulseAudio documentation for more details.
106 '';
107 };
108
109 support32Bit = mkOption {
110 type = types.bool;
111 default = false;
112 description = ''
113 Whether to include the 32-bit pulseaudio libraries in the system or not.
114 This is only useful on 64-bit systems and currently limited to x86_64-linux.
115 '';
116 };
117
118 configFile = mkOption {
119 type = types.nullOr types.path;
120 description = ''
121 The path to the default configuration options the PulseAudio server
122 should use. By default, the "default.pa" configuration
123 from the PulseAudio distribution is used.
124 '';
125 };
126
127 extraConfig = mkOption {
128 type = types.lines;
129 default = "";
130 description = ''
131 Literal string to append to <literal>configFile</literal>
132 and the config file generated by the pulseaudio module.
133 '';
134 };
135
136 extraClientConf = mkOption {
137 type = types.lines;
138 default = "";
139 description = ''
140 Extra configuration appended to pulse/client.conf file.
141 '';
142 };
143
144 package = mkOption {
145 type = types.package;
146 default = pulseaudioLight;
147 defaultText = "pkgs.pulseaudioLight";
148 example = literalExample "pkgs.pulseaudioFull";
149 description = ''
150 The PulseAudio derivation to use. This can be used to enable
151 features (such as JACK support, Bluetooth) via the
152 <literal>pulseaudioFull</literal> package.
153 '';
154 };
155
156 daemon = {
157 logLevel = mkOption {
158 type = types.str;
159 default = "notice";
160 description = ''
161 The log level that the system-wide pulseaudio daemon should use,
162 if activated.
163 '';
164 };
165
166 config = mkOption {
167 type = types.attrsOf types.unspecified;
168 default = {};
169 description = ''Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.'';
170 example = literalExample ''{ flat-volumes = "no"; }'';
171 };
172 };
173
174 zeroconf = {
175 discovery.enable =
176 mkEnableOption "discovery of pulseaudio sinks in the local network";
177 publish.enable =
178 mkEnableOption "publishing the pulseaudio sink in the local network";
179 };
180
181 # TODO: enable by default?
182 tcp = {
183 enable = mkEnableOption "tcp streaming support";
184
185 anonymousClients = {
186 allowAll = mkEnableOption "all anonymous clients to stream to the server";
187 allowedIpRanges = mkOption {
188 type = types.listOf types.str;
189 default = [];
190 example = literalExample ''[ "127.0.0.1" "192.168.1.0/24" ]'';
191 description = ''
192 A list of IP subnets that are allowed to stream to the server.
193 '';
194 };
195 };
196 };
197
198 };
199
200 };
201
202
203 config = mkMerge [
204 {
205 environment.etc = singleton {
206 target = "pulse/client.conf";
207 source = clientConf;
208 };
209
210 hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa";
211 }
212
213 (mkIf cfg.enable {
214 environment.systemPackages = [ overriddenPackage ];
215
216 environment.etc = [
217 { target = "asound.conf";
218 source = alsaConf; }
219
220 { target = "pulse/daemon.conf";
221 source = writeText "daemon.conf" (lib.generators.toKeyValue {} cfg.daemon.config); }
222 ];
223
224 # Allow PulseAudio to get realtime priority using rtkit.
225 security.rtkit.enable = true;
226
227 systemd.packages = [ overriddenPackage ];
228 })
229
230 (mkIf hasZeroconf {
231 services.avahi.enable = true;
232 })
233 (mkIf cfg.zeroconf.publish.enable {
234 services.avahi.publish.enable = true;
235 services.avahi.publish.userServices = true;
236 })
237
238 (mkIf nonSystemWide {
239 environment.etc = singleton {
240 target = "pulse/default.pa";
241 source = myConfigFile;
242 };
243 systemd.user = {
244 services.pulseaudio = {
245 restartIfChanged = true;
246 serviceConfig = {
247 RestartSec = "500ms";
248 PassEnvironment = "DISPLAY";
249 };
250 };
251 sockets.pulseaudio = {
252 wantedBy = [ "sockets.target" ];
253 };
254 };
255 })
256
257 (mkIf systemWide {
258 users.extraUsers.pulse = {
259 # For some reason, PulseAudio wants UID == GID.
260 uid = assert uid == gid; uid;
261 group = "pulse";
262 extraGroups = [ "audio" ];
263 description = "PulseAudio system service user";
264 home = stateDir;
265 createHome = true;
266 };
267
268 users.extraGroups.pulse.gid = gid;
269
270 systemd.services.pulseaudio = {
271 description = "PulseAudio System-Wide Server";
272 wantedBy = [ "sound.target" ];
273 before = [ "sound.target" ];
274 environment.PULSE_RUNTIME_PATH = stateDir;
275 serviceConfig = {
276 Type = "notify";
277 ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
278 Restart = "on-failure";
279 RestartSec = "500ms";
280 };
281 };
282
283 environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";
284 })
285 ];
286
287}