286 lines
7.7 KiB
C
286 lines
7.7 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2022 Collabora Ltd.
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include "script.h"
|
|
#include <pipewire/keys.h>
|
|
|
|
/*
|
|
* This is a WpPlugin subclass that wraps a single lua script and acts like
|
|
* a handle for that script. When enabled, through the WpObject activation
|
|
* mechanism, the script is executed. It then provides an API for the script
|
|
* to declare when it has finished its activation procedure, which can be
|
|
* asynchronous (this is Script.finish_activation in Lua).
|
|
* When disabled, this class destroys the global environment that was used
|
|
* in the Lua engine for excecuting that script, effectively destroying all
|
|
* objects that were held in Lua as global variables.
|
|
*/
|
|
|
|
struct _WpLuaScript
|
|
{
|
|
WpPlugin parent;
|
|
|
|
lua_State *L;
|
|
gchar *filename;
|
|
GVariant *args;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_LUA_ENGINE,
|
|
PROP_FILENAME,
|
|
PROP_ARGUMENTS,
|
|
};
|
|
|
|
G_DEFINE_TYPE (WpLuaScript, wp_lua_script, WP_TYPE_PLUGIN)
|
|
|
|
static void
|
|
wp_lua_script_init (WpLuaScript * self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
wp_lua_script_cleanup (WpLuaScript * self)
|
|
{
|
|
/* LUA_REGISTRYINDEX[self] = nil */
|
|
if (self->L) {
|
|
lua_pushnil (self->L);
|
|
lua_rawsetp (self->L, LUA_REGISTRYINDEX, self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_lua_script_finalize (GObject * object)
|
|
{
|
|
WpLuaScript *self = WP_LUA_SCRIPT (object);
|
|
|
|
wp_lua_script_cleanup (self);
|
|
g_clear_pointer (&self->L, wplua_unref);
|
|
g_clear_pointer (&self->filename, g_free);
|
|
g_clear_pointer (&self->args, g_variant_unref);
|
|
|
|
G_OBJECT_CLASS (wp_lua_script_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
wp_lua_script_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpLuaScript *self = WP_LUA_SCRIPT (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_LUA_ENGINE:
|
|
g_return_if_fail (self->L == NULL);
|
|
self->L = g_value_get_pointer (value);
|
|
if (self->L)
|
|
self->L = wplua_ref (self->L);
|
|
break;
|
|
case PROP_FILENAME:
|
|
self->filename = g_value_dup_string (value);
|
|
break;
|
|
case PROP_ARGUMENTS:
|
|
self->args = g_value_dup_variant (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
wp_lua_script_check_async_activation (WpLuaScript * self)
|
|
{
|
|
gboolean ret;
|
|
lua_rawgetp (self->L, LUA_REGISTRYINDEX, self);
|
|
lua_pushliteral (self->L, "Script");
|
|
lua_gettable (self->L, -2);
|
|
lua_pushliteral (self->L, "async_activation");
|
|
lua_gettable (self->L, -2);
|
|
ret = lua_toboolean (self->L, -1);
|
|
lua_pop (self->L, 3);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
wp_lua_script_detach_transition (WpLuaScript * self)
|
|
{
|
|
lua_rawgetp (self->L, LUA_REGISTRYINDEX, self);
|
|
lua_pushliteral (self->L, "Script");
|
|
lua_gettable (self->L, -2);
|
|
lua_pushliteral (self->L, "__transition");
|
|
lua_pushnil (self->L);
|
|
lua_settable (self->L, -3);
|
|
lua_pop (self->L, 2);
|
|
}
|
|
|
|
static int
|
|
script_finish_activation (lua_State * L)
|
|
{
|
|
WpLuaScript *self;
|
|
|
|
luaL_checktype (L, 1, LUA_TTABLE);
|
|
|
|
lua_pushliteral (L, "__self");
|
|
lua_gettable (L, 1);
|
|
luaL_checktype (L, -1, LUA_TLIGHTUSERDATA);
|
|
self = WP_LUA_SCRIPT ((gpointer) lua_topointer (L, -1));
|
|
lua_pop (L, 2);
|
|
|
|
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
script_finish_activation_with_error (lua_State * L)
|
|
{
|
|
WpTransition *transition = NULL;
|
|
const char *msg = NULL;
|
|
|
|
luaL_checktype (L, 1, LUA_TTABLE);
|
|
msg = luaL_checkstring (L, 2);
|
|
|
|
lua_pushliteral (L, "__transition");
|
|
lua_gettable (L, 1);
|
|
if (lua_type (L, -1) == LUA_TLIGHTUSERDATA)
|
|
transition = WP_TRANSITION ((gpointer) lua_topointer (L, -1));
|
|
lua_pop (L, 2);
|
|
|
|
if (transition)
|
|
wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED, "%s", msg));
|
|
return 0;
|
|
}
|
|
|
|
static const luaL_Reg script_api_methods[] = {
|
|
{ "finish_activation", script_finish_activation },
|
|
{ "finish_activation_with_error", script_finish_activation_with_error },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static int
|
|
wp_lua_script_sandbox (lua_State *L)
|
|
{
|
|
luaL_checktype (L, 1, LUA_TLIGHTUSERDATA); // self
|
|
luaL_checktype (L, 2, LUA_TLIGHTUSERDATA); // transition
|
|
luaL_checktype (L, 3, LUA_TFUNCTION); // the script chunk
|
|
|
|
/* create unique environment for this script */
|
|
lua_getglobal (L, "create_sandbox_env");
|
|
lua_call (L, 0, 1);
|
|
|
|
/* create "Script" API */
|
|
lua_pushliteral (L, "Script");
|
|
luaL_newlib (L, script_api_methods);
|
|
lua_pushliteral (L, "__self");
|
|
lua_pushvalue (L, 1);
|
|
lua_settable (L, -3);
|
|
lua_pushliteral (L, "__transition");
|
|
lua_pushvalue (L, 2);
|
|
lua_settable (L, -3);
|
|
lua_settable (L, -3);
|
|
|
|
/* store the environment */
|
|
/* LUA_REGISTRYINDEX[self] = env */
|
|
lua_pushvalue (L, 1); // self
|
|
lua_pushvalue (L, -2); // the table returned by create_sandbox_env
|
|
lua_rawset (L, LUA_REGISTRYINDEX);
|
|
|
|
/* set it as the 1st upvalue (_ENV) on the loaded script chunk (at index 3) */
|
|
lua_setupvalue (L, 3, 1);
|
|
|
|
/* anything remaining on the stack are function arguments */
|
|
int nargs = lua_gettop (L) - 3;
|
|
|
|
/* execute script */
|
|
lua_call (L, nargs, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
wp_lua_script_enable (WpPlugin * plugin, WpTransition * transition)
|
|
{
|
|
WpLuaScript *self = WP_LUA_SCRIPT (plugin);
|
|
g_autoptr (GError) error = NULL;
|
|
int top, nargs = 3;
|
|
|
|
if (!self->L) {
|
|
error = g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
"No lua state open; lua-scripting plugin is not enabled");
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
top = lua_gettop (self->L);
|
|
lua_pushcfunction (self->L, wp_lua_script_sandbox);
|
|
lua_pushlightuserdata (self->L, self);
|
|
lua_pushlightuserdata (self->L, transition);
|
|
|
|
/* load script */
|
|
if (!wplua_load_path (self->L, self->filename, &error)) {
|
|
lua_settop (self->L, top);
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
/* push script arguments */
|
|
if (self->args) {
|
|
wplua_gvariant_to_lua (self->L, self->args);
|
|
nargs++;
|
|
}
|
|
|
|
/* execute script */
|
|
if (!wplua_pcall (self->L, nargs, 0, &error)) {
|
|
lua_settop (self->L, top);
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
wp_lua_script_cleanup (self);
|
|
return;
|
|
}
|
|
|
|
if (!wp_lua_script_check_async_activation (self)) {
|
|
wp_lua_script_detach_transition (self);
|
|
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
|
|
} else {
|
|
g_signal_connect_object (transition, "notify::completed",
|
|
(GCallback) wp_lua_script_detach_transition, self, G_CONNECT_SWAPPED);
|
|
}
|
|
|
|
lua_settop (self->L, top);
|
|
}
|
|
|
|
static void
|
|
wp_lua_script_disable (WpPlugin * plugin)
|
|
{
|
|
WpLuaScript *self = WP_LUA_SCRIPT (plugin);
|
|
wp_lua_script_cleanup (self);
|
|
}
|
|
|
|
static void
|
|
wp_lua_script_class_init (WpLuaScriptClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
WpPluginClass *plugin_class = (WpPluginClass *) klass;
|
|
|
|
object_class->finalize = wp_lua_script_finalize;
|
|
object_class->set_property = wp_lua_script_set_property;
|
|
|
|
plugin_class->enable = wp_lua_script_enable;
|
|
plugin_class->disable = wp_lua_script_disable;
|
|
|
|
g_object_class_install_property (object_class, PROP_LUA_ENGINE,
|
|
g_param_spec_pointer ("lua-engine", "lua-engine", "lua-engine",
|
|
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_FILENAME,
|
|
g_param_spec_string ("filename", "filename", "filename", NULL,
|
|
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_ARGUMENTS,
|
|
g_param_spec_variant ("arguments", "arguments", "arguments",
|
|
G_VARIANT_TYPE_VARDICT, NULL,
|
|
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
}
|