modules: implement module-lua-scripting
a module that allows loading and executing Lua scripts from a configured directory
This commit is contained in:
350
modules/module-lua-scripting/api.c
Normal file
350
modules/module-lua-scripting/api.c
Normal file
@@ -0,0 +1,350 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wp/global-proxy.h"
|
||||
#include <wp/wp.h>
|
||||
#include <wplua/wplua.h>
|
||||
|
||||
#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);
|
||||
}
|
77
modules/module-lua-scripting/api.lua
Normal file
77
modules/module-lua-scripting/api.lua
Normal file
@@ -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 <george.kiagiadakis@collabora.com>
|
||||
--
|
||||
-- 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,
|
||||
}
|
91
modules/module-lua-scripting/engine.c
Normal file
91
modules/module-lua-scripting/engine.c
Normal file
@@ -0,0 +1,91 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <wp/wp.h>
|
||||
#include <wplua/wplua.h>
|
||||
|
||||
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;
|
||||
}
|
22
modules/module-lua-scripting/engine.h
Normal file
22
modules/module-lua-scripting/engine.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WIREPLUMBER_LUA_SCRIPTING_ENGINE_H__
|
||||
#define __WIREPLUMBER_LUA_SCRIPTING_ENGINE_H__
|
||||
|
||||
#include <wp/wp.h>
|
||||
#include <wplua/wplua.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define WP_TYPE_LUA_SCRIPTING_ENGINE \
|
||||
(wp_lua_scripting_engine_get_type ())
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
6
modules/module-lua-scripting/gresource.xml
Normal file
6
modules/module-lua-scripting/gresource.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/freedesktop/pipewire/wireplumber/m-lua-scripting/">
|
||||
<file compressed="true">api.lua</file>
|
||||
</gresource>
|
||||
</gresources>
|
Reference in New Issue
Block a user