Skip to content

Commit

Permalink
nixos/wireplumber: add extraConfig/extraScripts options
Browse files Browse the repository at this point in the history
Follow-up to NixOS#282377. NixOS#282377 broke `environment.etc."wireplumber<...>"`,
however WirePlumber did not yet have `extraConfig` style options for
configuring it ergonomically outside of `environment.etc`. This has
caused issues for people who had custom config files for WirePlumber, as
having to create a config package just to edit some settings is not as
ergonomic or discoverable as with a proper `extraConfig` style option.

This commit fixes this issue by adding the `extraConfig` option for
additional config file and the `extraScripts` option for additional
scripts to be used by config files.

With WirePlumber 0.5 it is possible to supply config files and scripts
via the `XDG_DATA_DIRS` variable to the WirePlumber daemon. This is how
the new options and with this change also the `configPackages` option
expose their files to the daemon. This way
`environment.etc."wireplumber"` works again for user configuration and
breakage of old configs from 23.11 to 24.05 should be limited to those
caused by the change in the config format from WirePlumber 0.4 to 0.5.
  • Loading branch information
hcsch committed Apr 21, 2024
1 parent b2245da commit 72ed337
Showing 1 changed file with 159 additions and 24 deletions.
183 changes: 159 additions & 24 deletions nixos/modules/services/desktops/pipewire/wireplumber.nix
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
{ config, lib, pkgs, ... }:

let
inherit (builtins) attrNames concatMap length;
inherit (builtins) concatMap;
inherit (lib) maintainers;
inherit (lib.attrsets) attrByPath filterAttrs;
inherit (lib.attrsets) attrByPath mapAttrsToList;
inherit (lib.lists) flatten optional;
inherit (lib.modules) mkIf;
inherit (lib.options) literalExpression mkOption;
inherit (lib.strings) hasPrefix;
inherit (lib.types) bool listOf package;
inherit (lib.strings) concatStringsSep makeSearchPath;
inherit (lib.types) bool listOf attrsOf package lines;
inherit (lib.path) subpath;

pwCfg = config.services.pipewire;
cfg = pwCfg.wireplumber;
pwUsedForAudio = pwCfg.audio.enable;

json = pkgs.formats.json { };

configSectionsToConfFile = path: value:
pkgs.writeTextDir
path
(concatStringsSep "\n" (
mapAttrsToList
(section: content: "${section} = " + (builtins.toJSON content))
value
));

mapConfigToFiles = config:
mapAttrsToList
(name: value: configSectionsToConfFile "share/wireplumber/wireplumber.conf.d/${name}.conf" value)
config;

mapScriptsToFiles = scripts:
mapAttrsToList
(relativePath: value: pkgs.writeTextDir (subpath.join ["share/wireplumber/scripts" relativePath]) value)
scripts;
in
{
meta.maintainers = [ maintainers.k900 ];
Expand All @@ -33,6 +55,114 @@ in
description = "The WirePlumber derivation to use.";
};

extraConfig = mkOption {
# Two layer attrset is necessary before using JSON, because of the whole
# config file not being a JSON object, but a concatenation of JSON objects
# in sections.
type = attrsOf (attrsOf json.type);
default = { };
example = literalExpression ''{
"log-level-debug" = {
"context.properties" = {
# Output Debug log messages as opposed to only the default level (Notice)
"log.level" = "D";
};
};
"wh-1000xm3-ldac-hq" = {
"monitor.bluez.rules" = [
{
matches = [
{
# Match any bluetooth device with ids equal to that of a WH-1000XM3
"device.name" = "~bluez_card.*";
"device.product.id" = "0x0cd3";
"device.vendor.id" = "usb:054c";
}
];
actions = {
update-props = {
# Set quality to high quality instead of the default of auto
"bluez5.a2dp.ldac.quality" = "hq";
};
};
}
];
};
}'';
description = ''
Additional configuration for the WirePlumber daemon when run in
single-instance mode (the default in nixpkgs and currently the only
supported way to run WirePlumber configured via `extraConfig`).
See also:
- [The configuration file][docs-the-conf-file]
- [Modifying configuration][docs-modifying-config]
- [Locations of files][docs-file-locations]
- and the [configuration section][docs-config-section] of the docs in general
Note that WirePlumber (and PipeWire) use dotted attribute names like
`device.product.id`. These are not nested, but flat objects for WirePlumber/PipeWire,
so to write these in nix expressions, remember to quote them like `"device.product.id"`.
Have a look at the example for this.
[docs-the-conf-file]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/conf_file.html
[docs-modifying-config]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/modifying_configuration.html
[docs-file-locations]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/locations.html
[docs-config-section]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html
'';
};

