diff --git a/lib/wp/meson.build b/lib/wp/meson.build index 7102b3b6..7525033d 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -38,3 +38,9 @@ gnome.generate_gir(wp_lib, includes: ['GLib-2.0', 'GObject-2.0'], install: true, ) + +wp_dep = declare_dependency( + link_with: wp_lib, + include_directories: wp_lib_include_dir, + dependencies: [gobject_dep] +) diff --git a/meson.build b/meson.build index 39a4e474..3886b8b5 100644 --- a/meson.build +++ b/meson.build @@ -11,9 +11,13 @@ project('wireplumber', ['c'], wireplumber_api_version = '0.1' gobject_dep = dependency('gobject-2.0') +gmodule_dep = dependency('gmodule-2.0') +gio_dep = dependency('gio-2.0') pipewire_dep = dependency('libpipewire-0.3') gnome = import('gnome') +wp_lib_include_dir = include_directories('lib') + subdir('lib') subdir('src') diff --git a/src/core.c b/src/core.c index ad47cac7..92b6b04b 100644 --- a/src/core.c +++ b/src/core.c @@ -10,10 +10,17 @@ #include "core.h" #include "loop-source.h" +#include "module-loader.h" #include "utils.h" +#include +#include + #include #include +#include + +#define WIREPLUMBER_DEFAULT_CONFIG_FILE "wireplumber.conf" struct _WpCore { @@ -26,32 +33,27 @@ struct _WpCore struct pw_remote *remote; struct spa_hook remote_listener; - struct pw_core_proxy *core_proxy; - struct spa_hook core_proxy_listener; - - struct pw_registry_proxy *registry_proxy; - struct spa_hook registry_proxy_listener; + WpModuleLoader *module_loader; + WpPluginRegistry *plugin_registry; + WpProxyRegistry *proxy_registry; GError *exit_error; }; G_DEFINE_TYPE (WpCore, wp_core, G_TYPE_OBJECT); -static const struct pw_registry_proxy_events registry_events = { - PW_VERSION_REGISTRY_PROXY_EVENTS, - //.global = registry_global, - //.global_remove = registry_global_remove, -}; +static gboolean +signal_handler (gpointer data) +{ + WpCore *self = WP_CORE (data); + wp_core_exit (self, WP_DOMAIN_CORE, WP_CODE_INTERRUPTED, + "interrupted by signal"); + return G_SOURCE_CONTINUE; +} -static const struct pw_core_proxy_events core_events = { - PW_VERSION_CORE_EVENTS, - //.done = core_done -}; - -static void on_state_changed (void * data, - enum pw_remote_state old_state, - enum pw_remote_state new_state, - const char * error) +static void +remote_state_changed (void * data, enum pw_remote_state old_state, + enum pw_remote_state new_state, const char * error) { WpCore *self = WP_CORE (data); @@ -60,20 +62,7 @@ static void on_state_changed (void * data, pw_remote_state_as_string (new_state)); switch (new_state) { - case PW_REMOTE_STATE_CONNECTED: - self->core_proxy = pw_remote_get_core_proxy (self->remote); - pw_core_proxy_add_listener (self->core_proxy, &self->core_proxy_listener, - &core_events, self); - - self->registry_proxy = pw_core_proxy_get_registry (self->core_proxy, - PW_TYPE_INTERFACE_Registry, PW_VERSION_REGISTRY, 0); - pw_registry_proxy_add_listener (self->registry_proxy, - &self->registry_proxy_listener, ®istry_events, self); - break; - case PW_REMOTE_STATE_UNCONNECTED: - self->core_proxy = NULL; - self->registry_proxy = NULL; wp_core_exit (self, WP_DOMAIN_CORE, WP_CODE_DISCONNECTED, "disconnected"); break; @@ -89,9 +78,122 @@ static void on_state_changed (void * data, static const struct pw_remote_events remote_events = { PW_VERSION_REMOTE_EVENTS, - .state_changed = on_state_changed, + .state_changed = remote_state_changed, }; +static gboolean +wp_core_parse_commands_file (WpCore * self, GInputStream * stream, + GError ** error) +{ + gchar buffer[4096]; + gssize bytes_read; + gchar *cur, *linestart, *saveptr; + gchar *cmd, *abi, *module; + gint lineno = 1; + gboolean eof = FALSE; + + linestart = cur = buffer; + + do { + bytes_read = g_input_stream_read (stream, cur, sizeof (buffer), NULL, error); + if (bytes_read < 0) + return FALSE; + else if (bytes_read == 0) { + eof = TRUE; + /* terminate the remaining data, so that we consume it all */ + if (cur != linestart) { + *cur = '\n'; + } + } + + bytes_read += (cur - linestart); + + while (cur - buffer < bytes_read) { + while (cur - buffer < bytes_read && *cur != '\n') + cur++; + + if (*cur == '\n') { + /* found the end of a line */ + *cur = '\0'; + + /* tokenize and execute */ + cmd = strtok_r (linestart, " ", &saveptr); + + if (g_strcmp0 (cmd, "load-module")) { + abi = strtok_r (NULL, " ", &saveptr); + module = strtok_r (NULL, " ", &saveptr); + + if (!abi || !module) { + g_set_error (error, WP_DOMAIN_CORE, WP_CODE_INVALID_ARGUMENT, + "expected ABI and MODULE at line %i", lineno); + return FALSE; + } else if (!wp_module_loader_load (self->module_loader, + self->plugin_registry, abi, module, error)) { + return FALSE; + } + } else { + g_set_error (error, WP_DOMAIN_CORE, WP_CODE_INVALID_ARGUMENT, + "unknown command '%s' at line %i", cmd, lineno); + return FALSE; + } + + /* continue with the next line */ + linestart = ++cur; + lineno++; + } + } + + /* reached the end of the data that was read */ + + if (cur - linestart >= sizeof (buffer)) { + g_set_error (error, WP_DOMAIN_CORE, WP_CODE_OPERATION_FAILED, + "line %i exceeds the maximum allowed line size (%d bytes)", + lineno, (gint) sizeof (buffer)); + return FALSE; + } else if (cur - linestart > 0) { + /* we have unparsed data, move it to the + * beginning of the buffer and continue */ + strncpy (buffer, linestart, cur - linestart); + linestart = buffer; + cur = buffer + (cur - linestart); + } + } while (!eof); + + return TRUE; +} + +static gboolean +wp_core_load_commands_file (WpCore * self) +{ + g_autoptr (GFile) file = NULL; + g_autoptr (GError) error = NULL; + g_autoptr (GFileInputStream) istream = NULL; + const gchar *filename; + + filename = g_getenv ("WIREPLUMBER_CONFIG_FILE"); + if (!filename) + filename = WIREPLUMBER_DEFAULT_CONFIG_FILE; + + file = g_file_new_for_path (filename); + istream = g_file_read (file, NULL, &error); + if (!istream) { + g_propagate_error (&self->exit_error, error); + error = NULL; + g_main_loop_quit (self->loop); + return FALSE; + } + + if (!wp_core_parse_commands_file (self, G_INPUT_STREAM (istream), &error)) { + g_propagate_prefixed_error (&self->exit_error, error, "Failed to read %s: ", + filename); + error = NULL; + g_main_loop_quit (self->loop); + return FALSE; + } + + return TRUE; +} + static void wp_core_init (WpCore * self) { @@ -104,6 +206,9 @@ wp_core_init (WpCore * self) pw_remote_add_listener (self->remote, &self->remote_listener, &remote_events, self); + + self->proxy_registry = wp_proxy_registry_new (self->remote); + self->plugin_registry = wp_plugin_registry_new (); } static void @@ -111,6 +216,15 @@ wp_core_finalize (GObject * obj) { WpCore *self = WP_CORE (obj); + /* ensure all proxies and plugins are unrefed, + * so that the registries can be disposed */ + g_object_run_dispose (G_OBJECT (self->plugin_registry)); + g_object_run_dispose (G_OBJECT (self->proxy_registry)); + + g_clear_object (&self->plugin_registry); + g_clear_object (&self->proxy_registry); + g_clear_object (&self->module_loader); + spa_hook_remove (&self->remote_listener); pw_remote_destroy (self->remote); @@ -143,12 +257,13 @@ wp_core_get_instance (void) } static gboolean -signal_handler (gpointer data) +wp_core_run_in_idle (WpCore * self) { - WpCore *self = WP_CORE (data); - wp_core_exit (self, WP_DOMAIN_CORE, WP_CODE_INTERRUPTED, - "interrupted by signal"); - return G_SOURCE_CONTINUE; + if (!wp_core_load_commands_file (self)) goto out; + if (pw_remote_connect (self->remote) < 0) goto out; + +out: + return G_SOURCE_REMOVE; } void @@ -158,7 +273,7 @@ wp_core_run (WpCore * self, GError ** error) g_unix_signal_add (SIGTERM, signal_handler, self); g_unix_signal_add (SIGHUP, signal_handler, self); - g_idle_add ((GSourceFunc) pw_remote_connect, self->remote); + g_idle_add ((GSourceFunc) wp_core_run_in_idle, self); g_main_loop_run (self->loop); diff --git a/src/meson.build b/src/meson.build index ca74a4d5..d0fac24d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,6 +2,7 @@ wp_sources = [ 'core.c', 'loop-source.c', 'main.c', + 'module-loader.c', 'utils.c', ] @@ -9,5 +10,5 @@ executable('wireplumber', wp_sources, c_args : [ '-D_GNU_SOURCE', '-DG_LOG_USE_STRUCTURED' ], install: true, - dependencies : [gobject_dep, pipewire_dep], + dependencies : [gobject_dep, gmodule_dep, gio_dep, pipewire_dep, wp_dep], ) diff --git a/src/module-loader.c b/src/module-loader.c new file mode 100644 index 00000000..8310e954 --- /dev/null +++ b/src/module-loader.c @@ -0,0 +1,80 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "module-loader.h" +#include "utils.h" + +#include + +struct _WpModuleLoader +{ + GObject parent; + const gchar *module_dir; +}; + +G_DEFINE_TYPE (WpModuleLoader, wp_module_loader, G_TYPE_OBJECT); + +static void +wp_module_loader_init (WpModuleLoader * self) +{ + self->module_dir = g_getenv ("WIREPLUMBER_MODULE_DIR"); +} + +static void +wp_module_loader_class_init (WpModuleLoaderClass * klass) +{ +} + +WpModuleLoader * +wp_module_loader_new (void) +{ + return g_object_new (wp_module_loader_get_type (), NULL); +} + +static gboolean +wp_module_loader_load_c (WpModuleLoader * self, WpPluginRegistry * registry, + const gchar * module_name, GError ** error) +{ + g_autofree gchar *module_path = NULL; + GModule *module; + gpointer module_init; + typedef void (*WpModuleInitFunc)(WpPluginRegistry *); + + module_path = g_module_build_path (self->module_dir, module_name); + module = g_module_open (module_path, G_MODULE_BIND_LOCAL); + if (!module) { + g_set_error (error, WP_DOMAIN_CORE, WP_CODE_OPERATION_FAILED, + "Failed to open module %s: %s", module_path, g_module_error ()); + return FALSE; + } + + if (!g_module_symbol (module, G_STRINGIFY (WP_MODULE_INIT_SYMBOL), + &module_init)) { + g_set_error (error, WP_DOMAIN_CORE, WP_CODE_OPERATION_FAILED, + "Failed to locate symbol " G_STRINGIFY (WP_MODULE_INIT_SYMBOL) " in %s", + module_path); + g_module_close (module); + return FALSE; + } + + ((WpModuleInitFunc) module_init) (registry); + return TRUE; +} + +gboolean +wp_module_loader_load (WpModuleLoader * self, WpPluginRegistry * registry, + const gchar * abi, const gchar * module_name, GError ** error) +{ + if (!g_strcmp0 (abi, "C")) { + return wp_module_loader_load_c (self, registry, module_name, error); + } else { + g_set_error (error, WP_DOMAIN_CORE, WP_CODE_INVALID_ARGUMENT, + "unknown module ABI %s", abi); + return FALSE; + } +} diff --git a/src/module-loader.h b/src/module-loader.h new file mode 100644 index 00000000..e095cdc8 --- /dev/null +++ b/src/module-loader.h @@ -0,0 +1,27 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __WIREPLUMBER_MODULE_LOADER_H__ +#define __WIREPLUMBER_MODULE_LOADER_H__ + +#include +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (WpModuleLoader, wp_module_loader, WP, MODULE_LOADER, GObject) + +WpModuleLoader * wp_module_loader_new (void); + +gboolean wp_module_loader_load (WpModuleLoader * self, + WpPluginRegistry * registry, const gchar * abi, const gchar * module_name, + GError ** error); + +G_END_DECLS + +#endif