conf: add conditions to modules, exec and objects

Make it possible to conditionally load modules, objects and exec by
adding match rules for context properties.

This makes it possible to only load a module when  property is set,
which makes it possible to unset a property in a local config to disable
module loading. One example is the x11 modules, which can then be
disabled on a per user bases based on config overrides.
This commit is contained in:
Wim Taymans 2023-02-07 09:50:46 +01:00
parent b9999b292d
commit 5552ff7fdd
2 changed files with 118 additions and 73 deletions

View file

@ -44,6 +44,9 @@ context.properties = {
vm.overrides = { vm.overrides = {
default.clock.min-quantum = 1024 default.clock.min-quantum = 1024
} }
# keys checked below to disable module loading
module.x11.bell = true
} }
context.spa-libs = { context.spa-libs = {
@ -68,13 +71,16 @@ context.spa-libs = {
context.modules = [ context.modules = [
#{ name = <module-name> #{ name = <module-name>
# [ args = { <key> = <value> ... } ] # ( args = { <key> = <value> ... } )
# [ flags = [ [ ifexists ] [ nofail ] ] # ( flags = [ ( ifexists ) ( nofail ) ] )
# ( condition = [ { <key> = <value> ... } ... ] )
#} #}
# #
# Loads a module with the given parameters. # Loads a module with the given parameters.
# If ifexists is given, the module is ignored when it is not found. # If ifexists is given, the module is ignored when it is not found.
# If nofail is given, module initialization failures are ignored. # If nofail is given, module initialization failures are ignored.
# If condition is given, the module is loaded only when the context
# properties all match the match rules.
# #
# Uses realtime scheduling to boost the audio thread priorities. This uses # Uses realtime scheduling to boost the audio thread priorities. This uses
@ -167,17 +173,21 @@ context.modules = [
#x11.xauthority = null #x11.xauthority = null
} }
flags = [ ifexists nofail ] flags = [ ifexists nofail ]
condition = [ { module.x11.bell = true } ]
} }
] ]
context.objects = [ context.objects = [
#{ factory = <factory-name> #{ factory = <factory-name>
# [ args = { <key> = <value> ... } ] # ( args = { <key> = <value> ... } )
# [ flags = [ [ nofail ] ] # ( flags = [ ( nofail ) ] )
# ( condition = [ { <key> = <value> ... } ... ] )
#} #}
# #
# Creates an object from a PipeWire factory with the given parameters. # Creates an object from a PipeWire factory with the given parameters.
# If nofail is given, errors are ignored (and no object is created). # If nofail is given, errors are ignored (and no object is created).
# If condition is given, the object is created only when the context properties
# all match the match rules.
# #
#{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } } #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } }
#{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] }
@ -256,9 +266,14 @@ context.objects = [
] ]
context.exec = [ context.exec = [
#{ path = <program-name> [ args = "<arguments>" ] } #{ path = <program-name>
# ( args = "<arguments>" )
# ( condition = [ { <key> = <value> ... } ... ] )
#}
# #
# Execute the given program with arguments. # Execute the given program with arguments.
# If condition is given, the program is executed only when the context
# properties all match the match rules.
# #
# You can optionally start the session manager here, # You can optionally start the session manager here,
# but it is better to start it as a systemd service. # but it is better to start it as a systemd service.

View file

@ -604,11 +604,71 @@ static int load_module(struct pw_context *context, const char *key, const char *
return 0; return 0;
} }
/*
* {
* # all keys must match the value. ~ in value starts regex.
* <key> = <value>
* ...
* }
*/
static bool find_match(struct spa_json *arr, const struct spa_dict *props)
{
struct spa_json it[1];
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) {
bool success = false;
if ((len = spa_json_next(&it[0], &value)) <= 0)
break;
str = spa_dict_lookup(props, key);
if (spa_json_is_null(value, len)) {
success = str == NULL;
} else {
if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0)
continue;
value = val;
len = strlen(val);
}
if (str != NULL) {
if (value[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(&preg);
}
} else if (strncmp(str, value, len) == 0 &&
strlen(str) == (size_t)len) {
success = true;
}
}
if (success) {
match++;
pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value);
}
else
fail++;
}
if (match > 0 && fail == 0)
return true;
}
return false;
}
/* /*
* context.modules = [ * context.modules = [
* { name = <module-name> * { name = <module-name>
* [ args = { <key> = <value> ... } ] * ( args = { <key> = <value> ... } )
* [ flags = [ [ ifexists ] [ nofail ] ] * ( flags = [ ( ifexists ) ( nofail ) ]
* ( condition = [ { key = value, .. } .. ] )
* } * }
* ] * ]
*/ */
@ -617,7 +677,7 @@ static int parse_modules(void *user_data, const char *location,
{ {
struct data *d = user_data; struct data *d = user_data;
struct pw_context *context = d->context; struct pw_context *context = d->context;
struct spa_json it[3]; struct spa_json it[4];
char key[512], *s; char key[512], *s;
int res = 0; int res = 0;
@ -631,6 +691,7 @@ static int parse_modules(void *user_data, const char *location,
while (spa_json_enter_object(&it[1], &it[2]) > 0) { while (spa_json_enter_object(&it[1], &it[2]) > 0) {
char *name = NULL, *args = NULL, *flags = NULL; char *name = NULL, *args = NULL, *flags = NULL;
bool have_match = true;
while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
const char *val; const char *val;
@ -653,8 +714,16 @@ static int parse_modules(void *user_data, const char *location,
len = spa_json_container_len(&it[2], val, len); len = spa_json_container_len(&it[2], val, len);
flags = (char*)val; flags = (char*)val;
spa_json_parse_stringn(val, len, flags, len+1); spa_json_parse_stringn(val, len, flags, len+1);
} else if (spa_streq(key, "condition")) {
if (!spa_json_is_array(val, len))
break;
spa_json_enter(&it[2], &it[3]);
have_match = find_match(&it[3], &context->properties->dict);
} }
} }
if (!have_match)
continue;
if (name != NULL) if (name != NULL)
res = load_module(context, name, args, flags); res = load_module(context, name, args, flags);
@ -698,8 +767,9 @@ static int create_object(struct pw_context *context, const char *key, const char
/* /*
* context.objects = [ * context.objects = [
* { factory = <factory-name> * { factory = <factory-name>
* [ args = { <key> = <value> ... } ] * ( args = { <key> = <value> ... } )
* [ flags = [ [ nofail ] ] ] * ( flags = [ ( nofail ) ] )
* ( condition = [ { key = value, .. } .. ] )
* } * }
* ] * ]
*/ */
@ -708,7 +778,7 @@ static int parse_objects(void *user_data, const char *location,
{ {
struct data *d = user_data; struct data *d = user_data;
struct pw_context *context = d->context; struct pw_context *context = d->context;
struct spa_json it[3]; struct spa_json it[4];
char key[512], *s; char key[512], *s;
int res = 0; int res = 0;
@ -722,6 +792,7 @@ static int parse_objects(void *user_data, const char *location,
while (spa_json_enter_object(&it[1], &it[2]) > 0) { while (spa_json_enter_object(&it[1], &it[2]) > 0) {
char *factory = NULL, *args = NULL, *flags = NULL; char *factory = NULL, *args = NULL, *flags = NULL;
bool have_match = true;
while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
const char *val; const char *val;
@ -745,8 +816,16 @@ static int parse_objects(void *user_data, const char *location,
flags = (char*)val; flags = (char*)val;
spa_json_parse_stringn(val, len, flags, len+1); spa_json_parse_stringn(val, len, flags, len+1);
} else if (spa_streq(key, "condition")) {
if (!spa_json_is_array(val, len))
break;
spa_json_enter(&it[2], &it[3]);
have_match = find_match(&it[3], &context->properties->dict);
} }
} }
if (!have_match)
continue;
if (factory != NULL) if (factory != NULL)
res = create_object(context, factory, args, flags); res = create_object(context, factory, args, flags);
@ -807,8 +886,9 @@ static int do_exec(struct pw_context *context, const char *key, const char *args
/* /*
* context.exec = [ * context.exec = [
* { path = <program-name> * { path = <program-name>
* [ args = "<arguments>" ] * ( args = "<arguments>" )
* ( condition = [ { key = value, .. } .. ] )
* } * }
* ] * ]
*/ */
@ -817,7 +897,7 @@ static int parse_exec(void *user_data, const char *location,
{ {
struct data *d = user_data; struct data *d = user_data;
struct pw_context *context = d->context; struct pw_context *context = d->context;
struct spa_json it[3]; struct spa_json it[4];
char key[512], *s; char key[512], *s;
int res = 0; int res = 0;
@ -831,6 +911,7 @@ static int parse_exec(void *user_data, const char *location,
while (spa_json_enter_object(&it[1], &it[2]) > 0) { while (spa_json_enter_object(&it[1], &it[2]) > 0) {
char *path = NULL, *args = NULL; char *path = NULL, *args = NULL;
bool have_match = true;
while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
const char *val; const char *val;
@ -845,8 +926,16 @@ static int parse_exec(void *user_data, const char *location,
} else if (spa_streq(key, "args")) { } else if (spa_streq(key, "args")) {
args = (char*)val; args = (char*)val;
spa_json_parse_stringn(val, len, args, len+1); spa_json_parse_stringn(val, len, args, len+1);
} else if (spa_streq(key, "condition")) {
if (!spa_json_is_array(val, len))
break;
spa_json_enter(&it[2], &it[3]);
have_match = find_match(&it[3], &context->properties->dict);
} }
} }
if (!have_match)
continue;
if (path != NULL) if (path != NULL)
res = do_exec(context, path, args); res = do_exec(context, path, args);
@ -1014,65 +1103,6 @@ int pw_context_conf_update_props(struct pw_context *context,
} }
/*
* {
* # all keys must match the value. ~ in value starts regex.
* <key> = <value>
* ...
* }
*/
static bool find_match(struct spa_json *arr, const struct spa_dict *props)
{
struct spa_json it[1];
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) {
bool success = false;
if ((len = spa_json_next(&it[0], &value)) <= 0)
break;
str = spa_dict_lookup(props, key);
if (spa_json_is_null(value, len)) {
success = str == NULL;
} else {
if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0)
continue;
value = val;
len = strlen(val);
}
if (str != NULL) {
if (value[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(&preg);
}
} else if (strncmp(str, value, len) == 0 &&
strlen(str) == (size_t)len) {
success = true;
}
}
if (success) {
match++;
pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value);
}
else
fail++;
}
if (match > 0 && fail == 0)
return true;
}
return false;
}
/** /**
* [ * [
* { * {