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