at 24.11-pre 17 kB view raw
1# PipeWire service. 2{ config, lib, pkgs, ... }: 3 4let 5 inherit (builtins) attrNames concatMap length; 6 inherit (lib) maintainers teams; 7 inherit (lib.attrsets) attrByPath attrsToList concatMapAttrs filterAttrs; 8 inherit (lib.lists) flatten optional optionals; 9 inherit (lib.modules) mkIf mkRemovedOptionModule; 10 inherit (lib.options) literalExpression mkEnableOption mkOption mkPackageOption; 11 inherit (lib.strings) concatMapStringsSep hasPrefix optionalString; 12 inherit (lib.types) attrsOf bool listOf package; 13 14 json = pkgs.formats.json {}; 15 mapToFiles = location: config: concatMapAttrs (name: value: { "share/pipewire/${location}.conf.d/${name}.conf" = json.generate "${name}" value; }) config; 16 extraConfigPkgFromFiles = locations: filesSet: pkgs.runCommand "pipewire-extra-config" { } '' 17 mkdir -p ${concatMapStringsSep " " (l: "$out/share/pipewire/${l}.conf.d") locations} 18 ${concatMapStringsSep ";" ({name, value}: "ln -s ${value} $out/${name}") (attrsToList filesSet)} 19 ''; 20 cfg = config.services.pipewire; 21 enable32BitAlsaPlugins = cfg.alsa.support32Bit 22 && pkgs.stdenv.isx86_64 23 && pkgs.pkgsi686Linux.pipewire != null; 24 25 # The package doesn't output to $out/lib/pipewire directly so that the 26 # overlays can use the outputs to replace the originals in FHS environments. 27 # 28 # This doesn't work in general because of missing development information. 29 jack-libs = pkgs.runCommand "jack-libs" {} '' 30 mkdir -p "$out/lib" 31 ln -s "${cfg.package.jack}/lib" "$out/lib/pipewire" 32 ''; 33 34 configPackages = cfg.configPackages; 35 36 extraConfigPkg = extraConfigPkgFromFiles 37 [ "pipewire" "client" "client-rt" "jack" "pipewire-pulse" ] 38 ( 39 mapToFiles "pipewire" cfg.extraConfig.pipewire 40 // mapToFiles "client" cfg.extraConfig.client 41 // mapToFiles "client-rt" cfg.extraConfig.client-rt 42 // mapToFiles "jack" cfg.extraConfig.jack 43 // mapToFiles "pipewire-pulse" cfg.extraConfig.pipewire-pulse 44 ); 45 46 configs = pkgs.buildEnv { 47 name = "pipewire-configs"; 48 paths = configPackages 49 ++ [ extraConfigPkg ] 50 ++ optionals cfg.wireplumber.enable cfg.wireplumber.configPackages; 51 pathsToLink = [ "/share/pipewire" ]; 52 }; 53 54 requiredLv2Packages = flatten 55 ( 56 concatMap 57 (p: 58 attrByPath ["passthru" "requiredLv2Packages"] [] p 59 ) 60 configPackages 61 ); 62 63 lv2Plugins = pkgs.buildEnv { 64 name = "pipewire-lv2-plugins"; 65 paths = cfg.extraLv2Packages ++ requiredLv2Packages; 66 pathsToLink = [ "/lib/lv2" ]; 67 }; 68in { 69 meta.maintainers = teams.freedesktop.members ++ [ maintainers.k900 ]; 70 71 ###### interface 72 options = { 73 services.pipewire = { 74 enable = mkEnableOption "PipeWire service"; 75 76 package = mkPackageOption pkgs "pipewire" { }; 77 78 socketActivation = mkOption { 79 default = true; 80 type = bool; 81 description = '' 82 Automatically run PipeWire when connections are made to the PipeWire socket. 83 ''; 84 }; 85 86 audio = { 87 enable = mkOption { 88 type = bool; 89 # this is for backwards compatibility 90 default = cfg.alsa.enable || cfg.jack.enable || cfg.pulse.enable; 91 defaultText = literalExpression "config.services.pipewire.alsa.enable || config.services.pipewire.jack.enable || config.services.pipewire.pulse.enable"; 92 description = "Whether to use PipeWire as the primary sound server"; 93 }; 94 }; 95 96 alsa = { 97 enable = mkEnableOption "ALSA support"; 98 support32Bit = mkEnableOption "32-bit ALSA support on 64-bit systems"; 99 }; 100 101 jack = { 102 enable = mkEnableOption "JACK audio emulation"; 103 }; 104 105 raopOpenFirewall = mkOption { 106 type = bool; 107 default = false; 108 description = '' 109 Opens UDP/6001-6002, required by RAOP/Airplay for timing and control data. 110 ''; 111 }; 112 113 pulse = { 114 enable = mkEnableOption "PulseAudio server emulation"; 115 }; 116 117 systemWide = mkOption { 118 type = bool; 119 default = false; 120 description = '' 121 If true, a system-wide PipeWire service and socket is enabled 122 allowing all users in the "pipewire" group to use it simultaneously. 123 If false, then user units are used instead, restricting access to 124 only one user. 125 126 Enabling system-wide PipeWire is however not recommended and disabled 127 by default according to 128 https://github.com/PipeWire/pipewire/blob/master/NEWS 129 ''; 130 }; 131 132 extraConfig = { 133 pipewire = mkOption { 134 type = attrsOf json.type; 135 default = {}; 136 example = { 137 "10-clock-rate" = { 138 "context.properties" = { 139 "default.clock.rate" = 44100; 140 }; 141 }; 142 "11-no-upmixing" = { 143 "stream.properties" = { 144 "channelmix.upmix" = false; 145 }; 146 }; 147 }; 148 description = '' 149 Additional configuration for the PipeWire server. 150 151 Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire.conf.d`. 152 153 See `man pipewire.conf` for details, and [the PipeWire wiki][wiki] for examples. 154 155 See also: 156 - [PipeWire wiki - virtual devices][wiki-virtual-device] for creating virtual devices or remapping channels 157 - [PipeWire wiki - filter-chain][wiki-filter-chain] for creating more complex processing pipelines 158 - [PipeWire wiki - network][wiki-network] for streaming audio over a network 159 160 [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire 161 [wiki-virtual-device]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices 162 [wiki-filter-chain]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Filter-Chain 163 [wiki-network]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Network 164 ''; 165 }; 166 client = mkOption { 167 type = attrsOf json.type; 168 default = {}; 169 example = { 170 "10-no-resample" = { 171 "stream.properties" = { 172 "resample.disable" = true; 173 }; 174 }; 175 }; 176 description = '' 177 Additional configuration for the PipeWire client library, used by most applications. 178 179 Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client.conf.d`. 180 181 See the [PipeWire wiki][wiki] for examples. 182 183 [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-client 184 ''; 185 }; 186 client-rt = mkOption { 187 type = attrsOf json.type; 188 default = {}; 189 example = { 190 "10-alsa-linear-volume" = { 191 "alsa.properties" = { 192 "alsa.volume-method" = "linear"; 193 }; 194 }; 195 }; 196 description = '' 197 Additional configuration for the PipeWire client library, used by real-time applications and legacy ALSA clients. 198 199 Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client-rt.conf.d`. 200 201 See the [PipeWire wiki][wiki] for examples of general configuration, and [PipeWire wiki - ALSA][wiki-alsa] for ALSA clients. 202 203 [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-client 204 [wiki-alsa]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-ALSA 205 ''; 206 }; 207 jack = mkOption { 208 type = attrsOf json.type; 209 default = {}; 210 example = { 211 "20-hide-midi" = { 212 "jack.properties" = { 213 "jack.show-midi" = false; 214 }; 215 }; 216 }; 217 description = '' 218 Additional configuration for the PipeWire JACK server and client library. 219 220 Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/jack.conf.d`. 221 222 See the [PipeWire wiki][wiki] for examples. 223 224 [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-JACK 225 ''; 226 }; 227 pipewire-pulse = mkOption { 228 type = attrsOf json.type; 229 default = {}; 230 example = { 231 "15-force-s16-info" = { 232 "pulse.rules" = [{ 233 matches = [ 234 { "application.process.binary" = "my-broken-app"; } 235 ]; 236 actions = { 237 quirks = [ "force-s16-info" ]; 238 }; 239 }]; 240 }; 241 }; 242 description = '' 243 Additional configuration for the PipeWire PulseAudio server. 244 245 Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire-pulse.conf.d`. 246 247 See `man pipewire-pulse.conf` for details, and [the PipeWire wiki][wiki] for examples. 248 249 See also: 250 - [PipeWire wiki - PulseAudio tricks guide][wiki-tricks] for more examples. 251 252 [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PulseAudio 253 [wiki-tricks]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Guide-PulseAudio-Tricks 254 ''; 255 }; 256 }; 257 258 configPackages = mkOption { 259 type = listOf package; 260 default = []; 261 example = literalExpression ''[ 262 (pkgs.writeTextDir "share/pipewire/pipewire.conf.d/10-loopback.conf" ''' 263 context.modules = [ 264 { name = libpipewire-module-loopback 265 args = { 266 node.description = "Scarlett Focusrite Line 1" 267 capture.props = { 268 audio.position = [ FL ] 269 stream.dont-remix = true 270 node.target = "alsa_input.usb-Focusrite_Scarlett_Solo_USB_Y7ZD17C24495BC-00.analog-stereo" 271 node.passive = true 272 } 273 playback.props = { 274 node.name = "SF_mono_in_1" 275 media.class = "Audio/Source" 276 audio.position = [ MONO ] 277 } 278 } 279 } 280 ] 281 ''') 282 ]''; 283 description = '' 284 List of packages that provide PipeWire configuration, in the form of 285 `share/pipewire/*/*.conf` files. 286 287 LV2 dependencies will be picked up from config packages automatically 288 via `passthru.requiredLv2Packages`. 289 ''; 290 }; 291 292 extraLv2Packages = mkOption { 293 type = listOf package; 294 default = []; 295 example = literalExpression "[ pkgs.lsp-plugins ]"; 296 description = '' 297 List of packages that provide LV2 plugins in `lib/lv2` that should 298 be made available to PipeWire for [filter chains][wiki-filter-chain]. 299 300 Config packages have their required LV2 plugins added automatically, 301 so they don't need to be specified here. Config packages need to set 302 `passthru.requiredLv2Packages` for this to work. 303 304 [wiki-filter-chain]: https://docs.pipewire.org/page_module_filter_chain.html 305 ''; 306 }; 307 }; 308 }; 309 310 imports = [ 311 (mkRemovedOptionModule ["services" "pipewire" "config"] '' 312 Overriding default PipeWire configuration through NixOS options never worked correctly and is no longer supported. 313 Please create drop-in configuration files via `services.pipewire.extraConfig` instead. 314 '') 315 (mkRemovedOptionModule ["services" "pipewire" "media-session"] '' 316 pipewire-media-session is no longer supported upstream and has been removed. 317 Please switch to `services.pipewire.wireplumber` instead. 318 '') 319 ]; 320 321 ###### implementation 322 config = mkIf cfg.enable { 323 assertions = [ 324 { 325 assertion = cfg.audio.enable -> !config.hardware.pulseaudio.enable; 326 message = "Using PipeWire as the sound server conflicts with PulseAudio. This option requires `hardware.pulseaudio.enable` to be set to false"; 327 } 328 { 329 assertion = cfg.jack.enable -> !config.services.jack.jackd.enable; 330 message = "PipeWire based JACK emulation doesn't use the JACK service. This option requires `services.jack.jackd.enable` to be set to false"; 331 } 332 { 333 # JACK intentionally not checked, as PW-on-JACK setups are a thing that some people may want 334 assertion = (cfg.alsa.enable || cfg.pulse.enable) -> cfg.audio.enable; 335 message = "Using PipeWire's ALSA/PulseAudio compatibility layers requires running PipeWire as the sound server. Set `services.pipewire.audio.enable` to true."; 336 } 337 { 338 assertion = length 339 (attrNames 340 ( 341 filterAttrs 342 (name: value: 343 hasPrefix "pipewire/" name || name == "pipewire" 344 ) 345 config.environment.etc 346 )) == 1; 347 message = "Using `environment.etc.\"pipewire<...>\"` directly is no longer supported in 24.05. Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` instead."; 348 } 349 ]; 350 351 environment.systemPackages = [ cfg.package ] 352 ++ optional cfg.jack.enable jack-libs; 353 354 systemd.packages = [ cfg.package ]; 355 356 # PipeWire depends on DBUS but doesn't list it. Without this booting 357 # into a terminal results in the service crashing with an error. 358 systemd.services.pipewire.bindsTo = [ "dbus.service" ]; 359 systemd.user.services.pipewire.bindsTo = [ "dbus.service" ]; 360 361 # Enable either system or user units. Note that for pipewire-pulse there 362 # are only user units, which work in both cases. 363 systemd.sockets.pipewire.enable = cfg.systemWide; 364 systemd.services.pipewire.enable = cfg.systemWide; 365 systemd.user.sockets.pipewire.enable = !cfg.systemWide; 366 systemd.user.services.pipewire.enable = !cfg.systemWide; 367 368 systemd.services.pipewire.environment.LV2_PATH = mkIf cfg.systemWide "${lv2Plugins}/lib/lv2"; 369 systemd.user.services.pipewire.environment.LV2_PATH = mkIf (!cfg.systemWide) "${lv2Plugins}/lib/lv2"; 370 371 # Mask pw-pulse if it's not wanted 372 systemd.user.services.pipewire-pulse.enable = cfg.pulse.enable; 373 systemd.user.sockets.pipewire-pulse.enable = cfg.pulse.enable; 374 375 systemd.sockets.pipewire.wantedBy = mkIf cfg.socketActivation [ "sockets.target" ]; 376 systemd.user.sockets.pipewire.wantedBy = mkIf cfg.socketActivation [ "sockets.target" ]; 377 systemd.user.sockets.pipewire-pulse.wantedBy = mkIf cfg.socketActivation [ "sockets.target" ]; 378 379 services.udev.packages = [ cfg.package ]; 380 381 # If any paths are updated here they must also be updated in the package test. 382 environment.etc = { 383 "alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable { 384 text = '' 385 pcm_type.pipewire { 386 libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_pcm_pipewire.so ; 387 ${optionalString enable32BitAlsaPlugins 388 "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"} 389 } 390 ctl_type.pipewire { 391 libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_ctl_pipewire.so ; 392 ${optionalString enable32BitAlsaPlugins 393 "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"} 394 } 395 ''; 396 }; 397 398 "alsa/conf.d/50-pipewire.conf" = mkIf cfg.alsa.enable { 399 source = "${cfg.package}/share/alsa/alsa.conf.d/50-pipewire.conf"; 400 }; 401 402 "alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable { 403 source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf"; 404 }; 405 pipewire.source = "${configs}/share/pipewire"; 406 }; 407 408 environment.sessionVariables.LD_LIBRARY_PATH = 409 mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ]; 410 411 networking.firewall.allowedUDPPorts = mkIf cfg.raopOpenFirewall [ 6001 6002 ]; 412 413 users = mkIf cfg.systemWide { 414 users.pipewire = { 415 uid = config.ids.uids.pipewire; 416 group = "pipewire"; 417 extraGroups = [ 418 "audio" 419 "video" 420 ] ++ optional config.security.rtkit.enable "rtkit"; 421 description = "PipeWire system service user"; 422 isSystemUser = true; 423 home = "/var/lib/pipewire"; 424 createHome = true; 425 }; 426 groups.pipewire.gid = config.ids.gids.pipewire; 427 }; 428 }; 429}