diff --git a/doc/dox/config/pipewire.conf.5.md b/doc/dox/config/pipewire.conf.5.md index 75e242a0b..e58909cce 100644 --- a/doc/dox/config/pipewire.conf.5.md +++ b/doc/dox/config/pipewire.conf.5.md @@ -434,13 +434,105 @@ The general rules object follows the following pattern: } ] ``` +Match rules are an array of rules. -The rules is an array of things to match and what actions to perform -when a match is found. +A rule is always a JSON object with two keys: matches and actions. The matches key is used to +define the conditions that need to be met for the rule to be evaluated as true, and the actions +key is used to define the actions that are performed when the rule is evaluated as true. + +The matches key is always a JSON array of objects, where each object defines a condition that needs +to be met. Each condition is a list of key-value pairs, where the key is the name of the property +that is being matched, and the value is the value that the property needs to have. Within a condition, +all the key-value pairs are combined with a logical AND, and all the conditions in the matches +array are combined with a logical OR. + +The actions key is always a JSON object, where each key-value pair defines an action that is +performed when the rule is evaluated as true. The action name is specific to the rule and is +defined by the rule’s documentation, but most frequently you will see the update-props action, +which is used to update the properties of the matched object. + +In the matches array, it is also possible to use regular expressions to match property values. +For example, to match all nodes with a name that starts with my_, you can use the following condition: + +``` +matches = [ + { + node.name = "~my_.*" + } +] +``` + +The ~ character signifies that the value is a regular expression. The exact syntax of the regular +expressions is the POSIX extended regex syntax, as described in the regex (7) man page. + +In addition to regular expressions, you may also use the ! character to negate a condition. For +example, to match all nodes with a name that does not start with my_, you can use the following condition: + +``` +matches = [ + { + node.name = "!~my_.*" + } +] +``` + +The ! character can be used with or without a regular expression. For example, to match all +nodes with a name that is not equal to my_node, you can use the following condition: + +``` +matches = [ + { + node.name = "!my_node" + } +] +``` + +The null value has a special meaning; it checks if the property is not available +(or unset). To check if a property is not set: + +``` +matches = [ + { + node.name = null + } +] +``` + +To check the existence of a property, one can use the !null condition, for example: + +``` +matches = [ + { + node.name = "!null" + } + { + node.name = !null # simplified syntax + } +] +``` +To handle the "null" string, one needs to escape the string. For example, to check +if a property has the string value "null", use: + +``` +matches = [ + { + node.name = "null" + } +] +``` +To handle anything but the "null" string, use: + +``` +matches = [ + { + node.name = "!\"null\"" + } + { + node.name = !"null" # simplified syntax + } +] +``` -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 diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c index 3c04e13a2..e735825fb 100644 --- a/src/pipewire/conf.c +++ b/src/pipewire/conf.c @@ -594,6 +594,14 @@ static int load_module(struct pw_context *context, const char *key, const char * * = * ... * } + * + * Some things that can match: + * + * null -> matches when the property is not found + * "null" -> matches when the property is found and has the string "null" + * !null -> matches when the property is found (any value) + * "!null" -> same as !null + * !"null" and "!\"null\"" matches anything that is not the string "null" */ static bool find_match(struct spa_json *arr, const struct spa_dict *props) { @@ -606,47 +614,66 @@ static bool find_match(struct spa_json *arr, const struct spa_dict *props) int len; while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { - bool success = false, is_null; + bool success = false, is_null, reg = false, parse_string = true; int skip = 0; if ((len = spa_json_next(&it[0], &value)) <= 0) break; - if (len > 0 && value[0] == '!') { + /* first decode a string, when there was a string, we assume it + * can not be null but the "null" string, unless there is a modifier, + * see below. */ + if (spa_json_is_string(value, len)) { + if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0) + continue; + value = val; + len = strlen(val); + parse_string = false; + } + + /* parse the modifiers, after the modifier we unescape the string + * again to be able to detect and handle null and "null" */ + if (len > skip && value[skip] == '!') { success = !success; skip++; + parse_string = true; + } + if (len > skip && value[skip] == '~') { + reg = true; + skip++; + parse_string = true; } str = spa_dict_lookup(props, key); - is_null = spa_json_is_null(value+skip, len-skip); + /* parse the remaining part of the string, if there was a modifier, + * we need to check for null again. Otherwise null was in quotes without + * a modifier. */ + is_null = parse_string && spa_json_is_null(value+skip, len-skip); if (is_null || str == NULL) { if (is_null && str == NULL) success = !success; } else { - if (spa_json_parse_stringn(value+skip, len-skip, val, sizeof(val)) < 0) + /* only unescape string once or again after modifier */ + if (!parse_string) { + memmove(val, value+skip, len-skip); + val[len-skip] = '\0'; + } else if (spa_json_parse_stringn(value+skip, len-skip, val, sizeof(val)) < 0) continue; - value = val; - len = strlen(val); - if (len > 0 && value[0] == '!') { - success = !success; - skip++; - } - if (value[skip] == '~') { + + if (reg) { regex_t preg; int res; - skip++; - if ((res = regcomp(&preg, value+skip, REG_EXTENDED | REG_NOSUB)) != 0) { + if ((res = regcomp(&preg, val, REG_EXTENDED | REG_NOSUB)) != 0) { char errbuf[1024]; regerror(res, &preg, errbuf, sizeof(errbuf)); - pw_log_warn("invalid regex %s: %s", value+skip, errbuf); + pw_log_warn("invalid regex %s: %s", val, errbuf); } else { if (regexec(&preg, str, 0, NULL, 0) == 0) success = !success; regfree(&preg); } - } else if (strncmp(str, value+skip, len-skip) == 0 && - strlen(str) == (size_t)(len-skip)) { + } else if (strcmp(str, val) == 0) { success = !success; } }