extraScripts = mkOption {
type = attrsOf lines;
default = { };
example = {
"test/hello-world.lua" = ''
print("Hello, world!")
'';
};
description = ''
Additional scripts for WirePlumber to be used by configuration files.
Every item in this attrset becomes a separate lua file with the path
relative to the `scripts` directory specified in the name of the item.
The scripts get passed to the WirePlumber service via the `XDG_DATA_DIRS`
variable. Scripts specified here are preferred over those shipped with
WirePlumber if they occupy the same relative path.
For a script to be loaded, it needs to be specified as part of a component,
and that component needs to be required by an active profile (e.g. `main`).
Components can be defined in config files either via `extraConfig` or `configPackages`.
For the hello-world example, you'd have to add the following `extraConfig`:
```nix
services.pipewire.wireplumber.extraConfig."99-hello-world" = {
"wireplumber.components" = [
{
name = "test/hello-world.lua";
type = "script/lua";
provides = "custom.hello-world";
}
];
"wireplumber.profiles" = {
main = {
"custom.hello-world" = "required";
};
};
};
```
See also:
- [Location of scripts][docs-file-locations-scripts]
- [Components & Profiles][docs-components-profiles]
- [Migration - Loading custom scripts][docs-migration-loading-custom-scripts]
[docs-file-locations-scripts]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/locations.html#location-of-scripts
[docs-components-profiles]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/components_and_profiles.html
[docs-migration-loading-custom-scripts]: https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/migration.html#loading-custom-scripts
'';
};

configPackages = mkOption {
type = listOf package;
default = [ ];
Expand All @@ -57,7 +187,7 @@ in

extraLv2Packages = mkOption {
type = listOf package;
default = [];
default = [ ];
example = literalExpression "[ pkgs.lsp-plugins ]";
description = ''
List of packages that provide LV2 plugins in `lib/lv2` that should
Expand Down Expand Up @@ -96,9 +226,22 @@ in
}
'';

extraConfigPkg = pkgs.buildEnv {
name = "wireplumber-extra-config";
paths = mapConfigToFiles cfg.extraConfig;
pathsToLink = [ "/share/wireplumber/wireplumber.conf.d" ];
};

extraScriptsPkg = pkgs.buildEnv {
name = "wireplumber-extra-scrips";
paths = mapScriptsToFiles cfg.extraScripts;
pathsToLink = [ "/share/wireplumber/scripts" ];
};

configPackages = cfg.configPackages
++ optional (!pwUsedForAudio) pwNotForAudioConfigPkg
++ optional pwCfg.systemWide systemwideConfigPkg;
++ [ extraConfigPkg extraScriptsPkg ]
++ optional (!pwUsedForAudio) pwNotForAudioConfigPkg
++ optional pwCfg.systemWide systemwideConfigPkg;

configs = pkgs.buildEnv {
name = "wireplumber-configs";
Expand All @@ -110,7 +253,7 @@ in
(
concatMap
(p:
attrByPath ["passthru" "requiredLv2Packages"] [] p
attrByPath [ "passthru" "requiredLv2Packages" ] [ ] p
)
configPackages
);
Expand All @@ -127,24 +270,10 @@ in
assertion = !config.hardware.bluetooth.hsphfpd.enable;
message = "Using WirePlumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false";
}
{
assertion = length
(attrNames
(
filterAttrs
(name: value:
hasPrefix "wireplumber/" name || name == "wireplumber"
)
config.environment.etc
)) == 1;
message = "Using `environment.etc.\"wireplumber<...>\"` directly is no longer supported in 24.05. Use `services.pipewire.wireplumber.configPackages` instead.";
}
];

environment.systemPackages = [ cfg.package ];

environment.etc.wireplumber.source = "${configs}/share/wireplumber";

systemd.packages = [ cfg.package ];

systemd.services.wireplumber.enable = pwCfg.systemWide;
Expand All @@ -156,10 +285,16 @@ in
systemd.services.wireplumber.environment = mkIf pwCfg.systemWide {
# Force WirePlumber to use system dbus.
DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/dbus/system_bus_socket";

# Make WirePlumber find our config/script files and lv2 plugins required by those
# (but also the configs/scripts shipped with WirePlumber)
XDG_DATA_DIRS = makeSearchPath "share" [ configs cfg.package ];
LV2_PATH = "${lv2Plugins}/lib/lv2";
};

systemd.user.services.wireplumber.environment.LV2_PATH =
mkIf (!pwCfg.systemWide) "${lv2Plugins}/lib/lv2";
systemd.user.services.wireplumber.environment = mkIf (!pwCfg.systemWide) {
XDG_DATA_DIRS = makeSearchPath "share" [ configs cfg.package ];
LV2_PATH = "${lv2Plugins}/lib/lv2";
};
};
}

0 comments on commit 72ed337

Please sign in to comment.