From a362889712dda1935f196c58df08744a8839e7a2 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 29 Oct 2020 13:53:23 -0300 Subject: [PATCH] pulse-server: Implement module load and unload Implement a minimal version of the LOAD_MODULE and UNLOAD_MODULE stream commands. The only supported module for now is the null sink. Modules are stored on a per-client bases, so that when clients are disconnected, the modules they loaded is removed too. This is enough to allow GNOME Network Displays to register a sink. --- src/modules/module-protocol-pulse/module.c | 203 ++++++++++++++++++ .../module-protocol-pulse/pulse-server.c | 121 ++++++++++- 2 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 src/modules/module-protocol-pulse/module.c diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c new file mode 100644 index 000000000..0449afff9 --- /dev/null +++ b/src/modules/module-protocol-pulse/module.c @@ -0,0 +1,203 @@ +/* PipeWire + * + * Copyright © 2020 Georges Basile Stavracas Neto + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +struct module; + +typedef void (*module_loaded_cb)(struct module *module, int error, void *userdata); + +struct module_events { + void (*removed) (void *data, struct module *module); + void (*error) (void *data); +}; + +struct module { + struct spa_list link; /**< link in client modules */ + struct pw_proxy *proxy; + struct spa_hook listener; + struct client *client; + struct message *reply; + + module_loaded_cb cb; + void *cb_data; + + struct module_events *events; + void *events_data; + + uint32_t idx; +}; + +static void module_proxy_removed(void *data) +{ + struct module *module = data; + + if (module->events) + module->events->removed(module->events_data, module); + + pw_proxy_destroy(module->proxy); +} + +static void module_proxy_destroy(void *data) +{ + struct module *module = data; + pw_log_info(NAME" %p: proxy %p destroy", module, module->proxy); + spa_hook_remove(&module->listener); + free(module); +} + +static void module_proxy_bound(void *data, uint32_t global_id) +{ + struct module *module = data; + + pw_log_info(NAME" module %p proxy %p bound", module, module->proxy); + + module->idx = global_id; + + if (module->cb) + module->cb(module, 0, module->cb_data); +} + +static void module_proxy_error(void *data, int seq, int res, const char *message) +{ + struct module *module = data; + struct impl *impl = module->client->impl; + + pw_log_info(NAME" %p module %p error %d", impl, module, res); + + module->idx = 0; + + if (module->cb) + module->cb(module, res, module->cb_data); + + pw_proxy_destroy(module->proxy); +} + +static int load_null_sink_module(struct client *client, struct module *module, struct pw_properties *props) +{ + static const struct pw_proxy_events proxy_events = { + .removed = module_proxy_removed, + .bound = module_proxy_bound, + .error = module_proxy_error, + .destroy = module_proxy_destroy, + }; + + module->proxy = pw_core_create_object(client->core, + "adapter", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE, + props ? &props->dict : NULL, 0); + if (module->proxy == NULL) + return -errno; + + pw_proxy_add_listener(module->proxy, &module->listener, &proxy_events, module); + return 0; +} + +static void add_props(struct pw_properties *props, const char *str) +{ + char *s = strdup(str), *p = s, *e, f; + const char *k, *v; + + while (*p) { + e = strchr(p, '='); + if (e == NULL) + break; + *e = '\0'; + k = p; + p = e+1; + + if (*p == '\"') { + p++; + f = '\"'; + } else { + f = ' '; + } + e = strchr(p, f); + if (e == NULL) + break; + *e = '\0'; + v = p; + p = e + 1; + pw_properties_set(props, k, v); + } + free(s); +} + +static int load_module(struct client *client, const char *name, const char *argument, module_loaded_cb cb, void *data) +{ + struct module *module = NULL; + int res = -ENOENT; + + if (strcmp(name, "module-null-sink") == 0) { + struct pw_properties *props = NULL; + const char *str; + + props = pw_properties_new_string(argument); + if (props == NULL) { + res = -EINVAL; + goto out; + } + if ((str = pw_properties_get(props, "sink_name")) != NULL) { + pw_properties_set(props, "node.name", str); + pw_properties_set(props, "sink_name", NULL); + } else { + pw_properties_set(props, "node.name", "null"); + } + if ((str = pw_properties_get(props, "sink_properties")) != NULL) { + add_props(props, str); + pw_properties_set(props, "sink_properties", NULL); + } + if ((str = pw_properties_get(props, "device.description")) != NULL) { + pw_properties_set(props, "node.description", str); + pw_properties_set(props, "device.description", NULL); + } + pw_properties_set(props, "factory.name", "support.null-audio-sink"); + + module = calloc(1, sizeof(struct module)); + module->client = client; + module->cb = cb; + module->cb_data = data; + + if ((res = load_null_sink_module(client, module, props)) < 0) + goto out; + } + +out: + if (res < 0) { + free(module); + module = NULL; + } + + return res; +} + +static void module_add_listener(struct module *module, struct module_events *events, void *userdata) +{ + module->events = events; + module->events_data = userdata; +} + +static void unload_module(struct module *module) +{ + pw_proxy_destroy(module->proxy); +} diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 4781f4a8b..9da4aa262 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -111,6 +111,7 @@ struct client { struct spa_list out_messages; struct spa_list operations; + struct spa_list modules; unsigned int disconnecting:1; }; @@ -190,6 +191,7 @@ struct impl { }; #include "collect.c" +#include "module.c" struct command { const char *name; @@ -3694,6 +3696,117 @@ static int do_kill(struct client *client, uint32_t command, uint32_t tag, struct return reply_simple_ack(client, tag); } +struct load_module_data { + struct client *client; + uint32_t tag; +}; + +static struct load_module_data *load_module_data_new(struct client *client, uint32_t tag) +{ + struct load_module_data *data = calloc(1, sizeof(struct load_module_data)); + data->client = client; + data->tag = tag; + return data; +} + +static void on_module_removed(void *data, struct module *module) +{ + struct client *client = data; + + pw_log_info(NAME" %p: client %p: module %d unloaded", client->impl, client, module->idx); + + spa_list_remove(&module->link); +} + +static void on_module_error(void *data) +{ + struct client *client = data; + + pw_log_info(NAME" %p: client %p: error loading module", client->impl, client); +} + +static void on_module_loaded(struct module *module, int error, void *data) +{ + struct load_module_data *d = data; + struct message *reply; + struct client *client; + uint32_t tag; + int res; + + struct module_events listener = { + on_module_removed, + on_module_error, + }; + + client = d->client; + tag = d->tag; + free(d); + + if (error < 0) { + pw_log_warn(NAME" %p: client %p: error loading module", client->impl, client); + reply_error (client, COMMAND_LOAD_MODULE, tag, error); + return; + } + + spa_list_append(&client->modules, &module->link); + module_add_listener(module, &listener, client); + + pw_log_info(NAME" %p: client %p: module %d loaded", client->impl, client, module->idx); + + reply = reply_new(client, tag); + message_put(reply, + TAG_U32, module->idx, + TAG_INVALID); + if ((res = send_message(client, reply)) < 0) + reply_error(client, COMMAND_LOAD_MODULE, tag, res); +} + +static int do_load_module(struct client *client, uint32_t command, uint32_t tag, struct message *m) +{ + struct load_module_data *data; + struct impl *impl = client->impl; + const char *name, *argument; + int res; + + if ((res = message_get(m, + TAG_STRING, &name, + TAG_STRING, &argument, + TAG_INVALID)) < 0) + return -EPROTO; + + pw_log_info(NAME" %p: %s name:%s argument:%s", impl, commands[command].name, name, argument); + + data = load_module_data_new(client, tag); + res = load_module(client, name, argument, on_module_loaded, data); + return res; +} + +static int do_unload_module(struct client *client, uint32_t command, uint32_t tag, struct message *m) +{ + struct impl *impl = client->impl; + struct module *module; + uint32_t module_idx; + int res; + + if ((res = message_get(m, + TAG_U32, &module_idx, + TAG_INVALID)) < 0) + return -EPROTO; + + pw_log_info(NAME" %p: %s id:%u", impl, commands[command].name, module_idx); + + spa_list_for_each(module, &client->modules, link) { + if (module->idx == module_idx) + break; + } + + if (module == NULL) + return -ENOENT; + + unload_module(module); + return reply_simple_ack(client, tag); +} + static const struct command commands[COMMAND_MAX] = { [COMMAND_ERROR] = { "ERROR", }, @@ -3761,8 +3874,8 @@ static const struct command commands[COMMAND_MAX] = [COMMAND_KILL_SINK_INPUT] = { "KILL_SINK_INPUT", do_kill, }, [COMMAND_KILL_SOURCE_OUTPUT] = { "KILL_SOURCE_OUTPUT", do_kill, }, - [COMMAND_LOAD_MODULE] = { "LOAD_MODULE", do_error_access, }, - [COMMAND_UNLOAD_MODULE] = { "UNLOAD_MODULE", do_error_access, }, + [COMMAND_LOAD_MODULE] = { "LOAD_MODULE", do_load_module, }, + [COMMAND_UNLOAD_MODULE] = { "UNLOAD_MODULE", do_unload_module, }, /* Obsolete */ [COMMAND_ADD_AUTOLOAD___OBSOLETE] = { "ADD_AUTOLOAD___OBSOLETE", do_error_access, }, @@ -3865,6 +3978,7 @@ static void client_free(struct client *client) { struct impl *impl = client->impl; struct message *msg; + struct module *module, *tmp; pw_log_info(NAME" %p: client %p free", impl, client); spa_list_remove(&client->link); @@ -3872,6 +3986,8 @@ static void client_free(struct client *client) pw_map_for_each(&client->streams, client_free_stream, client); pw_map_clear(&client->streams); + spa_list_for_each_safe(module, tmp, &client->modules, link) + unload_module(module); spa_list_consume(msg, &client->free_messages, link) message_free(client, msg, true, true); spa_list_consume(msg, &client->out_messages, link) @@ -4124,6 +4240,7 @@ on_connect(void *data, int fd, uint32_t mask) spa_list_init(&client->free_messages); spa_list_init(&client->out_messages); spa_list_init(&client->operations); + spa_list_init(&client->modules); client->props = pw_properties_new( PW_KEY_CLIENT_API, "pipewire-pulse",