diff --git a/NEWS.rst b/NEWS.rst index 409156f6..10925def 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,6 +1,76 @@ -WirePlumber 0.4.15 +WirePlumber 0.4.17 ~~~~~~~~~~~~~~~~~~ +Fixes: + + - Fixed a reference counting issue in the object managers that could cause + crashes due to memory corruption (#534) + + - Fixed an issue with filters linking to wrong targets, often with two sets + of links (#536) + + - Fixed a crash in the endpoints policy that would show up when log messages + were enabled at level 3 or higher + +Past releases +~~~~~~~~~~~~~ + +WirePlumber 0.4.16 +.................. + +Additions: + + - Added a new "sm-objects" script that allows loading objects on demand + via metadata entries that describe the object to load; this can be used to + load pipewire modules, such as filters or network sources/sinks, on demand + + - Added a mechanism to override device profile priorities in the configuration, + mainly as a way to re-prioritize Bluetooth codecs, but this also can be used + for other devices + + - Added a mechanism in the endpoints policy to allow connecting filters + between a certain endpoint's virtual sink and the device sink; this is + specifically intended to allow plugging a filter-chain to act as equalizer + on the Multimedia endpoint + + - Added wp_core_get_own_bound_id() method in WpCore + +Changes: + + - PipeWire 0.3.68 is now required + + - policy-dsp now has the ability to hide hardware nodes behind the DSP sink + to prevent hardware misuse or damage + + - JSON parsing in Lua now allows keys inside objects to be without quotes + + - Added optional argument in the Lua JSON parse() method to limit recursions, + making it possible to partially parse a JSON object + + - It is now possible to pass ``nil`` in Lua object constructors that expect an + optional properties object; previously, omitting the argument was the only + way to skip the properties + + - The endpoints policy now marks the endpoint nodes as "passive" instead of + marking their links, adjusting for the behavior change in PipeWire 0.3.68 + + - Removed the "passive" property from si-standard-link, since only nodes are + marked as passive now + +Fixes: + + - Fixed the ``wpctl clear-default`` command to completely clear all the + default nodes state instead of only the last set default + + - Reduced the amount of globals that initially match the interest in the + object manager + + - Used an idle callback instead of pw_core_sync() in the object manager to + expose tmp globals + +WirePlumber 0.4.15 +.................. + Additions: - A new "DSP policy" module has been added; its purpose is to automatically @@ -51,9 +121,6 @@ Changes/Fixes: - Added some missing `\since` annotations and made them show up in the generated gobject-introspection file, to help bindings generators -Past releases -~~~~~~~~~~~~~ - WirePlumber 0.4.14 .................. diff --git a/docs/meson.build b/docs/meson.build index 3bbbecdc..f24ae6dd 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -141,7 +141,7 @@ if build_gir dependencies: [wp_dep, dummy_dep], namespace: 'Wp', nsversion: wireplumber_api_version, - export_packages: 'wireplumber-0.4', + export_packages: 'wireplumber-' + wireplumber_api_version, header: 'wp/wp.h', sources: [wpenums_h, wp_gtkdoc_h, wp_lib_headers], include_directories: [wpenums_include_dir], diff --git a/docs/rst/daemon/configuration/alsa.rst b/docs/rst/daemon/configuration/alsa.rst index 95dea8bc..cf00019c 100644 --- a/docs/rst/daemon/configuration/alsa.rst +++ b/docs/rst/daemon/configuration/alsa.rst @@ -441,7 +441,7 @@ This can be done in 3 different ways: 1. Use pavucontrol and toggle the codecs in the output advanced section. - 2. Modify the ``["iec958.codecs"]`` node property to contain suported codecs. + 2. Modify the ``["iec958.codecs"]`` node property to contain supported codecs. Example ``~/.config/wireplumber/main.lua.d/51-alsa-spdif.lua``: diff --git a/lib/wp/core.c b/lib/wp/core.c index 0b721e11..a57efc25 100644 --- a/lib/wp/core.c +++ b/lib/wp/core.c @@ -806,6 +806,26 @@ wp_core_is_connected (WpCore * self) return self->pw_core && self->info; } +/*! + * \brief Gets the bound id of the client object that is created as a result + * of this core being connected to the PipeWire daemon + * + * \ingroup wpcore + * \since 0.4.16 + * \param self the core + * \returns the bound id of this client + */ +guint32 +wp_core_get_own_bound_id (WpCore * self) +{ + struct pw_client *client; + + g_return_val_if_fail (wp_core_is_connected (self), SPA_ID_INVALID); + + client = pw_core_get_client (self->pw_core); + return pw_proxy_get_bound_id ((struct pw_proxy *) client); +} + /*! * \brief Gets the cookie of the core's connected PipeWire instance * diff --git a/lib/wp/core.h b/lib/wp/core.h index 6907ca41..0cacd3ed 100644 --- a/lib/wp/core.h +++ b/lib/wp/core.h @@ -74,6 +74,9 @@ gboolean wp_core_is_connected (WpCore * self); /* Properties */ +WP_API +guint32 wp_core_get_own_bound_id (WpCore * self); + WP_API guint32 wp_core_get_remote_cookie (WpCore * self); diff --git a/lib/wp/metadata.c b/lib/wp/metadata.c index 6111cc90..cea20959 100644 --- a/lib/wp/metadata.c +++ b/lib/wp/metadata.c @@ -651,8 +651,7 @@ wp_impl_metadata_activate_execute_step (WpObject * object, case STEP_BIND: { g_autoptr (WpCore) core = wp_object_get_core (object); struct pw_core *pw_core = wp_core_get_pw_core (core); - struct spa_dict_item items[1]; - struct spa_dict *props = NULL, prop_impl; + const struct pw_properties *props = NULL; /* no pw_core -> we are not connected */ if (!pw_core) { @@ -663,16 +662,9 @@ wp_impl_metadata_activate_execute_step (WpObject * object, return; } - /* TODO: Ideally, we should use the properties from pw_impl_metadata here, - * but the pw_impl_metadata_get_properties is not implemented in pipewire - * yet, so we add the name property manually for now */ - if (self->name) { - items[0] = SPA_DICT_ITEM_INIT(PW_KEY_METADATA_NAME, self->name); - prop_impl = SPA_DICT_INIT_ARRAY(items); - props = &prop_impl; - } + props = pw_impl_metadata_get_properties (self->impl); wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core, - PW_TYPE_INTERFACE_Metadata, props, priv->iface, 0) + PW_TYPE_INTERFACE_Metadata, &props->dict, priv->iface, 0) ); break; } diff --git a/lib/wp/object-manager.c b/lib/wp/object-manager.c index 981e9926..a86de348 100644 --- a/lib/wp/object-manager.c +++ b/lib/wp/object-manager.c @@ -1054,6 +1054,7 @@ expose_tmp_globals (WpCore *core) { WpRegistry *self = wp_core_get_registry (core); g_autoptr (GPtrArray) tmp_globals = NULL; + g_autoptr (GPtrArray) object_managers = NULL; /* in case the registry was cleared in the meantime... */ if (G_UNLIKELY (!self->tmp_globals)) @@ -1093,9 +1094,13 @@ expose_tmp_globals (WpCore *core) g_ptr_array_index (self->globals, g->id) = wp_global_ref (g); } + object_managers = g_ptr_array_copy (self->object_managers, + (GCopyFunc) g_object_ref, NULL); + g_ptr_array_set_free_func (object_managers, g_object_unref); + /* notify object managers */ - for (guint i = 0; i < self->object_managers->len; i++) { - WpObjectManager *om = g_ptr_array_index (self->object_managers, i); + for (guint i = 0; i < object_managers->len; i++) { + WpObjectManager *om = g_ptr_array_index (object_managers, i); for (guint i = 0; i < tmp_globals->len; i++) { WpGlobal *g = g_ptr_array_index (tmp_globals, i); diff --git a/lib/wp/properties.c b/lib/wp/properties.c index 40ca375b..4ec55b39 100644 --- a/lib/wp/properties.c +++ b/lib/wp/properties.c @@ -128,8 +128,8 @@ wp_properties_new_valist (const gchar * key, va_list args) * be parsed from the given string * * \ingroup wpproperties - * \param str a string containing a whitespace separated list of key=value pairs - * (ex. "key1=value1 key2=value2") + * \param str a string containing either a whitespace separated list of key=value + * pairs (ex. "key1=value1 key2=value2") or a JSON object (ex. '{"key1":"value1"}') * \returns (transfer full): the newly constructed properties set */ WpProperties * diff --git a/modules/module-lua-scripting/api/api.c b/modules/module-lua-scripting/api/api.c index 1a55e595..2eca385b 100644 --- a/modules/module-lua-scripting/api/api.c +++ b/modules/module-lua-scripting/api/api.c @@ -19,7 +19,6 @@ WP_LOG_TOPIC_EXTERN (log_topic_lua_scripting) void wp_lua_scripting_pod_init (lua_State *L); void wp_lua_scripting_json_init (lua_State *L); -void push_luajson (lua_State *L, WpSpaJson *json); /* helpers */ @@ -182,6 +181,15 @@ core_get_vm_type (lua_State *L) 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) { @@ -288,6 +296,7 @@ static const luaL_Reg core_funcs[] = { { "get_properties", core_get_properties }, { "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 }, @@ -940,7 +949,7 @@ impl_metadata_new (lua_State *L) const char *name = luaL_checkstring (L, 1); WpProperties *properties = NULL; - if (lua_type (L, 2) != LUA_TNONE) { + if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) { luaL_checktype (L, 2, LUA_TTABLE); properties = wplua_table_to_properties (L, 2); } @@ -960,7 +969,7 @@ device_new (lua_State *L) const char *factory = luaL_checkstring (L, 1); WpProperties *properties = NULL; - if (lua_type (L, 2) != LUA_TNONE) { + if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) { luaL_checktype (L, 2, LUA_TTABLE); properties = wplua_table_to_properties (L, 2); } @@ -980,7 +989,7 @@ spa_device_new (lua_State *L) const char *factory = luaL_checkstring (L, 1); WpProperties *properties = NULL; - if (lua_type (L, 2) != LUA_TNONE) { + if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) { luaL_checktype (L, 2, LUA_TTABLE); properties = wplua_table_to_properties (L, 2); } @@ -1038,7 +1047,7 @@ node_new (lua_State *L) const char *factory = luaL_checkstring (L, 1); WpProperties *properties = NULL; - if (lua_type (L, 2) != LUA_TNONE) { + if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) { luaL_checktype (L, 2, LUA_TTABLE); properties = wplua_table_to_properties (L, 2); } @@ -1147,7 +1156,7 @@ impl_node_new (lua_State *L) const char *factory = luaL_checkstring (L, 1); WpProperties *properties = NULL; - if (lua_type (L, 2) != LUA_TNONE) { + if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) { luaL_checktype (L, 2, LUA_TTABLE); properties = wplua_table_to_properties (L, 2); } @@ -1183,7 +1192,7 @@ link_new (lua_State *L) const char *factory = luaL_checkstring (L, 1); WpProperties *properties = NULL; - if (lua_type (L, 2) != LUA_TNONE) { + if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) { luaL_checktype (L, 2, LUA_TTABLE); properties = wplua_table_to_properties (L, 2); } diff --git a/modules/module-lua-scripting/api/json.c b/modules/module-lua-scripting/api/json.c index 26977a61..a0867605 100644 --- a/modules/module-lua-scripting/api/json.c +++ b/modules/module-lua-scripting/api/json.c @@ -97,8 +97,8 @@ spa_json_is_object (lua_State *L) return 1; } -void -push_luajson (lua_State *L, WpSpaJson *json) +static void +push_luajson (lua_State *L, WpSpaJson *json, gint n_recursions) { /* Null */ if (wp_spa_json_is_null (json)) { @@ -127,20 +127,20 @@ push_luajson (lua_State *L, WpSpaJson *json) } /* Array */ - else if (wp_spa_json_is_array (json)) { + else if (wp_spa_json_is_array (json) && n_recursions > 0) { g_auto (GValue) item = G_VALUE_INIT; g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json); guint i = 1; lua_newtable (L); for (; wp_iterator_next (it, &item); g_value_unset (&item)) { WpSpaJson *j = g_value_get_boxed (&item); - push_luajson (L, j); + push_luajson (L, j, n_recursions - 1); lua_rawseti (L, -2, i++); } } /* Object */ - else if (wp_spa_json_is_object (json)) { + else if (wp_spa_json_is_object (json) && n_recursions > 0) { g_auto (GValue) item = G_VALUE_INIT; g_autoptr (WpIterator) it = wp_spa_json_new_iterator (json); lua_newtable (L); @@ -154,7 +154,7 @@ push_luajson (lua_State *L, WpSpaJson *json) if (!wp_iterator_next (it, &item)) break; value = g_value_get_boxed (&item); - push_luajson (L, value); + push_luajson (L, value, n_recursions - 1); lua_setfield (L, -2, key_str); } } @@ -171,7 +171,8 @@ static int spa_json_parse (lua_State *L) { WpSpaJson *json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON); - push_luajson (L, json); + gint n_recursions = luaL_opt (L, luaL_checkinteger, 2, INT_MAX); + push_luajson (L, json, n_recursions); return 1; } diff --git a/modules/module-si-audio-virtual.c b/modules/module-si-audio-virtual.c index 00dbced5..424980d3 100644 --- a/modules/module-si-audio-virtual.c +++ b/modules/module-si-audio-virtual.c @@ -250,6 +250,8 @@ si_audio_virtual_enable_active (WpSessionItem *si, WpTransition *transition) (self->direction == WP_DIRECTION_OUTPUT) ? "Capture" : "Playback"); g_autofree gchar *media = g_strdup_printf ("Audio/%s", (self->direction == WP_DIRECTION_OUTPUT) ? "Source" : "Sink"); + const gchar *passive = + (self->direction == WP_DIRECTION_OUTPUT) ? "in" : "out"; if (!wp_session_item_is_configured (si)) { wp_transition_return_error (transition, @@ -266,6 +268,7 @@ si_audio_virtual_enable_active (WpSessionItem *si, WpTransition *transition) PW_KEY_FACTORY_NAME, "support.null-audio-sink", PW_KEY_NODE_DESCRIPTION, desc, PW_KEY_NODE_AUTOCONNECT, "true", + PW_KEY_NODE_PASSIVE, passive, "monitor.channel-volumes", "true", "wireplumber.is-virtual", "true", NULL)); diff --git a/modules/module-si-standard-link.c b/modules/module-si-standard-link.c index 231621c3..5b6b2674 100644 --- a/modules/module-si-standard-link.c +++ b/modules/module-si-standard-link.c @@ -24,7 +24,6 @@ struct _WpSiStandardLink GWeakRef in_item; const gchar *out_item_port_context; const gchar *in_item_port_context; - gboolean passive; gboolean passthrough; /* activate */ @@ -70,7 +69,6 @@ si_standard_link_reset (WpSessionItem * item) g_weak_ref_set (&self->in_item, NULL); self->out_item_port_context = NULL; self->in_item_port_context = NULL; - self->passive = FALSE; self->passthrough = FALSE; WP_SESSION_ITEM_CLASS (si_standard_link_parent_class)->reset (item); @@ -120,9 +118,6 @@ si_standard_link_configure (WpSessionItem * item, WpProperties * p) self->in_item_port_context = wp_properties_get (si_props, "in.item.port.context"); - str = wp_properties_get (si_props, "passive"); - self->passive = str && pw_properties_parse_bool (str); - str = wp_properties_get (si_props, "passthrough"); self->passthrough = str && pw_properties_parse_bool (str); @@ -354,8 +349,6 @@ create_links (WpSiStandardLink * self, WpTransition * transition, wp_properties_setf (props, PW_KEY_LINK_OUTPUT_PORT, "%u", out_port.port_id); wp_properties_setf (props, PW_KEY_LINK_INPUT_NODE, "%u", best_port->node_id); wp_properties_setf (props, PW_KEY_LINK_INPUT_PORT, "%u", best_port->port_id); - if (self->passive) - wp_properties_set (props, PW_KEY_LINK_PASSIVE, "true"); wp_debug_object (self, "create pw link: %u:%u (%s) -> %u:%u (%s)", out_port.node_id, out_port.port_id, diff --git a/po/fa.po b/po/fa.po index fbf1b98e..5ca7bee0 100644 --- a/po/fa.po +++ b/po/fa.po @@ -1,22 +1,22 @@ # Persian translation for WirePlumber. # Copyright (C) 2022 WirePlumber's COPYRIGHT HOLDER # This file is distributed under the same license as the WirePlumber package. -# Danial Behzadi , 2022. +# Danial Behzadi , 2022-2023. # msgid "" msgstr "" "Project-Id-Version: WirePlumber master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/" "issues\n" -"POT-Creation-Date: 2022-07-08 03:32+0000\n" -"PO-Revision-Date: 2022-08-07 04:59+0430\n" +"POT-Creation-Date: 2023-10-06 03:31+0000\n" +"PO-Revision-Date: 2023-10-06 16:22+0330\n" "Last-Translator: Danial Behzadi \n" "Language-Team: Persian \n" "Language: fa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.3.2\n" #. WirePlumber #. @@ -48,11 +48,15 @@ msgstr "" #. ensure the device has an appropriate name #. deduplicate devices with the same name #. ensure the device has a description -#: src/scripts/monitors/alsa.lua:228 +#: src/scripts/monitors/alsa.lua:236 +msgid "Loopback" +msgstr "حلقهٔ معکوس" + +#: src/scripts/monitors/alsa.lua:238 msgid "Built-in Audio" msgstr "صدای توکار" -#: src/scripts/monitors/alsa.lua:230 +#: src/scripts/monitors/alsa.lua:240 msgid "Modem" msgstr "مودم" diff --git a/po/he.po b/po/he.po index 90c0efaa..a0e56ffc 100644 --- a/po/he.po +++ b/po/he.po @@ -4,10 +4,10 @@ msgid "" msgstr "" "Project-Id-Version: pipewire\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/" -"issues/new\n" -"POT-Creation-Date: 2022-04-09 15:19+0300\n" -"PO-Revision-Date: 2021-03-02 14:40+0000\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/" +"issues\n" +"POT-Creation-Date: 2023-03-04 13:34+0000\n" +"PO-Revision-Date: 2023-12-06 10:07+0200\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew \n" @@ -16,9 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 4.4.2\n" -"X-Poedit-Language: Hebrew\n" -"X-Poedit-Country: Israel\n" +"X-Generator: Poedit 3.4.1\n" #. WirePlumber #. @@ -28,6 +26,7 @@ msgstr "" #. SPDX-License-Identifier: MIT #. Receive script arguments from config.lua #. ensure config.properties is not nil +#. unique device/node name tables #. preprocess rules and create Interest objects #. applies properties from config.rules when asked to #. set the device id and spa factory name; REQUIRED, do not change @@ -43,15 +42,68 @@ msgstr "" #. ensure the node has a description #. also sanitize description, replace ':' with ' ' #. add api.alsa.card.* properties for rule matching purposes +#. apply VM overrides #. apply properties from config.rules #. create the node #. ensure the device has an appropriate name #. deduplicate devices with the same name #. ensure the device has a description -#: src/scripts/monitors/alsa.lua:222 -msgid "Built-in Audio" -msgstr "צליל פנימי" +#: src/scripts/monitors/alsa.lua:236 +msgid "Loopback" +msgstr "לולאה חוזרת" -#: src/scripts/monitors/alsa.lua:224 +#: src/scripts/monitors/alsa.lua:238 +msgid "Built-in Audio" +msgstr "צליל מובנה" + +#: src/scripts/monitors/alsa.lua:240 msgid "Modem" msgstr "מודם" + +#. ensure the device has a nick +#. set the icon name +#. form factor -> icon +#. apply properties from config.rules +#. override the device factory to use ACP +#. use device reservation, if available +#. unlike pipewire-media-session, this logic here keeps the device +#. acquired at all times and destroys it if someone else acquires +#. create the device +#. attempt to acquire again +#. destroy the device +#. TODO enable the jack device +#. TODO disable the jack device +#. create the device +#. handle create-object to prepare device +#. handle object-removed to destroy device reservations and recycle device name +#. reset the name tables to make sure names are recycled +#. activate monitor +#. create the JACK device (for PipeWire to act as client to a JACK server) +#. enable device reservation if requested +#. if the reserve-device plugin is enabled, at the point of script execution +#. it is expected to be connected. if it is not, assume the d-bus connection +#. has failed and continue without it +#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in +#. case D-Bus service is restarted +#. create the monitor +#. WirePlumber +#. +#. Copyright © 2021 Collabora Ltd. +#. @author George Kiagiadakis +#. +#. SPDX-License-Identifier: MIT +#. preprocess rules and create Interest objects +#. applies properties from config.rules when asked to +#. set the device id and spa factory name; REQUIRED, do not change +#. set the default pause-on-idle setting +#. set the node name +#. sanitize name +#. deduplicate nodes with the same name +#. set the node description +#: src/scripts/monitors/libcamera.lua:88 +msgid "Built-in Front Camera" +msgstr "מצלמה פנימית מובנית" + +#: src/scripts/monitors/libcamera.lua:90 +msgid "Built-in Back Camera" +msgstr "מצלמה אחורית מובנית" diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf index ad93ffbd..51043700 100644 --- a/src/config/wireplumber.conf +++ b/src/config/wireplumber.conf @@ -38,7 +38,8 @@ context.modules = [ ## [ flags = [ ifexists | nofail ] ] ## } - ## Uses RTKit to boost the data thread priority. + # Uses RTKit to boost the data thread priority. Also allows clamping + # of utilisation when using the Completely Fair Scheduler on Linux. { name = libpipewire-module-rt args = { @@ -46,6 +47,8 @@ context.modules = [ # rt.prio = 88 # rt.time.soft = -1 # rt.time.hard = -1 + # uclamp.min = 0 + # uclamp.max = 1024 } flags = [ ifexists, nofail ] } @@ -68,6 +71,7 @@ wireplumber.profiles = { main = { check.no-media-session = required support.settings = required + metadata.sm-objects = required hardware.audio = required hardware.bluetooth = required hardware.video-capture = required @@ -239,6 +243,12 @@ wireplumber.components = [ provides = metadata.filters } + ## Provide the "sm-objects" pw_metadata, supporting dynamic loadable objects + { + name = sm-objects.lua, type = script/lua + provides = metadata.sm-objects + } + ## Device monitors' optional features { type = virtual, provides = monitor.alsa.reserve-device, diff --git a/src/scripts/linking/link-target.lua b/src/scripts/linking/link-target.lua index 96c1218f..4736a5b9 100644 --- a/src/scripts/linking/link-target.lua +++ b/src/scripts/linking/link-target.lua @@ -42,8 +42,6 @@ AsyncEventHook { tostring (si_props ["node.name"]), tostring (si_props ["node.id"]))) local exclusive = cutils.parseBool (si_props ["node.exclusive"]) - local passive = cutils.parseBool (si_props ["node.passive"]) or - cutils.parseBool (target_props ["node.passive"]) -- break rescan if tried more than 5 times with same target if si_flags.failed_peer_id ~= nil and @@ -79,10 +77,9 @@ AsyncEventHook { local is_virtual_client_link = target_props ["item.factory.name"] == "si-audio-virtual" log:info (si, - string.format ("link %s <-> %s passive:%s, passthrough:%s, exclusive:%s, virtual-client:%s", + string.format ("link %s <-> %s passthrough:%s, exclusive:%s, virtual-client:%s", tostring (si_props ["node.name"]), tostring (target_props ["node.name"]), - tostring (passive), tostring (passthrough), tostring (exclusive), tostring (is_virtual_client_link))) @@ -92,7 +89,6 @@ AsyncEventHook { if not si_link:configure { ["out.item"] = out_item, ["in.item"] = in_item, - ["passive"] = passive, ["passthrough"] = passthrough, ["exclusive"] = exclusive, ["out.item.port.context"] = "output", diff --git a/src/scripts/policy-dsp.lua b/src/scripts/policy-dsp.lua index 55f86c68..ce23a67a 100644 --- a/src/scripts/policy-dsp.lua +++ b/src/scripts/policy-dsp.lua @@ -28,7 +28,12 @@ nodes_om = ObjectManager { Interest { type = "node" }, } +clients_om = ObjectManager { + Interest { type = "client" } +} + filter_chains = {} +hidden_nodes = {} nodes_om:connect("object-added", function (om, node) for _, r in ipairs(config.rules or {}) do @@ -43,6 +48,17 @@ nodes_om:connect("object-added", function (om, node) filter_chains[id] = LocalModule("libpipewire-module-filter-chain", r.filter_chain, {}, true) end end + + if r.hide_parent then + Log.debug("Hiding node " .. node["bound-id"] .. " from clients") + for client in clients_om:iterate { type = "client" } do + if not client["properties"]["wireplumber.daemon"] then + client:update_permissions { [node["bound-id"]] = "-" } + end + end + hidden_nodes[node["bound-id"]] = id + end + end end end @@ -58,4 +74,13 @@ nodes_om:connect("object-removed", function (om, node) end end) +clients_om:connect("object-added", function (om, client) + for id, _ in pairs(hidden_nodes) do + if not client["properties"]["wireplumber.daemon"] then + client:update_permissions { [id] = "-" } + end + end +end) + nodes_om:activate() +clients_om:activate() diff --git a/src/scripts/sm-objects.lua b/src/scripts/sm-objects.lua new file mode 100644 index 00000000..c22b29d7 --- /dev/null +++ b/src/scripts/sm-objects.lua @@ -0,0 +1,103 @@ +-- WirePlumber +-- +-- Copyright © 2023 Collabora Ltd. +-- @author George Kiagiadakis +-- +-- SPDX-License-Identifier: MIT +-- +-- The script exposes a metadata object named "sm-objects" that clients can +-- use to load objects into the WirePlumber daemon process. The objects are +-- loaded as soon as the metadata is set and are destroyed when the metadata +-- is cleared. +-- +-- To load an object, a client needs to set a metadata entry with: +-- +-- * subject: +-- The ID of the owner of the object; you can use 0 here, but the +-- idea is to be able to restrict which clients can change and/or +-- delete these objects by using IDs of other objects appropriately +-- +-- * key: "" +-- This is the name that will be used to identify the object. +-- If an object with the same name already exists, it will be destroyed. +-- Note that the keys are unique per subject, so you can have multiple +-- objects with the same name as long as they are owned by different subjects. +-- +-- * type: "Spa:String:JSON" +-- +-- * value: "{ type = , +-- name = , +-- args = { ...object arguments... } }" +-- The object type can be one of the following: +-- - "pw-module": loads a pipewire module: `name` and `args` are interpreted +-- just like a module entry in pipewire.conf +-- - "metadata": loads a metadata object with `metadata.name` = `name` +-- and any additional properties provided in `args` +-- + +on_demand_objects = {} + +object_constructors = { + ["pw-module"] = LocalModule, + ["metadata"] = function (name, args) + local m = ImplMetadata (name, args) + m:activate (Features.ALL, function (m, e) + if e then + Log.warning ("failed to activate on-demand metadata `" .. name .. "`: " .. tostring (e)) + end + end) + return m + end +} + +function handle_metadata_changed (m, subject, key, type, value) + -- destroy all objects when metadata is cleared + if not key then + on_demand_objects = {} + return + end + + local object_id = key .. "@" .. tostring(subject) + + -- destroy existing object instance, if needed + if on_demand_objects[object_id] then + Log.debug("destroy on-demand object: " .. object_id) + on_demand_objects[object_id] = nil + end + + if value then + local json = Json.Raw(value) + if not json:is_object() then + Log.warning("loading '".. object_id .. "' failed: expected JSON object, got: '" .. value .. "'") + return + end + + local obj = json:parse(1) + if not obj.type then + Log.warning("loading '".. object_id .. "' failed: no object type specified") + return + end + if not obj.name then + Log.warning("loading '".. object_id .. "' failed: no object name specified") + return + end + + local constructor = object_constructors[obj.type] + if not constructor then + Log.warning("loading '".. object_id .. "' failed: unknown object type: " .. obj.type) + return + end + + Log.info("load on-demand object: " .. object_id .. " -> " .. obj.name) + on_demand_objects[object_id] = constructor(obj.name, obj.args) + end +end + +objects_metadata = ImplMetadata ("sm-objects") +objects_metadata:activate (Features.ALL, function (m, e) + if e then + Log.warning ("failed to activate the sm-objects metadata: " .. tostring (e)) + else + m:connect("changed", handle_metadata_changed) + end +end) diff --git a/tests/wplua/scripts/json.lua b/tests/wplua/scripts/json.lua index c4588d21..52a8a382 100644 --- a/tests/wplua/scripts/json.lua +++ b/tests/wplua/scripts/json.lua @@ -203,3 +203,40 @@ assert (val.name == "wireplumber") assert (val.version[1] == 0) assert (val.version[2] == 4) assert (val.version[3] == 7) + +-- recursion limit +json = Json.Raw ("{ name = wireplumber, version = [0, 4, 15], args = { test = [0, 1] } }") + +val = json:parse (0) +assert (type (val) == "string") +assert (val == "{ name = wireplumber, version = [0, 4, 15], args = { test = [0, 1] } }") + +val = json:parse (1) +assert (type (val) == "table") +assert (val.name == "wireplumber") +assert (type (val.version) == "string") +assert (val.version == "[0, 4, 15]") +assert (type (val.args) == "string") +assert (val.args == "{ test = [0, 1] }") + +val = json:parse(2) +assert (type (val) == "table") +assert (val.name == "wireplumber") +assert (type (val.version) == "table") +assert (val.version[1] == 0) +assert (val.version[2] == 4) +assert (val.version[3] == 15) +assert (type (val.args) == "table") +assert (val.args.test == "[0, 1]") + +val = json:parse(3) +assert (type (val) == "table") +assert (val.name == "wireplumber") +assert (type (val.version) == "table") +assert (val.version[1] == 0) +assert (val.version[2] == 4) +assert (val.version[3] == 15) +assert (type (val.args) == "table") +assert (type (val.args.test) == "table") +assert (val.args.test[1] == 0) +assert (val.args.test[2] == 1)