Files
wireplumber/modules/module-lua-scripting/api/api.c
George Kiagiadakis a6bea40172 core: add wp_core_get_own_bound_id() method
This allows retrieving the bound-id of our own client
2023-11-10 11:27:13 +02:00

1569 lines
40 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "lua.h"
#include <glib/gstdio.h>
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <wplua/wplua.h>
#include <libintl.h>
#define URI_API "resource:///org/freedesktop/pipewire/wireplumber/m-lua-scripting/api.lua"
void wp_lua_scripting_pod_init (lua_State *L);
void wp_lua_scripting_json_init (lua_State *L);
/* helpers */
static WpCore *
get_wp_core (lua_State *L)
{
WpCore *core = NULL;
lua_pushliteral (L, "wireplumber_core");
lua_gettable (L, LUA_REGISTRYINDEX);
core = lua_touserdata (L, -1);
lua_pop (L, 1);
return core;
}
static WpCore *
get_wp_export_core (lua_State *L)
{
WpCore *core = NULL;
lua_pushliteral (L, "wireplumber_export_core");
lua_gettable (L, LUA_REGISTRYINDEX);
if (wplua_isobject (L, -1, WP_TYPE_CORE))
core = wplua_toobject (L, -1);
lua_pop (L, 1);
return core ? core : get_wp_core(L);
}
/* GLib */
static int
glib_get_monotonic_time (lua_State *L)
{
lua_pushinteger (L, g_get_monotonic_time ());
return 1;
}
static int
glib_get_real_time (lua_State *L)
{
lua_pushinteger (L, g_get_real_time ());
return 1;
}
static gboolean
access_parse_mode (const gchar * mode_str, gint *mode)
{
*mode = 0;
if (!mode_str)
return FALSE;
else {
for (guint i = 0; i < strlen (mode_str); i++) {
switch (mode_str[i]) {
case 'r': *mode |= R_OK; break;
case 'w': *mode |= W_OK; break;
case 'x': *mode |= X_OK; break;
case 'f': *mode |= F_OK; break;
case '-': break;
default:
return FALSE;
}
}
}
return TRUE;
}
static int
glib_access (lua_State *L)
{
const gchar *filename = luaL_checkstring (L, 1);
int mode = 0;
if (!access_parse_mode (luaL_checkstring (L, 2), &mode))
luaL_error (L, "invalid mode string: '%s'", lua_tostring (L, 2));
lua_pushboolean (L, g_access (filename, mode) >= 0);
return 1;
}
static const luaL_Reg glib_methods[] = {
{ "get_monotonic_time", glib_get_monotonic_time },
{ "get_real_time", glib_get_real_time },
{ "access", glib_access },
{ NULL, NULL }
};
/* GSource */
static int
source_destroy (lua_State *L)
{
GSource *source = wplua_checkboxed (L, 1, G_TYPE_SOURCE);
g_source_destroy (source);
return 0;
}
static const luaL_Reg source_methods[] = {
{ "destroy", source_destroy },
{ NULL, NULL }
};
/* i18n */
static int
i18n_gettext (lua_State *L)
{
const gchar * msgid = luaL_checkstring (L, 1);
lua_pushstring (L, dgettext (GETTEXT_PACKAGE, msgid));
return 1;
}
static int
i18n_ngettext (lua_State *L)
{
const gchar * msgid = luaL_checkstring (L, 1);
const gchar *msgid_plural = luaL_checkstring (L, 2);
gulong n = luaL_checkinteger (L, 3);
lua_pushstring (L, dngettext (GETTEXT_PACKAGE, msgid, msgid_plural, n));
return 1;
}
static const luaL_Reg i18n_funcs[] = {
{ "gettext", i18n_gettext },
{ "ngettext", i18n_ngettext },
{ NULL, NULL }
};
/* WpCore */
static int
core_get_info (lua_State *L)
{
WpCore * core = get_wp_core (L);
g_autoptr (WpProperties) p = wp_core_get_remote_properties (core);
lua_newtable (L);
lua_pushinteger (L, wp_core_get_remote_cookie (core));
lua_setfield (L, -2, "cookie");
lua_pushstring (L, wp_core_get_remote_name (core));
lua_setfield (L, -2, "name");
lua_pushstring (L, wp_core_get_remote_user_name (core));
lua_setfield (L, -2, "user_name");
lua_pushstring (L, wp_core_get_remote_host_name (core));
lua_setfield (L, -2, "host_name");
lua_pushstring (L, wp_core_get_remote_version (core));
lua_setfield (L, -2, "version");
wplua_properties_to_table (L, p);
lua_setfield (L, -2, "properties");
return 1;
}
static int
core_get_vm_type (lua_State *L)
{
WpCore * core = get_wp_core (L);
g_autofree gchar *vm = wp_core_get_vm_type (core);
lua_pushstring (L, vm);
return 1;
}
static int
core_get_own_bound_id (lua_State *L)
{
WpCore * core = get_wp_core (L);
guint32 id = wp_core_get_own_bound_id (core);
lua_pushinteger (L, id);
return 1;
}
static int
core_idle_add (lua_State *L)
{
GSource *source = NULL;
luaL_checktype (L, 1, LUA_TFUNCTION);
wp_core_idle_add_closure (get_wp_core (L), &source,
wplua_function_to_closure (L, 1));
wplua_pushboxed (L, G_TYPE_SOURCE, source);
return 1;
}
static int
core_timeout_add (lua_State *L)
{
GSource *source = NULL;
lua_Integer timeout_ms = luaL_checkinteger (L, 1);
luaL_checktype (L, 2, LUA_TFUNCTION);
wp_core_timeout_add_closure (get_wp_core (L), &source, timeout_ms,
wplua_function_to_closure (L, 2));
wplua_pushboxed (L, G_TYPE_SOURCE, source);
return 1;
}
static void
on_core_done (WpCore * core, GAsyncResult * res, GClosure * closure)
{
g_autoptr (GError) error = NULL;
GValue val = G_VALUE_INIT;
int n_vals = 0;
if (!wp_core_sync_finish (core, res, &error)) {
g_value_init (&val, G_TYPE_STRING);
g_value_set_string (&val, error->message);
n_vals = 1;
}
g_closure_invoke (closure, NULL, n_vals, &val, NULL);
g_value_unset (&val);
g_closure_invalidate (closure);
g_closure_unref (closure);
}
static int
core_sync (lua_State *L)
{
luaL_checktype (L, 1, LUA_TFUNCTION);
GClosure * closure = wplua_function_to_closure (L, 1);
g_closure_sink (g_closure_ref (closure));
wp_core_sync (get_wp_core (L), NULL, (GAsyncReadyCallback) on_core_done,
closure);
return 0;
}
static gboolean
core_disconnect (WpCore * core)
{
wp_core_disconnect (core);
return G_SOURCE_REMOVE;
}
static int
core_quit (lua_State *L)
{
WpCore * core = get_wp_core (L);
g_autoptr (WpProperties) p = wp_core_get_properties (core);
const gchar *daemon = wp_properties_get (p, "wireplumber.daemon");
if (!g_strcmp0 (daemon, "true")) {
wp_warning ("script attempted to quit, but the engine is "
"running in the wireplumber daemon; ignoring");
return 0;
}
/* wp_core_disconnect() will immediately destroy the lua plugin
and the lua engine, so we cannot call it directly */
wp_core_idle_add (core, NULL, G_SOURCE_FUNC (core_disconnect), core, NULL);
return 0;
}
#include "require.c"
static int
core_require_api (lua_State *L)
{
WpCore * core = get_wp_core (L);
g_autoptr (WpProperties) p = wp_core_get_properties (core);
const gchar *daemon = wp_properties_get (p, "wireplumber.daemon");
if (!g_strcmp0 (daemon, "true")) {
wp_warning ("script attempted to load an API module, but the engine is "
"running in the wireplumber daemon; ignoring");
return 0;
}
return wp_require_api_transition_new_from_lua (L, core);
}
static const luaL_Reg core_funcs[] = {
{ "get_info", core_get_info },
{ "get_vm_type", core_get_vm_type },
{ "get_own_bound_id", core_get_own_bound_id },
{ "idle_add", core_idle_add },
{ "timeout_add", core_timeout_add },
{ "sync", core_sync },
{ "quit", core_quit },
{ "require_api", core_require_api },
{ NULL, NULL }
};
/* WpLog */
static int
log_log (lua_State *L, GLogLevelFlags lvl)
{
lua_Debug ar = {0};
const gchar *message, *tmp;
gchar domain[25];
gchar line_str[11];
gconstpointer instance = NULL;
GType type = G_TYPE_INVALID;
int index = 1;
if (!wp_log_level_is_enabled (lvl))
return 0;
g_warn_if_fail (lua_getstack (L, 1, &ar) == 1);
g_warn_if_fail (lua_getinfo (L, "nSl", &ar) == 1);
if (wplua_isobject (L, 1, G_TYPE_OBJECT)) {
instance = wplua_toobject (L, 1);
type = G_TYPE_FROM_INSTANCE (instance);
index++;
}
else if (wplua_isboxed (L, 1, G_TYPE_BOXED)) {
instance = wplua_toboxed (L, 1);
type = wplua_gvalue_userdata_type (L, 1);
index++;
}
message = luaL_checkstring (L, index);
tmp = ar.source ? g_strrstr (ar.source, ".lua") : NULL;
snprintf (domain, 25, "script/%.*s",
tmp ? MIN((gint)(tmp - ar.source), 17) : 17,
ar.source);
snprintf (line_str, 11, "%d", ar.currentline);
ar.name = ar.name ? ar.name : "chunk";
wp_log_structured_standard (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 }
};
/* WpPlugin */
static int
plugin_find (lua_State *L)
{
const char *name = luaL_checkstring (L, 1);
WpPlugin *plugin = wp_plugin_find (get_wp_core (L), name);
if (plugin)
wplua_pushobject (L, plugin);
else
lua_pushnil (L);
return 1;
}
static const luaL_Reg plugin_funcs[] = {
{ "find", plugin_find },
{ NULL, NULL }
};
/* WpObject */
static void
object_activate_done (WpObject *o, GAsyncResult * res, GClosure * closure)
{
g_autoptr (GError) error = NULL;
GValue val[2] = { G_VALUE_INIT, G_VALUE_INIT };
int n_vals = 1;
if (!wp_object_activate_finish (o, res, &error)) {
wp_message_object (o, "%s", error->message);
if (closure) {
g_value_init (&val[1], G_TYPE_STRING);
g_value_set_string (&val[1], error->message);
n_vals = 2;
}
}
if (closure) {
g_value_init_from_instance (&val[0], o);
g_closure_invoke (closure, NULL, n_vals, val, NULL);
g_value_unset (&val[0]);
g_value_unset (&val[1]);
g_closure_invalidate (closure);
g_closure_unref (closure);
}
}
static int
object_activate (lua_State *L)
{
WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
WpObjectFeatures features = luaL_checkinteger (L, 2);
GClosure * closure = luaL_opt (L, wplua_checkclosure, 3, NULL);
if (closure)
g_closure_sink (g_closure_ref (closure));
wp_object_activate (o, features, NULL,
(GAsyncReadyCallback) object_activate_done, closure);
return 0;
}
static int
object_deactivate (lua_State *L)
{
WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
WpObjectFeatures features = luaL_checkinteger (L, 2);
wp_object_deactivate (o, features);
return 0;
}
static int
object_get_active_features (lua_State *L)
{
WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
WpObjectFeatures features = wp_object_get_active_features (o);
lua_pushinteger (L, features);
return 1;
}
static int
object_get_supported_features (lua_State *L)
{
WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT);
WpObjectFeatures features = wp_object_get_supported_features (o);
lua_pushinteger (L, features);
return 1;
}
static const luaL_Reg object_methods[] = {
{ "activate", object_activate },
{ "deactivate", object_deactivate },
{ "get_active_features", object_get_active_features },
{ "get_supported_features", object_get_supported_features },
{ NULL, NULL }
};
/* WpProxy */
static int
proxy_get_interface_type (lua_State *L)
{
WpProxy * p = wplua_checkobject (L, 1, WP_TYPE_PROXY);
guint32 version = 0;
const gchar *type = wp_proxy_get_interface_type (p, &version);
lua_pushstring (L, type);
lua_pushinteger (L, version);
return 2;
}
static const luaL_Reg proxy_methods[] = {
{ "get_interface_type", proxy_get_interface_type },
{ 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_next (lua_State *L)
{
WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR);
g_auto (GValue) v = G_VALUE_INIT;
if (it && wp_iterator_next (it, &v)) {
return wplua_gvalue_to_lua (L, &v);
} else {
lua_pushnil (L);
return 1;
}
}
static int
push_wpiterator (lua_State *L, WpIterator *it)
{
lua_pushcfunction (L, iterator_next);
wplua_pushboxed (L, WP_TYPE_ITERATOR, it);
return 2;
}
/* Metadata WpIterator */
static int
metadata_iterator_next (lua_State *L)
{
WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR);
g_auto (GValue) item = G_VALUE_INIT;
if (wp_iterator_next (it, &item)) {
guint32 s = 0;
const gchar *k = NULL, *t = NULL, *v = NULL;
wp_metadata_iterator_item_extract (&item, &s, &k, &t, &v);
lua_pushinteger (L, s);
lua_pushstring (L, k);
lua_pushstring (L, t);
lua_pushstring (L, v);
return 4;
} else {
lua_pushnil (L);
return 1;
}
}
static int
push_metadata_wpiterator (lua_State *L, WpIterator *it)
{
lua_pushcfunction (L, metadata_iterator_next);
wplua_pushboxed (L, WP_TYPE_ITERATOR, it);
return 2;
}
/* 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 = WP_CONSTRAINT_TYPE_PW_GLOBAL_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_NOT_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 GType
parse_gtype (const gchar *str)
{
g_autofree gchar *typestr = NULL;
GType res = G_TYPE_INVALID;
g_return_val_if_fail (str, res);
/* "device" -> "WpDevice" */
typestr = g_strdup_printf ("Wp%s", str);
if (typestr[2] != 0) {
typestr[2] = g_ascii_toupper (typestr[2]);
res = g_type_from_name (typestr);
}
return res;
}
static int
object_interest_new_index (lua_State *L, int idx, GType def_type)
{
WpObjectInterest *interest = NULL;
GType type = def_type;
luaL_checktype (L, idx, LUA_TTABLE);
/* type = "string" */
lua_pushliteral (L, "type");
if (lua_gettable (L, idx) == LUA_TSTRING) {
type = parse_gtype (lua_tostring (L, -1));
if (type == G_TYPE_INVALID)
luaL_error (L, "Interest: unknown type '%s'", lua_tostring (L, -1));
}
else if (def_type == G_TYPE_INVALID)
luaL_error (L, "Interest: expected 'type' as string");
lua_pop (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, idx)) {
/* 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;
}
static int
object_interest_new (lua_State *L)
{
return object_interest_new_index (L, 1, G_TYPE_INVALID);
}
static int
object_interest_matches (lua_State *L)
{
WpObjectInterest *interest = wplua_checkboxed (L, 1, WP_TYPE_OBJECT_INTEREST);
gboolean matches = FALSE;
if (wplua_isobject (L, 2, G_TYPE_OBJECT)) {
matches = wp_object_interest_matches (interest, wplua_toobject (L, 2));
}
else if (lua_istable (L, 2)) {
g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
matches = wp_object_interest_matches (interest, props);
} else
luaL_argerror (L, 2, "expected GObject or table");
lua_pushboolean (L, matches);
return 1;
}
static const luaL_Reg object_interest_methods[] = {
{ "matches", object_interest_matches },
{ NULL, NULL }
};
static WpObjectInterest *
get_optional_object_interest (lua_State *L, int idx, GType def_type)
{
if (lua_isnoneornil (L, idx))
return NULL;
else if (lua_isuserdata (L, idx))
return wplua_checkboxed (L, idx, WP_TYPE_OBJECT_INTEREST);
else if (lua_istable (L, idx)) {
object_interest_new_index (L, idx, def_type);
return wplua_toboxed (L, -1);
} else {
luaL_error (L, "expected Interest or none/nil");
return NULL;
}
}
/* 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)) {
WpObjectInterest *interest =
wplua_checkboxed (L, -1, WP_TYPE_OBJECT_INTEREST);
wp_object_manager_add_interest_full (om, wp_object_interest_ref (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);
wp_core_install_object_manager (get_wp_core (L), om);
return 0;
}
static int
object_manager_get_n_objects (lua_State *L)
{
WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER);
lua_pushinteger (L, wp_object_manager_get_n_objects (om));
return 1;
}
static int
object_manager_iterate (lua_State *L)
{
WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER);
WpObjectInterest *oi = get_optional_object_interest (L, 2, G_TYPE_OBJECT);
WpIterator *it = oi ?
wp_object_manager_new_filtered_iterator_full (om,
wp_object_interest_ref (oi)) :
wp_object_manager_new_iterator (om);
return push_wpiterator (L, it);
}
static int
object_manager_lookup (lua_State *L)
{
WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER);
WpObjectInterest *oi = get_optional_object_interest (L, 2, G_TYPE_OBJECT);
WpObject *o = oi ?
wp_object_manager_lookup_full (om, wp_object_interest_ref (oi)) :
wp_object_manager_lookup (om, G_TYPE_OBJECT, NULL);
if (o) {
wplua_pushobject (L, o);
return 1;
}
return 0;
}
static const luaL_Reg object_manager_methods[] = {
{ "activate", object_manager_activate },
{ "get_n_objects", object_manager_get_n_objects },
{ "iterate", object_manager_iterate },
{ "lookup", object_manager_lookup },
{ NULL, NULL }
};
/* WpMetadata */
static int
metadata_iterate (lua_State *L)
{
WpMetadata *metadata = wplua_checkobject (L, 1, WP_TYPE_METADATA);
lua_Integer subject = luaL_checkinteger (L, 2);
WpIterator *it = wp_metadata_new_iterator (metadata, subject);
return push_metadata_wpiterator (L, it);
}
static int
metadata_find (lua_State *L)
{
WpMetadata *metadata = wplua_checkobject (L, 1, WP_TYPE_METADATA);
lua_Integer subject = luaL_checkinteger (L, 2);
const char *key = luaL_checkstring (L, 3), *v = NULL, *t = NULL;
v = wp_metadata_find (metadata, subject, key, &t);
lua_pushstring (L, v);
lua_pushstring (L, t);
return 2;
}
static int
metadata_set (lua_State *L)
{
WpMetadata *metadata = wplua_checkobject (L, 1, WP_TYPE_METADATA);
lua_Integer subject = luaL_checkinteger (L, 2);
const char *key = luaL_opt (L, luaL_checkstring, 3, NULL);
const char *type = luaL_opt (L, luaL_checkstring, 4, NULL);
const char *value = luaL_opt (L, luaL_checkstring, 5, NULL);
wp_metadata_set (metadata, subject, key, type, value);
return 0;
}
static const luaL_Reg metadata_methods[] = {
{ "iterate", metadata_iterate },
{ "find", metadata_find },
{ "set", metadata_set },
{ NULL, NULL }
};
/* WpImplMetadata */
static int
impl_metadata_new (lua_State *L)
{
const char *name = luaL_checkstring (L, 1);
WpProperties *properties = NULL;
if (lua_type (L, 2) != LUA_TNONE) {
luaL_checktype (L, 2, LUA_TTABLE);
properties = wplua_table_to_properties (L, 2);
}
WpImplMetadata *m = wp_impl_metadata_new_full (get_wp_core (L),
name, properties);
if (m)
wplua_pushobject (L, m);
return m ? 1 : 0;
}
/* WpEndpoint */
static const luaL_Reg endpoint_methods[] = {
{ NULL, NULL }
};
/* Device */
static int
device_new (lua_State *L)
{
const char *factory = luaL_checkstring (L, 1);
WpProperties *properties = NULL;
if (lua_type (L, 2) != LUA_TNONE) {
luaL_checktype (L, 2, LUA_TTABLE);
properties = wplua_table_to_properties (L, 2);
}
WpDevice *d = wp_device_new_from_factory (get_wp_export_core (L),
factory, properties);
if (d)
wplua_pushobject (L, d);
return d ? 1 : 0;
}
/* WpSpaDevice */
static int
spa_device_new (lua_State *L)
{
const char *factory = luaL_checkstring (L, 1);
WpProperties *properties = NULL;
if (lua_type (L, 2) != LUA_TNONE) {
luaL_checktype (L, 2, LUA_TTABLE);
properties = wplua_table_to_properties (L, 2);
}
WpSpaDevice *d = wp_spa_device_new_from_spa_factory (get_wp_export_core (L),
factory, properties);
if (d)
wplua_pushobject (L, d);
return d ? 1 : 0;
}
static int
spa_device_iterate_managed_objects (lua_State *L)
{
WpSpaDevice *device = wplua_checkobject (L, 1, WP_TYPE_SPA_DEVICE);
WpIterator *it = wp_spa_device_new_managed_object_iterator (device);
return push_wpiterator (L, it);
}
static int
spa_device_get_managed_object (lua_State *L)
{
WpSpaDevice *device = wplua_checkobject (L, 1, WP_TYPE_SPA_DEVICE);
guint id = luaL_checkinteger (L, 2);
GObject *obj = wp_spa_device_get_managed_object (device, id);
if (obj)
wplua_pushobject (L, obj);
return obj ? 1 : 0;
}
static int
spa_device_store_managed_object (lua_State *L)
{
WpSpaDevice *device = wplua_checkobject (L, 1, WP_TYPE_SPA_DEVICE);
guint id = luaL_checkinteger (L, 2);
GObject *obj = (lua_type (L, 3) != LUA_TNIL) ?
g_object_ref (wplua_checkobject (L, 3, G_TYPE_OBJECT)) : NULL;
wp_spa_device_store_managed_object (device, id, obj);
return 0;
}
static const luaL_Reg spa_device_methods[] = {
{ "iterate_managed_objects", spa_device_iterate_managed_objects },
{ "get_managed_object", spa_device_get_managed_object },
{ "store_managed_object", spa_device_store_managed_object },
{ NULL, NULL }
};
/* Node */
static int
node_new (lua_State *L)
{
const char *factory = luaL_checkstring (L, 1);
WpProperties *properties = NULL;
if (lua_type (L, 2) != LUA_TNONE) {
luaL_checktype (L, 2, LUA_TTABLE);
properties = wplua_table_to_properties (L, 2);
}
WpNode *d = wp_node_new_from_factory (get_wp_export_core (L),
factory, properties);
if (d)
wplua_pushobject (L, d);
return d ? 1 : 0;
}
static int
node_get_state (lua_State *L)
{
WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
const gchar *error = NULL;
WpNodeState state = wp_node_get_state (node, &error);
wplua_enum_to_lua (L, state, WP_TYPE_NODE_STATE);
lua_pushstring (L, error ? error : "");
return 2;
}
static int
node_get_n_input_ports (lua_State *L)
{
WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
guint max = 0;
guint ports = wp_node_get_n_input_ports (node, &max);
lua_pushinteger (L, ports);
lua_pushinteger (L, max);
return 2;
}
static int
node_get_n_output_ports (lua_State *L)
{
WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
guint max = 0;
guint ports = wp_node_get_n_output_ports (node, &max);
lua_pushinteger (L, ports);
lua_pushinteger (L, max);
return 2;
}
static int
node_get_n_ports (lua_State *L)
{
WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
guint ports = wp_node_get_n_ports (node);
lua_pushinteger (L, ports);
return 1;
}
static int
node_iterate_ports (lua_State *L)
{
WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
WpObjectInterest *oi = get_optional_object_interest (L, 2, WP_TYPE_PORT);
WpIterator *it = oi ?
wp_node_new_ports_filtered_iterator_full (node,
wp_object_interest_ref (oi)) :
wp_node_new_ports_iterator (node);
return push_wpiterator (L, it);
}
static int
node_lookup_port (lua_State *L)
{
WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
WpObjectInterest *oi = get_optional_object_interest (L, 2, WP_TYPE_PORT);
WpPort *port = oi ?
wp_node_lookup_port_full (node, wp_object_interest_ref (oi)) :
wp_node_lookup_port (node, G_TYPE_OBJECT, NULL);
if (port) {
wplua_pushobject (L, port);
return 1;
}
return 0;
}
static int
node_send_command (lua_State *L)
{
WpNode *node = wplua_checkobject (L, 1, WP_TYPE_NODE);
const char *command = luaL_checkstring (L, 2);
wp_node_send_command (node, command);
return 0;
}
static const luaL_Reg node_methods[] = {
{ "get_state", node_get_state },
{ "get_n_input_ports", node_get_n_input_ports },
{ "get_n_output_ports", node_get_n_output_ports },
{ "get_n_ports", node_get_n_ports },
{ "iterate_ports", node_iterate_ports },
{ "lookup_port", node_lookup_port },
{ "send_command", node_send_command },
{ NULL, NULL }
};
/* ImplNode */
static int
impl_node_new (lua_State *L)
{
const char *factory = luaL_checkstring (L, 1);
WpProperties *properties = NULL;
if (lua_type (L, 2) != LUA_TNONE) {
luaL_checktype (L, 2, LUA_TTABLE);
properties = wplua_table_to_properties (L, 2);
}
WpImplNode *d = wp_impl_node_new_from_pw_factory (get_wp_export_core (L),
factory, properties);
if (d)
wplua_pushobject (L, d);
return d ? 1 : 0;
}
/* Port */
static int
port_get_direction (lua_State *L)
{
WpPort *port = wplua_checkobject (L, 1, WP_TYPE_PORT);
WpDirection direction = wp_port_get_direction (port);
wplua_enum_to_lua (L, direction, WP_TYPE_DIRECTION);
return 1;
}
static const luaL_Reg port_methods[] = {
{ "get_direction", port_get_direction },
{ NULL, NULL }
};
/* Link */
static int
link_new (lua_State *L)
{
const char *factory = luaL_checkstring (L, 1);
WpProperties *properties = NULL;
if (lua_type (L, 2) != LUA_TNONE) {
luaL_checktype (L, 2, LUA_TTABLE);
properties = wplua_table_to_properties (L, 2);
}
WpLink *l = wp_link_new_from_factory (get_wp_core (L), factory, properties);
if (l)
wplua_pushobject (L, l);
return l ? 1 : 0;
}
/* Client */
static gboolean
client_parse_permissions (const gchar * perms_str, guint32 *perms)
{
*perms = 0;
if (!perms_str)
return FALSE;
else if (g_strcmp0 (perms_str, "all") == 0)
*perms = PW_PERM_ALL;
else {
for (guint i = 0; i < strlen (perms_str); i++) {
switch (perms_str[i]) {
case 'r': *perms |= PW_PERM_R; break;
case 'w': *perms |= PW_PERM_W; break;
case 'x': *perms |= PW_PERM_X; break;
case 'm': *perms |= PW_PERM_M; break;
case '-': break;
default:
return FALSE;
}
}
}
return TRUE;
}
static int
client_update_permissions (lua_State *L)
{
WpClient *client = wplua_checkobject (L, 1, WP_TYPE_CLIENT);
g_autoptr (GArray) arr = NULL;
luaL_checktype (L, 2, LUA_TTABLE);
lua_pushnil(L);
while (lua_next (L, -2)) {
struct pw_permission perm = {0};
if (lua_type (L, -2) == LUA_TSTRING &&
(!g_ascii_strcasecmp (lua_tostring(L, -2), "any") ||
!g_ascii_strcasecmp (lua_tostring(L, -2), "all")))
perm.id = PW_ID_ANY;
else if (lua_isinteger (L, -2))
perm.id = lua_tointeger (L, -2);
else
luaL_error (L, "invalid key for permissions array");
if (!client_parse_permissions (lua_tostring (L, -1), &perm.permissions))
luaL_error (L, "invalid permission string: '%s'", lua_tostring (L, -1));
if (!arr)
arr = g_array_new (FALSE, FALSE, sizeof (struct pw_permission));
g_array_append_val (arr, perm);
lua_pop (L, 1);
}
wp_client_update_permissions_array (client, arr->len,
(const struct pw_permission *) arr->data);
return 0;
}
static int
client_send_error (lua_State *L)
{
WpClient *client = wplua_checkobject (L, 1, WP_TYPE_CLIENT);
guint id = luaL_checkinteger (L, 2);
int res = luaL_checkinteger (L, 3);
const char *message = luaL_checkstring (L, 4);
wp_client_send_error (client, id, res, message);
return 0;
}
static const luaL_Reg client_methods[] = {
{ "update_permissions", client_update_permissions },
{ "send_error", client_send_error },
{ NULL, NULL }
};
/* WpSessionItem */
static int
session_item_new (lua_State *L)
{
const char *type = luaL_checkstring (L, 1);
WpSessionItem *si = wp_session_item_make (get_wp_core (L), type);
if (si)
wplua_pushobject (L, si);
return si ? 1 : 0;
}
static int
session_item_get_associated_proxy (lua_State *L)
{
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
const char *typestr = luaL_checkstring (L, 2);
WpProxy *proxy = wp_session_item_get_associated_proxy (si,
parse_gtype (typestr));
if (proxy)
wplua_pushobject (L, proxy);
return proxy ? 1 : 0;
}
static int
session_item_reset (lua_State *L)
{
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
wp_session_item_reset (si);
return 0;
}
static int
session_item_configure (lua_State *L)
{
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
WpProperties *props = wp_properties_new_empty ();
/* validate arguments */
luaL_checktype (L, 2, LUA_TTABLE);
/* build the configuration properties */
lua_pushnil (L);
while (lua_next (L, 2)) {
const gchar *key = NULL;
g_autofree gchar *var = NULL;
switch (lua_type (L, -1)) {
case LUA_TBOOLEAN:
var = g_strdup_printf ("%u", lua_toboolean (L, -1));
break;
case LUA_TNUMBER:
if (lua_isinteger (L, -1))
var = g_strdup_printf ("%lld", lua_tointeger (L, -1));
else
var = g_strdup_printf ("%f", lua_tonumber (L, -1));
break;
case LUA_TSTRING:
var = g_strdup (lua_tostring (L, -1));
break;
case LUA_TUSERDATA: {
GValue *v = lua_touserdata (L, -1);
gpointer p = g_value_peek_pointer (v);
var = g_strdup_printf ("%p", p);
break;
}
default:
luaL_error (L, "configure does not support lua type ",
lua_typename(L, lua_type(L, -1)));
break;
}
key = luaL_tolstring (L, -2, NULL);
wp_properties_set (props, key, var);
lua_pop (L, 2);
}
lua_pushboolean (L, wp_session_item_configure (si, props));
return 1;
}
static int
session_item_register (lua_State *L)
{
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
wp_session_item_register (g_object_ref (si));
return 0;
}
static int
session_item_remove (lua_State *L)
{
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
wp_session_item_remove (si);
return 0;
}
static const luaL_Reg session_item_methods[] = {
{ "get_associated_proxy", session_item_get_associated_proxy },
{ "reset", session_item_reset },
{ "configure", session_item_configure },
{ "register", session_item_register },
{ "remove", session_item_remove },
{ NULL, NULL }
};
/* WpSiAdapter */
static int
si_adapter_get_ports_format (lua_State *L)
{
WpSiAdapter *adapter = wplua_checkobject (L, 1, WP_TYPE_SI_ADAPTER);
const gchar *mode = NULL;
WpSpaPod *format = wp_si_adapter_get_ports_format (adapter, &mode);
wplua_pushboxed (L, WP_TYPE_SPA_POD, format);
lua_pushstring (L, mode);
return 2;
}
static void
si_adapter_set_ports_format_done (WpObject *o, GAsyncResult * res,
GClosure * closure)
{
g_autoptr (GError) error = NULL;
GValue val[2] = { G_VALUE_INIT, G_VALUE_INIT };
int n_vals = 1;
if (!wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (o), res, &error)) {
wp_message_object (o, "%s", error->message);
if (closure) {
g_value_init (&val[1], G_TYPE_STRING);
g_value_set_string (&val[1], error->message);
n_vals = 2;
}
}
if (closure) {
g_value_init_from_instance (&val[0], o);
g_closure_invoke (closure, NULL, n_vals, val, NULL);
g_value_unset (&val[0]);
g_value_unset (&val[1]);
g_closure_invalidate (closure);
g_closure_unref (closure);
}
}
static int
si_adapter_set_ports_format (lua_State *L)
{
WpSiAdapter *adapter = wplua_checkobject (L, 1, WP_TYPE_SI_ADAPTER);
WpSpaPod *format = wplua_checkboxed (L, 2, WP_TYPE_SPA_POD);
const gchar *mode = luaL_checkstring (L, 3);
GClosure * closure = luaL_opt (L, wplua_checkclosure, 4, NULL);
if (closure)
g_closure_sink (g_closure_ref (closure));
wp_si_adapter_set_ports_format (adapter, wp_spa_pod_ref (format), mode,
(GAsyncReadyCallback) si_adapter_set_ports_format_done, closure);
return 0;
}
static const luaL_Reg si_adapter_methods[] = {
{ "get_ports_format", si_adapter_get_ports_format },
{ "set_ports_format", si_adapter_set_ports_format },
{ NULL, NULL }
};
/* WpPipewireObject */
static int
pipewire_object_iterate_params (lua_State *L)
{
WpPipewireObject *pwobj = wplua_checkobject (L, 1, WP_TYPE_PIPEWIRE_OBJECT);
const gchar *id = luaL_checkstring (L, 2);
WpIterator *it = wp_pipewire_object_enum_params_sync (pwobj, id, NULL);
return push_wpiterator (L, it);
}
static int
pipewire_object_set_param (lua_State *L)
{
WpPipewireObject *pwobj = wplua_checkobject (L, 1, WP_TYPE_PIPEWIRE_OBJECT);
const gchar *id = luaL_checkstring (L, 2);
WpSpaPod *pod = wplua_checkboxed (L, 3, WP_TYPE_SPA_POD);
wp_pipewire_object_set_param (pwobj, id, 0, wp_spa_pod_ref (pod));
return 0;
}
static const luaL_Reg pipewire_object_methods[] = {
{ "iterate_params", pipewire_object_iterate_params },
{ "set_param" , pipewire_object_set_param },
{ "set_params" , pipewire_object_set_param }, /* deprecated, compat only */
{ NULL, NULL }
};
/* WpState */
static int
state_new (lua_State *L)
{
const gchar *name = luaL_checkstring (L, 1);
WpState *state = wp_state_new (name);
wplua_pushobject (L, state);
return 1;
}
static int
state_clear (lua_State *L)
{
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
wp_state_clear (state);
return 0;
}
static int
state_save (lua_State *L)
{
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
luaL_checktype (L, 2, LUA_TTABLE);
g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
g_autoptr (GError) error = NULL;
gboolean saved = wp_state_save (state, props, &error);
lua_pushboolean (L, saved);
lua_pushstring (L, error ? error->message : "");
return 2;
}
static int
state_load (lua_State *L)
{
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
g_autoptr (WpProperties) props = wp_state_load (state);
wplua_properties_to_table (L, props);
return 1;
}
static const luaL_Reg state_methods[] = {
{ "clear", state_clear },
{ "save" , state_save },
{ "load" , state_load },
{ NULL, NULL }
};
/* ImplModule */
static int
impl_module_new (lua_State *L)
{
const char *name, *args = NULL;
WpProperties *properties = NULL;
name = luaL_checkstring (L, 1);
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL)
args = luaL_checkstring (L, 2);
if (lua_type (L, 3) != LUA_TNONE && lua_type (L, 3) != LUA_TNIL) {
luaL_checktype (L, 3, LUA_TTABLE);
properties = wplua_table_to_properties (L, 3);
}
bool load_file = false; // Load args as file path
if (lua_type (L, 4) != LUA_TNONE && lua_type (L, 4) != LUA_TNIL) {
luaL_checktype (L, 4, LUA_TBOOLEAN);
load_file = lua_toboolean(L, 4);
}
WpImplModule *m = NULL;
if (load_file) {
m = wp_impl_module_load_file (get_wp_export_core (L),
name, args, properties);
} else {
m = wp_impl_module_load (get_wp_export_core (L),
name, args, properties);
}
if (m) {
wplua_pushobject (L, m);
return 1;
} else {
return 0;
}
}
void
wp_lua_scripting_api_init (lua_State *L)
{
g_autoptr (GError) error = NULL;
luaL_newlib (L, glib_methods);
lua_setglobal (L, "GLib");
luaL_newlib (L, i18n_funcs);
lua_setglobal (L, "I18n");
luaL_newlib (L, log_funcs);
lua_setglobal (L, "WpLog");
luaL_newlib (L, core_funcs);
lua_setglobal (L, "WpCore");
luaL_newlib (L, plugin_funcs);
lua_setglobal (L, "WpPlugin");
wp_lua_scripting_pod_init (L);
wp_lua_scripting_json_init (L);
wplua_register_type_methods (L, G_TYPE_SOURCE,
NULL, source_methods);
wplua_register_type_methods (L, WP_TYPE_OBJECT,
NULL, object_methods);
wplua_register_type_methods (L, WP_TYPE_PROXY,
NULL, proxy_methods);
wplua_register_type_methods (L, WP_TYPE_GLOBAL_PROXY,
NULL, global_proxy_methods);
wplua_register_type_methods (L, WP_TYPE_OBJECT_INTEREST,
object_interest_new, object_interest_methods);
wplua_register_type_methods (L, WP_TYPE_OBJECT_MANAGER,
object_manager_new, object_manager_methods);
wplua_register_type_methods (L, WP_TYPE_METADATA,
NULL, metadata_methods);
wplua_register_type_methods (L, WP_TYPE_IMPL_METADATA,
impl_metadata_new, NULL);
wplua_register_type_methods (L, WP_TYPE_ENDPOINT,
NULL, endpoint_methods);
wplua_register_type_methods (L, WP_TYPE_DEVICE,
device_new, NULL);
wplua_register_type_methods (L, WP_TYPE_SPA_DEVICE,
spa_device_new, spa_device_methods);
wplua_register_type_methods (L, WP_TYPE_NODE,
node_new, node_methods);
wplua_register_type_methods (L, WP_TYPE_IMPL_NODE,
impl_node_new, NULL);
wplua_register_type_methods (L, WP_TYPE_PORT,
NULL, port_methods);
wplua_register_type_methods (L, WP_TYPE_LINK,
link_new, NULL);
wplua_register_type_methods (L, WP_TYPE_CLIENT,
NULL, client_methods);
wplua_register_type_methods (L, WP_TYPE_SESSION_ITEM,
session_item_new, session_item_methods);
wplua_register_type_methods (L, WP_TYPE_SI_ADAPTER,
NULL, si_adapter_methods);
wplua_register_type_methods (L, WP_TYPE_PIPEWIRE_OBJECT,
NULL, pipewire_object_methods);
wplua_register_type_methods (L, WP_TYPE_STATE,
state_new, state_methods);
wplua_register_type_methods (L, WP_TYPE_IMPL_MODULE,
impl_module_new, NULL);
if (!wplua_load_uri (L, URI_API, &error) ||
!wplua_pcall (L, 0, 0, &error)) {
wp_critical ("Failed to load api: %s", error->message);
}
}