wplua: move under modules/module-lua-scripting
It's unlikely that wplua will ever be useful outside the context of module-lua-scripting, so let's move it to keep all the code in one place
This commit is contained in:
108
modules/module-lua-scripting/wplua/boxed.c
Normal file
108
modules/module-lua-scripting/wplua/boxed.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wplua.h"
|
||||
#include "private.h"
|
||||
#include <wp/wp.h>
|
||||
|
||||
static lua_CFunction
|
||||
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
|
||||
{
|
||||
if (reg) {
|
||||
while (reg->name) {
|
||||
if (!g_strcmp0 (method, reg->name))
|
||||
return reg->func;
|
||||
reg++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_gboxed___index (lua_State *L)
|
||||
{
|
||||
luaL_argcheck (L, wplua_isboxed (L, 1, G_TYPE_BOXED), 1,
|
||||
"expected userdata storing GValue<GBoxed>");
|
||||
GValue *obj_v = lua_touserdata (L, 1);
|
||||
const gchar *key = luaL_checkstring (L, 2);
|
||||
lua_CFunction func = NULL;
|
||||
GHashTable *vtables;
|
||||
|
||||
lua_pushliteral (L, "wplua_vtables");
|
||||
lua_gettable (L, LUA_REGISTRYINDEX);
|
||||
vtables = wplua_toboxed (L, -1);
|
||||
lua_pop (L, 1);
|
||||
|
||||
/* search in registered vtables */
|
||||
if (!func) {
|
||||
GType type = G_VALUE_TYPE (obj_v);
|
||||
while (!func && type) {
|
||||
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
|
||||
func = find_method_in_luaL_Reg (reg, key);
|
||||
type = g_type_parent (type);
|
||||
}
|
||||
}
|
||||
|
||||
if (func) {
|
||||
lua_pushcfunction (L, func);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
_wplua_init_gboxed (lua_State *L)
|
||||
{
|
||||
static const luaL_Reg gboxed_meta[] = {
|
||||
{ "__gc", _wplua_gvalue_userdata___gc },
|
||||
{ "__eq", _wplua_gvalue_userdata___eq },
|
||||
{ "__index", _wplua_gboxed___index },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
luaL_newmetatable (L, "GBoxed");
|
||||
luaL_setfuncs (L, gboxed_meta, 0);
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
|
||||
void
|
||||
wplua_pushboxed (lua_State * L, GType type, gpointer object)
|
||||
{
|
||||
g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED);
|
||||
|
||||
GValue *v = _wplua_pushgvalue_userdata (L, type);
|
||||
wp_trace_boxed (type, object, "pushing to Lua, v=%p", v);
|
||||
g_value_take_boxed (v, object);
|
||||
|
||||
luaL_getmetatable (L, "GBoxed");
|
||||
lua_setmetatable (L, -2);
|
||||
}
|
||||
|
||||
gpointer
|
||||
wplua_toboxed (lua_State *L, int idx)
|
||||
{
|
||||
g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_BOXED), NULL);
|
||||
return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
|
||||
}
|
||||
|
||||
gpointer
|
||||
wplua_checkboxed (lua_State *L, int idx, GType type)
|
||||
{
|
||||
if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
|
||||
wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
|
||||
luaL_argerror (L, idx, "expected userdata storing GValue<GBoxed>");
|
||||
}
|
||||
return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
|
||||
}
|
||||
|
||||
gboolean
|
||||
wplua_isboxed (lua_State *L, int idx, GType type)
|
||||
{
|
||||
if (!g_type_is_a (type, G_TYPE_BOXED)) return FALSE;
|
||||
return _wplua_isgvalue_userdata (L, idx, type);
|
||||
}
|
181
modules/module-lua-scripting/wplua/closure.c
Normal file
181
modules/module-lua-scripting/wplua/closure.c
Normal file
@@ -0,0 +1,181 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wplua.h"
|
||||
#include "private.h"
|
||||
#include <wp/wp.h>
|
||||
|
||||
/* This structure is added to a lua global and it's only referenced from there;
|
||||
When the lua_State closes, it is unrefed and its finalize function below
|
||||
invalidates all the closures so that nothing attempts to call into a lua
|
||||
function after the state is closed */
|
||||
typedef struct _WpLuaClosureStore WpLuaClosureStore;
|
||||
struct _WpLuaClosureStore
|
||||
{
|
||||
GPtrArray *closures;
|
||||
};
|
||||
|
||||
static WpLuaClosureStore *
|
||||
_wplua_closure_store_new (void)
|
||||
{
|
||||
WpLuaClosureStore *self = g_rc_box_new (WpLuaClosureStore);
|
||||
self->closures = g_ptr_array_new ();
|
||||
return self;
|
||||
}
|
||||
|
||||
static void
|
||||
_wplua_closure_store_finalize (WpLuaClosureStore * self)
|
||||
{
|
||||
for (guint i = self->closures->len; i > 0; i--) {
|
||||
GClosure *c = g_ptr_array_index (self->closures, i-1);
|
||||
g_closure_ref (c);
|
||||
g_closure_invalidate (c);
|
||||
g_ptr_array_remove_index_fast (self->closures, i-1);
|
||||
g_closure_unref (c);
|
||||
}
|
||||
g_ptr_array_unref (self->closures);
|
||||
}
|
||||
|
||||
static WpLuaClosureStore *
|
||||
_wplua_closure_store_ref (WpLuaClosureStore * self)
|
||||
{
|
||||
return g_rc_box_acquire (self);
|
||||
}
|
||||
|
||||
static void
|
||||
_wplua_closure_store_unref (WpLuaClosureStore * self)
|
||||
{
|
||||
g_rc_box_release_full (self, (GDestroyNotify) _wplua_closure_store_finalize);
|
||||
}
|
||||
|
||||
G_DEFINE_BOXED_TYPE(WpLuaClosureStore, _wplua_closure_store,
|
||||
_wplua_closure_store_ref, _wplua_closure_store_unref)
|
||||
|
||||
|
||||
typedef struct _WpLuaClosure WpLuaClosure;
|
||||
struct _WpLuaClosure
|
||||
{
|
||||
GClosure closure;
|
||||
int func_ref;
|
||||
GPtrArray *closures;
|
||||
};
|
||||
|
||||
static void
|
||||
_wplua_closure_marshal (GClosure *closure, GValue *return_value,
|
||||
guint n_param_values, const GValue *param_values,
|
||||
gpointer invocation_hint, gpointer marshal_data)
|
||||
{
|
||||
static int reentrant = 0;
|
||||
lua_State *L = closure->data;
|
||||
int func_ref = ((WpLuaClosure *) closure)->func_ref;
|
||||
|
||||
/* invalid closure, skip it */
|
||||
if (func_ref == LUA_NOREF || func_ref == LUA_REFNIL)
|
||||
return;
|
||||
|
||||
/* stop the garbage collector */
|
||||
if (reentrant == 0)
|
||||
lua_gc (L, LUA_GCSTOP, 0);
|
||||
|
||||
/* push the function */
|
||||
lua_rawgeti (L, LUA_REGISTRYINDEX, func_ref);
|
||||
|
||||
/* push arguments */
|
||||
for (guint i = 0; i < n_param_values; i++)
|
||||
wplua_gvalue_to_lua (L, ¶m_values[i]);
|
||||
|
||||
/* call in protected mode */
|
||||
reentrant++;
|
||||
int res = _wplua_pcall (L, n_param_values, return_value ? 1 : 0);
|
||||
reentrant--;
|
||||
|
||||
/* handle the result */
|
||||
if (res == LUA_OK && return_value) {
|
||||
wplua_lua_to_gvalue (L, -1, return_value);
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
|
||||
/* clean up */
|
||||
lua_gc (L, LUA_GCCOLLECT, 0);
|
||||
if (reentrant == 0)
|
||||
lua_gc (L, LUA_GCRESTART, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
_wplua_closure_invalidate (lua_State *L, WpLuaClosure *c)
|
||||
{
|
||||
wp_trace_boxed (G_TYPE_CLOSURE, c, "invalidated");
|
||||
luaL_unref (L, LUA_REGISTRYINDEX, c->func_ref);
|
||||
c->func_ref = LUA_NOREF;
|
||||
}
|
||||
|
||||
static void
|
||||
_wplua_closure_finalize (lua_State *L, WpLuaClosure *c)
|
||||
{
|
||||
g_ptr_array_remove_fast (c->closures, c);
|
||||
g_ptr_array_unref (c->closures);
|
||||
}
|
||||
|
||||
GClosure *
|
||||
wplua_checkclosure (lua_State *L, int idx)
|
||||
{
|
||||
luaL_checktype (L, idx, LUA_TFUNCTION);
|
||||
return wplua_function_to_closure (L, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* wplua_function_to_closure:
|
||||
*
|
||||
* Make a GClosure out of a Lua function at index @em idx
|
||||
*
|
||||
* Returns: (transfer floating): the new closure
|
||||
*/
|
||||
GClosure *
|
||||
wplua_function_to_closure (lua_State *L, int idx)
|
||||
{
|
||||
g_return_val_if_fail (lua_isfunction(L, idx), NULL);
|
||||
|
||||
GClosure *c = g_closure_new_simple (sizeof (WpLuaClosure), L);
|
||||
WpLuaClosure *wlc = (WpLuaClosure *) c;
|
||||
WpLuaClosureStore *store;
|
||||
|
||||
lua_pushvalue (L, idx);
|
||||
wlc->func_ref = luaL_ref (L, LUA_REGISTRYINDEX);
|
||||
|
||||
wp_trace_boxed (G_TYPE_CLOSURE, c, "created, func_ref = %d", wlc->func_ref);
|
||||
|
||||
g_closure_set_marshal (c, _wplua_closure_marshal);
|
||||
g_closure_add_invalidate_notifier (c, L,
|
||||
(GClosureNotify) _wplua_closure_invalidate);
|
||||
g_closure_add_finalize_notifier (c, L,
|
||||
(GClosureNotify) _wplua_closure_finalize);
|
||||
|
||||
/* keep a weak ref of the closure in the store's array,
|
||||
so that we can invalidate the closure when lua_State closes;
|
||||
keep a strong ref of the array in the closure so that
|
||||
_wplua_closure_finalize() works even after the state is closed */
|
||||
lua_pushliteral (L, "wplua_closures");
|
||||
lua_gettable (L, LUA_REGISTRYINDEX);
|
||||
store = wplua_toboxed (L, -1);
|
||||
lua_pop (L, 1);
|
||||
|
||||
g_ptr_array_add (store->closures, c);
|
||||
wlc->closures = g_ptr_array_ref (store->closures);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
_wplua_init_closure (lua_State *L)
|
||||
{
|
||||
lua_pushliteral (L, "wplua_closures");
|
||||
wplua_pushboxed (L,
|
||||
_wplua_closure_store_get_type (),
|
||||
_wplua_closure_store_new ());
|
||||
lua_settable (L, LUA_REGISTRYINDEX);
|
||||
}
|
6
modules/module-lua-scripting/wplua/gresource.xml
Normal file
6
modules/module-lua-scripting/wplua/gresource.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/freedesktop/pipewire/wireplumber/wplua/">
|
||||
<file compressed="true">sandbox.lua</file>
|
||||
</gresource>
|
||||
</gresources>
|
33
modules/module-lua-scripting/wplua/meson.build
Normal file
33
modules/module-lua-scripting/wplua/meson.build
Normal file
@@ -0,0 +1,33 @@
|
||||
wplua_lib_sources = [
|
||||
'boxed.c',
|
||||
'closure.c',
|
||||
'object.c',
|
||||
'userdata.c',
|
||||
'value.c',
|
||||
'wplua.c',
|
||||
]
|
||||
|
||||
wplua_resources = gnome.compile_resources(
|
||||
'wplua-resources',
|
||||
'gresource.xml',
|
||||
c_name: '_wplua',
|
||||
extra_args: '--manual-register',
|
||||
source_dir: meson.current_source_dir())
|
||||
|
||||
wplua_lib = static_library('wplua-' + wireplumber_api_version,
|
||||
[ wplua_lib_sources, wplua_resources ],
|
||||
c_args : [
|
||||
'-D_GNU_SOURCE',
|
||||
'-DG_LOG_USE_STRUCTURED',
|
||||
'-DG_LOG_DOMAIN="wplua"',
|
||||
],
|
||||
install: false,
|
||||
include_directories: wplua_include_dir,
|
||||
dependencies : [wp_dep, lua_dep],
|
||||
)
|
||||
|
||||
wplua_dep = declare_dependency(
|
||||
link_with: wplua_lib,
|
||||
include_directories: wplua_include_dir,
|
||||
dependencies: [wp_dep, lua_dep],
|
||||
)
|
246
modules/module-lua-scripting/wplua/object.c
Normal file
246
modules/module-lua-scripting/wplua/object.c
Normal file
@@ -0,0 +1,246 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wplua.h"
|
||||
#include "private.h"
|
||||
#include <wp/wp.h>
|
||||
|
||||
static int
|
||||
_wplua_gobject_call (lua_State *L)
|
||||
{
|
||||
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
|
||||
const char *sig_name = lua_tostring (L, 2);
|
||||
guint n_params = lua_gettop (L) - 2;
|
||||
GSignalQuery query;
|
||||
guint sig_id = 0;
|
||||
GQuark detail = 0;
|
||||
|
||||
if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
|
||||
&sig_id, &detail, FALSE)))
|
||||
luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
|
||||
sig_name);
|
||||
|
||||
g_signal_query (sig_id, &query);
|
||||
|
||||
if (G_UNLIKELY (!(query.signal_flags & G_SIGNAL_ACTION)))
|
||||
luaL_error (L, "lua code is not allowed to emit non-action signal '%s::%s'",
|
||||
G_OBJECT_TYPE_NAME (obj), sig_name);
|
||||
|
||||
if (G_UNLIKELY (query.n_params > n_params))
|
||||
luaL_error (L, "not enough arguments for '%s::%s': expected %d, got %d",
|
||||
G_OBJECT_TYPE_NAME (obj), sig_name, query.n_params, n_params);
|
||||
|
||||
GValue ret = G_VALUE_INIT;
|
||||
GValue *vals = g_newa (GValue, n_params + 1);
|
||||
memset (vals, 0, sizeof (GValue) * (n_params + 1));
|
||||
|
||||
if (query.return_type != G_TYPE_NONE)
|
||||
g_value_init (&ret, query.return_type);
|
||||
|
||||
g_value_init_from_instance (&vals[0], obj);
|
||||
for (guint i = 0; i < n_params; i++) {
|
||||
g_value_init (&vals[i+1], query.param_types[i]);
|
||||
wplua_lua_to_gvalue (L, i+3, &vals[i+1]);
|
||||
}
|
||||
|
||||
g_signal_emitv (vals, sig_id, detail, &ret);
|
||||
|
||||
for (guint i = 0; i < n_params + 1; i++) {
|
||||
g_value_unset (&vals[i]);
|
||||
}
|
||||
|
||||
int n_ret = 0;
|
||||
if (query.return_type != G_TYPE_NONE)
|
||||
n_ret = wplua_gvalue_to_lua (L, &ret);
|
||||
|
||||
g_value_unset (&ret);
|
||||
return n_ret;
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_gobject_connect (lua_State *L)
|
||||
{
|
||||
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
|
||||
const char *sig_name = luaL_checkstring (L, 2);
|
||||
luaL_checktype (L, 3, LUA_TFUNCTION);
|
||||
|
||||
guint sig_id = 0;
|
||||
GQuark detail = 0;
|
||||
|
||||
if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
|
||||
&sig_id, &detail, FALSE)))
|
||||
luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
|
||||
sig_name);
|
||||
|
||||
GClosure *closure = wplua_function_to_closure (L, 3);
|
||||
gulong handler =
|
||||
g_signal_connect_closure_by_id (obj, sig_id, detail, closure, FALSE);
|
||||
|
||||
lua_pushinteger (L, handler);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static lua_CFunction
|
||||
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
|
||||
{
|
||||
if (reg) {
|
||||
while (reg->name) {
|
||||
if (!g_strcmp0 (method, reg->name))
|
||||
return reg->func;
|
||||
reg++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_gobject___index (lua_State *L)
|
||||
{
|
||||
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
|
||||
const gchar *key = luaL_checkstring (L, 2);
|
||||
lua_CFunction func = NULL;
|
||||
GHashTable *vtables;
|
||||
|
||||
lua_pushliteral (L, "wplua_vtables");
|
||||
lua_gettable (L, LUA_REGISTRYINDEX);
|
||||
vtables = wplua_toboxed (L, -1);
|
||||
lua_pop (L, 1);
|
||||
|
||||
if (!g_strcmp0 (key, "call"))
|
||||
func = _wplua_gobject_call;
|
||||
else if (!g_strcmp0 (key, "connect"))
|
||||
func = _wplua_gobject_connect;
|
||||
|
||||
/* search in registered vtables */
|
||||
if (!func) {
|
||||
GType type = G_TYPE_FROM_INSTANCE (obj);
|
||||
while (!func && type) {
|
||||
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
|
||||
func = find_method_in_luaL_Reg (reg, key);
|
||||
type = g_type_parent (type);
|
||||
}
|
||||
}
|
||||
|
||||
/* search in registered vtables of interfaces */
|
||||
if (!func) {
|
||||
g_autofree GType *interfaces =
|
||||
g_type_interfaces (G_TYPE_FROM_INSTANCE (obj), NULL);
|
||||
GType *type = interfaces;
|
||||
while (!func && *type) {
|
||||
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (*type));
|
||||
func = find_method_in_luaL_Reg (reg, key);
|
||||
type++;
|
||||
}
|
||||
}
|
||||
|
||||
if (func) {
|
||||
lua_pushcfunction (L, func);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
/* search in properties */
|
||||
GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
|
||||
GParamSpec *pspec = g_object_class_find_property (klass, key);
|
||||
if (pspec && (pspec->flags & G_PARAM_READABLE)) {
|
||||
g_auto (GValue) v = G_VALUE_INIT;
|
||||
g_value_init (&v, pspec->value_type);
|
||||
g_object_get_property (obj, key, &v);
|
||||
return wplua_gvalue_to_lua (L, &v);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_gobject___newindex (lua_State *L)
|
||||
{
|
||||
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
|
||||
const gchar *key = luaL_checkstring (L, 2);
|
||||
|
||||
/* search in properties */
|
||||
GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
|
||||
GParamSpec *pspec = g_object_class_find_property (klass, key);
|
||||
if (pspec && (pspec->flags & G_PARAM_WRITABLE)) {
|
||||
g_auto (GValue) v = G_VALUE_INIT;
|
||||
g_value_init (&v, pspec->value_type);
|
||||
wplua_lua_to_gvalue (L, 3, &v);
|
||||
g_object_set_property (obj, key, &v);
|
||||
} else {
|
||||
luaL_error (L, "attempted to assign unknown or non-writable property '%s'",
|
||||
key);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_gobject__tostring (lua_State *L)
|
||||
{
|
||||
GObject *obj;
|
||||
gchar *str;
|
||||
|
||||
obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
|
||||
str = g_strdup_printf (WP_OBJECT_FORMAT, WP_OBJECT_ARGS (obj));
|
||||
lua_pushstring (L, str);
|
||||
g_free (str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
_wplua_init_gobject (lua_State *L)
|
||||
{
|
||||
static const luaL_Reg gobject_meta[] = {
|
||||
{ "__gc", _wplua_gvalue_userdata___gc },
|
||||
{ "__eq", _wplua_gvalue_userdata___eq },
|
||||
{ "__index", _wplua_gobject___index },
|
||||
{ "__newindex", _wplua_gobject___newindex },
|
||||
{ "__tostring", _wplua_gobject__tostring },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
luaL_newmetatable (L, "GObject");
|
||||
luaL_setfuncs (L, gobject_meta, 0);
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
|
||||
void
|
||||
wplua_pushobject (lua_State * L, gpointer object)
|
||||
{
|
||||
g_return_if_fail (G_IS_OBJECT (object));
|
||||
|
||||
GValue *v = _wplua_pushgvalue_userdata (L, G_TYPE_FROM_INSTANCE (object));
|
||||
wp_trace_object (object, "pushing to Lua, v=%p", v);
|
||||
g_value_take_object (v, object);
|
||||
|
||||
luaL_getmetatable (L, "GObject");
|
||||
lua_setmetatable (L, -2);
|
||||
}
|
||||
|
||||
gpointer
|
||||
wplua_toobject (lua_State *L, int idx)
|
||||
{
|
||||
g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_OBJECT), NULL);
|
||||
return g_value_get_object ((GValue *) lua_touserdata (L, idx));
|
||||
}
|
||||
|
||||
gpointer
|
||||
wplua_checkobject (lua_State *L, int idx, GType type)
|
||||
{
|
||||
if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
|
||||
wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
|
||||
luaL_argerror (L, idx, "expected userdata storing GValue<GObject>");
|
||||
}
|
||||
return g_value_get_object ((GValue *) lua_touserdata (L, idx));
|
||||
}
|
||||
|
||||
gboolean
|
||||
wplua_isobject (lua_State *L, int idx, GType type)
|
||||
{
|
||||
if (!g_type_is_a (type, G_TYPE_OBJECT)) return FALSE;
|
||||
return _wplua_isgvalue_userdata (L, idx, type);
|
||||
}
|
37
modules/module-lua-scripting/wplua/private.h
Normal file
37
modules/module-lua-scripting/wplua/private.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WPLUA_PRIVATE_H__
|
||||
#define __WPLUA_PRIVATE_H__
|
||||
|
||||
#include "wplua.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* boxed.c */
|
||||
void _wplua_init_gboxed (lua_State *L);
|
||||
|
||||
/* closure.c */
|
||||
void _wplua_init_closure (lua_State *L);
|
||||
|
||||
/* object.c */
|
||||
void _wplua_init_gobject (lua_State *L);
|
||||
|
||||
/* userdata.c */
|
||||
GValue * _wplua_pushgvalue_userdata (lua_State * L, GType type);
|
||||
gboolean _wplua_isgvalue_userdata (lua_State *L, int idx, GType type);
|
||||
|
||||
int _wplua_gvalue_userdata___gc (lua_State *L);
|
||||
int _wplua_gvalue_userdata___eq (lua_State *L);
|
||||
|
||||
/* wplua.c */
|
||||
int _wplua_pcall (lua_State *L, int nargs, int nret);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
111
modules/module-lua-scripting/wplua/sandbox.lua
Normal file
111
modules/module-lua-scripting/wplua/sandbox.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2020 Collabora Ltd.
|
||||
-- @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
--
|
||||
-- Based on https://github.com/kikito/sandbox.lua
|
||||
-- Copyright © 2013 Enrique García Cota
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
local SANDBOX_CONFIG = ...
|
||||
local SANDBOX_ENV = {}
|
||||
|
||||
local function populate_env(id)
|
||||
local module, method = id:match('([^%.]+)%.([^%.]+)')
|
||||
if module then
|
||||
SANDBOX_ENV[module] = SANDBOX_ENV[module] or {}
|
||||
SANDBOX_ENV[module][method] = _G[module][method]
|
||||
else
|
||||
SANDBOX_ENV[id] = _G[id]
|
||||
end
|
||||
end
|
||||
|
||||
-- List of safe functions and packages
|
||||
if SANDBOX_CONFIG["minimal_std"] then
|
||||
-- minimal list, used for config files
|
||||
([[
|
||||
_VERSION ipairs pairs select tonumber tostring type
|
||||
|
||||
table
|
||||
|
||||
string.byte string.char string.find string.format string.gmatch
|
||||
string.gsub string.len string.lower string.match string.reverse
|
||||
string.sub string.upper
|
||||
|
||||
]]):gsub('%S+', populate_env)
|
||||
else
|
||||
-- full list, used for scripts
|
||||
([[
|
||||
_VERSION assert error ipairs next pairs print
|
||||
pcall select tonumber tostring type xpcall
|
||||
|
||||
table utf8
|
||||
|
||||
math.abs math.acos math.asin math.atan math.ceil
|
||||
math.cos math.deg math.exp math.tointeger math.floor math.fmod
|
||||
math.huge math.ult math.log math.maxinteger math.mininteger math.max
|
||||
math.min math.modf math.pi math.rad math.random
|
||||
math.sin math.sqrt math.tan math.type
|
||||
|
||||
string.byte string.char string.find string.format string.gmatch
|
||||
string.gsub string.len string.lower string.match string.reverse
|
||||
string.sub string.upper
|
||||
|
||||
os.clock os.difftime os.time os.date os.getenv
|
||||
|
||||
]]):gsub('%S+', populate_env)
|
||||
end
|
||||
|
||||
-- Additionally export everything in SANDBOX_EXPORT
|
||||
if type(SANDBOX_EXPORT) == "table" then
|
||||
for k, v in pairs(SANDBOX_EXPORT) do
|
||||
SANDBOX_ENV[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Additionally protect packages from malicious scripts trying to override methods
|
||||
for k, v in pairs(SANDBOX_ENV) do
|
||||
if type(v) == "table" then
|
||||
SANDBOX_ENV[k] = setmetatable({}, {
|
||||
__index = v,
|
||||
__newindex = function(_, attr_name, _)
|
||||
error('Can not modify ' .. k .. '.' .. attr_name .. '. Protected by the sandbox.')
|
||||
end
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if SANDBOX_CONFIG["isolate_env"] then
|
||||
-- in isolate_env mode, use a separate enviornment for each loaded chunk and
|
||||
-- store all of them in a global table so that they are not garbage collected
|
||||
SANDBOX_ENV_LIST = {}
|
||||
|
||||
function sandbox(chunk, ...)
|
||||
-- chunk's environment will be an empty table with __index
|
||||
-- to access our SANDBOX_ENV (without being able to write it)
|
||||
local env = setmetatable({}, {
|
||||
__index = SANDBOX_ENV,
|
||||
})
|
||||
-- store the chunk's environment so that it is not garbage collected
|
||||
table.insert(SANDBOX_ENV_LIST, env)
|
||||
-- set it as the chunk's 1st upvalue (__ENV)
|
||||
debug.setupvalue(chunk, 1, env)
|
||||
-- execute the chunk
|
||||
chunk(...)
|
||||
end
|
||||
else
|
||||
-- in common_env mode, use the same environment for all loaded chunks
|
||||
-- chunk's environment will be an empty table with __index
|
||||
-- to access our SANDBOX_ENV (without being able to write it)
|
||||
SANDBOX_COMMON_ENV = setmetatable({}, {
|
||||
__index = SANDBOX_ENV,
|
||||
})
|
||||
|
||||
function sandbox(chunk, ...)
|
||||
-- set it as the chunk's 1st upvalue (__ENV)
|
||||
debug.setupvalue(chunk, 1, SANDBOX_COMMON_ENV)
|
||||
-- execute the chunk
|
||||
chunk(...)
|
||||
end
|
||||
end
|
78
modules/module-lua-scripting/wplua/userdata.c
Normal file
78
modules/module-lua-scripting/wplua/userdata.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wplua.h"
|
||||
#include "private.h"
|
||||
#include <wp/wp.h>
|
||||
|
||||
GValue *
|
||||
_wplua_pushgvalue_userdata (lua_State * L, GType type)
|
||||
{
|
||||
GValue *v = lua_newuserdata (L, sizeof (GValue));
|
||||
memset (v, 0, sizeof (GValue));
|
||||
g_value_init (v, type);
|
||||
return v;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_wplua_isgvalue_userdata (lua_State *L, int idx, GType type)
|
||||
{
|
||||
GValue *v;
|
||||
|
||||
if (!lua_isuserdata (L, idx))
|
||||
return FALSE;
|
||||
if (lua_rawlen (L, idx) != sizeof (GValue))
|
||||
return FALSE;
|
||||
if (!(v = lua_touserdata (L, idx)))
|
||||
return FALSE;
|
||||
if (type != G_TYPE_NONE && !g_type_is_a (G_VALUE_TYPE (v), type))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GType
|
||||
wplua_gvalue_userdata_type (lua_State *L, int idx)
|
||||
{
|
||||
GValue *v;
|
||||
|
||||
if (!lua_isuserdata (L, idx))
|
||||
return G_TYPE_INVALID;
|
||||
if (lua_rawlen (L, idx) != sizeof (GValue))
|
||||
return G_TYPE_INVALID;
|
||||
if (!(v = lua_touserdata (L, idx)))
|
||||
return G_TYPE_INVALID;
|
||||
|
||||
return G_VALUE_TYPE (v);
|
||||
}
|
||||
|
||||
int
|
||||
_wplua_gvalue_userdata___gc (lua_State *L)
|
||||
{
|
||||
GValue *v = lua_touserdata (L, 1);
|
||||
wp_trace_boxed (G_VALUE_TYPE (v), g_value_peek_pointer (v),
|
||||
"collected, v=%p", v);
|
||||
g_value_unset (v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
_wplua_gvalue_userdata___eq (lua_State *L)
|
||||
{
|
||||
if (_wplua_isgvalue_userdata (L, 1, G_TYPE_NONE) &&
|
||||
_wplua_isgvalue_userdata (L, 2, G_TYPE_NONE)) {
|
||||
GValue *v1 = lua_touserdata (L, 1);
|
||||
GValue *v2 = lua_touserdata (L, 2);
|
||||
gpointer p1 = g_value_peek_pointer (v1);
|
||||
gpointer p2 = g_value_peek_pointer (v2);
|
||||
lua_pushboolean (L, (p1 == p2));
|
||||
} else {
|
||||
lua_pushboolean (L, FALSE);
|
||||
}
|
||||
return 1;
|
||||
}
|
342
modules/module-lua-scripting/wplua/value.c
Normal file
342
modules/module-lua-scripting/wplua/value.c
Normal file
@@ -0,0 +1,342 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wplua.h"
|
||||
#include "private.h"
|
||||
#include <wp/wp.h>
|
||||
|
||||
WpProperties *
|
||||
wplua_table_to_properties (lua_State *L, int idx)
|
||||
{
|
||||
WpProperties *p = wp_properties_new_empty ();
|
||||
const gchar *key, *value;
|
||||
int table = lua_absindex (L, idx);
|
||||
|
||||
lua_pushnil(L);
|
||||
while (lua_next (L, table) != 0) {
|
||||
/* copy key & value to convert them to string */
|
||||
key = luaL_tolstring (L, -2, NULL);
|
||||
value = luaL_tolstring (L, -2, NULL);
|
||||
wp_properties_set (p, key, value);
|
||||
lua_pop (L, 3);
|
||||
}
|
||||
|
||||
/* sort, because the lua table has a random order and it's too messy to read */
|
||||
wp_properties_sort (p);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
wplua_properties_to_table (lua_State *L, WpProperties *p)
|
||||
{
|
||||
lua_newtable (L);
|
||||
if (p) {
|
||||
g_autoptr (WpIterator) it = wp_properties_new_iterator (p);
|
||||
GValue v = G_VALUE_INIT;
|
||||
const gchar *key, *value;
|
||||
|
||||
while (wp_iterator_next (it, &v)) {
|
||||
WpPropertiesItem *pi = g_value_get_boxed (&v);
|
||||
key = wp_properties_item_get_key (pi);
|
||||
value = wp_properties_item_get_value (pi);
|
||||
lua_pushstring (L, key);
|
||||
lua_pushstring (L, value);
|
||||
lua_settable (L, -3);
|
||||
g_value_unset (&v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GVariant *
|
||||
wplua_lua_to_gvariant (lua_State *L, int idx)
|
||||
{
|
||||
switch (lua_type (L, idx)) {
|
||||
case LUA_TNIL:
|
||||
return g_variant_new ("()");
|
||||
case LUA_TBOOLEAN:
|
||||
return g_variant_new_boolean (lua_toboolean (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));
|
||||
case LUA_TSTRING:
|
||||
return g_variant_new_string (lua_tostring (L, idx));
|
||||
case LUA_TTABLE: {
|
||||
GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
|
||||
const gchar *key;
|
||||
int table = lua_absindex (L, idx);
|
||||
|
||||
lua_pushnil (L);
|
||||
while (lua_next (L, table) != 0) {
|
||||
/* copy key to convert it to string */
|
||||
lua_pushvalue (L, -2);
|
||||
key = lua_tostring (L, -1);
|
||||
g_variant_builder_add (&b, "{sv}", key, wplua_lua_to_gvariant (L, -2));
|
||||
lua_pop (L, 2);
|
||||
}
|
||||
return g_variant_builder_end (&b);
|
||||
}
|
||||
default:
|
||||
wp_warning ("skipping bad value (its type cannot be represented in GVariant)");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
wplua_gvariant_to_lua (lua_State *L, GVariant *variant)
|
||||
{
|
||||
if (variant == NULL || g_variant_is_of_type (variant, G_VARIANT_TYPE_UNIT)) {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT16)) {
|
||||
lua_pushinteger (L, g_variant_get_int16 (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT32)) {
|
||||
lua_pushinteger (L, g_variant_get_int32 (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT64)) {
|
||||
lua_pushinteger (L, g_variant_get_int64 (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT16)) {
|
||||
lua_pushinteger (L, g_variant_get_uint16 (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)) {
|
||||
lua_pushinteger (L, g_variant_get_uint32 (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT64)) {
|
||||
lua_pushinteger (L, g_variant_get_uint64 (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_DOUBLE)) {
|
||||
lua_pushnumber (L, g_variant_get_double (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN)) {
|
||||
lua_pushboolean (L, g_variant_get_boolean (variant));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
|
||||
lua_pushstring (L, g_variant_get_string (variant, NULL));
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT)) {
|
||||
g_autoptr (GVariant) v = g_variant_get_variant (variant);
|
||||
wplua_gvariant_to_lua (L, v);
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_DICTIONARY)) {
|
||||
gsize n_children, i;
|
||||
n_children = g_variant_n_children (variant);
|
||||
lua_createtable (L, 0, n_children);
|
||||
for (i = 0; i < n_children; i++) {
|
||||
g_autoptr (GVariant) key, value;
|
||||
g_variant_get_child (variant, i, "{@?@*}", &key, &value);
|
||||
wplua_gvariant_to_lua (L, key);
|
||||
/* if the key is a string convertible to integer, convert it */
|
||||
if (lua_type (L, -1) == LUA_TSTRING) {
|
||||
int isnum = 0;
|
||||
lua_Integer num = lua_tointegerx (L, -1, &isnum);
|
||||
if (isnum) {
|
||||
lua_pop (L, 1);
|
||||
lua_pushinteger (L, num);
|
||||
}
|
||||
}
|
||||
wplua_gvariant_to_lua (L, value);
|
||||
lua_settable (L, -3);
|
||||
}
|
||||
}
|
||||
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_ARRAY)) {
|
||||
gsize n_children, i;
|
||||
n_children = g_variant_n_children (variant);
|
||||
lua_createtable (L, n_children, 0);
|
||||
for (i = 0; i < n_children; i++) {
|
||||
g_autoptr (GVariant) value;
|
||||
value = g_variant_get_child_value (variant, i);
|
||||
wplua_gvariant_to_lua (L, value);
|
||||
lua_seti (L, -2, i + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
g_autofree gchar *type_name =
|
||||
g_variant_type_dup_string (g_variant_get_type (variant));
|
||||
wp_warning ("Unhandled GVariant type %s", type_name);
|
||||
lua_pushnil (L);
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
wplua_lua_to_enum (lua_State *L, int idx, GType enum_type)
|
||||
{
|
||||
if (lua_type (L, idx) == LUA_TSTRING) {
|
||||
g_autoptr (GEnumClass) klass = g_type_class_ref (enum_type);
|
||||
GEnumValue *value = g_enum_get_value_by_nick (klass, lua_tostring (L, idx));
|
||||
if (value)
|
||||
return value->value;
|
||||
else
|
||||
luaL_error (L, "Invalid enum value '%s'", lua_tostring (L, idx));
|
||||
}
|
||||
return lua_tointeger (L, idx);
|
||||
}
|
||||
|
||||
void
|
||||
wplua_enum_to_lua (lua_State *L, gint enum_val, GType enum_type)
|
||||
{
|
||||
g_autoptr (GEnumClass) klass = g_type_class_ref (enum_type);
|
||||
GEnumValue *value = g_enum_get_value (klass, enum_val);
|
||||
if (value)
|
||||
lua_pushstring (L, value->value_nick);
|
||||
else
|
||||
lua_pushinteger (L, enum_val);
|
||||
}
|
||||
|
||||
void
|
||||
wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v)
|
||||
{
|
||||
switch (g_type_fundamental (G_VALUE_TYPE (v))) {
|
||||
case G_TYPE_CHAR:
|
||||
if (lua_type (L, idx) == LUA_TSTRING)
|
||||
g_value_set_schar (v, *lua_tostring (L, idx));
|
||||
else
|
||||
g_value_set_schar (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_UCHAR:
|
||||
g_value_set_uchar (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_INT:
|
||||
g_value_set_int (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_UINT:
|
||||
g_value_set_uint (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_LONG:
|
||||
g_value_set_long (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_ULONG:
|
||||
g_value_set_ulong (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_INT64:
|
||||
g_value_set_int64 (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_UINT64:
|
||||
g_value_set_uint64 (v, lua_tonumber (L, idx));
|
||||
break;
|
||||
case G_TYPE_FLOAT:
|
||||
g_value_set_float (v, lua_tonumber (L, idx));
|
||||
break;
|
||||
case G_TYPE_DOUBLE:
|
||||
g_value_set_double (v, lua_tonumber (L, idx));
|
||||
break;
|
||||
case G_TYPE_BOOLEAN:
|
||||
g_value_set_boolean (v, lua_toboolean (L, idx));
|
||||
break;
|
||||
case G_TYPE_STRING:
|
||||
g_value_set_string (v, lua_tostring (L, idx));
|
||||
break;
|
||||
case G_TYPE_POINTER:
|
||||
if (lua_type (L, idx) == LUA_TLIGHTUSERDATA)
|
||||
g_value_set_pointer (v, lua_touserdata (L, idx));
|
||||
break;
|
||||
case G_TYPE_BOXED:
|
||||
if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
|
||||
g_value_set_boxed (v, wplua_toboxed (L, idx));
|
||||
/* table -> WpProperties */
|
||||
else if (lua_istable (L, idx) && G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
|
||||
g_value_take_boxed (v, wplua_table_to_properties (L, idx));
|
||||
break;
|
||||
case G_TYPE_OBJECT:
|
||||
case G_TYPE_INTERFACE:
|
||||
if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
|
||||
g_value_set_object (v, wplua_toobject (L, idx));
|
||||
break;
|
||||
case G_TYPE_ENUM:
|
||||
g_value_set_enum (v, wplua_lua_to_enum (L, idx, G_VALUE_TYPE (v)));
|
||||
break;
|
||||
case G_TYPE_FLAGS:
|
||||
g_value_set_flags (v, lua_tointeger (L, idx));
|
||||
break;
|
||||
case G_TYPE_VARIANT:
|
||||
g_value_set_variant (v, wplua_lua_to_gvariant (L, idx));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
wplua_gvalue_to_lua (lua_State *L, const GValue *v)
|
||||
{
|
||||
switch (g_type_fundamental (G_VALUE_TYPE (v))) {
|
||||
case G_TYPE_CHAR:
|
||||
lua_pushinteger (L, g_value_get_schar (v));
|
||||
break;
|
||||
case G_TYPE_UCHAR:
|
||||
lua_pushinteger (L, g_value_get_uchar (v));
|
||||
break;
|
||||
case G_TYPE_INT:
|
||||
lua_pushinteger (L, g_value_get_int (v));
|
||||
break;
|
||||
case G_TYPE_UINT:
|
||||
lua_pushinteger (L, g_value_get_uint (v));
|
||||
break;
|
||||
case G_TYPE_LONG:
|
||||
lua_pushinteger (L, g_value_get_long (v));
|
||||
break;
|
||||
case G_TYPE_ULONG:
|
||||
lua_pushinteger (L, g_value_get_ulong (v));
|
||||
break;
|
||||
case G_TYPE_INT64:
|
||||
lua_pushinteger (L, g_value_get_int64 (v));
|
||||
break;
|
||||
case G_TYPE_UINT64:
|
||||
lua_pushnumber (L, g_value_get_uint64 (v));
|
||||
break;
|
||||
case G_TYPE_FLOAT:
|
||||
lua_pushnumber (L, g_value_get_float (v));
|
||||
break;
|
||||
case G_TYPE_DOUBLE:
|
||||
lua_pushnumber (L, g_value_get_double (v));
|
||||
break;
|
||||
case G_TYPE_BOOLEAN:
|
||||
lua_pushboolean (L, g_value_get_boolean (v));
|
||||
break;
|
||||
case G_TYPE_STRING:
|
||||
lua_pushstring (L, g_value_get_string (v));
|
||||
break;
|
||||
case G_TYPE_POINTER:
|
||||
lua_pushlightuserdata (L, g_value_get_pointer (v));
|
||||
break;
|
||||
case G_TYPE_BOXED:
|
||||
if (G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
|
||||
wplua_properties_to_table (L, g_value_get_boxed (v));
|
||||
else
|
||||
wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
|
||||
break;
|
||||
case G_TYPE_OBJECT:
|
||||
case G_TYPE_INTERFACE:
|
||||
wplua_pushobject (L, g_value_dup_object (v));
|
||||
break;
|
||||
case G_TYPE_ENUM:
|
||||
wplua_enum_to_lua (L, g_value_get_enum (v), G_VALUE_TYPE (v));
|
||||
break;
|
||||
case G_TYPE_FLAGS:
|
||||
/* FIXME: push as userdata with methods */
|
||||
lua_pushinteger (L, g_value_get_flags (v));
|
||||
break;
|
||||
case G_TYPE_PARAM: {
|
||||
GParamSpec *pspec = g_value_get_param (v);
|
||||
lua_pushstring (L, pspec->name);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_VARIANT: {
|
||||
GVariant *variant = g_value_get_variant (v);
|
||||
wplua_gvariant_to_lua (L, variant);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
lua_pushnil (L);
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
278
modules/module-lua-scripting/wplua/wplua.c
Normal file
278
modules/module-lua-scripting/wplua/wplua.c
Normal file
@@ -0,0 +1,278 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wplua.h"
|
||||
#include "private.h"
|
||||
#include <wp/wp.h>
|
||||
|
||||
#define URI_SANDBOX "resource:///org/freedesktop/pipewire/wireplumber/wplua/sandbox.lua"
|
||||
|
||||
extern void _wplua_register_resource (void);
|
||||
|
||||
G_DEFINE_QUARK (wplua, wp_domain_lua);
|
||||
|
||||
static void
|
||||
_wplua_openlibs (lua_State *L)
|
||||
{
|
||||
/* http://www.lua.org/manual/5.3/manual.html#luaL_requiref
|
||||
* http://www.lua.org/source/5.3/linit.c.html */
|
||||
static const luaL_Reg loadedlibs[] = {
|
||||
{"_G", luaopen_base},
|
||||
/* {LUA_LOADLIBNAME, luaopen_package}, */
|
||||
/* {LUA_COLIBNAME, luaopen_coroutine}, */
|
||||
{LUA_TABLIBNAME, luaopen_table},
|
||||
/* {LUA_IOLIBNAME, luaopen_io}, */
|
||||
{LUA_OSLIBNAME, luaopen_os},
|
||||
{LUA_STRLIBNAME, luaopen_string},
|
||||
{LUA_MATHLIBNAME, luaopen_math},
|
||||
{LUA_UTF8LIBNAME, luaopen_utf8},
|
||||
{LUA_DBLIBNAME, luaopen_debug},
|
||||
{NULL, NULL}
|
||||
};
|
||||
const luaL_Reg *lib;
|
||||
|
||||
for (lib = loadedlibs; lib->func; lib++) {
|
||||
luaL_requiref (L, lib->name, lib->func, 1);
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_errhandler (lua_State *L)
|
||||
{
|
||||
luaL_traceback (L, L, NULL, 1);
|
||||
wp_warning ("%s\n%s", lua_tostring (L, -2), lua_tostring (L, -1));
|
||||
lua_pop (L, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
_wplua_pcall (lua_State *L, int nargs, int nret)
|
||||
{
|
||||
int hpos = lua_gettop (L) - nargs;
|
||||
int ret = LUA_OK;
|
||||
|
||||
lua_pushcfunction (L, _wplua_errhandler);
|
||||
lua_insert (L, hpos);
|
||||
|
||||
ret = lua_pcall (L, nargs, nret, hpos);
|
||||
switch (ret) {
|
||||
case LUA_ERRMEM:
|
||||
wp_critical ("not enough memory");
|
||||
break;
|
||||
case LUA_ERRERR:
|
||||
wp_critical ("error running the message handler");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
lua_remove (L, hpos);
|
||||
return ret;
|
||||
}
|
||||
|
||||
lua_State *
|
||||
wplua_new (void)
|
||||
{
|
||||
static gboolean resource_registered = FALSE;
|
||||
lua_State *L = luaL_newstate ();
|
||||
|
||||
wp_debug ("initializing lua_State %p", L);
|
||||
|
||||
if (!resource_registered) {
|
||||
_wplua_register_resource ();
|
||||
resource_registered = TRUE;
|
||||
}
|
||||
|
||||
_wplua_openlibs (L);
|
||||
_wplua_init_gboxed (L);
|
||||
_wplua_init_gobject (L);
|
||||
_wplua_init_closure (L);
|
||||
|
||||
{
|
||||
GHashTable *t = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
lua_pushliteral (L, "wplua_vtables");
|
||||
wplua_pushboxed (L, G_TYPE_HASH_TABLE, t);
|
||||
lua_settable (L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
return L;
|
||||
}
|
||||
|
||||
void
|
||||
wplua_free (lua_State * L)
|
||||
{
|
||||
wp_debug ("closing lua_State %p", L);
|
||||
lua_close (L);
|
||||
}
|
||||
|
||||
void
|
||||
wplua_enable_sandbox (lua_State * L, WpLuaSandboxFlags flags)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
wp_debug ("enabling Lua sandbox");
|
||||
|
||||
lua_newtable (L);
|
||||
lua_pushliteral (L, "minimal_std");
|
||||
lua_pushboolean (L, (flags & WP_LUA_SANDBOX_MINIMAL_STD));
|
||||
lua_settable (L, -3);
|
||||
lua_pushliteral (L, "isolate_env");
|
||||
lua_pushboolean (L, (flags & WP_LUA_SANDBOX_ISOLATE_ENV));
|
||||
lua_settable (L, -3);
|
||||
|
||||
if (!wplua_load_uri (L, URI_SANDBOX, 1, 0, &error)) {
|
||||
wp_critical ("Failed to load sandbox: %s", error->message);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
wplua_register_type_methods (lua_State * L, GType type,
|
||||
lua_CFunction constructor, const luaL_Reg * methods)
|
||||
{
|
||||
g_return_if_fail (L != NULL);
|
||||
g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_OBJECT ||
|
||||
G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED ||
|
||||
G_TYPE_FUNDAMENTAL (type) == G_TYPE_INTERFACE);
|
||||
|
||||
/* register methods */
|
||||
if (methods) {
|
||||
GHashTable *vtables;
|
||||
|
||||
lua_pushliteral (L, "wplua_vtables");
|
||||
lua_gettable (L, LUA_REGISTRYINDEX);
|
||||
vtables = wplua_toboxed (L, -1);
|
||||
lua_pop (L, 1);
|
||||
|
||||
wp_debug ("Registering methods for '%s'", g_type_name (type));
|
||||
|
||||
if (G_UNLIKELY (g_hash_table_contains (vtables, GUINT_TO_POINTER (type)))) {
|
||||
wp_critical ("type '%s' was already registered", g_type_name (type));
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_insert (vtables, GUINT_TO_POINTER (type), (gpointer) methods);
|
||||
}
|
||||
|
||||
/* register constructor */
|
||||
if (constructor) {
|
||||
luaL_Buffer b;
|
||||
|
||||
wp_debug ("Registering class for '%s'", g_type_name (type));
|
||||
|
||||
luaL_buffinit (L, &b);
|
||||
luaL_addstring (&b, g_type_name (type));
|
||||
luaL_addchar (&b, '_');
|
||||
luaL_addstring (&b, "new");
|
||||
luaL_pushresult (&b);
|
||||
lua_pushcfunction (L, constructor);
|
||||
lua_setglobal (L, lua_tostring (L, -2));
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
|
||||
const gchar * name, int nargs, int nres, GError **error)
|
||||
{
|
||||
int ret;
|
||||
int sandbox = 0;
|
||||
int args_top = lua_gettop (L);
|
||||
|
||||
/* wrap with sandbox() if it's loaded */
|
||||
if (lua_getglobal (L, "sandbox") == LUA_TFUNCTION)
|
||||
sandbox = 1;
|
||||
else
|
||||
lua_pop (L, 1);
|
||||
|
||||
/* skip shebang, if present */
|
||||
if (g_str_has_prefix (buf, "#!/")) {
|
||||
const char *tmp = strchr (buf, '\n');
|
||||
size -= (tmp - buf);
|
||||
buf = tmp;
|
||||
}
|
||||
|
||||
ret = luaL_loadbuffer (L, buf, size, name);
|
||||
if (ret != LUA_OK) {
|
||||
g_set_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_COMPILATION,
|
||||
"Failed to compile: %s", lua_tostring (L, -1));
|
||||
lua_pop (L, nargs + sandbox + 1);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* push sandbox() and the chunk below the arguments */
|
||||
lua_rotate (L, args_top, -nargs);
|
||||
|
||||
ret = _wplua_pcall (L, nargs + sandbox, nres);
|
||||
if (ret != LUA_OK) {
|
||||
g_set_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME,
|
||||
"Runtime error while loading '%s'", name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
|
||||
int nargs, int nres, GError **error)
|
||||
{
|
||||
g_return_val_if_fail (L != NULL, FALSE);
|
||||
g_return_val_if_fail (buf != NULL, FALSE);
|
||||
g_return_val_if_fail (size != 0, FALSE);
|
||||
|
||||
g_autofree gchar *name =
|
||||
g_strdup_printf ("buffer@%p;size=%" G_GSIZE_FORMAT, buf, size);
|
||||
return _wplua_load_buffer (L, buf, size, name, nargs, nres, error);
|
||||
}
|
||||
|
||||
gboolean
|
||||
wplua_load_uri (lua_State * L, const gchar *uri, int nargs, int nres,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr (GFile) file = NULL;
|
||||
g_autoptr (GBytes) bytes = NULL;
|
||||
g_autoptr (GError) err = NULL;
|
||||
g_autofree gchar *name = NULL;
|
||||
gconstpointer data;
|
||||
gsize size;
|
||||
|
||||
g_return_val_if_fail (L != NULL, FALSE);
|
||||
g_return_val_if_fail (uri != NULL, FALSE);
|
||||
|
||||
file = g_file_new_for_uri (uri);
|
||||
if (!(bytes = g_file_load_bytes (file, NULL, NULL, &err))) {
|
||||
g_propagate_prefixed_error (error, err, "Failed to load '%s':", uri);
|
||||
err = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
name = g_path_get_basename (uri);
|
||||
data = g_bytes_get_data (bytes, &size);
|
||||
return _wplua_load_buffer (L, data, size, name, nargs, nres, error);
|
||||
}
|
||||
|
||||
gboolean
|
||||
wplua_load_path (lua_State * L, const gchar *path, int nargs, int nres,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree gchar *abs_path = NULL;
|
||||
g_autofree gchar *uri = NULL;
|
||||
|
||||
g_return_val_if_fail (L != NULL, FALSE);
|
||||
g_return_val_if_fail (path != NULL, FALSE);
|
||||
|
||||
if (!g_path_is_absolute (path)) {
|
||||
g_autofree gchar *cwd = g_get_current_dir ();
|
||||
abs_path = g_build_filename (cwd, path, NULL);
|
||||
}
|
||||
|
||||
if (!(uri = g_filename_to_uri (abs_path ? abs_path : path, NULL, error)))
|
||||
return FALSE;
|
||||
|
||||
return wplua_load_uri (L, uri, nargs, nres, error);
|
||||
}
|
100
modules/module-lua-scripting/wplua/wplua.h
Normal file
100
modules/module-lua-scripting/wplua/wplua.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WPLUA_H__
|
||||
#define __WPLUA_H__
|
||||
|
||||
#include <wp/wp.h>
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* WP_DOMAIN_LUA:
|
||||
*
|
||||
* @brief A <a href="https://developer.gnome.org/glib/stable/glib-Error-Reporting.html#GError">
|
||||
* GError</a> domain for errors that occurred within the context of the
|
||||
* WirePlumber lua library.
|
||||
*/
|
||||
#define WP_DOMAIN_LUA (wp_domain_lua_quark ())
|
||||
GQuark wp_domain_lua_quark (void);
|
||||
|
||||
/**
|
||||
* WpLuaError:
|
||||
*
|
||||
* @brief
|
||||
* @em WP_LUA_ERROR_COMPILATION: a compilation error, i.e. invalid Lua code
|
||||
* @em WP_LUA_ERROR_RUNTIME: a runtime error, i.e. misbehaving Lua code
|
||||
*
|
||||
* Error codes that can appear in a
|
||||
* <a href="https://developer.gnome.org/glib/stable/glib-Error-Reporting.html#GError">
|
||||
* GError</a> when the error domain is %WP_DOMAIN_LUA
|
||||
*/
|
||||
typedef enum {
|
||||
WP_LUA_ERROR_COMPILATION,
|
||||
WP_LUA_ERROR_RUNTIME,
|
||||
} WpLuaError;
|
||||
|
||||
typedef enum {
|
||||
WP_LUA_SANDBOX_MINIMAL_STD,
|
||||
WP_LUA_SANDBOX_ISOLATE_ENV,
|
||||
} WpLuaSandboxFlags;
|
||||
|
||||
lua_State * wplua_new (void);
|
||||
void wplua_free (lua_State * L);
|
||||
|
||||
void wplua_enable_sandbox (lua_State * L, WpLuaSandboxFlags flags);
|
||||
|
||||
void wplua_register_type_methods (lua_State * L, GType type,
|
||||
lua_CFunction constructor, const luaL_Reg * methods);
|
||||
|
||||
GType wplua_gvalue_userdata_type (lua_State *L, int idx);
|
||||
|
||||
/* push -> transfer full; get -> transfer none */
|
||||
void wplua_pushobject (lua_State * L, gpointer object);
|
||||
gpointer wplua_toobject (lua_State *L, int idx);
|
||||
gpointer wplua_checkobject (lua_State *L, int idx, GType type);
|
||||
gboolean wplua_isobject (lua_State *L, int idx, GType type);
|
||||
|
||||
/* push -> transfer full; get -> transfer none */
|
||||
void wplua_pushboxed (lua_State * L, GType type, gpointer object);
|
||||
gpointer wplua_toboxed (lua_State *L, int idx);
|
||||
gpointer wplua_checkboxed (lua_State *L, int idx, GType type);
|
||||
gboolean wplua_isboxed (lua_State *L, int idx, GType type);
|
||||
|
||||
/* transfer floating */
|
||||
GClosure * wplua_checkclosure (lua_State *L, int idx);
|
||||
GClosure * wplua_function_to_closure (lua_State *L, int idx);
|
||||
|
||||
void wplua_enum_to_lua (lua_State *L, gint enum_val, GType enum_type);
|
||||
gint wplua_lua_to_enum (lua_State *L, int idx, GType enum_type);
|
||||
|
||||
void wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v);
|
||||
int wplua_gvalue_to_lua (lua_State *L, const GValue *v);
|
||||
|
||||
GVariant * wplua_lua_to_gvariant (lua_State *L, int idx);
|
||||
void wplua_gvariant_to_lua (lua_State *L, GVariant *p);
|
||||
|
||||
WpProperties * wplua_table_to_properties (lua_State *L, int idx);
|
||||
void wplua_properties_to_table (lua_State *L, WpProperties *p);
|
||||
|
||||
gboolean wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
|
||||
int nargs, int nres, GError **error);
|
||||
gboolean wplua_load_uri (lua_State * L, const gchar *uri, int nargs, int nres,
|
||||
GError **error);
|
||||
gboolean wplua_load_path (lua_State * L, const gchar *path, int nargs, int nres,
|
||||
GError **error);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(lua_State, wplua_free)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user