Merge branch 'master' into next

This commit is contained in:
George Kiagiadakis 2023-12-23 18:34:00 +02:00
commit c2d125b0da
19 changed files with 385 additions and 65 deletions

View File

@ -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
..................

View File

@ -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],

View File

@ -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``:

View File

@ -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
*

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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 *

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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));

View File

@ -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,

View File

@ -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 <dani.behzi@ubuntu.com>, 2022.
# Danial Behzadi <dani.behzi@ubuntu.com>, 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 <dani.behzi@ubuntu.com>\n"
"Language-Team: Persian <fa@li.org>\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 "مودم"

View File

@ -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 <sh.yaron@gmail.com>\n"
"Language-Team: Hebrew <https://translate.fedoraproject.org/projects/pipewire/"
"pipewire/he/>\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 <george.kiagiadakis@collabora.com>
#.
#. 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 "מצלמה אחורית מובנית"

View File

@ -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,

View File

@ -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",

View File

@ -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()

103
src/scripts/sm-objects.lua Normal file
View File

@ -0,0 +1,103 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- 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: "<UNIQUE-OBJECT-NAME>"
-- 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 = <object-type>,
-- name = <object-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)

View File

@ -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)