From 7e9e261fa68fa7920be05b5de98ee5335c5e46de Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 15 Mar 2024 11:55:18 +0100 Subject: [PATCH] conf: support property rules Add pw_conf_section_update_props_rules() that will not only update the properties of a section but wil also apply rules in section.rules and match against the context properties. Use this by default when using pw_context_conf_update_props(). Add a new method to get a string name of the VM type. Place the cpu.vm.name in the context properties. This makes it possible to deprecate the vm.overrides with something more flexible based on rules. Update the conf files and docs to refect this. --- doc/dox/config/pipewire.conf.5.md | 30 ++++++++++- spa/include/spa/support/cpu.h | 40 +++++++++++++++ src/daemon/minimal.conf.in | 16 ++++-- src/daemon/pipewire-avb.conf.in | 11 +++- src/daemon/pipewire-pulse.conf.in | 15 ++++-- src/daemon/pipewire.conf.in | 16 ++++-- src/modules/module-avb/avb.c | 2 + src/modules/module-protocol-pulse.c | 51 ++++++++++++------- .../module-protocol-pulse/pulse-server.c | 2 + src/pipewire/conf.c | 22 ++++++-- src/pipewire/conf.h | 4 ++ src/pipewire/context.c | 10 +++- 12 files changed, 178 insertions(+), 41 deletions(-) diff --git a/doc/dox/config/pipewire.conf.5.md b/doc/dox/config/pipewire.conf.5.md index 3c1868480..75e242a0b 100644 --- a/doc/dox/config/pipewire.conf.5.md +++ b/doc/dox/config/pipewire.conf.5.md @@ -167,6 +167,11 @@ PipeWire socket clients can connect to. Configures the CPU to zero denormals automatically. This will be enabled for the data processing thread only, when enabled. +@PAR@ pipewire.conf cpu.vm.name = null +This will be set automatically when the context is created and will +contain the name of the VM. It is typically used to write match rules +to set extra properties. + @PAR@ pipewire.conf default.clock.rate = 48000 The default clock rate determines the real time duration of the min/max/default quantums. You might want to change the quantums when @@ -245,7 +250,8 @@ it. Disable this if you want to globally disable DBus support in the process. @PAR@ pipewire.conf vm.overrides = { default.clock.min-quantum = 1024 } Any property in the vm.overrides property object will override the property -in the context.properties when PipeWire detects it is running in a VM. +in the context.properties when PipeWire detects it is running in a VM. This +is deprected, use the context.properties.rules instead. The context properties may also contain custom values. For example, the `context.modules` and `context.objects` sections can declare @@ -436,6 +442,28 @@ The available actions and their values depend on the specific rule that is used. Usually it is possible to update some properties or set some quirks on the object. +# CONTEXT PROPERTIES RULES @IDX@ pipewire.conf + +`context.properties.rules` can be used to dynamically update the properties +based on other properties. + +A typical case is to update custom settings when running inside a VM. +The `cpu.vm.name` is automatically set when running in a VM with the name of +the VM. A match rule can be written to set custom properties like this: + +``` +context.properties.rules = [ + { matches = [ { cpu.vm.name = !null } ] + actions = { + update-props = { + # These overrides are only applied when running in a vm. + default.clock.min-quantum = 1024 + } + } + } +} +``` + # NODE RULES @IDX@ pipewire.conf The node.rules are evaluated every time the properties on a node are set diff --git a/spa/include/spa/support/cpu.h b/spa/include/spa/support/cpu.h index 7ec80006b..350dee78f 100644 --- a/spa/include/spa/support/cpu.h +++ b/spa/include/spa/support/cpu.h @@ -87,6 +87,46 @@ struct spa_cpu { struct spa_interface iface; }; #define SPA_CPU_VM_ACRN (1 << 13) #define SPA_CPU_VM_POWERVM (1 << 14) +static inline const char *spa_cpu_vm_type_to_string(uint32_t vm_type) +{ + switch(vm_type) { + case SPA_CPU_VM_NONE: + return NULL; + case SPA_CPU_VM_KVM: + return "kvm"; + case SPA_CPU_VM_QEMU: + return "qemu"; + case SPA_CPU_VM_BOCHS: + return "bochs"; + case SPA_CPU_VM_XEN: + return "xen"; + case SPA_CPU_VM_UML: + return "uml"; + case SPA_CPU_VM_VMWARE: + return "vmware"; + case SPA_CPU_VM_ORACLE: + return "oracle"; + case SPA_CPU_VM_MICROSOFT: + return "microsoft"; + case SPA_CPU_VM_ZVM: + return "zvm"; + case SPA_CPU_VM_PARALLELS: + return "parallels"; + case SPA_CPU_VM_BHYVE: + return "bhyve"; + case SPA_CPU_VM_QNX: + return "qnx"; + case SPA_CPU_VM_ACRN: + return "acrn"; + case SPA_CPU_VM_POWERVM: + return "powervm"; + case SPA_CPU_VM_OTHER: + return "other"; + default: + return "unknown"; + } +} + /** * methods */ diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 08a9f38e1..f79ea4170 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -40,11 +40,6 @@ context.properties = { # settings.check-quantum = true settings.check-rate = true - # - # These overrides are only applied when running in a vm. - vm.overrides = { - default.clock.min-quantum = 1024 - } # This config can use udev or hardcoded ALSA devices. Make sure to # change the alsa device below when disabling udev @@ -54,6 +49,17 @@ context.properties = { minimal.use-pulse = true } +context.properties.rules = [ + { matches = [ { cpu.vm.name = !null } ] + actions = { + update-props = { + # These overrides are only applied when running in a vm. + default.clock.min-quantum = 1024 + } + } + } +} + context.spa-libs = { # = # diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in index 3752a4ec7..afb99f96d 100644 --- a/src/daemon/pipewire-avb.conf.in +++ b/src/daemon/pipewire-avb.conf.in @@ -67,7 +67,14 @@ avb.properties = { # the addresses this server listens on #ifname = "eth0.2" ifname = "enp3s0" - # These overrides are only applied when running in a vm. - vm.overrides = { +} + +avb.properties.rules = [ + { matches = [ { cpu.vm.name = !null } ] + actions = { + update-props = { + # These overrides are only applied when running in a vm. + } + } } } diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 1379ecf3c..8d72489d7 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -110,12 +110,19 @@ pulse.properties = { #pulse.idle.timeout = 0 # don't pause after underruns #pulse.default.format = F32 #pulse.default.position = [ FL FR ] - # These overrides are only applied when running in a vm. - vm.overrides = { - pulse.min.quantum = 1024/48000 # 22ms - } } +pulse.properties.rules = [ + { matches = [ { cpu.vm.name = !null } ] + actions = { + update-props = { + # These overrides are only applied when running in a vm. + pulse.min.quantum = 1024/48000 # 22ms + } + } + } +] + # client/stream specific properties pulse.rules = [ { diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 71926f6a0..fb38ac255 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -40,11 +40,6 @@ context.properties = { # #settings.check-quantum = false #settings.check-rate = false - # - # These overrides are only applied when running in a vm. - vm.overrides = { - default.clock.min-quantum = 1024 - } # keys checked below to disable module loading module.x11.bell = true @@ -55,6 +50,17 @@ context.properties = { module.jackdbus-detect = true } +context.properties.rules = [ + { matches = [ { cpu.vm.name = !null } ] + actions = { + update-props = { + # These overrides are only applied when running in a vm. + default.clock.min-quantum = 1024 + } + } + } +} + context.spa-libs = { # = # diff --git a/src/modules/module-avb/avb.c b/src/modules/module-avb/avb.c index 61ee48eaa..7bfa85eb9 100644 --- a/src/modules/module-avb/avb.c +++ b/src/modules/module-avb/avb.c @@ -32,6 +32,8 @@ struct pw_avb *pw_avb_new(struct pw_context *context, pw_context_conf_update_props(context, "avb.properties", props); if ((str = pw_properties_get(props, "vm.overrides")) != NULL) { + pw_log_warn("vm.overrides in avb.properties are deprecated, " + "use avb.properties.rules instead"); if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) pw_properties_update_string(props, str, strlen(str)); pw_properties_set(props, "vm.overrides", NULL); diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c index e50dc752c..30b72d112 100644 --- a/src/modules/module-protocol-pulse.c +++ b/src/modules/module-protocol-pulse.c @@ -71,11 +71,17 @@ * #pulse.min.quantum = 128/48000 # 2.7ms * #pulse.default.format = F32 * #pulse.default.position = [ FL FR ] - * # These overrides are only applied when running in a vm. - * vm.overrides = { - * pulse.min.quantum = 1024/48000 # 22ms - * } * } + * pulse.properties.rules = [ + * { matches = [ { cpu.vm.name = !null } ] + * actions = { + * update-props = { + * # These overrides are only applied when running in a vm. + * pulse.min.quantum = 1024/48000 # 22ms + * } + * } + * } + * ] *\endcode * * ### Connection options @@ -185,19 +191,6 @@ * This is equivalent to the PulseAudio `default-sample-channels` and * `default-channel-map` options in `/etc/pulse/daemon.conf`. * - * ### VM options - * - *\code{.unparsed} - * vm.overrides = { - * pulse.min.quantum = 1024/48000 # 22ms - * } - *\endcode - * - * When running in a VM, the `vm.override` section will override the properties - * in pulse.properties with the given values. This might be interesting because - * VMs usually can't support the low latency settings that are possible on real - * hardware. - * * ### Quirk options * *\code{.unparsed} @@ -240,6 +233,30 @@ * #{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] } * ] *\endcode + + * ## Dynamic properties + * + * The pulse.properties can be dyanmically updated with rules. It supports + * an `update-props` action. The matches will be performed on the values in + * context.properties. + * + *\code{.unparsed} + * pulse.properties.rules = [ + * { matches = [ { cpu.vm.name = !null } ] + * actions = { + * update-props = { + * # These overrides are only applied when running in a vm. + * pulse.min.quantum = 1024/48000 # 22ms + * } + * } + * } + * ] + *\endcode + * + * In the above example, when running in a VM, the rule will override the properties + * in pulse.properties with the given values. This might be interesting because + * VMs usually can't support the low latency settings that are possible on real + * hardware. * * ## Stream settings and rules * diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 9bfae0f0a..d7e4e10ab 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -5502,6 +5502,8 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context, pw_context_conf_update_props(context, "pulse.properties", props); if ((str = pw_properties_get(props, "vm.overrides")) != NULL) { + pw_log_warn("vm.overrides in pulse.properties are deprecated, " + "use pulse.properties.rules instead"); if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) pw_properties_update_string(props, str, strlen(str)); pw_properties_set(props, "vm.overrides", NULL); diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c index d01a28d6e..3c04e13a2 100644 --- a/src/pipewire/conf.c +++ b/src/pipewire/conf.c @@ -986,26 +986,38 @@ static int update_props(void *user_data, const char *location, const char *key, } SPA_EXPORT -int pw_conf_section_update_props(const struct spa_dict *conf, - const char *section, struct pw_properties *props) +int pw_conf_section_update_props_rules(const struct spa_dict *conf, + const struct spa_dict *context, const char *section, + struct pw_properties *props) { struct data data = { .props = props }; int res; const char *str; + char key[128]; res = pw_conf_section_for_each(conf, section, update_props, &data); str = pw_properties_get(props, "config.ext"); if (res == 0 && str != NULL) { - char key[128]; snprintf(key, sizeof(key), "%s.%s", section, str); res = pw_conf_section_for_each(conf, key, update_props, &data); } + if (res == 0 && context != NULL) { + snprintf(key, sizeof(key), "%s.rules", section); + res = pw_conf_section_match_rules(conf, key, context, update_props, &data); + } return res == 0 ? data.count : res; } +SPA_EXPORT +int pw_conf_section_update_props(const struct spa_dict *conf, + const char *section, struct pw_properties *props) +{ + return pw_conf_section_update_props_rules(conf, NULL, section, props); +} + static bool valid_conf_name(const char *str) { return spa_streq(str, "null") || spa_strendswith(str, ".conf"); @@ -1208,8 +1220,8 @@ SPA_EXPORT int pw_context_conf_update_props(struct pw_context *context, const char *section, struct pw_properties *props) { - return pw_conf_section_update_props(&context->conf->dict, - section, props); + return pw_conf_section_update_props_rules(&context->conf->dict, + &context->properties->dict, section, props); } SPA_EXPORT diff --git a/src/pipewire/conf.h b/src/pipewire/conf.h index 6031f3c44..66898b1f9 100644 --- a/src/pipewire/conf.h +++ b/src/pipewire/conf.h @@ -24,6 +24,10 @@ int pw_conf_save_state(const char *prefix, const char *name, const struct pw_pro int pw_conf_section_update_props(const struct spa_dict *conf, const char *section, struct pw_properties *props); +int pw_conf_section_update_props_rules(const struct spa_dict *conf, + const struct spa_dict *context, const char *section, + struct pw_properties *props); + int pw_conf_section_for_each(const struct spa_dict *conf, const char *section, int (*callback) (void *data, const char *location, const char *section, const char *str, size_t len), diff --git a/src/pipewire/context.c b/src/pipewire/context.c index eec4f2225..a94119af9 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -180,7 +180,7 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, struct pw_context *this; const char *lib, *str; void *dbus_iface = NULL; - uint32_t n_support; + uint32_t n_support, vm_type; struct pw_properties *pr, *conf; struct spa_cpu *cpu; int res = 0; @@ -244,6 +244,10 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, n_support = pw_get_support(this->support, SPA_N_ELEMENTS(this->support) - 6); cpu = spa_support_find(this->support, n_support, SPA_TYPE_INTERFACE_CPU); + vm_type = SPA_CPU_VM_NONE; + if (cpu != NULL && (vm_type = spa_cpu_get_vm_type(cpu)) != SPA_CPU_VM_NONE) + pw_properties_set(properties, "cpu.vm.name", spa_cpu_vm_type_to_string(vm_type)); + res = pw_context_conf_update_props(this, "context.properties", properties); pw_log_info("%p: parsed %d context.properties items", this, res); @@ -253,7 +257,9 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, } if ((str = pw_properties_get(properties, "vm.overrides")) != NULL) { - if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) + pw_log_warn("vm.overrides in context.properties are deprecated, " + "use context.properties.rules instead"); + if (vm_type != SPA_CPU_VM_NONE) pw_properties_update_string(properties, str, strlen(str)); pw_properties_set(properties, "vm.overrides", NULL); }