diff --git a/modules/meson.build b/modules/meson.build index 9f303850..49ffca70 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -232,3 +232,23 @@ shared_library( install_dir : wireplumber_module_dir, dependencies : [wp_dep, pipewire_dep], ) + +m_lua_scripting_resources = gnome.compile_resources( + 'm-lua-scripting-resources', + 'module-lua-scripting/gresource.xml', + source_dir: 'module-lua-scripting', + c_name: '_m_lua_scripting_resources') + +shared_library( + 'wireplumber-module-lua-scripting', + [ + 'module-lua-scripting.c', + 'module-lua-scripting/engine.c', + 'module-lua-scripting/api.c', + m_lua_scripting_resources, + ], + c_args : [common_c_args, '-DG_LOG_DOMAIN="m-lua-scripting"'], + install : true, + install_dir : wireplumber_module_dir, + dependencies : [wp_dep, pipewire_dep, wplua_dep], +) diff --git a/modules/module-lua-scripting.c b/modules/module-lua-scripting.c new file mode 100644 index 00000000..2ac3c4a9 --- /dev/null +++ b/modules/module-lua-scripting.c @@ -0,0 +1,182 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include +#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); + +struct _WpLuaScriptingPlugin +{ + WpPlugin parent; + + /* properties */ + gchar *profile; + + /* data */ + WpCore *export_core; + gchar *config_ext; +}; + +enum { + PROP_0, + PROP_PROFILE, +}; + +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) + +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_plugin_get_core (WP_PLUGIN (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_activate (WpPlugin * plugin) +{ + WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (plugin); + g_autoptr (WpCore) core = wp_plugin_get_core (plugin); + g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core); + g_autoptr (WpConfigParser) engine = NULL; + + /* initialize secondary connection to pipewire */ + self->export_core = wp_core_clone (core); + wp_core_update_properties (self->export_core, wp_properties_new ( + PW_KEY_APP_NAME, "WirePlumber (export)", + NULL)); + if (!wp_core_connect (self->export_core)) { + wp_warning_object (self, "failed to connect export core"); + return; + } + + /* load the lua scripts & execute them via the engine */ + self->config_ext = g_strdup_printf ("%s/lua", self->profile); + wp_configuration_add_extension (config, self->config_ext, + WP_TYPE_LUA_SCRIPTING_ENGINE); + + engine = wp_configuration_get_parser (config, self->config_ext); + g_signal_connect (engine, "init-lua-context", + G_CALLBACK (wp_lua_scripting_plugin_init_lua_ctx), plugin); + + wp_configuration_reload (config, self->config_ext); +} + +static void +wp_lua_scripting_plugin_deactivate (WpPlugin * plugin) +{ + WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (plugin); + g_autoptr (WpCore) core = wp_plugin_get_core (plugin); + + if (core) { + g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core); + wp_configuration_remove_extension (config, self->config_ext); + } + g_clear_pointer (&self->config_ext, g_free); + g_clear_object (&self->export_core); +} + +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; + + plugin_class->activate = wp_lua_scripting_plugin_activate; + plugin_class->deactivate = wp_lua_scripting_plugin_deactivate; + + 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)); +} + +WP_PLUGIN_EXPORT void +wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) +{ + const gchar *profile; + + if (!g_variant_lookup (args, "profile", "&s", &profile)) { + wp_warning_object (module, "module-lua-scripting requires a 'profile'"); + return; + } + + wp_plugin_register (g_object_new (wp_lua_scripting_plugin_get_type (), + "name", "lua-scripting", + "module", module, + "profile", profile, + NULL)); +} diff --git a/modules/module-lua-scripting/api.c b/modules/module-lua-scripting/api.c new file mode 100644 index 00000000..c4fa12ea --- /dev/null +++ b/modules/module-lua-scripting/api.c @@ -0,0 +1,350 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include "wp/global-proxy.h" +#include +#include + +#define URI_API "resource:///org/freedesktop/pipewire/wireplumber/m-lua-scripting/api.lua" + +/* WpDebug */ + +static int +log_log (lua_State *L, GLogLevelFlags lvl) +{ + lua_Debug ar; + const gchar *message; + gchar line_str[11]; + gconstpointer instance = NULL; + GType type = G_TYPE_INVALID; + int index = 1; + + if (!wp_log_level_is_enabled (lvl)) + return 0; + + lua_getstack (L, 1, &ar); + lua_getinfo (L, "nSl", &ar); + + if (wplua_isobject (L, 1, G_TYPE_OBJECT)) { + instance = wplua_toobject (L, 1); + type = G_TYPE_FROM_INSTANCE (instance); + index++; + } + + message = luaL_checkstring (L, index); + sprintf (line_str, "%d", ar.currentline); + + wp_log_structured_standard (G_LOG_DOMAIN, lvl, + ar.source, line_str, ar.name, type, instance, "%s", message); + return 0; +} + +static int +log_warning (lua_State *L) { return log_log (L, G_LOG_LEVEL_WARNING); } + +static int +log_message (lua_State *L) { return log_log (L, G_LOG_LEVEL_MESSAGE); } + +static int +log_info (lua_State *L) { return log_log (L, G_LOG_LEVEL_INFO); } + +static int +log_debug (lua_State *L) { return log_log (L, G_LOG_LEVEL_DEBUG); } + +static int +log_trace (lua_State *L) { return log_log (L, WP_LOG_LEVEL_TRACE); } + +static const luaL_Reg log_funcs[] = { + { "warning", log_warning }, + { "message", log_message }, + { "info", log_info }, + { "debug", log_debug }, + { "trace", log_trace }, + { NULL, NULL } +}; + +/* WpGlobalProxy */ + +static int +global_proxy_request_destroy (lua_State *L) +{ + WpGlobalProxy * p = wplua_checkobject (L, 1, WP_TYPE_GLOBAL_PROXY); + wp_global_proxy_request_destroy (p); + return 0; +} + +static const luaL_Reg global_proxy_methods[] = { + { "request_destroy", global_proxy_request_destroy }, + { NULL, NULL } +}; + +/* WpIterator */ + +static int +iterator_reset (lua_State *L) +{ + WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR); + wp_iterator_reset (it); + return 0; +} + +static int +iterator_next (lua_State *L) +{ + WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR); + GValue v = G_VALUE_INIT; + if (wp_iterator_next (it, &v)) { + return wplua_gvalue_to_lua (L, &v); + } else { + lua_pushnil (L); + return 1; + } +} + +static const luaL_Reg iterator_methods[] = { + { "reset", iterator_reset }, + { "next", iterator_next }, + { NULL, NULL } +}; + +/* WpObjectInterest */ + +static GVariant * +constraint_value_to_variant (lua_State *L, int idx) +{ + switch (lua_type (L, idx)) { + case LUA_TBOOLEAN: + return g_variant_new_boolean (lua_toboolean (L, idx)); + case LUA_TSTRING: + return g_variant_new_string (lua_tostring (L, idx)); + case LUA_TNUMBER: + if (lua_isinteger (L, idx)) + return g_variant_new_int64 (lua_tointeger (L, idx)); + else + return g_variant_new_double (lua_tonumber (L, idx)); + default: + return NULL; + } +} + +static void +object_interest_new_add_constraint (lua_State *L, GType type, + WpObjectInterest *interest) +{ + int constraint_idx; + WpConstraintType ctype; + const gchar *subject; + WpConstraintVerb verb; + GVariant *value = NULL; + + constraint_idx = lua_absindex (L, -1); + + /* verify this is a Constraint{} */ + if (lua_type (L, constraint_idx) != LUA_TTABLE) { + luaL_error (L, "Interest: expected Constraint at index %d", + lua_tointeger (L, -2)); + } + + if (luaL_getmetafield (L, constraint_idx, "__name") == LUA_TNIL || + g_strcmp0 (lua_tostring (L, -1), "Constraint") != 0) { + luaL_error (L, "Interest: expected Constraint at index %d", + lua_tointeger (L, -2)); + } + lua_pop (L, 1); + + /* get the constraint type */ + lua_pushliteral (L, "type"); + if (lua_gettable (L, constraint_idx) == LUA_TNUMBER) + ctype = lua_tointeger (L, -1); + else + ctype = g_type_is_a (type, WP_TYPE_GLOBAL_PROXY) ? + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY : WP_CONSTRAINT_TYPE_G_PROPERTY; + lua_pop (L, 1); + + /* get t[1] (the subject) and t[2] (the verb) */ + lua_geti (L, constraint_idx, 1); + subject = lua_tostring (L, -1); + + lua_geti (L, constraint_idx, 2); + verb = lua_tostring (L, -1)[0]; + + switch (verb) { + case WP_CONSTRAINT_VERB_EQUALS: + case WP_CONSTRAINT_VERB_MATCHES: { + lua_geti (L, constraint_idx, 3); + value = constraint_value_to_variant (L, -1); + if (G_UNLIKELY (!value)) + luaL_error (L, "Constraint: bad value type"); + break; + } + case WP_CONSTRAINT_VERB_IN_RANGE: { + GVariant *values[2]; + lua_geti (L, constraint_idx, 3); + lua_geti (L, constraint_idx, 4); + values[0] = constraint_value_to_variant (L, -2); + values[1] = constraint_value_to_variant (L, -1); + if (G_UNLIKELY (!values[0] || !values[1])) { + g_clear_pointer (&values[0], g_variant_unref); + g_clear_pointer (&values[1], g_variant_unref); + luaL_error (L, "Constraint: bad value type"); + } + value = g_variant_new_tuple (values, 2); + break; + } + case WP_CONSTRAINT_VERB_IN_LIST: { + GPtrArray *values = + g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref); + int i = 3; + while (lua_geti (L, constraint_idx, i++) != LUA_TNIL) { + GVariant *tmp = constraint_value_to_variant (L, -1); + if (G_UNLIKELY (!tmp)) { + g_ptr_array_unref (values); + luaL_error (L, "Constraint: bad value type"); + } + g_ptr_array_add (values, g_variant_ref_sink (tmp)); + lua_pop (L, 1); + } + value = g_variant_new_tuple ((GVariant **) values->pdata, values->len); + g_ptr_array_unref (values); + break; + } + default: + break; + } + + wp_object_interest_add_constraint (interest, ctype, subject, verb, value); + lua_settop (L, constraint_idx); +} + +static int +object_interest_new (lua_State *L) +{ + WpObjectInterest *interest = NULL; + GType type = 0; + gchar *typestr; + + luaL_checktype (L, 1, LUA_TTABLE); + + /* type = "string" -> required */ + lua_pushliteral (L, "type"); + if (lua_gettable (L, -2) != LUA_TSTRING) + luaL_error (L, "Interest: expected 'type' as string"); + + /* "device" -> "WpDevice" */ + typestr = g_strdup_printf ("Wp%s", lua_tostring (L, -1)); + if (typestr[2] != 0) { + typestr[2] = g_ascii_toupper (typestr[2]); + type = g_type_from_name (typestr); + } + g_free (typestr); + lua_pop (L, 1); + + if (!type) + luaL_error (L, "Interest: unknown type '%s'", lua_tostring (L, -1)); + + interest = wp_object_interest_new_type (type); + wplua_pushboxed (L, WP_TYPE_OBJECT_INTEREST, interest); + + /* add constraints */ + lua_pushnil (L); + while (lua_next (L, 1)) { + /* if the key isn't "type" */ + if (!(lua_type (L, -2) == LUA_TSTRING && + !g_strcmp0 ("type", lua_tostring (L, -2)))) + object_interest_new_add_constraint (L, type, interest); + lua_pop (L, 1); + } + + return 1; +} + +/* WpObjectManager */ + +static int +object_manager_new (lua_State *L) +{ + WpObjectManager *om; + + /* validate arguments */ + luaL_checktype (L, 1, LUA_TTABLE); + + /* push to Lua asap to have a way to unref in case of error */ + om = wp_object_manager_new (); + wplua_pushobject (L, om); + + lua_pushnil (L); + while (lua_next (L, 1)) { + if (!wplua_isboxed (L, -1, WP_TYPE_OBJECT_INTEREST)) + luaL_error (L, "ObjectManager: expected Interest"); + + /* steal the interest out of the GValue to avoid doing mem copy */ + GValue *v = lua_touserdata (L, -1); + wp_object_manager_add_interest_full (om, g_value_get_boxed (v)); + memset (v, 0, sizeof (GValue)); + g_value_init (v, WP_TYPE_OBJECT_INTEREST); + + lua_pop (L, 1); + } + + /* request all the features for Lua scripts to make their job easier */ + wp_object_manager_request_object_features (om, + WP_TYPE_OBJECT, WP_OBJECT_FEATURES_ALL); + + return 1; +} + +static int +object_manager_activate (lua_State *L) +{ + WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER); + WpCore *core; + + lua_pushliteral (L, "wireplumber_core"); + lua_gettable (L, LUA_REGISTRYINDEX); + core = lua_touserdata (L, -1); + + wp_core_install_object_manager (core, om); + return 0; +} + +static int +object_manager_iterate (lua_State *L) +{ + WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER); + WpIterator *it = wp_object_manager_iterate (om); + wplua_pushboxed (L, WP_TYPE_ITERATOR, it); + return 1; +} + +static const luaL_Reg object_manager_methods[] = { + { "activate", object_manager_activate }, + { "iterate", object_manager_iterate }, + { NULL, NULL } +}; + +void +wp_lua_scripting_api_init (lua_State *L) +{ + g_autoptr (GError) error = NULL; + + luaL_newlib (L, log_funcs); + lua_setglobal (L, "WpDebug"); + + wplua_register_type_methods (L, WP_TYPE_GLOBAL_PROXY, + NULL, global_proxy_methods); + wplua_register_type_methods (L, WP_TYPE_ITERATOR, + NULL, iterator_methods); + wplua_register_type_methods (L, WP_TYPE_OBJECT_INTEREST, + object_interest_new, NULL); + wplua_register_type_methods (L, WP_TYPE_OBJECT_MANAGER, + object_manager_new, object_manager_methods); + + wplua_load_uri (L, URI_API, &error); + if (G_UNLIKELY (error)) + wp_critical ("Failed to load api: %s", error->message); +} diff --git a/modules/module-lua-scripting/api.lua b/modules/module-lua-scripting/api.lua new file mode 100644 index 00000000..50bcb4a7 --- /dev/null +++ b/modules/module-lua-scripting/api.lua @@ -0,0 +1,77 @@ +-- WirePlumber +-- +-- This file contains the API that is made available to the Lua scripts +-- +-- Copyright © 2020 Collabora Ltd. +-- @author George Kiagiadakis +-- +-- SPDX-License-Identifier: MIT + +local function Constraint (spec) + assert (type(spec[1]) == "string", "Constraint: expected subject as string"); + assert (type(spec[2]) == "string", "Constraint: expected verb as string"); + + local subject = spec[1] + local verb = spec[2] + local verbs = { + ["="] = "equals", + ["c"] = "in-list", + ["~"] = "in-range", + ["#"] = "matches", + ["+"] = "is-present", + ["-"] = "is-absent" + } + + -- check and convert verb to its short version + local verb_is_valid = false + for k, v in pairs(verbs) do + if verb == k or verb == v then + verb = k + spec[2] = k + verb_is_valid = true + break + end + end + assert (verb_is_valid, "Constraint: invalid verb '" .. verb .. "'") + + -- check and convert type to its integer value + local type = spec["type"] + if type then + local valid_types = { "pw-global", "pw", "gobject" } + local type_is_valid = false + + for i, v in ipairs(valid_types) do + if type == v then + spec["type"] = i + type_is_valid = true + break + end + end + + assert(type_is_valid, "Constraint: invalid subject type '" .. type .. "'") + end + + -- check if we got the right amount of values + if verb == "=" or verb == "#" then + assert (spec[3] ~= nil, + "Constraint: " .. verbs[verb] .. ": expected constraint value") + elseif verb == "c" then + assert (spec[3] ~= nil, + "Constraint: " .. verbs[verb] .. ": expected at least one constraint value") + elseif verb == "~" then + assert (spec[3] ~= nil and spec[4] ~= nil, + "Constraint: " .. verbs[verb] .. ": expected two values") + else + assert (spec[3] == nil, + "Constraint: " .. verbs[verb] .. ": expected no value, but there is one") + end + + return debug.setmetatable(spec, { __name = "Constraint" }) +end + +SANDBOX_EXPORT = { + Log = WpDebug, + ObjectManager = WpObjectManager_new, + Interest = WpObjectInterest_new, + Constraint = Constraint, +} diff --git a/modules/module-lua-scripting/engine.c b/modules/module-lua-scripting/engine.c new file mode 100644 index 00000000..ed7251bf --- /dev/null +++ b/modules/module-lua-scripting/engine.c @@ -0,0 +1,91 @@ +/* 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 new file mode 100644 index 00000000..58c8d073 --- /dev/null +++ b/modules/module-lua-scripting/engine.h @@ -0,0 +1,22 @@ +/* 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 diff --git a/modules/module-lua-scripting/gresource.xml b/modules/module-lua-scripting/gresource.xml new file mode 100644 index 00000000..93907588 --- /dev/null +++ b/modules/module-lua-scripting/gresource.xml @@ -0,0 +1,6 @@ + + + + api.lua + +