nixos/wireplumber: add extraConfig/extraScripts options

Follow-up to #282377. #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.
This commit is contained in:
Hans Christian Schmitz 2024-02-28 17:23:33 +01:00
parent b2245daba6
commit 72ed33777c
No known key found for this signature in database
GPG Key ID: 1D729DE4757DE669

View File

@ -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 ];
@ -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 = [ ];
@ -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
@ -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";
@ -110,7 +253,7 @@ in
(
concatMap
(p:
attrByPath ["passthru" "requiredLv2Packages"] [] p
attrByPath [ "passthru" "requiredLv2Packages" ] [ ] p
)
configPackages
);
@ -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;
@ -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";
};
};
}