diff --git a/modules/meson.build b/modules/meson.build index 3dd92842..fd3faa31 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -186,8 +186,8 @@ shared_library( 'wireplumber-module-lua-scripting', [ 'module-lua-scripting.c', - 'module-lua-scripting/engine.c', 'module-lua-scripting/api.c', + 'module-lua-scripting/config.c', m_lua_scripting_resources, ], c_args : [common_c_args, '-DG_LOG_DOMAIN="m-lua-scripting"'], diff --git a/modules/module-lua-scripting.c b/modules/module-lua-scripting.c index 86e9f202..c94c380c 100644 --- a/modules/module-lua-scripting.c +++ b/modules/module-lua-scripting.c @@ -10,107 +10,33 @@ #include #include -#define WP_TYPE_LUA_SCRIPTING_ENGINE \ - (wp_lua_scripting_engine_get_type ()) -GType wp_lua_scripting_engine_get_type (); void wp_lua_scripting_api_init (lua_State *L); +gboolean wp_lua_scripting_load_configuration (const gchar * conf_file, + WpCore * core, GError ** error); struct _WpLuaScriptingPlugin { - WpPlugin parent; + WpComponentLoader parent; - /* properties */ - gchar *profile; - - /* data */ WpCore *export_core; - gchar *config_ext; - - WpConfiguration *config; -}; - -enum { - PROP_0, - PROP_PROFILE, + lua_State *L; }; G_DECLARE_FINAL_TYPE (WpLuaScriptingPlugin, wp_lua_scripting_plugin, - WP, LUA_SCRIPTING_PLUGIN, WpPlugin) -G_DEFINE_TYPE (WpLuaScriptingPlugin, wp_lua_scripting_plugin, WP_TYPE_PLUGIN) + WP, LUA_SCRIPTING_PLUGIN, WpComponentLoader) +G_DEFINE_TYPE (WpLuaScriptingPlugin, wp_lua_scripting_plugin, + WP_TYPE_COMPONENT_LOADER) static void wp_lua_scripting_plugin_init (WpLuaScriptingPlugin * self) { } -static void -wp_lua_scripting_plugin_finalize (GObject * object) -{ - WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (object); - - g_clear_pointer (&self->profile, g_free); - - G_OBJECT_CLASS (wp_lua_scripting_plugin_parent_class)->finalize (object); -} - -static void -wp_lua_scripting_plugin_set_property (GObject * object, guint property_id, - const GValue * value, GParamSpec * pspec) -{ - WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (object); - - switch (property_id) { - case PROP_PROFILE: - g_clear_pointer (&self->profile, g_free); - self->profile = g_value_dup_string (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -wp_lua_scripting_plugin_get_property (GObject * object, guint property_id, - GValue * value, GParamSpec * pspec) -{ - WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (object); - - switch (property_id) { - case PROP_PROFILE: - g_value_set_string (value, self->profile); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -wp_lua_scripting_plugin_init_lua_ctx (WpConfigParser * engine, lua_State * L, - WpLuaScriptingPlugin * self) -{ - g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); - - lua_pushliteral (L, "wireplumber_core"); - lua_pushlightuserdata (L, core); - lua_settable (L, LUA_REGISTRYINDEX); - - lua_pushliteral (L, "wireplumber_export_core"); - lua_pushlightuserdata (L, self->export_core); - lua_settable (L, LUA_REGISTRYINDEX); - - wp_lua_scripting_api_init (L); -} - static void wp_lua_scripting_plugin_enable (WpPlugin * plugin, WpTransition * transition) { WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (plugin); g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin)); - g_autoptr (WpConfigParser) engine = NULL; - - self->config = wp_configuration_get_instance (core); /* initialize secondary connection to pipewire */ self->export_core = wp_core_clone (core); @@ -124,16 +50,20 @@ wp_lua_scripting_plugin_enable (WpPlugin * plugin, WpTransition * transition) return; } - /* load the lua scripts & execute them via the engine */ - self->config_ext = g_strdup_printf ("%s/lua", self->profile); - wp_configuration_add_extension (self->config, self->config_ext, - WP_TYPE_LUA_SCRIPTING_ENGINE); + /* init lua engine */ + self->L = wplua_new (); - engine = wp_configuration_get_parser (self->config, self->config_ext); - g_signal_connect_object (engine, "init-lua-context", - G_CALLBACK (wp_lua_scripting_plugin_init_lua_ctx), self, 0); + lua_pushliteral (self->L, "wireplumber_core"); + lua_pushlightuserdata (self->L, core); + lua_settable (self->L, LUA_REGISTRYINDEX); + + lua_pushliteral (self->L, "wireplumber_export_core"); + lua_pushlightuserdata (self->L, self->export_core); + lua_settable (self->L, LUA_REGISTRYINDEX); + + wp_lua_scripting_api_init (self->L); + wplua_enable_sandbox (self->L, WP_LUA_SANDBOX_ISOLATE_ENV); - wp_configuration_reload (self->config, self->config_ext); wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0); } @@ -142,47 +72,88 @@ wp_lua_scripting_plugin_disable (WpPlugin * plugin) { WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (plugin); - if (self->config && self->config_ext) - wp_configuration_remove_extension (self->config, self->config_ext); - g_clear_object (&self->config); - g_clear_pointer (&self->config_ext, g_free); + g_clear_pointer (&self->L, wplua_free); g_clear_object (&self->export_core); } +static gboolean +wp_lua_scripting_plugin_supports_type (WpComponentLoader * cl, + const gchar * type) +{ + return (!g_strcmp0 (type, "script/lua") || !g_strcmp0 (type, "config/lua")); +} + +static gchar * +find_script (const gchar * script) +{ + if (g_path_is_absolute (script) && + g_file_test (script, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) + return g_strdup (script); + + /* /etc/wireplumber/scripts */ + { + g_autofree gchar * file = g_build_filename ( + wp_get_config_dir (), "scripts", script, NULL); + if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) + return g_steal_pointer (&file); + } + + /* {XDG_DATA_DIRS,/usr/local/share,/usr/share}/wireplumber/scripts */ + const gchar * const * data_dirs = g_get_system_data_dirs (); + while (*data_dirs) { + g_autofree gchar * file = g_build_filename ( + *data_dirs, "wireplumber", "scripts", script, NULL); + if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) + return g_steal_pointer (&file); + data_dirs++; + } + return NULL; +} + +static gboolean +wp_lua_scripting_plugin_load (WpComponentLoader * cl, const gchar * component, + const gchar * type, GVariant * args, GError ** error) +{ + WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (cl); + + /* interpret component as a script */ + if (!g_strcmp0 (type, "script/lua")) { + g_autofree gchar * file = find_script (component); + if (!file) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Could not locate script '%s'", component); + return FALSE; + } + return wplua_load_path (self->L, file, error); + } + /* interpret component as a configuration file */ + else if (!g_strcmp0 (type, "config/lua")) { + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (cl)); + return wp_lua_scripting_load_configuration (component, core, error); + } + + g_return_val_if_reached (FALSE); +} + static void wp_lua_scripting_plugin_class_init (WpLuaScriptingPluginClass * klass) { - GObjectClass *object_class = (GObjectClass *) klass; WpPluginClass *plugin_class = (WpPluginClass *) klass; - - object_class->finalize = wp_lua_scripting_plugin_finalize; - object_class->set_property = wp_lua_scripting_plugin_set_property; - object_class->get_property = wp_lua_scripting_plugin_get_property; + WpComponentLoaderClass *cl_class = (WpComponentLoaderClass *) klass; plugin_class->enable = wp_lua_scripting_plugin_enable; plugin_class->disable = wp_lua_scripting_plugin_disable; - g_object_class_install_property(object_class, PROP_PROFILE, - g_param_spec_string ("profile", "profile", - "The configuration profile", NULL, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + cl_class->supports_type = wp_lua_scripting_plugin_supports_type; + cl_class->load = wp_lua_scripting_plugin_load; } WP_PLUGIN_EXPORT gboolean wireplumber__module_init (WpCore * core, GVariant * args, GError ** error) { - const gchar *profile; - - if (!g_variant_lookup (args, "profile", "&s", &profile)) { - g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, - "module-lua-scripting requires a 'profile'"); - return FALSE; - } - wp_plugin_register (g_object_new (wp_lua_scripting_plugin_get_type (), "name", "lua-scripting", "core", core, - "profile", profile, NULL)); return TRUE; } diff --git a/modules/module-lua-scripting/config.c b/modules/module-lua-scripting/config.c new file mode 100644 index 00000000..fabc0f76 --- /dev/null +++ b/modules/module-lua-scripting/config.c @@ -0,0 +1,226 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +static gboolean +add_spa_libs (lua_State *L, WpCore * core, GError ** error) +{ + switch (lua_getglobal (L, "spa_libs")) { + case LUA_TTABLE: + break; + case LUA_TNIL: + goto done; + default: + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "Expected 'spa_libs' to be a table"); + return FALSE; + } + + lua_pushnil (L); + while (lua_next (L, -2)) { + if (lua_type (L, -2) != LUA_TSTRING || + lua_type (L, -1) != LUA_TSTRING) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "'spa_libs' must be a table with string keys and string values"); + return FALSE; + } + + const gchar *regex = lua_tostring (L, -2); + const gchar *lib = lua_tostring (L, -1); + + int ret = pw_context_add_spa_lib (wp_core_get_pw_context (core), regex, + lib); + if (ret < 0) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "failed to add spa lib ('%s' on '%s'): %s", regex, lib, + g_strerror (-ret)); + return FALSE; + } + + lua_pop (L, 1); /* pop the value */ + } + +done: + lua_pop (L, 1); /* pop the spa_libs table */ + return TRUE; +} + +static GVariant * +lua_to_gvariant (lua_State *L, int index) +{ + GVariantBuilder b; + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + + int table = lua_absindex (L, index); + + lua_pushnil (L); + while (lua_next (L, table)) { + /* each argument must have a string as key */ + if (lua_type (L, -2) != LUA_TSTRING) { + wp_warning ("skipping bad component argument key"); + continue; /* skip, it's probably harmless */ + } + const char *key = lua_tostring (L, -2); + + switch (lua_type (L, -1)) { + case LUA_TBOOLEAN: + g_variant_builder_add (&b, "{sv}", key, + g_variant_new_boolean (lua_toboolean (L, -1))); + break; + + case LUA_TNUMBER: + if (lua_isinteger (L, -1)) { + g_variant_builder_add (&b, "{sv}", key, + g_variant_new_int64 (lua_tointeger (L, -1))); + } else { + g_variant_builder_add (&b, "{sv}", key, + g_variant_new_double (lua_tonumber (L, -1))); + } + break; + + case LUA_TSTRING: + g_variant_builder_add (&b, "{sv}", key, + g_variant_new_string (lua_tostring (L, -1))); + break; + + case LUA_TTABLE: + g_variant_builder_add (&b, "{sv}", key, lua_to_gvariant (L, -1)); + break; + + default: + wp_warning ("skipping bad component argument value"); + break; + } + + lua_pop (L, 1); + } + + return g_variant_builder_end (&b); +} + +static gboolean +load_components (lua_State *L, WpCore * core, GError ** error) +{ + switch (lua_getglobal (L, "components")) { + case LUA_TTABLE: + break; + case LUA_TNIL: + goto done; + default: + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "Expected 'components' to be a table"); + return FALSE; + } + + lua_pushnil (L); + while (lua_next (L, -2)) { + /* value must be a table */ + if (lua_type (L, -1) != LUA_TTABLE) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "'components' must be a table with tables as values"); + return FALSE; + } + + /* record indexes to the current key and value of the components table */ + int key = lua_absindex (L, -2); + int table = lua_absindex (L, -1); + + /* get component */ + if (lua_geti (L, table, 1) != LUA_TSTRING) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "components['%s'] has a non-string or unspecified component name", + lua_tostring (L, key)); + return FALSE; + } + const char * component = lua_tostring (L, -1); + + /* get component type */ + if (lua_getfield (L, table, "type") != LUA_TSTRING) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "components['%s'] has a non-string or unspecified component type", + lua_tostring (L, key)); + return FALSE; + } + const char * type = lua_tostring (L, -1); + + /* optional component arguments */ + GVariant *args = NULL; + if (lua_getfield (L, table, "args") == LUA_TTABLE) { + args = lua_to_gvariant (L, -1); + } + + if (!wp_core_load_component (core, component, type, args, error)) + return FALSE; + + /* clear the stack up to the key */ + lua_settop (L, key); + } + +done: + lua_pop (L, 1); /* pop the components table */ + return TRUE; +} + +gboolean +wp_lua_scripting_load_configuration (const gchar * conf_file, + WpCore * core, GError ** error) +{ + g_autofree gchar * path = NULL; + g_autoptr (lua_State) L = wplua_new (); + gboolean found = FALSE; + + wplua_enable_sandbox (L, WP_LUA_SANDBOX_MINIMAL_STD); + + /* load conf_file itself */ + path = g_build_filename (wp_get_config_dir (), conf_file, NULL); + if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { + wp_info ("loading config file: %s", path); + if (!wplua_load_path (L, path, error)) + return FALSE; + found = TRUE; + } + g_clear_pointer (&path, g_free); + + /* aggregate split files from the ${conf_file}.d subdirectory */ + path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.d", + wp_get_config_dir (), conf_file); + if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { + g_autoptr (GDir) conf_dir = g_dir_open (path, 0, error); + if (!conf_dir) + return FALSE; + + const gchar *filename = NULL; + while ((filename = g_dir_read_name (conf_dir))) { + /* Only parse files that have the proper extension */ + if (g_str_has_suffix (filename, ".lua")) { + g_autofree gchar * file = g_build_filename (path, filename, NULL); + wp_info ("loading config file: %s", file); + if (!wplua_load_path (L, file, error)) + return FALSE; + found = TRUE; + } + } + } + + if (!found) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Could not locate configuration file '%s'", conf_file); + return FALSE; + } + + if (!add_spa_libs (L, core, error)) + return FALSE; + + if (!load_components (L, core, error)) + return FALSE; + + return TRUE; +} diff --git a/modules/module-lua-scripting/engine.c b/modules/module-lua-scripting/engine.c deleted file mode 100644 index ed7251bf..00000000 --- a/modules/module-lua-scripting/engine.c +++ /dev/null @@ -1,91 +0,0 @@ -/* WirePlumber - * - * Copyright © 2020 Collabora Ltd. - * @author George Kiagiadakis - * - * SPDX-License-Identifier: MIT - */ - -#include -#include - -struct _WpLuaScriptingEngine -{ - GObject parent; - lua_State *L; -}; - -enum { - SIGNAL_INIT_LUA_CONTEXT, - N_SIGNALS, -}; - -static guint signals[N_SIGNALS] = {0}; - -static void wp_lua_scripting_engine_parser_iface_init (WpConfigParserInterface * iface); - -G_DECLARE_FINAL_TYPE (WpLuaScriptingEngine, wp_lua_scripting_engine, - WP, LUA_SCRIPTING_ENGINE, GObject) -G_DEFINE_TYPE_WITH_CODE (WpLuaScriptingEngine, wp_lua_scripting_engine, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (WP_TYPE_CONFIG_PARSER, - wp_lua_scripting_engine_parser_iface_init)) - -static void -wp_lua_scripting_engine_init (WpLuaScriptingEngine * self) -{ -} - -static void -wp_lua_scripting_engine_finalize (GObject * object) -{ - WpLuaScriptingEngine * self = WP_LUA_SCRIPTING_ENGINE (object); - - g_clear_pointer (&self->L, wplua_free); - - G_OBJECT_CLASS (wp_lua_scripting_engine_parent_class)->finalize (object); -} - -static void -wp_lua_scripting_engine_class_init (WpLuaScriptingEngineClass * klass) -{ - GObjectClass * object_class = (GObjectClass *) klass; - - object_class->finalize = wp_lua_scripting_engine_finalize; - - signals[SIGNAL_INIT_LUA_CONTEXT] = g_signal_new ("init-lua-context", - G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 1, G_TYPE_POINTER); -} - -static gboolean -wp_lua_scripting_engine_add_file (WpConfigParser * parser, const gchar * file) -{ - WpLuaScriptingEngine * self = WP_LUA_SCRIPTING_ENGINE (parser); - g_autoptr (GError) error = NULL; - - if (!wplua_load_path (self->L, file, &error)) { - wp_warning_object (self, "%s", error->message); - if (error->domain != WP_DOMAIN_LUA || error->code != WP_LUA_ERROR_RUNTIME) - return FALSE; - } - return TRUE; -} - -static void -wp_lua_scripting_engine_reset (WpConfigParser * parser) -{ - WpLuaScriptingEngine * self = WP_LUA_SCRIPTING_ENGINE (parser); - - g_clear_pointer (&self->L, wplua_free); - self->L = wplua_new (); - g_signal_emit (self, signals[SIGNAL_INIT_LUA_CONTEXT], 0, self->L); - wplua_enable_sandbox (self->L); -} - -static void -wp_lua_scripting_engine_parser_iface_init (WpConfigParserInterface * iface) -{ - iface->add_file = wp_lua_scripting_engine_add_file; - iface->reset = wp_lua_scripting_engine_reset; -} diff --git a/modules/module-lua-scripting/engine.h b/modules/module-lua-scripting/engine.h deleted file mode 100644 index 58c8d073..00000000 --- a/modules/module-lua-scripting/engine.h +++ /dev/null @@ -1,22 +0,0 @@ -/* WirePlumber - * - * Copyright © 2020 Collabora Ltd. - * @author George Kiagiadakis - * - * SPDX-License-Identifier: MIT - */ - -#ifndef __WIREPLUMBER_LUA_SCRIPTING_ENGINE_H__ -#define __WIREPLUMBER_LUA_SCRIPTING_ENGINE_H__ - -#include -#include - -G_BEGIN_DECLS - -#define WP_TYPE_LUA_SCRIPTING_ENGINE \ - (wp_lua_scripting_engine_get_type ()) - -G_END_DECLS - -#endif