From 7f08cadca42461c25c476929d71b1d085f7e55f5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Sat, 22 Jan 2022 11:51:11 +0100 Subject: [PATCH] pulse-server: move quirks to config section Make a pulse.rules section in the config file with match rules. Add support for setting custom client properties and quirks. --- src/daemon/pipewire-pulse.conf.in | 36 ++++ .../module-protocol-pulse/pulse-server.c | 4 +- src/modules/module-protocol-pulse/quirks.c | 163 ++++++++++++------ 3 files changed, 145 insertions(+), 58 deletions(-) diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 302daad97..4b3da7949 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -80,3 +80,39 @@ stream.properties = { #channelmix.upmix = false #channelmix.lfe-cutoff = 0 } + +# client/stream specific properties +pulse.rules = [ + { + matches = [ + { + # all keys must match the value. ~ starts regex. + #client.name = "Firefox" + #application.process.binary = "teams" + #application.name = "~teams.*" + } + ] + actions = { + update-props = { + #node.latency = 512/48000 + } + # Possible quirks:" + # force-s16-info forces sink and source info as S16 format + # remove-capture-dont-move removes the capture DONT_MOVE flag + #quirks = [ ] + } + } + { + matches = [ + { application.process.binary = "teams" } + { application.process.binary = "skypeforlinux" } + ] + actions = { quirks = [ force-s16-info ] } + } + { + matches = [ + { application.process.binary = "firefox" } + ] + actions = { quirks = [ remove-capture-dont-move ] } + } +] diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 11de89095..a6bbd63b2 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -888,6 +888,8 @@ static int do_set_client_name(struct client *client, uint32_t command, uint32_t pw_log_info("[%s] %s tag:%d", client->name, commands[command].name, tag); + client_update_quirks(client); + if (client->core == NULL) { client->core = pw_context_connect(impl->context, pw_properties_copy(client->props), 0); @@ -911,8 +913,6 @@ static int do_set_client_name(struct client *client, uint32_t command, uint32_t res = reply_set_client_name(client, tag); } - client_update_quirks(client); - return res; error: pw_log_error("%p: failed to connect client: %s", impl, spa_strerror(res)); diff --git a/src/modules/module-protocol-pulse/quirks.c b/src/modules/module-protocol-pulse/quirks.c index 8d218fa1d..351ed094e 100644 --- a/src/modules/module-protocol-pulse/quirks.c +++ b/src/modules/module-protocol-pulse/quirks.c @@ -30,23 +30,7 @@ #include "log.h" #include "quirks.h" - -#define QUOTE(...) #__VA_ARGS__ - -static const char quirks_rules[] = -"# List of quirks" -"#" -"# All key/value pairs need to match before the quirks are applied." -"#" -"# Possible quirks:" -"# force-s16-info forces sink and source info as S16 format" -"# remove-capture-dont-move removes the capture DONT_MOVE flag" -"#\n" -"[" -" { application.process.binary = teams, quirks = [ force-s16-info ] }," -" { application.process.binary = skypeforlinux, quirks = [ force-s16-info ] }," -" { application.process.binary = firefox, quirks = [ remove-capture-dont-move ] }," -"]"; +#include "internal.h" static uint64_t parse_quirks(const char *str) { @@ -62,72 +46,139 @@ static uint64_t parse_quirks(const char *str) return 0; } -static int match(const char *rules, struct spa_dict *dict, uint64_t *quirks) +static bool find_match(struct spa_json *arr, const struct spa_dict *props) { - struct spa_json rules_json = SPA_JSON_INIT(rules, strlen(rules)); - struct spa_json rules_arr, it[2]; + struct spa_json it[1]; - if (spa_json_enter_array(&rules_json, &rules_arr) <= 0) - return -EINVAL; - - while (spa_json_enter_object(&rules_arr, &it[0]) > 0) { - char key[256]; - int match = true; - uint64_t quirks_cur = 0; + while (spa_json_enter_object(arr, &it[0]) > 0) { + char key[256], val[1024]; + const char *str, *value; + int match = 0, fail = 0; + int len; while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { - char val[4096]; - const char *str, *value; - int len; bool success = false; - if (spa_streq(key, "quirks")) { - if (spa_json_enter_array(&it[0], &it[1]) > 0) { - while (spa_json_get_string(&it[1], val, sizeof(val)) > 0) - quirks_cur |= parse_quirks(val); - } - continue; - } if ((len = spa_json_next(&it[0], &value)) <= 0) break; + str = spa_dict_lookup(props, key); + if (spa_json_is_null(value, len)) { - value = NULL; + success = str == NULL; } else { if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0) continue; value = val; + len = strlen(val); } - str = spa_dict_lookup(dict, key); - if (value == NULL) { - success = str == NULL; - } else if (str != NULL) { + if (str != NULL) { if (value[0] == '~') { - regex_t r; - if (regcomp(&r, value+1, REG_EXTENDED | REG_NOSUB) == 0) { - if (regexec(&r, str, 0, NULL, 0) == 0) + regex_t preg; + if (regcomp(&preg, value+1, REG_EXTENDED | REG_NOSUB) == 0) { + if (regexec(&preg, str, 0, NULL, 0) == 0) success = true; - regfree(&r); + regfree(&preg); } - } else if (spa_streq(str, value)) { + } else if (strncmp(str, value, len) == 0 && + strlen(str) == (size_t)len) { success = true; } } - - if (!success) { - match = false; - break; + if (success) { + match++; + pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value); } - } - if (match) { - *quirks = quirks_cur; - return 1; + else + fail++; } + if (match > 0 && fail == 0) + return true; + } + return false; +} + +static int pw_conf_match_rules(const char *rules, size_t size, const struct spa_dict *props, + int (*matched) (void *data, const char *action, const char *val, int len), + void *data) +{ + const char *val; + struct spa_json it[4], actions; + int count = 0; + + spa_json_init(&it[0], rules, size); + if (spa_json_enter_array(&it[0], &it[1]) < 0) + return 0; + + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + char key[64]; + bool have_match = false, have_actions = false; + + while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + if (spa_streq(key, "matches")) { + if (spa_json_enter_array(&it[2], &it[3]) < 0) + break; + + have_match = find_match(&it[3], props); + } + else if (spa_streq(key, "actions")) { + if (spa_json_enter_object(&it[2], &actions) > 0) + have_actions = true; + } + else if (spa_json_next(&it[2], &val) <= 0) + break; + } + if (!have_match || !have_actions) + continue; + + while (spa_json_get_string(&actions, key, sizeof(key)) > 0) { + int res, len; + pw_log_debug("action %s", key); + + if ((len = spa_json_next(&actions, &val)) <= 0) + break; + + if (spa_json_is_container(val, len)) + len = spa_json_container_len(&actions, val, len); + + if ((res = matched(data, key, val, len)) < 0) + return res; + + count += res; + } + } + return count; +} + +static int client_rule_matched(void *data, const char *action, const char *val, int len) +{ + struct client *client = data; + + if (spa_streq(action, "update-props")) { + pw_properties_update_string(client->props, val, len); + } else if (spa_streq(action, "quirks")) { + struct spa_json quirks = SPA_JSON_INIT(val, len), it[1]; + uint64_t quirks_cur = 0; + char v[128]; + + if (spa_json_enter_array(&quirks, &it[0]) > 0) { + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0) + quirks_cur |= parse_quirks(v); + } + client->quirks = quirks_cur; } return 0; } int client_update_quirks(struct client *client) { - return match(quirks_rules, &client->props->dict, &client->quirks); + struct impl *impl = client->impl; + struct pw_context *context = impl->context; + const char *rules; + + if ((rules = pw_context_get_conf_section(context, "pulse.rules")) == NULL) + return 0; + + return pw_conf_match_rules(rules, strlen(rules), &client->props->dict, + client_rule_matched, client); }