Hi all, This simple module mimics the module-gconf behavior, using GSettings instead of GConf. The original intend for keeping the same hierarchy for the settings is to be able to migrate from GConf (using gsettings-data-convert for example). This would allow utilities like paprefs to move from GConf, which is now deprecated. Sylvain --- configure.ac | 20 ++ src/Makefile.am | 30 +- src/modules/gconf/module-gconf.c | 298 +------------------ src/modules/gsettings/gsettings-helper.c | 124 ++++++++ src/modules/gsettings/module-gsettings.c | 104 +++++++ .../org.freedesktop.pulseaudio.gschema.xml.in | 106 +++++++ src/modules/stdin-util.h | 315 +++++++++++++++++++++ 7 files changed, 697 insertions(+), 300 deletions(-) create mode 100644 src/modules/gsettings/gsettings-helper.c create mode 100644 src/modules/gsettings/module-gsettings.c create mode 100644 src/modules/gsettings/org.freedesktop.pulseaudio.gschema.xml.in create mode 100644 src/modules/stdin-util.h diff --git a/configure.ac b/configure.ac index ee1b437..d8cac8f 100644 --- a/configure.ac +++ b/configure.ac @@ -894,6 +894,22 @@ AS_IF([test "x$enable_gconf" = "xyes" && test "x$HAVE_GCONF" = "x0"], AM_CONDITIONAL([HAVE_GCONF], [test "x$HAVE_GCONF" = x1]) +#### GSettings support (optional) #### + +AC_ARG_ENABLE([gsettings], + AS_HELP_STRING([--disable-gsettings],[Disable optional GSettings support])) + +AS_IF([test "x$enable_gsetttings" != "xno"], + [PKG_CHECK_MODULES(GSETTINGS, [ gio-2.0 >= 2.26.0 ], HAVE_GSETTINGS=1, HAVE_GSETTINGS=0)], + HAVE_GSETTINGS=0) + +AS_IF([test "x$enable_gsettings" = "xyes" && test "x$HAVE_GSETTINGS" = "x0"], + [AC_MSG_ERROR([*** GSettings support not found])]) + +AM_CONDITIONAL([HAVE_GSETTINGS], [test "x$HAVE_GSETTINGS" = x1]) + +GLIB_GSETTINGS + #### Avahi support (optional) #### AC_ARG_ENABLE([avahi], @@ -1517,6 +1533,8 @@ AC_CONFIG_FILES([PulseAudioConfig.cmake:PulseAudioConfig.cmake.in], [m4 PulseAudioConfig.cmake > PulseAudioConfig.cmake.gen && mv PulseAudioConfig.cmake.gen PulseAudioConfig.cmake]) AC_CONFIG_FILES([PulseAudioConfigVersion.cmake]) +AC_CONFIG_FILES([src/modules/gsettings/org.freedesktop.pulseaudio.gschema.xml]) + AC_OUTPUT # ========================================================================== @@ -1531,6 +1549,7 @@ AS_IF([test "x$HAVE_WAVEOUT" = "x1"], ENABLE_WAVEOUT=yes, ENABLE_WAVEOUT=no) AS_IF([test "x$HAVE_GLIB20" = "x1"], ENABLE_GLIB20=yes, ENABLE_GLIB20=no) AS_IF([test "x$HAVE_GTK30" = "x1"], ENABLE_GTK30=yes, ENABLE_GTK30=no) AS_IF([test "x$HAVE_GCONF" = "x1"], ENABLE_GCONF=yes, ENABLE_GCONF=no) +AS_IF([test "x$HAVE_GSETTINGS" = "x1"], ENABLE_GSETTINGS=yes, ENABLE_GSETTINGS=no) AS_IF([test "x$HAVE_AVAHI" = "x1"], ENABLE_AVAHI=yes, ENABLE_AVAHI=no) AS_IF([test "x$HAVE_JACK" = "x1"], ENABLE_JACK=yes, ENABLE_JACK=no) AS_IF([test "x$HAVE_LIBASYNCNS" = "x1"], ENABLE_LIBASYNCNS=yes, ENABLE_LIBASYNCNS=no) @@ -1593,6 +1612,7 @@ echo " Enable GLib 2.0: ${ENABLE_GLIB20} Enable Gtk+ 3.0: ${ENABLE_GTK30} Enable GConf: ${ENABLE_GCONF} + Enable GSettings: ${ENABLE_GSETTINGS} Enable Avahi: ${ENABLE_AVAHI} Enable Jack: ${ENABLE_JACK} Enable Async DNS: ${ENABLE_LIBASYNCNS} diff --git a/src/Makefile.am b/src/Makefile.am index b0ca2bc..d96283f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -104,7 +104,8 @@ EXTRA_DIST = \ daemon/pulseaudio.desktop.in \ map-file \ daemon/pulseaudio-system.conf \ - modules/echo-cancel/adrian-license.txt + modules/echo-cancel/adrian-license.txt \ + $(gsettings_SCHEMAS:.xml=.xml.in) pulseconf_DATA = \ default.pa \ @@ -1382,6 +1383,14 @@ pulselibexec_PROGRAMS += \ gconf-helper endif +if HAVE_GSETTINGS +modlibexec_LTLIBRARIES += \ + module-gsettings.la + +pulselibexec_PROGRAMS += \ + gsettings-helper +endif + if HAVE_WAVEOUT modlibexec_LTLIBRARIES += \ module-waveout.la @@ -1527,6 +1536,7 @@ SYMDEF_FILES = \ module-raop-sink-symdef.h \ module-raop-discover-symdef.h \ module-gconf-symdef.h \ + module-gsettings-symdef.h \ module-position-event-sounds-symdef.h \ module-role-ducking-symdef.h \ module-augment-properties-symdef.h \ @@ -2121,6 +2131,20 @@ gconf_helper_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon- gconf_helper_CFLAGS = $(AM_CFLAGS) $(GCONF_CFLAGS) gconf_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) +# GSettings support +module_gsettings_la_SOURCES = modules/gsettings/module-gsettings.c +module_gsettings_la_LDFLAGS = $(MODULE_LDFLAGS) +module_gsettings_la_LIBADD = $(MODULE_LIBADD) +module_gsettings_la_CFLAGS = $(AM_CFLAGS) -DPA_GSETTINGS_HELPER=\"$(pulselibexecdir)/gsettings-helper\" + +gsettings_helper_SOURCES = modules/gsettings/gsettings-helper.c +gsettings_helper_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon- at PA_MAJORMINOR@.la libpulse.la $(GSETTINGS_LIBS) +gsettings_helper_CFLAGS = $(AM_CFLAGS) $(GSETTINGS_CFLAGS) +gsettings_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +gsettings_SCHEMAS = modules/gsettings/org.freedesktop.pulseaudio.gschema.xml + at GSETTINGS_RULES@ + # Bluetooth policy module_bluetooth_policy_la_SOURCES = modules/bluetooth/module-bluetooth-policy.c module_bluetooth_policy_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -2201,8 +2225,8 @@ module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) # Some minor stuff # ################################### -CLEANFILES += daemon/pulseaudio.desktop -DISTCLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 pulseaudio.service +CLEANFILES += daemon/pulseaudio.desktop $(gsettings_SCHEMAS) +DISTCLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 pulseaudio.service $(gsettings_SCHEMAS) if OS_IS_WIN32 SYMLINK_PROGRAM=cd $(DESTDIR)$(bindir) && cp diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c index 1e1b855..fae7910 100644 --- a/src/modules/gconf/module-gconf.c +++ b/src/modules/gconf/module-gconf.c @@ -17,27 +17,7 @@ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. ***/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> -#include <unistd.h> -#include <stdlib.h> -#include <errno.h> -#include <signal.h> -#include <sys/types.h> -#include <sys/wait.h> - -#include <pulse/xmalloc.h> -#include <pulsecore/module.h> -#include <pulsecore/core.h> -#include <pulsecore/core-util.h> -#include <pulsecore/log.h> -#include <pulse/mainloop-api.h> -#include <pulsecore/core-error.h> -#include <pulsecore/start-child.h> - +#include "../stdin-util.h" #include "module-gconf-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering"); @@ -45,282 +25,6 @@ PA_MODULE_DESCRIPTION("GConf Adapter"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); -#define MAX_MODULES 10 -#define BUF_MAX 2048 - -struct userdata; - -struct module_item { - char *name; - char *args; - uint32_t index; -}; - -struct pa_module_info { - struct userdata *userdata; - char *name; - - struct module_item items[MAX_MODULES]; - unsigned n_items; -}; - -struct userdata { - pa_core *core; - pa_module *module; - - pa_hashmap *module_infos; - - pid_t pid; - - int fd; - int fd_type; - pa_io_event *io_event; - - char buf[BUF_MAX]; - size_t buf_fill; -}; - -static int fill_buf(struct userdata *u) { - ssize_t r; - pa_assert(u); - - if (u->buf_fill >= BUF_MAX) { - pa_log("read buffer overflow"); - return -1; - } - - if ((r = pa_read(u->fd, u->buf + u->buf_fill, BUF_MAX - u->buf_fill, &u->fd_type)) <= 0) - return -1; - - u->buf_fill += (size_t) r; - return 0; -} - -static int read_byte(struct userdata *u) { - int ret; - pa_assert(u); - - if (u->buf_fill < 1) - if (fill_buf(u) < 0) - return -1; - - ret = u->buf[0]; - pa_assert(u->buf_fill > 0); - u->buf_fill--; - memmove(u->buf, u->buf+1, u->buf_fill); - return ret; -} - -static char *read_string(struct userdata *u) { - pa_assert(u); - - for (;;) { - char *e; - - if ((e = memchr(u->buf, 0, u->buf_fill))) { - char *ret = pa_xstrdup(u->buf); - u->buf_fill -= (size_t) (e - u->buf +1); - memmove(u->buf, e+1, u->buf_fill); - return ret; - } - - if (fill_buf(u) < 0) - return NULL; - } -} - -static void unload_one_module(struct pa_module_info *m, unsigned i) { - struct userdata *u; - - pa_assert(m); - pa_assert(i < m->n_items); - - u = m->userdata; - - if (m->items[i].index == PA_INVALID_INDEX) - return; - - pa_log_debug("Unloading module #%i", m->items[i].index); - pa_module_unload_by_index(u->core, m->items[i].index, true); - m->items[i].index = PA_INVALID_INDEX; - pa_xfree(m->items[i].name); - pa_xfree(m->items[i].args); - m->items[i].name = m->items[i].args = NULL; -} - -static void unload_all_modules(struct pa_module_info *m) { - unsigned i; - - pa_assert(m); - - for (i = 0; i < m->n_items; i++) - unload_one_module(m, i); - - m->n_items = 0; -} - -static void load_module( - struct pa_module_info *m, - unsigned i, - const char *name, - const char *args, - bool is_new) { - - struct userdata *u; - pa_module *mod; - - pa_assert(m); - pa_assert(name); - pa_assert(args); - - u = m->userdata; - - if (!is_new) { - if (m->items[i].index != PA_INVALID_INDEX && - pa_streq(m->items[i].name, name) && - pa_streq(m->items[i].args, args)) - return; - - unload_one_module(m, i); - } - - pa_log_debug("Loading module '%s' with args '%s' due to GConf configuration.", name, args); - - m->items[i].name = pa_xstrdup(name); - m->items[i].args = pa_xstrdup(args); - m->items[i].index = PA_INVALID_INDEX; - - if (!(mod = pa_module_load(u->core, name, args))) { - pa_log("pa_module_load() failed"); - return; - } - - m->items[i].index = mod->index; -} - -static void module_info_free(void *p) { - struct pa_module_info *m = p; - - pa_assert(m); - - unload_all_modules(m); - pa_xfree(m->name); - pa_xfree(m); -} - -static int handle_event(struct userdata *u) { - int opcode; - int ret = 0; - - do { - if ((opcode = read_byte(u)) < 0) { - if (errno == EINTR || errno == EAGAIN) - break; - goto fail; - } - - switch (opcode) { - case '!': - /* The helper tool is now initialized */ - ret = 1; - break; - - case '+': { - char *name; - struct pa_module_info *m; - unsigned i, j; - - if (!(name = read_string(u))) - goto fail; - - if (!(m = pa_hashmap_get(u->module_infos, name))) { - m = pa_xnew(struct pa_module_info, 1); - m->userdata = u; - m->name = name; - m->n_items = 0; - pa_hashmap_put(u->module_infos, m->name, m); - } else - pa_xfree(name); - - i = 0; - while (i < MAX_MODULES) { - char *module, *args; - - if (!(module = read_string(u))) { - if (i > m->n_items) m->n_items = i; - goto fail; - } - - if (!*module) { - pa_xfree(module); - break; - } - - if (!(args = read_string(u))) { - pa_xfree(module); - - if (i > m->n_items) m->n_items = i; - goto fail; - } - - load_module(m, i, module, args, i >= m->n_items); - - i++; - - pa_xfree(module); - pa_xfree(args); - } - - /* Unload all removed modules */ - for (j = i; j < m->n_items; j++) - unload_one_module(m, j); - - m->n_items = i; - - break; - } - - case '-': { - char *name; - - if (!(name = read_string(u))) - goto fail; - - pa_hashmap_remove_and_free(u->module_infos, name); - pa_xfree(name); - - break; - } - } - } while (u->buf_fill > 0 && ret == 0); - - return ret; - -fail: - pa_log("Unable to read or parse data from client."); - return -1; -} - -static void io_event_cb( - pa_mainloop_api*a, - pa_io_event* e, - int fd, - pa_io_event_flags_t events, - void *userdata) { - - struct userdata *u = userdata; - - if (handle_event(u) < 0) { - - if (u->io_event) { - u->core->mainloop->io_free(u->io_event); - u->io_event = NULL; - } - - pa_module_unload_request(u->module, true); - } -} - int pa__init(pa_module*m) { struct userdata *u; int r; diff --git a/src/modules/gsettings/gsettings-helper.c b/src/modules/gsettings/gsettings-helper.c new file mode 100644 index 0000000..2a91da3 --- /dev/null +++ b/src/modules/gsettings/gsettings-helper.c @@ -0,0 +1,124 @@ +/*** + This file is part of PulseAudio. + + Copyright 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/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <gio/gio.h> +#include <glib.h> + +#include <pulsecore/core-util.h> + +#define PA_GSETTINGS_MODULE_SCHEMA "org.freedesktop.pulseaudio.module" +#define PA_GSETTINGS_MODULES_SCHEMA "org.freedesktop.pulseaudio.modules" +#define PA_GSETTINGS_MODULES_PATH "/org/freedesktop/pulseaudio/modules/" + +static void modules_callback(GSettings *settings, gchar *key, gpointer user_data); + +static void handle_module(gchar *name) { + GSettings *settings; + gchar p[1024]; + gboolean enabled, locked; + int i; + + pa_snprintf(p, sizeof(p), PA_GSETTINGS_MODULES_PATH"%s/", name); + + if (!(settings = g_settings_new_with_path(PA_GSETTINGS_MODULE_SCHEMA, + p))) + return; + + locked = g_settings_get_boolean(settings, "locked"); + + if (locked) + return; + + enabled = g_settings_get_boolean(settings, "enabled"); + + printf("%c%s%c", enabled ? '+' : '-', name, 0); + + if (enabled) { + for (i = 0; i < 10; i++) { + gchar *n, *a; + + pa_snprintf(p, sizeof(p), "name%d", i); + n = g_settings_get_string(settings, p); + + pa_snprintf(p, sizeof(p), "args%i", i); + a = g_settings_get_string(settings, p); + + printf("%s%c%s%c", n, 0, a, 0); + + g_free(n); + g_free(a); + } + + printf("%c", 0); + } + + fflush(stdout); + + g_object_unref(G_OBJECT(settings)); +} + +static void modules_callback(GSettings *settings, gchar *key, gpointer user_data) { + handle_module(user_data); +} + +int main(int argc, char *argv[]) { + GMainLoop *g; + GSettings *settings; + gchar **modules, **m; + +#if !GLIB_CHECK_VERSION(2,36,0) + g_type_init(); +#endif + + if (!(settings = g_settings_new(PA_GSETTINGS_MODULES_SCHEMA))) + goto fail; + + g_signal_connect(settings, "changed", (GCallback) modules_callback, NULL); + + modules = g_settings_list_children (settings); + + for (m = modules; *m; m++) { + g_signal_connect(g_settings_get_child (settings, *m), "changed", + (GCallback) modules_callback, *m); + handle_module(*m); + } + + /* Signal the parent that we are now initialized */ + printf("!"); + fflush(stdout); + + g = g_main_loop_new(NULL, FALSE); + g_main_loop_run(g); + g_main_loop_unref(g); + + g_object_unref(G_OBJECT(settings)); + + return 0; + +fail: + return 1; +} diff --git a/src/modules/gsettings/module-gsettings.c b/src/modules/gsettings/module-gsettings.c new file mode 100644 index 0000000..a3524a5 --- /dev/null +++ b/src/modules/gsettings/module-gsettings.c @@ -0,0 +1,104 @@ +/*** + This file is part of PulseAudio. + + Copyright 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 "../stdin-util.h" +#include "module-gsettings-symdef.h" + +PA_MODULE_AUTHOR("Sylvain Baubeau"); +PA_MODULE_DESCRIPTION("GSettings Adapter"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(true); + +int pa__init(pa_module*m) { + struct userdata *u; + int r; + + u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->module_infos = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) module_info_free); + u->pid = (pid_t) -1; + u->fd = -1; + u->fd_type = 0; + u->io_event = NULL; + u->buf_fill = 0; + + if ((u->fd = pa_start_child_for_read( +#if defined(__linux__) && !defined(__OPTIMIZE__) + pa_run_from_build_tree() ? PA_BUILDDIR "/gsettings-helper" : +#endif + PA_GSETTINGS_HELPER, NULL, &u->pid)) < 0) + goto fail; + + u->io_event = m->core->mainloop->io_new( + m->core->mainloop, + u->fd, + PA_IO_EVENT_INPUT, + io_event_cb, + u); + + do { + if ((r = handle_event(u)) < 0) + goto fail; + + /* Read until the client signalled us that it is ready with + * initialization */ + } while (r != 1); + + return 0; + +fail: + pa__done(m); + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->pid != (pid_t) -1) { + kill(u->pid, SIGTERM); + + for (;;) { + if (waitpid(u->pid, NULL, 0) >= 0) + break; + + if (errno != EINTR) { + pa_log("waitpid() failed: %s", pa_cstrerror(errno)); + break; + } + } + } + + if (u->io_event) + m->core->mainloop->io_free(u->io_event); + + if (u->fd >= 0) + pa_close(u->fd); + + if (u->module_infos) + pa_hashmap_free(u->module_infos); + + pa_xfree(u); +} diff --git a/src/modules/gsettings/org.freedesktop.pulseaudio.gschema.xml.in b/src/modules/gsettings/org.freedesktop.pulseaudio.gschema.xml.in new file mode 100644 index 0000000..79138d7 --- /dev/null +++ b/src/modules/gsettings/org.freedesktop.pulseaudio.gschema.xml.in @@ -0,0 +1,106 @@ +<schemalist gettext-domain="pulseaudio"> + <schema id="org.freedesktop.pulseaudio.module"> + <key name="name" type="s"> + <default>''</default> + <summary>Module name</summary> + <description>Name of the pulseaudio module</description> + </key> + + <key name="enabled" type="b"> + <default>false</default> + </key> + + <key name="locked" type="b"> + <default>false</default> + </key> + + <key name="args0" type="s"> + <default>''</default> + </key> + + <key name="args1" type="s"> + <default>''</default> + </key> + + <key name="args2" type="s"> + <default>''</default> + </key> + + <key name="args3" type="s"> + <default>''</default> + </key> + + <key name="args4" type="s"> + <default>''</default> + </key> + + <key name="args5" type="s"> + <default>''</default> + </key> + + <key name="args6" type="s"> + <default>''</default> + </key> + + <key name="args7" type="s"> + <default>''</default> + </key> + + <key name="args8" type="s"> + <default>''</default> + </key> + + <key name="args9" type="s"> + <default>''</default> + </key> + + <key name="name0" type="s"> + <default>''</default> + </key> + + <key name="name1" type="s"> + <default>''</default> + </key> + + <key name="name2" type="s"> + <default>''</default> + </key> + + <key name="name3" type="s"> + <default>''</default> + </key> + + <key name="name4" type="s"> + <default>''</default> + </key> + + <key name="name5" type="s"> + <default>''</default> + </key> + + <key name="name6" type="s"> + <default>''</default> + </key> + + <key name="name7" type="s"> + <default>''</default> + </key> + + <key name="name8" type="s"> + <default>''</default> + </key> + + <key name="name9" type="s"> + <default>''</default> + </key> + </schema> + + <schema id="org.freedesktop.pulseaudio" path="/org/freedesktop/pulseaudio/"> + <child name="modules" schema="org.freedesktop.pulseaudio.modules"/> + </schema> + + <schema id="org.freedesktop.pulseaudio.modules" path="/org/freedesktop/pulseaudio/modules/"> + <child name="remote-access" schema="org.freedesktop.pulseaudio.module"/> + </schema> + +</schemalist> diff --git a/src/modules/stdin-util.h b/src/modules/stdin-util.h new file mode 100644 index 0000000..9ec93f2 --- /dev/null +++ b/src/modules/stdin-util.h @@ -0,0 +1,315 @@ +/*** + This file is part of PulseAudio. + + Copyright 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/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/module.h> +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulse/mainloop-api.h> +#include <pulsecore/core-error.h> +#include <pulsecore/start-child.h> + +#define MAX_MODULES 10 +#define BUF_MAX 2048 + +struct userdata; + +struct module_item { + char *name; + char *args; + uint32_t index; +}; + +struct pa_module_info { + struct userdata *userdata; + char *name; + + struct module_item items[MAX_MODULES]; + unsigned n_items; +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_hashmap *module_infos; + + pid_t pid; + + int fd; + int fd_type; + pa_io_event *io_event; + + char buf[BUF_MAX]; + size_t buf_fill; +}; + +static int fill_buf(struct userdata *u) { + ssize_t r; + pa_assert(u); + + if (u->buf_fill >= BUF_MAX) { + pa_log("read buffer overflow"); + return -1; + } + + if ((r = pa_read(u->fd, u->buf + u->buf_fill, BUF_MAX - u->buf_fill, &u->fd_type)) <= 0) + return -1; + + u->buf_fill += (size_t) r; + return 0; +} + +static int read_byte(struct userdata *u) { + int ret; + pa_assert(u); + + if (u->buf_fill < 1) + if (fill_buf(u) < 0) + return -1; + + ret = u->buf[0]; + pa_assert(u->buf_fill > 0); + u->buf_fill--; + memmove(u->buf, u->buf+1, u->buf_fill); + return ret; +} + +static char *read_string(struct userdata *u) { + pa_assert(u); + + for (;;) { + char *e; + + if ((e = memchr(u->buf, 0, u->buf_fill))) { + char *ret = pa_xstrdup(u->buf); + u->buf_fill -= (size_t) (e - u->buf +1); + memmove(u->buf, e+1, u->buf_fill); + return ret; + } + + if (fill_buf(u) < 0) + return NULL; + } +} + +static void unload_one_module(struct pa_module_info *m, unsigned i) { + struct userdata *u; + + pa_assert(m); + pa_assert(i < m->n_items); + + u = m->userdata; + + if (m->items[i].index == PA_INVALID_INDEX) + return; + + pa_log_debug("Unloading module #%i", m->items[i].index); + pa_module_unload_by_index(u->core, m->items[i].index, true); + m->items[i].index = PA_INVALID_INDEX; + pa_xfree(m->items[i].name); + pa_xfree(m->items[i].args); + m->items[i].name = m->items[i].args = NULL; +} + +static void unload_all_modules(struct pa_module_info *m) { + unsigned i; + + pa_assert(m); + + for (i = 0; i < m->n_items; i++) + unload_one_module(m, i); + + m->n_items = 0; +} + +static void load_module( + struct pa_module_info *m, + unsigned i, + const char *name, + const char *args, + bool is_new) { + + struct userdata *u; + pa_module *mod; + + pa_assert(m); + pa_assert(name); + pa_assert(args); + + u = m->userdata; + + if (!is_new) { + if (m->items[i].index != PA_INVALID_INDEX && + pa_streq(m->items[i].name, name) && + pa_streq(m->items[i].args, args)) + return; + + unload_one_module(m, i); + } + + pa_log_debug("Loading module '%s' with args '%s' due to GConf/GSettings configuration.", name, args); + + m->items[i].name = pa_xstrdup(name); + m->items[i].args = pa_xstrdup(args); + m->items[i].index = PA_INVALID_INDEX; + + if (!(mod = pa_module_load(u->core, name, args))) { + pa_log("pa_module_load() failed"); + return; + } + + m->items[i].index = mod->index; +} + +static void module_info_free(void *p) { + struct pa_module_info *m = p; + + pa_assert(m); + + unload_all_modules(m); + pa_xfree(m->name); + pa_xfree(m); +} + +static int handle_event(struct userdata *u) { + int opcode; + int ret = 0; + + do { + if ((opcode = read_byte(u)) < 0) { + if (errno == EINTR || errno == EAGAIN) + break; + goto fail; + } + + switch (opcode) { + case '!': + /* The helper tool is now initialized */ + ret = 1; + break; + + case '+': { + char *name; + struct pa_module_info *m; + unsigned i, j; + + if (!(name = read_string(u))) + goto fail; + + if (!(m = pa_hashmap_get(u->module_infos, name))) { + m = pa_xnew(struct pa_module_info, 1); + m->userdata = u; + m->name = name; + m->n_items = 0; + pa_hashmap_put(u->module_infos, m->name, m); + } else + pa_xfree(name); + + i = 0; + while (i < MAX_MODULES) { + char *module, *args; + + if (!(module = read_string(u))) { + if (i > m->n_items) m->n_items = i; + goto fail; + } + + if (!*module) { + pa_xfree(module); + break; + } + + if (!(args = read_string(u))) { + pa_xfree(module); + + if (i > m->n_items) m->n_items = i; + goto fail; + } + + load_module(m, i, module, args, i >= m->n_items); + + i++; + + pa_xfree(module); + pa_xfree(args); + } + + /* Unload all removed modules */ + for (j = i; j < m->n_items; j++) + unload_one_module(m, j); + + m->n_items = i; + + break; + } + + case '-': { + char *name; + + if (!(name = read_string(u))) + goto fail; + + pa_hashmap_remove_and_free(u->module_infos, name); + pa_xfree(name); + + break; + } + } + } while (u->buf_fill > 0 && ret == 0); + + return ret; + +fail: + pa_log("Unable to read or parse data from client."); + return -1; +} + +static void io_event_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct userdata *u = userdata; + + if (handle_event(u) < 0) { + + if (u->io_event) { + u->core->mainloop->io_free(u->io_event); + u->io_event = NULL; + } + + pa_module_unload_request(u->module, true); + } +} -- 2.5.0