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