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