/* WirePlumber * * Copyright © 2020 Collabora Ltd. * @author George Kiagiadakis <george.kiagiadakis@collabora.com> * * SPDX-License-Identifier: MIT */ #include <wp/wp.h> #include <wplua/wplua.h> #define URI_API "resource:///org/freedesktop/pipewire/wireplumber/m-lua-scripting/api.lua" /* helpers */ static WpCore * get_wp_core (lua_State *L) { lua_pushliteral (L, "wireplumber_core"); lua_gettable (L, LUA_REGISTRYINDEX); return lua_touserdata (L, -1); } static WpCore * get_wp_export_core (lua_State *L) { lua_pushliteral (L, "wireplumber_export_core"); lua_gettable (L, LUA_REGISTRYINDEX); return lua_touserdata (L, -1); } /* WpDebug */ static int log_log (lua_State *L, GLogLevelFlags lvl) { lua_Debug ar; const gchar *message; gchar line_str[11]; gconstpointer instance = NULL; GType type = G_TYPE_INVALID; int index = 1; if (!wp_log_level_is_enabled (lvl)) return 0; lua_getstack (L, 1, &ar); lua_getinfo (L, "nSl", &ar); if (wplua_isobject (L, 1, G_TYPE_OBJECT)) { instance = wplua_toobject (L, 1); type = G_TYPE_FROM_INSTANCE (instance); index++; } message = luaL_checkstring (L, index); sprintf (line_str, "%d", ar.currentline); wp_log_structured_standard (G_LOG_DOMAIN, lvl, ar.source, line_str, ar.name, type, instance, "%s", message); return 0; } static int log_warning (lua_State *L) { return log_log (L, G_LOG_LEVEL_WARNING); } static int log_message (lua_State *L) { return log_log (L, G_LOG_LEVEL_MESSAGE); } static int log_info (lua_State *L) { return log_log (L, G_LOG_LEVEL_INFO); } static int log_debug (lua_State *L) { return log_log (L, G_LOG_LEVEL_DEBUG); } static int log_trace (lua_State *L) { return log_log (L, WP_LOG_LEVEL_TRACE); } static const luaL_Reg log_funcs[] = { { "warning", log_warning }, { "message", log_message }, { "info", log_info }, { "debug", log_debug }, { "trace", log_trace }, { NULL, NULL } }; /* WpObject */ static void object_activate_done (WpObject *o, GAsyncResult *res, gpointer data) { g_autoptr (GError) error = NULL; if (!wp_object_activate_finish (o, res, &error)) { wp_warning_object (o, "failed to activate: %s", error->message); } } static int object_activate (lua_State *L) { WpObject *o = wplua_checkobject (L, 1, WP_TYPE_OBJECT); WpObjectFeatures features = 0; if (lua_type (L, 2) != LUA_TNONE) { features = luaL_checkinteger (L, 2); } else { features = WP_OBJECT_FEATURES_ALL; } wp_object_activate (o, features, NULL, (GAsyncReadyCallback) object_activate_done, NULL); return 0; } static const luaL_Reg object_methods[] = { { "activate", object_activate }, { 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 (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 = g_type_is_a (type, WP_TYPE_GLOBAL_PROXY) ? WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY : WP_CONSTRAINT_TYPE_G_PROPERTY; lua_pop (L, 1); /* get t[1] (the subject) and t[2] (the verb) */ lua_geti (L, constraint_idx, 1); subject = lua_tostring (L, -1); lua_geti (L, constraint_idx, 2); verb = lua_tostring (L, -1)[0]; switch (verb) { case WP_CONSTRAINT_VERB_EQUALS: case WP_CONSTRAINT_VERB_MATCHES: { lua_geti (L, constraint_idx, 3); value = constraint_value_to_variant (L, -1); if (G_UNLIKELY (!value)) luaL_error (L, "Constraint: bad value type"); break; } case WP_CONSTRAINT_VERB_IN_RANGE: { GVariant *values[2]; lua_geti (L, constraint_idx, 3); lua_geti (L, constraint_idx, 4); values[0] = constraint_value_to_variant (L, -2); values[1] = constraint_value_to_variant (L, -1); if (G_UNLIKELY (!values[0] || !values[1])) { g_clear_pointer (&values[0], g_variant_unref); g_clear_pointer (&values[1], g_variant_unref); luaL_error (L, "Constraint: bad value type"); } value = g_variant_new_tuple (values, 2); break; } case WP_CONSTRAINT_VERB_IN_LIST: { GPtrArray *values = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref); int i = 3; while (lua_geti (L, constraint_idx, i++) != LUA_TNIL) { GVariant *tmp = constraint_value_to_variant (L, -1); if (G_UNLIKELY (!tmp)) { g_ptr_array_unref (values); luaL_error (L, "Constraint: bad value type"); } g_ptr_array_add (values, g_variant_ref_sink (tmp)); lua_pop (L, 1); } value = g_variant_new_tuple ((GVariant **) values->pdata, values->len); g_ptr_array_unref (values); break; } default: break; } wp_object_interest_add_constraint (interest, ctype, subject, verb, value); lua_settop (L, constraint_idx); } static int object_interest_new (lua_State *L) { WpObjectInterest *interest = NULL; GType type = 0; gchar *typestr; luaL_checktype (L, 1, LUA_TTABLE); /* type = "string" -> required */ lua_pushliteral (L, "type"); if (lua_gettable (L, -2) != LUA_TSTRING) luaL_error (L, "Interest: expected 'type' as string"); /* "device" -> "WpDevice" */ typestr = g_strdup_printf ("Wp%s", lua_tostring (L, -1)); if (typestr[2] != 0) { typestr[2] = g_ascii_toupper (typestr[2]); type = g_type_from_name (typestr); } g_free (typestr); lua_pop (L, 1); if (!type) luaL_error (L, "Interest: unknown type '%s'", lua_tostring (L, -1)); interest = wp_object_interest_new_type (type); wplua_pushboxed (L, WP_TYPE_OBJECT_INTEREST, interest); /* add constraints */ lua_pushnil (L); while (lua_next (L, 1)) { /* if the key isn't "type" */ if (!(lua_type (L, -2) == LUA_TSTRING && !g_strcmp0 ("type", lua_tostring (L, -2)))) object_interest_new_add_constraint (L, type, interest); lua_pop (L, 1); } return 1; } /* WpObjectManager */ static int object_manager_new (lua_State *L) { WpObjectManager *om; /* validate arguments */ luaL_checktype (L, 1, LUA_TTABLE); /* push to Lua asap to have a way to unref in case of error */ om = wp_object_manager_new (); wplua_pushobject (L, om); lua_pushnil (L); while (lua_next (L, 1)) { if (!wplua_isboxed (L, -1, WP_TYPE_OBJECT_INTEREST)) luaL_error (L, "ObjectManager: expected Interest"); /* steal the interest out of the GValue to avoid doing mem copy */ GValue *v = lua_touserdata (L, -1); wp_object_manager_add_interest_full (om, g_value_get_boxed (v)); memset (v, 0, sizeof (GValue)); g_value_init (v, WP_TYPE_OBJECT_INTEREST); lua_pop (L, 1); } /* request all the features for Lua scripts to make their job easier */ wp_object_manager_request_object_features (om, WP_TYPE_OBJECT, WP_OBJECT_FEATURES_ALL); return 1; } static int object_manager_activate (lua_State *L) { WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER); wp_core_install_object_manager (get_wp_core (L), om); return 0; } static int object_manager_iterate (lua_State *L) { WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER); WpIterator *it = wp_object_manager_iterate (om); return push_wpiterator (L, it); } static int object_manager_lookup (lua_State *L) { WpObjectManager *om = wplua_checkobject (L, 1, WP_TYPE_OBJECT_MANAGER); WpObject *o = NULL; if (lua_isuserdata (L, 2)) { WpObjectInterest *oi = wplua_checkboxed (L, 2, WP_TYPE_OBJECT_INTEREST); o = wp_object_manager_lookup_full (om, wp_object_interest_ref (oi)); } else { o = wp_object_manager_lookup (om, WP_TYPE_OBJECT, NULL); } wplua_pushobject (L, o); return 1; } static const luaL_Reg object_manager_methods[] = { { "activate", object_manager_activate }, { "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); g_autoptr (WpIterator) it = wp_metadata_iterate (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 const luaL_Reg metadata_methods[] = { { "iterate", metadata_iterate }, { "find", metadata_find }, { NULL, NULL } }; /* WpSession */ static int session_iterate_endpoints (lua_State *L) { WpSession *session = wplua_checkobject (L, 1, WP_TYPE_SESSION); WpIterator *it = wp_session_iterate_endpoints (session); return push_wpiterator (L, it); } static int session_iterate_links (lua_State *L) { WpSession *session = wplua_checkobject (L, 1, WP_TYPE_SESSION); WpIterator *it = wp_session_iterate_links (session); return push_wpiterator (L, it); } static const luaL_Reg session_methods[] = { { "iterate_endpoints", session_iterate_endpoints }, { "iterate_links", session_iterate_links }, { NULL, NULL } }; /* WpEndpoint */ static int endpoint_iterate_streams (lua_State *L) { WpEndpoint *ep = wplua_checkobject (L, 1, WP_TYPE_ENDPOINT); WpIterator *it = wp_endpoint_iterate_streams (ep); return push_wpiterator (L, it); } static int endpoint_create_link (lua_State *L) { WpEndpoint *ep = wplua_checkobject (L, 1, WP_TYPE_ENDPOINT); luaL_checktype (L, 2, LUA_TTABLE); WpProperties *props = wplua_table_to_properties (L, 2); wp_endpoint_create_link (ep, props); return 0; } static const luaL_Reg endpoint_methods[] = { { "iterate_streams", endpoint_iterate_streams }, { "create_link", endpoint_create_link }, { NULL, NULL } }; /* WpEndpointLink */ static int endpoint_link_get_state (lua_State *L) { WpEndpointLink *eplink = wplua_checkobject (L, 1, WP_TYPE_ENDPOINT_LINK); const gchar *error = NULL; WpEndpointLinkState state = wp_endpoint_link_get_state (eplink, &error); g_autoptr (GEnumClass) state_class = g_type_class_ref (WP_TYPE_ENDPOINT_LINK_STATE); lua_pushstring (L, g_enum_get_value (state_class, state)->value_nick); if (error) lua_pushstring (L, error); return error ? 2 : 1; } static int endpoint_link_request_state (lua_State *L) { WpEndpointLink *eplink = wplua_checkobject (L, 1, WP_TYPE_ENDPOINT_LINK); const gchar *states[] = { "inactive", "active" }; int state = luaL_checkoption (L, 2, NULL, states); wp_endpoint_link_request_state (eplink, (WpEndpointLinkState) (state+1)); return 0; } static int endpoint_link_get_linked_object_ids (lua_State *L) { WpEndpointLink *eplink = wplua_checkobject (L, 1, WP_TYPE_ENDPOINT_LINK); guint32 output_endpoint, output_stream; guint32 input_endpoint, input_stream; wp_endpoint_link_get_linked_object_ids (eplink, &output_endpoint, &output_stream, &input_endpoint, &input_stream); lua_pushinteger (L, output_endpoint); lua_pushinteger (L, output_stream); lua_pushinteger (L, input_endpoint); lua_pushinteger (L, input_stream); return 4; } static const luaL_Reg endpoint_link_methods[] = { { "get_state", endpoint_link_get_state }, { "request_state", endpoint_link_request_state }, { "get_linked_object_ids", endpoint_link_get_linked_object_ids }, { 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); wplua_pushobject (L, d); return 1; } /* 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); wplua_pushobject (L, d); return 1; } 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[] = { { "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); wplua_pushobject (L, d); return 1; } static const luaL_Reg node_methods[] = { { 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); wplua_pushobject (L, d); return 1; } void wp_lua_scripting_api_init (lua_State *L) { g_autoptr (GError) error = NULL; luaL_newlib (L, log_funcs); lua_setglobal (L, "WpDebug"); wplua_register_type_methods (L, WP_TYPE_OBJECT, NULL, object_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, NULL); 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_SESSION, NULL, session_methods); wplua_register_type_methods (L, WP_TYPE_ENDPOINT, NULL, endpoint_methods); wplua_register_type_methods (L, WP_TYPE_ENDPOINT_LINK, NULL, endpoint_link_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_load_uri (L, URI_API, &error); if (G_UNLIKELY (error)) wp_critical ("Failed to load api: %s", error->message); }