This patch adds a new feature to the core which allows to exchange messages between objects. An object can register/unregister a message handler with pa_core_message_handler_{register, unregister}() while any other object can send a message to the handler using the pa_core_send_message() function. A message has 5 arguments (apart from passing the core): recipient: The name of the message handler that will receive the message message: message command message_parameters: A string containing additional parameters message_data: void pointer to some parameter structure, can be used as alternative to message_parameters response: Pointer to a response string that will be filled by the message handler. The caller is responsible to free the string. The patch is a precondition for the following patches that also allow clients to send messages to pulseaudio objects. Because not every message handler should be visible to clients, a flag was added to the handler structure which allows to mark a handler as public or private. There is no restriction on object names, except that a handler name always starts with a "/". The intention is to use a path-like syntax, for example /core/sink_1 for a sink or /name/instances/index for modules. The exact naming convention still needs to be agreed. Message groups are also implemented, so that a handler can subscribe to a message group using pa_core_message_handler_group_[un]subscribe() to receive messages sent to the group. To distinguish group and handler names, group names lack the leading "/". Some of the code used to implement the message groups was adapted from hook-list.c. Message groups are created/deleted implicitely on subscription/unsubscription. The messaging interface can serve as a full replacement for the current hook system with several advantages: - no need to change header files when a new handler/group is implemented - slightly simpler registration interface - multi-purpose message handlers that can handle multiple events - mesage handlers may also be accessible from the client side --- src/Makefile.am | 2 + src/pulsecore/core-messages.c | 420 ++++++++++++++++++++++++++++++++++++++++++ src/pulsecore/core-messages.h | 67 +++++++ src/pulsecore/core.c | 4 + src/pulsecore/core.h | 2 +- 5 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 src/pulsecore/core-messages.c create mode 100644 src/pulsecore/core-messages.h diff --git a/src/Makefile.am b/src/Makefile.am index 3ff1139f..42047823 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -676,6 +676,7 @@ libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES = \ pulsecore/core-format.c pulsecore/core-format.h \ pulsecore/core-rtclock.c pulsecore/core-rtclock.h \ pulsecore/core-util.c pulsecore/core-util.h \ + pulsecore/core-messages.c pulsecore/core-messages.h \ pulsecore/creds.h \ pulsecore/dynarray.c pulsecore/dynarray.h \ pulsecore/endianmacros.h \ @@ -957,6 +958,7 @@ libpulsecore_ at PA_MAJORMINOR@_la_SOURCES = \ pulsecore/core-scache.c pulsecore/core-scache.h \ pulsecore/core-subscribe.c pulsecore/core-subscribe.h \ pulsecore/core.c pulsecore/core.h \ + pulsecore/core-messages.c pulsecore/core-messages.h \ pulsecore/hook-list.c pulsecore/hook-list.h \ pulsecore/ltdl-helper.c pulsecore/ltdl-helper.h \ pulsecore/modargs.c pulsecore/modargs.h \ diff --git a/src/pulsecore/core-messages.c b/src/pulsecore/core-messages.c new file mode 100644 index 00000000..9ebaab24 --- /dev/null +++ b/src/pulsecore/core-messages.c @@ -0,0 +1,420 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman at cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "core-messages.h" + +struct pa_core_message_handler { + char *recipient; + char *description; + bool is_private; + pa_idxset *groups; + pa_core_message_handler_cb_t callback; + void *userdata; +}; + +struct pa_handler_group_member { + char *name; + int priority; + bool dead; + PA_LLIST_FIELDS(struct pa_handler_group_member); +}; + +struct pa_handler_group { + int n_dead; + int n_sending; + pa_core *core; + PA_LLIST_HEAD(struct pa_handler_group_member, handler_list); +}; + +/* Message handler functions */ + +/* Find a message handler for a given name or group */ +static int find_message_handler(pa_core *c, const char *search_string, struct pa_core_message_handler **handler) { + pa_assert(c); + pa_assert(search_string); + + *handler = NULL; + + /* Check if the search string exactly matches a handler name */ + if (search_string[0] == '/') + *handler = pa_hashmap_get(c->message_handlers, search_string); + + else { + /* Check for group name */ + char *name = pa_sprintf_malloc("/groups/%s", search_string); + *handler = pa_hashmap_get(c->message_handlers, name); + pa_xfree(name); + } + + /* Nothing found, return failure */ + if (!*handler) + return -1; + + return 0; +} + +/* Compares the left most characters of a to b */ +static bool str_starts_with(const char *a, const char *b) { + uint32_t i, l; + + if (!a) { + if (!b) + return true; + return false; + } + + l = strlen(b); + + /* a must contain at least as many characters as b */ + if (strlen(a) < l) + return false; + + for (i = 0; i < l; i++) { + if (a[i] != b[i]) + return false; + } + + return true; +} + +/* Test existence and scope of a handler */ +int pa_core_message_handler_test(pa_core *c, const char *search_string) { + int ret; + struct pa_core_message_handler *handler; + + if ((ret = find_message_handler(c, search_string, &handler)) < 0) + return PA_CORE_HANDLER_NONE; + if (handler->is_private) + return PA_CORE_HANDLER_PRIVATE; + return PA_CORE_HANDLER_PUBLIC; +} + +/* Register message handler. recipient_name must be a unique name starting with "/". */ +static void handler_register(pa_core *c, const char *recipient_name, const char *description, bool is_private, pa_core_message_handler_cb_t cb, void *userdata) { + struct pa_core_message_handler *handler; + + /* Name must not be in use. */ + pa_assert(find_message_handler(c, recipient_name, &handler) < 0); + + handler = pa_xnew0(struct pa_core_message_handler, 1); + handler->userdata = userdata; + handler->callback = cb; + handler->recipient = pa_xstrdup(recipient_name); + handler->description = pa_xstrdup(description); + handler->groups = NULL; + handler->is_private = is_private; + + pa_assert_se(pa_hashmap_put(c->message_handlers, handler->recipient, (void *) handler) == 0); +} + +void pa_core_message_handler_register(pa_core *c, const char *recipient_name, const char *description, bool is_private, pa_core_message_handler_cb_t cb, void *userdata) { + + pa_assert(c); + pa_assert(recipient_name); + pa_assert(cb); + pa_assert(userdata); + + /* Ensure that the recipient name is not empty and starts with "/". */ + pa_assert(recipient_name[0] == '/'); + + /* Prefix /groups/ is reserved for message groups */ + pa_assert(!str_starts_with(recipient_name, "/groups/")); + + return handler_register(c, recipient_name, description, is_private, cb, userdata); +} + +/* Unregister a message handler */ +static void handler_unregister(pa_core *c, const char *recipient_name) { + struct pa_core_message_handler *handler; + + pa_assert(c); + pa_assert(recipient_name); + + pa_assert_se(handler = pa_hashmap_get(c->message_handlers, recipient_name)); + + /* Unsubscribe from groups */ + if (handler->groups) { + char *group, *tmp_group; + uint32_t idx; + + PA_IDXSET_FOREACH(group, handler->groups, idx) { + /* group will be destroyed when the group is removed from + * the idxset in pa_core_message_handler_group_unsubscribe(), + * therefore we need to copy the string before passing it + * to the function. */ + tmp_group = pa_xstrdup(group); + + pa_core_message_handler_group_unsubscribe(c, handler->recipient, tmp_group); + pa_xfree(tmp_group); + } + + pa_assert(pa_idxset_isempty(handler->groups)); + pa_idxset_free(handler->groups, NULL); + } + + pa_assert_se(handler = pa_hashmap_remove(c->message_handlers, recipient_name)); + + pa_xfree(handler->recipient); + pa_xfree(handler->description); + pa_xfree(handler); +} + +void pa_core_message_handler_unregister(pa_core *c, const char *recipient_name) { + + /* Message groups are unregistered implicitely. */ + pa_assert(!str_starts_with(recipient_name, "/groups/")); + + handler_unregister(c, recipient_name); +} + +/* Send a message to a recipient or recipient group */ +int pa_core_send_message(pa_core *c, const char *recipient_name, const char *message, const char *message_parameters, void *message_data, char **response) { + struct pa_core_message_handler *handler; + + pa_assert(c); + pa_assert(recipient_name); + pa_assert(message); + pa_assert(response); + + *response = NULL; + + if (find_message_handler(c, recipient_name, &handler) < 0) + return PA_CORE_SEND_NO_RECIPIENT; + + if (handler->callback(handler->recipient, message, message_parameters, message_data, response, handler->userdata) < 0) + return PA_CORE_SEND_FAILURE; + + return PA_CORE_SEND_OK; +} + +/* Set handler description */ +int pa_core_message_handler_set_description(pa_core *c, const char *recipient_name, const char *description) { + struct pa_core_message_handler *handler; + + pa_assert(c); + pa_assert(recipient_name); + + if (find_message_handler(c, recipient_name, &handler) < 0) + return -1; + + pa_xfree(handler->description); + handler->description = pa_xstrdup(description); + + return 0; +} + +/* Set handler private flag, mainly useful to change group visibility because + * groups are always created private. */ +int pa_core_message_handler_set_private(pa_core *c, const char *recipient_name, bool is_private) { + struct pa_core_message_handler *handler; + + pa_assert(c); + pa_assert(recipient_name); + + if (find_message_handler(c, recipient_name, &handler) < 0) + return -1; + + handler->is_private = is_private; + + return 0; +} + +/* Callback for message groups. Mostly stolen from hook-list.c. */ +static int handler_group_callback(const char *recipient, const char *message, const char *message_parameters, void *message_data, char **response, void *userdata) { + struct pa_handler_group_member *gm, *next; + struct pa_handler_group *g = (struct pa_handler_group *) userdata; + int n_errors = 0; + int ret; + + g->n_sending ++; + + PA_LLIST_FOREACH(gm, g->handler_list) { + if (gm->dead) + continue; + + ret = pa_core_send_message(g->core, gm->name, message, message_parameters, message_data, response); + if (ret == PA_CORE_SEND_NO_RECIPIENT) { + gm->dead = true; + g->n_dead++; + } else if (ret != PA_CORE_SEND_OK) + n_errors++; + + /* Ignore return string */ + pa_xfree(*response); + } + + g->n_sending --; + pa_assert(g->n_sending >= 0); + + if (g->n_sending == 0) { + for (gm = g->handler_list; g->n_dead > 0 && gm; gm = next) { + next = gm->next; + + if (gm->dead) { + PA_LLIST_REMOVE(struct pa_handler_group_member, g->handler_list, gm); + pa_xfree(gm->name); + pa_xfree(gm); + g->n_dead--; + } + } + + pa_assert(g->n_dead == 0); + } + + *response = pa_xstrdup("OK"); + return -n_errors; + +} + +int pa_core_message_handler_group_subscribe(pa_core *c, const char *recipient_name, const char *group_name, int priority) { + struct pa_core_message_handler *handler, *recipient; + struct pa_handler_group *g; + struct pa_handler_group_member *gm, *insert_after, *current; + + pa_assert(c); + pa_assert(recipient_name); + pa_assert(group_name); + + /* Group names must not start with "/". */ + if (group_name[0] == '/') + return -1; + + /* Handler must be already registered. */ + if (!(recipient = pa_hashmap_get(c->message_handlers, recipient_name))) + return -1; + + /* If the group does not exist, create it. */ + if (find_message_handler(c, group_name, &handler) < 0) { + char *name; + + g = pa_xnew0(struct pa_handler_group, 1); + + name = pa_sprintf_malloc("/groups/%s", group_name); + handler_register(c, name, "Group callback", true, handler_group_callback, g); + pa_xfree(name); + + pa_assert(find_message_handler(c, group_name, &handler) >= 0); + } + + /* Add group to the group list of the handler. */ + if (!recipient->groups) + recipient->groups = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_idxset_put(recipient->groups, pa_xstrdup(group_name), NULL); + + g = (struct pa_handler_group *) handler->userdata; + g->core = c; + gm = pa_xnew0(struct pa_handler_group_member, 1); + gm->name = pa_xstrdup(recipient_name); + gm->priority = priority; + gm->dead = false; + + /* Add handler to the handler list of the group. */ + insert_after = NULL; + for (current = g->handler_list; current; current = current->next) { + if (priority < current->priority) + break; + insert_after = current; + } + + PA_LLIST_INSERT_AFTER(struct pa_handler_group_member, g->handler_list, insert_after, gm); + + return 0; +} + +int pa_core_message_handler_group_unsubscribe(pa_core *c, const char *recipient_name, const char *group_name) { + struct pa_core_message_handler *handler; + struct pa_handler_group *g; + struct pa_handler_group_member *gm, *next; + char *real_group_name; + pa_idxset *group_list; + + pa_assert(c); + pa_assert(recipient_name); + pa_assert(group_name); + + /* Handler must exist. */ + if (!(handler = pa_hashmap_get(c->message_handlers, recipient_name))) + return -1; + + group_list = handler->groups; + real_group_name = pa_sprintf_malloc("/groups/%s", group_name); + + /* Group must exist. */ + if (!(handler = pa_hashmap_get(c->message_handlers, real_group_name))) { + pa_xfree(real_group_name); + return -1; + } + + /* Remove entry from the group list of the handler */ + if (group_list) { + uint32_t idx; + char *group; + + PA_IDXSET_FOREACH(group, group_list, idx) { + if (pa_streq(group, group_name)) { + pa_idxset_remove_by_index(group_list, idx); + pa_xfree(group); + } + } + } + + /* Remove entry from the handler list of the group */ + g = (struct pa_handler_group *) handler->userdata; + gm = g->handler_list; + while (gm) { + if (pa_streq(gm->name, recipient_name)) { + if (g->n_sending) { + g->n_dead++; + gm->dead = true; + gm = gm->next; + } else { + next = gm->next; + PA_LLIST_REMOVE(struct pa_handler_group_member, g->handler_list, gm); + pa_xfree(gm->name); + pa_xfree(gm); + gm = next; + } + } else + gm = gm->next; + } + + /* If the last member was removed, also remove the group */ + if (!g->handler_list) { + handler_unregister(c, real_group_name); + pa_xfree(g); + } + + pa_xfree(real_group_name); + return 0; +} diff --git a/src/pulsecore/core-messages.h b/src/pulsecore/core-messages.h new file mode 100644 index 00000000..d85f69f4 --- /dev/null +++ b/src/pulsecore/core-messages.h @@ -0,0 +1,67 @@ +#ifndef foocoremessageshfoo +#define foocoremessageshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> + +/* Message handler types and functions */ + +/* Values returned by pa_core_test_message_handler() */ +enum { + PA_CORE_HANDLER_NONE, + PA_CORE_HANDLER_PRIVATE, + PA_CORE_HANDLER_PUBLIC +}; + +/* Values returned by pa_core_send_message() */ +enum { + PA_CORE_SEND_OK, + PA_CORE_SEND_NO_RECIPIENT, + PA_CORE_SEND_FAILURE +}; + +/* Prototype for message callback */ +typedef int (*pa_core_message_handler_cb_t)( + const char *recipient, + const char *message, + const char *message_parameters, + void *message_data, + char **response, + void *userdata); + +/* Handler registration */ +void pa_core_message_handler_register(pa_core *c, const char *recipient_name, const char *description, bool is_private, pa_core_message_handler_cb_t cb, void *userdata); +void pa_core_message_handler_unregister(pa_core *c, const char *recipient_name); + +/* Check availability and scope of handler */ +int pa_core_message_handler_test(pa_core *c, const char *recipient_name); + +/* Send message to recipient */ +int pa_core_send_message(pa_core *c, const char *recipient_name, const char *message, const char *message_parameters, void *message_data, char **response); + +/* Set handler properties */ +int pa_core_message_handler_set_description(pa_core *c, const char *recipient_name, const char *description); +int pa_core_message_handler_set_private(pa_core *c, const char *recipient_name, bool is_private); + +/* Message group functions */ +int pa_core_message_handler_group_subscribe(pa_core *c, const char *recipient_name, const char *group_name, int priority); +int pa_core_message_handler_group_unsubscribe(pa_core *c, const char *recipient_name, const char *group_name); +#endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index e01677d5..3145caaf 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -103,6 +103,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); c->shared = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); c->default_source = NULL; c->default_sink = NULL; @@ -204,6 +205,9 @@ static void core_free(pa_object *o) { pa_assert(pa_hashmap_isempty(c->shared)); pa_hashmap_free(c->shared); + pa_assert(pa_hashmap_isempty(c->message_handlers)); + pa_hashmap_free(c->message_handlers); + pa_assert(pa_hashmap_isempty(c->modules_pending_unload)); pa_hashmap_free(c->modules_pending_unload); diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index 79a095d2..bc8a371e 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -160,7 +160,7 @@ struct pa_core { pa_idxset *clients, *cards, *sinks, *sources, *sink_inputs, *source_outputs, *modules, *scache; /* Some hashmaps for all sorts of entities */ - pa_hashmap *namereg, *shared; + pa_hashmap *namereg, *shared, *message_handlers; /* The default sink/source as configured by the user. If the user hasn't * explicitly configured anything, these are set to NULL. These are strings -- 2.11.0