at 18.09-beta 9.1 kB view raw
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 = "/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", and 105 only users in the "audio" group will have access to the server. 106 Please read the PulseAudio documentation for more details. 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 <literal>configFile</literal> 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 = pkgs.pulseaudio; 148 defaultText = "pkgs.pulseaudio"; 149 example = literalExample "pkgs.pulseaudioFull"; 150 description = '' 151 The PulseAudio derivation to use. This can be used to enable 152 features (such as JACK support, Bluetooth) via the 153 <literal>pulseaudioFull</literal> package. 154 ''; 155 }; 156 157 daemon = { 158 logLevel = mkOption { 159 type = types.str; 160 default = "notice"; 161 description = '' 162 The log level that the system-wide pulseaudio daemon should use, 163 if activated. 164 ''; 165 }; 166 167 config = mkOption { 168 type = types.attrsOf types.unspecified; 169 default = {}; 170 description = ''Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.''; 171 example = literalExample ''{ flat-volumes = "no"; }''; 172 }; 173 }; 174 175 zeroconf = { 176 discovery.enable = 177 mkEnableOption "discovery of pulseaudio sinks in the local network"; 178 publish.enable = 179 mkEnableOption "publishing the pulseaudio sink in the local network"; 180 }; 181 182 # TODO: enable by default? 183 tcp = { 184 enable = mkEnableOption "tcp streaming support"; 185 186 anonymousClients = { 187 allowAll = mkEnableOption "all anonymous clients to stream to the server"; 188 allowedIpRanges = mkOption { 189 type = types.listOf types.str; 190 default = []; 191 example = literalExample ''[ "127.0.0.1" "192.168.1.0/24" ]''; 192 description = '' 193 A list of IP subnets that are allowed to stream to the server. 194 ''; 195 }; 196 }; 197 }; 198 199 }; 200 201 }; 202 203 204 config = mkMerge [ 205 { 206 environment.etc = singleton { 207 target = "pulse/client.conf"; 208 source = clientConf; 209 }; 210 211 hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa"; 212 } 213 214 (mkIf cfg.enable { 215 environment.systemPackages = [ overriddenPackage ]; 216 217 sound.enable = true; 218 219 environment.etc = [ 220 { target = "asound.conf"; 221 source = alsaConf; } 222 223 { target = "pulse/daemon.conf"; 224 source = writeText "daemon.conf" (lib.generators.toKeyValue {} cfg.daemon.config); } 225 226 { target = "openal/alsoft.conf"; 227 source = writeText "alsoft.conf" "drivers=pulse"; } 228 229 { target = "libao.conf"; 230 source = writeText "libao.conf" "default_driver=pulse"; } 231 ]; 232 233 # Allow PulseAudio to get realtime priority using rtkit. 234 security.rtkit.enable = true; 235 236 systemd.packages = [ overriddenPackage ]; 237 }) 238 239 (mkIf hasZeroconf { 240 services.avahi.enable = true; 241 }) 242 (mkIf cfg.zeroconf.publish.enable { 243 services.avahi.publish.enable = true; 244 services.avahi.publish.userServices = true; 245 }) 246 247 (mkIf nonSystemWide { 248 environment.etc = singleton { 249 target = "pulse/default.pa"; 250 source = myConfigFile; 251 }; 252 systemd.user = { 253 services.pulseaudio = { 254 restartIfChanged = true; 255 serviceConfig = { 256 RestartSec = "500ms"; 257 PassEnvironment = "DISPLAY"; 258 }; 259 }; 260 sockets.pulseaudio = { 261 wantedBy = [ "sockets.target" ]; 262 }; 263 }; 264 }) 265 266 (mkIf systemWide { 267 users.users.pulse = { 268 # For some reason, PulseAudio wants UID == GID. 269 uid = assert uid == gid; uid; 270 group = "pulse"; 271 extraGroups = [ "audio" ]; 272 description = "PulseAudio system service user"; 273 home = stateDir; 274 createHome = true; 275 }; 276 277 users.groups.pulse.gid = gid; 278 279 systemd.services.pulseaudio = { 280 description = "PulseAudio System-Wide Server"; 281 wantedBy = [ "sound.target" ]; 282 before = [ "sound.target" ]; 283 environment.PULSE_RUNTIME_PATH = stateDir; 284 serviceConfig = { 285 Type = "notify"; 286 ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}"; 287 Restart = "on-failure"; 288 RestartSec = "500ms"; 289 }; 290 }; 291 292 environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie"; 293 }) 294 ]; 295 296}