From a6d30c6f7792a51de530894fa4e71d5864c7a44b Mon Sep 17 00:00:00 2001 From: Matthew Horan Date: Wed, 25 Oct 2023 09:43:03 -0400 Subject: [PATCH 01/29] docs: fix typo in ALSA passthrough instructions --- docs/rst/configuration/alsa.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rst/configuration/alsa.rst b/docs/rst/configuration/alsa.rst index 8340795b..d5e20843 100644 --- a/docs/rst/configuration/alsa.rst +++ b/docs/rst/configuration/alsa.rst @@ -471,7 +471,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``: From 98f622f7186ccd4cdb401d75c1f1aaff559b4e37 Mon Sep 17 00:00:00 2001 From: Julian Bouzas Date: Thu, 26 Oct 2023 10:49:25 -0400 Subject: [PATCH 02/29] m-default-nodes: clear all previous configured nodes if metadata changes to NULL This will completely clear all the default nodes (current and previous ones) if the configured metadata value has been set to NULL. This is needed so that the 'wpctl clear-default' command completely clears all the default nodes state. --- modules/module-default-nodes.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/module-default-nodes.c b/modules/module-default-nodes.c index 822b75cc..aaf29389 100644 --- a/modules/module-default-nodes.c +++ b/modules/module-default-nodes.c @@ -64,6 +64,13 @@ wp_default_nodes_init (WpDefaultNodes * self) { } +static void +clear_prev_config_values (WpDefaultNode *def) +{ + for (gint i = 0; i < N_PREV_CONFIGS; i++) + g_clear_pointer (&def->prev_config_value[i], g_free); +} + static void update_prev_config_values (WpDefaultNode *def) { @@ -460,12 +467,14 @@ on_metadata_changed (WpMetadata *m, guint32 subject, if (value && !g_strcmp0 (type, "Spa:String:JSON")) { g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (value); g_autofree gchar *name = NULL; - if (wp_spa_json_object_get (json, "name", "s", &name, NULL)) + if (wp_spa_json_object_get (json, "name", "s", &name, NULL)) { self->defaults[node_t].config_value = g_strdup (name); + update_prev_config_values (&self->defaults[node_t]); + } + } else if (!value) { + clear_prev_config_values (&self->defaults[node_t]); } - update_prev_config_values (&self->defaults[node_t]); - wp_debug_object (m, "changed '%s' -> '%s'", key, self->defaults[node_t].config_value); From 7a65d76a57a5a656a5d9385b0144d15b376ddc7d Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 29 Oct 2023 11:03:36 +1000 Subject: [PATCH 03/29] policy-dsp: add ability to hide parent nodes some hardware devices are never supposed to be accessed directly by clients, and are designed under the assumption that they will be front-loaded by some sort of DSP. add a hide_parent property to policy-dsp and revoke all permissions to the bound node of a DSP graph where this is set to prevent hardware misuse or damage by poorly behaved/configured clients. Signed-off-by: James Calligeros --- src/scripts/policy-dsp.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) 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() From 7394f478d6aba3dc51026a9854813a48f2332da0 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Nov 2023 22:04:51 +1000 Subject: [PATCH 04/29] conf: update module-rt usage update the module-rt description and commented-out defaults to reflect the addition of utilisation clamping to the module. Signed-off-by: James Calligeros --- src/config/bluetooth.conf | 5 ++++- src/config/wireplumber.conf | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/config/bluetooth.conf b/src/config/bluetooth.conf index 7a7c1568..e4d666d1 100644 --- a/src/config/bluetooth.conf +++ b/src/config/bluetooth.conf @@ -35,13 +35,16 @@ context.modules = [ # If nofail is given, module initialization failures are ignored. # - # 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 = { nice.level = -11 #rt.prio = 88 #rt.time.soft = -1 #rt.time.hard = -1 + #uclamp.min = 0 + #uclamp.max = 1024 } flags = [ ifexists nofail ] } diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf index 35473e29..85d7be12 100644 --- a/src/config/wireplumber.conf +++ b/src/config/wireplumber.conf @@ -38,13 +38,16 @@ context.modules = [ # If nofail is given, module initialization failures are ignored. # - # 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 = { nice.level = -11 #rt.prio = 88 #rt.time.soft = -1 #rt.time.hard = -1 + #uclamp.min = 0 + #uclamp.max = 1024 } flags = [ ifexists nofail ] } From 3fa87510d1f523901c41039f39522f5c48989e54 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Nov 2023 21:57:53 +1000 Subject: [PATCH 05/29] config: allow passing arguments to pipewire modules Sometimes, it may be necessary to pass arguments in to a Pipewire module being loaded. Allow this to be done using the same format as load_module()/load_optional_module(). Signed-off-by: James Calligeros --- src/config/common/00-functions.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/common/00-functions.lua b/src/config/common/00-functions.lua index 4278e6f9..451ad4f5 100644 --- a/src/config/common/00-functions.lua +++ b/src/config/common/00-functions.lua @@ -14,10 +14,10 @@ function load_optional_module(m, a) end end -function load_pw_module(m) +function load_pw_module(m, a) assert(type(m) == "string", "module name is mandatory, bail out"); if not components[m] then - components[m] = { "libpipewire-module-" .. m, type = "pw_module" } + components[m] = { "libpipewire-module-" .. m, type = "pw_module", args = a } end end From 6eed30cf776519496e6d178d910a2b33a7071aff Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 7 Nov 2023 20:24:12 +0200 Subject: [PATCH 06/29] properties: update doc to mention that it's possible to use JSON in _new_string() --- lib/wp/properties.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wp/properties.c b/lib/wp/properties.c index 42c328e6..b8663737 100644 --- a/lib/wp/properties.c +++ b/lib/wp/properties.c @@ -127,8 +127,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 * From 0bc6ca6a2da3cbe6936354e0e9890a748ad8d999 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 17 Oct 2023 15:40:54 +0300 Subject: [PATCH 07/29] lua: json: allow keys inside objects to be without quotes --- modules/module-lua-scripting/api/json.c | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/module-lua-scripting/api/json.c b/modules/module-lua-scripting/api/json.c index 83074576..611c9248 100644 --- a/modules/module-lua-scripting/api/json.c +++ b/modules/module-lua-scripting/api/json.c @@ -145,7 +145,6 @@ push_luajson (lua_State *L, WpSpaJson *json) WpSpaJson *key = g_value_get_boxed (&item); g_autofree gchar *key_str = NULL; WpSpaJson *value = NULL; - g_warn_if_fail (wp_spa_json_is_string (key)); key_str = wp_spa_json_parse_string (key); g_warn_if_fail (key_str); g_value_unset (&item); From e88fa840f21d5817f3a44753facec72c4961b0b1 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 17 Oct 2023 15:44:05 +0300 Subject: [PATCH 08/29] lua: json: add optional argument in the json parse() method to limit the number of recursions This allows partially parsing a json object, allowing some parts to be passed on as strings to another component that does its own parsing (ex. a pipewire module) --- modules/module-lua-scripting/api/json.c | 13 +++++---- tests/wplua/scripts/json.lua | 37 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/modules/module-lua-scripting/api/json.c b/modules/module-lua-scripting/api/json.c index 611c9248..2883a021 100644 --- a/modules/module-lua-scripting/api/json.c +++ b/modules/module-lua-scripting/api/json.c @@ -95,7 +95,7 @@ spa_json_is_object (lua_State *L) } static void -push_luajson (lua_State *L, WpSpaJson *json) +push_luajson (lua_State *L, WpSpaJson *json, gint n_recursions) { /* Null */ if (wp_spa_json_is_null (json)) { @@ -124,20 +124,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); @@ -151,7 +151,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); } } @@ -168,7 +168,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/tests/wplua/scripts/json.lua b/tests/wplua/scripts/json.lua index c0b5d638..0b18f648 100644 --- a/tests/wplua/scripts/json.lua +++ b/tests/wplua/scripts/json.lua @@ -186,3 +186,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) From a6bea4017269874e1202d9eff748c222e6d18d33 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 17 Oct 2023 18:07:32 +0300 Subject: [PATCH 09/29] core: add wp_core_get_own_bound_id() method This allows retrieving the bound-id of our own client --- lib/wp/core.c | 20 ++++++++++++++++++++ lib/wp/core.h | 3 +++ modules/module-lua-scripting/api/api.c | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/lib/wp/core.c b/lib/wp/core.c index 5429f2ed..9d98b27c 100644 --- a/lib/wp/core.c +++ b/lib/wp/core.c @@ -653,6 +653,26 @@ wp_core_is_connected (WpCore * self) return self->pw_core != NULL; } +/*! + * \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 37997a8c..58eaad2c 100644 --- a/lib/wp/core.h +++ b/lib/wp/core.h @@ -64,6 +64,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/modules/module-lua-scripting/api/api.c b/modules/module-lua-scripting/api/api.c index a3ab36ef..a5f13fd4 100644 --- a/modules/module-lua-scripting/api/api.c +++ b/modules/module-lua-scripting/api/api.c @@ -174,6 +174,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) { @@ -270,6 +279,7 @@ core_require_api (lua_State *L) 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 }, From cf4fb87b351fc8a3d34c3bd6be20b075e2c6634c Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 10 Nov 2023 13:21:05 +0200 Subject: [PATCH 10/29] lua api: allow nil to be passed on all constructors that take optional properties --- modules/module-lua-scripting/api/api.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/module-lua-scripting/api/api.c b/modules/module-lua-scripting/api/api.c index a5f13fd4..d33e79fd 100644 --- a/modules/module-lua-scripting/api/api.c +++ b/modules/module-lua-scripting/api/api.c @@ -873,7 +873,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); } @@ -899,7 +899,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); } @@ -919,7 +919,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); } @@ -977,7 +977,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); } @@ -1086,7 +1086,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); } @@ -1122,7 +1122,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); } From 74d3b550d3e7e4899ec2a8d32081c53de30f72d7 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 10 Nov 2023 16:15:34 +0200 Subject: [PATCH 11/29] metadata: export using the pw_impl_metadata properties --- lib/wp/metadata.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/wp/metadata.c b/lib/wp/metadata.c index f5b0ac46..9486dbc6 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; } From 5b1ff7377fe2950ce361a76d13d9b3667c117ce0 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Mon, 20 Nov 2023 11:56:07 +0200 Subject: [PATCH 12/29] si-audio-endpoint: mark the nodes as passive instead of marking the links This is a behavioural change in effect since pipewire 0.3.68, where pipewire's link-factory ignores the link.passive property and the node.pasive = in/out/true is used instead. --- modules/module-si-audio-endpoint.c | 3 +++ src/scripts/policy-endpoint-device.lua | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/module-si-audio-endpoint.c b/modules/module-si-audio-endpoint.c index 9847fe75..7e5f44b5 100644 --- a/modules/module-si-audio-endpoint.c +++ b/modules/module-si-audio-endpoint.c @@ -237,6 +237,8 @@ si_audio_endpoint_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, @@ -252,6 +254,7 @@ si_audio_endpoint_enable_active (WpSessionItem *si, WpTransition *transition) PW_KEY_MEDIA_CLASS, media, PW_KEY_FACTORY_NAME, "support.null-audio-sink", PW_KEY_NODE_DESCRIPTION, desc, + PW_KEY_NODE_PASSIVE, passive, "monitor.channel-volumes", "true", "wireplumber.is-endpoint", "true", NULL)); diff --git a/src/scripts/policy-endpoint-device.lua b/src/scripts/policy-endpoint-device.lua index 0ba39b03..b593a09f 100644 --- a/src/scripts/policy-endpoint-device.lua +++ b/src/scripts/policy-endpoint-device.lua @@ -110,7 +110,6 @@ function createLink (si_ep, si_target) ["in.item"] = in_item, ["out.item.port.context"] = "output", ["in.item.port.context"] = "input", - ["passive"] = true, ["is.policy.endpoint.device.link"] = true, } then Log.warning (si_link, "failed to configure si-standard-link") From 0a7bd4fe86fe7e70f9557fe1f68b30e998773da8 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Mon, 20 Nov 2023 12:00:54 +0200 Subject: [PATCH 13/29] si-standard-link: remove the "passive" property PipeWire no longer uses the link.passive property, so there's no point in adding it here. The node.passive property is used directly. --- modules/module-si-standard-link.c | 7 ------- src/scripts/policy-node.lua | 8 ++------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/modules/module-si-standard-link.c b/modules/module-si-standard-link.c index 4afe4c8d..4cd0c4a1 100644 --- a/modules/module-si-standard-link.c +++ b/modules/module-si-standard-link.c @@ -22,7 +22,6 @@ struct _WpSiStandardLink GWeakRef in_item; const gchar *out_item_port_context; const gchar *in_item_port_context; - gboolean passive; gboolean passthrough; /* activate */ @@ -68,7 +67,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); @@ -118,9 +116,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); @@ -352,8 +347,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/src/scripts/policy-node.lua b/src/scripts/policy-node.lua index ed06fa25..99ad8473 100644 --- a/src/scripts/policy-node.lua +++ b/src/scripts/policy-node.lua @@ -73,21 +73,17 @@ function createLink (si, si_target, passthrough, exclusive) out_item = si_target end - local passive = parseBool(si_props["node.passive"]) or - parseBool(target_props["node.passive"]) - Log.info ( - string.format("link %s <-> %s passive:%s, passthrough:%s, exclusive:%s", + string.format("link %s <-> %s passthrough:%s, exclusive:%s", tostring(si_props["node.name"]), tostring(target_props["node.name"]), - tostring(passive), tostring(passthrough), tostring(exclusive))) + tostring(passthrough), tostring(exclusive))) -- create and configure link local si_link = SessionItem ( "si-standard-link" ) if not si_link:configure { ["out.item"] = out_item, ["in.item"] = in_item, - ["passive"] = passive, ["passthrough"] = passthrough, ["exclusive"] = exclusive, ["out.item.port.context"] = "output", From 5922f1f0778d2114bd1b19953b3aa934cc8785d8 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Mon, 20 Nov 2023 12:05:53 +0200 Subject: [PATCH 14/29] meson: bump the required pipewire version to 0.3.68 This is for the passive links behavioural change --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 8a4d6564..77eb5494 100644 --- a/meson.build +++ b/meson.build @@ -49,7 +49,7 @@ gmodule_dep = dependency('gmodule-2.0', version : glib_req_version) gio_dep = dependency('gio-2.0', version : glib_req_version) giounix_dep = dependency('gio-unix-2.0', version : glib_req_version) spa_dep = dependency('libspa-0.2', version: '>= 0.2') -pipewire_dep = dependency('libpipewire-0.3', version: '>= 0.3.52') +pipewire_dep = dependency('libpipewire-0.3', version: '>= 0.3.68') mathlib = cc.find_library('m') threads_dep = dependency('threads') libintl_dep = dependency('intl') From eba3d9d6f8d711b50b6c3adc67125037ba81c222 Mon Sep 17 00:00:00 2001 From: Ashok Sidipotu Date: Wed, 22 Nov 2023 05:59:01 +0100 Subject: [PATCH 15/29] policy-endpoint-client.lua: avoid connecting filters to endpoints Equalizer Node or filter nodes are considered as regular linkable and its output is connected to endpoint. Prevent this by skipping over filter nodes in this script. --- src/scripts/policy-endpoint-client.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/scripts/policy-endpoint-client.lua b/src/scripts/policy-endpoint-client.lua index ed4202fb..0b71b028 100644 --- a/src/scripts/policy-endpoint-client.lua +++ b/src/scripts/policy-endpoint-client.lua @@ -239,6 +239,8 @@ linkables_om = ObjectManager { Interest { type = "SiLinkable", "item.factory.name", "=", "si-audio-adapter", type = "pw-global" }, Constraint { "active-features", "!", 0, type = "gobject" }, + Constraint { + "node.link-group", "-" }, } } links_om = ObjectManager { Interest { type = "SiLink", From 83e990bf2d295e0bb72769bf01dbdc647107cc89 Mon Sep 17 00:00:00 2001 From: Ashok Sidipotu Date: Wed, 22 Nov 2023 06:15:27 +0100 Subject: [PATCH 16/29] policy-endpoint-device.lua: enhance link log msgs --- src/scripts/policy-endpoint-device.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/scripts/policy-endpoint-device.lua b/src/scripts/policy-endpoint-device.lua index b593a09f..e6116f14 100644 --- a/src/scripts/policy-endpoint-device.lua +++ b/src/scripts/policy-endpoint-device.lua @@ -99,9 +99,11 @@ function createLink (si_ep, si_target) out_item = si_target end - Log.info (string.format("link %s <-> %s", - ep_props["name"], - target_props["node.name"])) + local link_string = string.format("link %s <-> %s ", + (is_filter and ep_props["node.name"] or ep_props["name"]), + target_props["node.name"]) + + Log.info(si_link, link_string) -- create and configure link local si_link = SessionItem ( "si-standard-link" ) @@ -119,13 +121,15 @@ function createLink (si_ep, si_target) -- register si_link:register () + Log.info (si_link, " activating link " .. link_string) + -- activate si_link:activate (Feature.SessionItem.ACTIVE, function (l, e) if e then - Log.warning (l, "failed to activate si-standard-link: " .. tostring(e)) + Log.warning (l, "failed to activate link: " .. link_string .. tostring(e)) l:remove () else - Log.info (l, "activated si-standard-link") + Log.info (l, "activated link " .. link_string) end end) end From 9f6066ea0da46cba15f7d84cb9135017cba55a36 Mon Sep 17 00:00:00 2001 From: Ashok Sidipotu Date: Wed, 22 Nov 2023 06:49:10 +0100 Subject: [PATCH 17/29] policy-endpoint-device.lua: logic to connect endpoint to filter While handling endpoints, first check to see if there is a filter intending to connect to it. Also prevent Endpoints connecting to filters unless otherwise configured. --- src/scripts/create-item.lua | 1 + src/scripts/policy-endpoint-device.lua | 28 +++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/scripts/create-item.lua b/src/scripts/create-item.lua index 3b2cc832..6d8056aa 100644 --- a/src/scripts/create-item.lua +++ b/src/scripts/create-item.lua @@ -26,6 +26,7 @@ function configProperties(node) ["priority.session"] = np["priority.session"], ["device.id"] = np["device.id"], ["card.profile.device"] = np["card.profile.device"], + ["target.endpoint"] = np["target.endpoint"], } for k, v in pairs(np) do diff --git a/src/scripts/policy-endpoint-device.lua b/src/scripts/policy-endpoint-device.lua index e6116f14..87787528 100644 --- a/src/scripts/policy-endpoint-device.lua +++ b/src/scripts/policy-endpoint-device.lua @@ -41,10 +41,26 @@ function scheduleRescan () end end +function findFilterTarget (props) + + for si_target in linkables_om:iterate { + -- exclude filter targets + Constraint { "node.link-group", "+" }, + } do + local si_props = si_target.properties + if si_props["target.endpoint"] and si_props["target.endpoint"] == props["name"] then + return si_target + end + end +end + function findTargetByDefaultNode (target_media_class) local def_id = default_nodes:call("get-default-node", target_media_class) if def_id ~= Id.INVALID then - for si_target in linkables_om:iterate() do + for si_target in linkables_om:iterate { + -- exclude filter targets + Constraint { "node.link-group", "-" }, + } do local target_node = si_target:get_associated_proxy ("node") if target_node["bound-id"] == def_id then return si_target @@ -55,7 +71,10 @@ function findTargetByDefaultNode (target_media_class) end function findTargetByFirstAvailable (target_media_class) - for si_target in linkables_om:iterate() do + for si_target in linkables_om:iterate { + -- exclude filter targets + Constraint { "node.link-group", "-" }, + } do local target_node = si_target:get_associated_proxy ("node") if target_node.properties["media.class"] == target_media_class then return si_target @@ -76,7 +95,10 @@ function findUndefinedTarget (si_ep) return nil end - local si_target = findTargetByDefaultNode (target_media_class) + local si_target = findFilterTarget (si_ep.properties) + if not si_target then + si_target = findTargetByDefaultNode (target_media_class) + end if not si_target then si_target = findTargetByFirstAvailable (target_media_class) end From a87d0478ea446541b329a057b8fcf121c0d9f1c9 Mon Sep 17 00:00:00 2001 From: Ashok Sidipotu Date: Wed, 22 Nov 2023 07:13:51 +0100 Subject: [PATCH 18/29] policy-endpoint-device.lua: connect filter output to actual sinks The device script will also scan for the filter output streams and connect them to the actual sink devices. --- src/scripts/policy-endpoint-device.lua | 50 +++++++++++++++++++------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/scripts/policy-endpoint-device.lua b/src/scripts/policy-endpoint-device.lua index 87787528..4440c17c 100644 --- a/src/scripts/policy-endpoint-device.lua +++ b/src/scripts/policy-endpoint-device.lua @@ -19,7 +19,13 @@ self.pending_rescan = false function rescan () -- check endpoints and register new links for si_ep in endpoints_om:iterate() do - handleEndpoint (si_ep) + handleLinkable(si_ep) + end + + for filter in streams_om:iterate { + Constraint { "node.link-group", "+" }, + } do + handleFilter(filter) end end @@ -42,7 +48,6 @@ function scheduleRescan () end function findFilterTarget (props) - for si_target in linkables_om:iterate { -- exclude filter targets Constraint { "node.link-group", "+" }, @@ -87,6 +92,7 @@ function findUndefinedTarget (si_ep) local media_class = si_ep.properties["media.class"] local target_class_assoc = { ["Audio/Source"] = "Audio/Source", + ["Stream/Output/Audio"] = "Audio/Sink", ["Audio/Sink"] = "Audio/Sink", ["Video/Source"] = "Video/Source", } @@ -156,13 +162,23 @@ function createLink (si_ep, si_target) end) end -function handleEndpoint (si_ep) - Log.info (si_ep, "handling endpoint " .. si_ep.properties["name"]) +function handleFilter(filter) + handleLinkable(filter) +end + +function handleLinkable (si) + local si_props = si.properties + local is_filter = (si_props["node.link-group"] ~= nil) + if is_filter then + Log.info (si, "handling filter " .. si_props["node.name"]) + else + Log.info (si, "handling endpoint " .. si_props["name"]) + end -- find proper target item - local si_target = findUndefinedTarget (si_ep) + local si_target = findUndefinedTarget (si) if not si_target then - Log.info (si_ep, "... target item not found") + Log.info (si, "... target item not found") return end @@ -170,23 +186,23 @@ function handleEndpoint (si_ep) for link in links_om:iterate() do local out_id = tonumber(link.properties["out.item.id"]) local in_id = tonumber(link.properties["in.item.id"]) - if out_id == si_ep.id or in_id == si_ep.id then - local is_out = out_id == si_ep.id and true or false + if out_id == si.id or in_id == si.id then + local is_out = out_id == si.id and true or false for peer in linkables_om:iterate() do if peer.id == (is_out and in_id or out_id) then if peer.id == si_target.id then - Log.info (si_ep, "... already linked to proper target") + Log.info (si, "... already linked to proper target") return end -- remove old link if active, otherwise schedule rescan if ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then link:remove () - Log.info (si_ep, "... moving to new target") + Log.info (si, "... moving to new target") else scheduleRescan () - Log.info (si_ep, "... scheduled rescan") + Log.info (si, "... scheduled rescan") return end @@ -196,7 +212,7 @@ function handleEndpoint (si_ep) end -- create new link - createLink (si_ep, si_target) + createLink (si, si_target) end function unhandleLinkable (si) @@ -227,6 +243,15 @@ linkables_om = ObjectManager { Constraint { "active-features", "!", 0, type = "gobject" }, } } +streams_om = ObjectManager { + Interest { + type = "SiLinkable", + -- only handle stream si-audio-adapter items + Constraint { "item.factory.name", "=", "si-audio-adapter", type = "pw-global" }, + Constraint { "active-features", "!", 0, type = "gobject" }, + Constraint { "media.class", "=", "Stream/Output/Audio" }, + } +} links_om = ObjectManager { Interest { type = "SiLink", @@ -257,3 +282,4 @@ end) endpoints_om:activate() linkables_om:activate() links_om:activate() +streams_om:activate() From 256f60ebf2fc243896ba864b36437fa49d84adc8 Mon Sep 17 00:00:00 2001 From: Ashok Sidipotu Date: Wed, 15 Nov 2023 15:33:24 +0530 Subject: [PATCH 19/29] policy-device-profile.lua: add a method to prioritize BT codecs. --- src/config/main.lua.d/40-device-defaults.lua | 24 ++++- src/scripts/policy-device-profile.lua | 93 ++++++++++++++++---- 2 files changed, 97 insertions(+), 20 deletions(-) diff --git a/src/config/main.lua.d/40-device-defaults.lua b/src/config/main.lua.d/40-device-defaults.lua index 54e6b931..19202914 100644 --- a/src/config/main.lua.d/40-device-defaults.lua +++ b/src/config/main.lua.d/40-device-defaults.lua @@ -38,6 +38,27 @@ device_defaults.persistent_profiles = { }, } +device_defaults.profile_priorities = { + { + matches = { + { + -- Matches all bluez devices + { "device.name", "matches", "bluez_card.*" }, + }, + }, + -- lower the index higher the priority + priorities = { + -- "a2dp-sink-sbc", + -- "a2dp-sink-aptx_ll", + -- "a2dp-sink-aptx", + -- "a2dp-sink-aptx_hd", + -- "a2dp-sink-ldac", + -- "a2dp-sink-aac", + -- "a2dp-sink-sbc_xq", + } + }, +} + function device_defaults.enable() if device_defaults.enabled == false then return @@ -48,7 +69,8 @@ function device_defaults.enable() -- Selects appropriate profile for devices load_script("policy-device-profile.lua", { - persistent = device_defaults.persistent_profiles + persistent = device_defaults.persistent_profiles, + priorities = device_defaults.profile_priorities }) -- Selects appropriate device routes ("ports" in pulseaudio terminology) diff --git a/src/scripts/policy-device-profile.lua b/src/scripts/policy-device-profile.lua index 7d8637e2..af10f9b7 100644 --- a/src/scripts/policy-device-profile.lua +++ b/src/scripts/policy-device-profile.lua @@ -8,24 +8,31 @@ local self = {} self.config = ... or {} self.config.persistent = self.config.persistent or {} +self.config.priorities = self.config.priorities or {} self.active_profiles = {} self.default_profile_plugin = Plugin.find("default-profile") --- Preprocess persisten profiles and create Interest objects -for _, p in ipairs(self.config.persistent or {}) do - p.interests = {} - for _, i in ipairs(p.matches) do - local interest_desc = { type = "properties" } - for _, c in ipairs(i) do - c.type = "pw" - table.insert(interest_desc, Constraint(c)) +function createIntrestObjects(t) + for _, p in ipairs(t or {}) do + p.interests = {} + for _, i in ipairs(p.matches) do + local interest_desc = { type = "properties" } + for _, c in ipairs(i) do + c.type = "pw" + table.insert(interest_desc, Constraint(c)) + end + local interest = Interest(interest_desc) + table.insert(p.interests, interest) end - local interest = Interest(interest_desc) - table.insert(p.interests, interest) + p.matches = nil end - p.matches = nil end +-- Preprocess persistent profiles and create Interest objects +createIntrestObjects(self.config.persistent) +-- Preprocess profile priorities and create Interest objects +createIntrestObjects(self.config.priorities) + -- Checks whether a device profile is persistent or not function isProfilePersistent(device_props, profile_name) for _, p in ipairs(self.config.persistent or {}) do @@ -44,7 +51,6 @@ function isProfilePersistent(device_props, profile_name) return false end - function parseParam(param, id) local parsed = param:parse() if parsed.pod_type == "Object" and parsed.object_id == id then @@ -89,12 +95,52 @@ function findDefaultProfile (device) return nil end -function findBestProfile (device) +-- returns the priorities, if defined +function getDevicePriorities(device_props, profile_name) + for _, p in ipairs(self.config.priorities or {}) do + for _, interest in ipairs(p.interests) do + if interest:matches(device_props) then + return p.priorities + end + end + end + + return nil +end + +-- find profiles based on user preferences. +function findPreferredProfile(device) + local priority_table = getDevicePriorities(device.properties) + + if not priority_table or #priority_table == 0 then + return nil + else + Log.info("priority table found for device " .. + device.properties["device.name"]) + end + + for _, priority_profile in ipairs(priority_table) do + for p in device:iterate_params("EnumProfile") do + device_profile = parseParam(p, "EnumProfile") + if device_profile.name == priority_profile then + Log.info("Selected user preferred profile " .. + device_profile.name .. " for " .. device.properties["device.name"]) + return device_profile + end + end + end + + return nil +end + +-- find profiles based on inbuilt priorities. +function findBestProfile(device) -- Takes absolute priority if available or unknown local profile_prop = device.properties["device.profile"] local off_profile = nil local best_profile = nil local unk_profile = nil + local profile = nil for p in device:iterate_params("EnumProfile") do profile = parseParam(p, "EnumProfile") @@ -116,14 +162,19 @@ function findBestProfile (device) end if best_profile ~= nil then - return best_profile + profile = best_profile elseif unk_profile ~= nil then - return unk_profile + profile = unk_profile elseif off_profile ~= nil then - return off_profile + profile = off_profile end - return nil + if profile ~= nil then + Log.info("Found best profile " .. profile.name .. " for " .. device.properties["device.name"]) + return profile + else + return nil + end end function handleProfiles (device, new_device) @@ -156,9 +207,13 @@ function handleProfiles (device, new_device) Log.info ("Default profile not found for " .. dev_name) end - local best_profile = findBestProfile (device) + local best_profile = findPreferredProfile(device) + + if not best_profile then + best_profile = findBestProfile(device) + end + if best_profile ~= nil then - Log.info ("Found best profile " .. best_profile.name .. " for " .. dev_name) setDeviceProfile (device, dev_id, dev_name, best_profile) else Log.info ("Best profile not found on " .. dev_name) From 0ac2947aed039236bb2409336d7e87d0ef09756a Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 17 Oct 2023 18:12:18 +0300 Subject: [PATCH 20/29] scripts: add new sm-objects script This allows loading objects on demand by adding entries on the "sm-objects" metadata object. It is useful to dynamically load pipewire modules such as loopbacks or network modules without having to start a new pipewire process with a hardcoded config file. It is also useful to load new metadata objects in order to implement the singleton metatada concept as discussed in pipewire!1742 This may be expanded in the future to be able to load other types of objects. The key name, combined with the subject, is considered a unique id for this instance of the object. The value should be a json object with a 'type' specifying the type of object, together with a 'name' and 'args' --- src/config/main.lua.d/90-enable-all.lua | 3 + src/scripts/sm-objects.lua | 103 ++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/scripts/sm-objects.lua diff --git a/src/config/main.lua.d/90-enable-all.lua b/src/config/main.lua.d/90-enable-all.lua index 1aa194df..37d790ea 100644 --- a/src/config/main.lua.d/90-enable-all.lua +++ b/src/config/main.lua.d/90-enable-all.lua @@ -21,3 +21,6 @@ load_script("intended-roles.lua") -- Automatically suspends idle nodes after 3 seconds load_script("suspend-node.lua") + +-- Allows loading objects on demand via metadata +load_script("sm-objects.lua") 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) From 0d249b8a13d7168fe54fa6eb1db1c4a5fcc8d3f8 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Wed, 22 Nov 2023 16:20:02 +0200 Subject: [PATCH 21/29] release 0.4.16 --- NEWS.rst | 61 +++++++++++++++++++++++++++++++++++++++++++++++++---- meson.build | 2 +- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 409156f6..f8b48281 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,6 +1,62 @@ -WirePlumber 0.4.15 +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 + +Past releases +~~~~~~~~~~~~~ + +WirePlumber 0.4.15 +.................. + Additions: - A new "DSP policy" module has been added; its purpose is to automatically @@ -51,9 +107,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/meson.build b/meson.build index 77eb5494..7e6721e8 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wireplumber', ['c'], - version : '0.4.15', + version : '0.4.16', license : 'MIT', meson_version : '>= 0.59.0', default_options : [ From e0cb9a845fe337cfaab369f743c354bfb7ce5970 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 25 Nov 2023 12:27:15 +0000 Subject: [PATCH 22/29] Update fa.po --- po/fa.po | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 "مودم" From e9fc965b3222884c8234ddfc760308d29527f62a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 29 Nov 2023 11:00:08 +0100 Subject: [PATCH 23/29] scripts: log si_link after creating it Or else we get a exception because it is nil. --- src/scripts/policy-endpoint-device.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/policy-endpoint-device.lua b/src/scripts/policy-endpoint-device.lua index 4440c17c..57e22c5c 100644 --- a/src/scripts/policy-endpoint-device.lua +++ b/src/scripts/policy-endpoint-device.lua @@ -131,10 +131,11 @@ function createLink (si_ep, si_target) (is_filter and ep_props["node.name"] or ep_props["name"]), target_props["node.name"]) - Log.info(si_link, link_string) - -- create and configure link local si_link = SessionItem ( "si-standard-link" ) + + Log.info(si_link, link_string) + if not si_link:configure { ["out.item"] = out_item, ["in.item"] = in_item, From a063d48281acf535d519115db21e573e8d9f47d4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 29 Nov 2023 11:01:34 +0100 Subject: [PATCH 24/29] scripts: pass is_filter to createLink createLink uses an is_link variable that is unset. Fix this by passing the is_link from the caller. --- src/scripts/policy-endpoint-device.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/policy-endpoint-device.lua b/src/scripts/policy-endpoint-device.lua index 57e22c5c..102e2368 100644 --- a/src/scripts/policy-endpoint-device.lua +++ b/src/scripts/policy-endpoint-device.lua @@ -111,7 +111,7 @@ function findUndefinedTarget (si_ep) return si_target end -function createLink (si_ep, si_target) +function createLink (si_ep, si_target, is_filter) local out_item = nil local in_item = nil local ep_props = si_ep.properties @@ -213,7 +213,7 @@ function handleLinkable (si) end -- create new link - createLink (si, si_target) + createLink (si, si_target, is_filter) end function unhandleLinkable (si) From 4cc387d81d217c7019accdc00f387c5a7650be46 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Thu, 30 Nov 2023 12:54:21 +0200 Subject: [PATCH 25/29] object-manager: ref all object managers before exposing tmp globals It is possible that during this process some object managers emit their "installed" signal, and it is possible that some object managers are destroyed within the handler of this signal, ending up with a dangling object manager pointer (since we do not ref object managers in the registry) and with a modified object_managers list during iteration... Related to: #534 --- lib/wp/object-manager.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/wp/object-manager.c b/lib/wp/object-manager.c index 816db6dc..46ec02b9 100644 --- a/lib/wp/object-manager.c +++ b/lib/wp/object-manager.c @@ -1052,6 +1052,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)) @@ -1091,9 +1092,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); From e193ae0efc75c66e4f286e253f4c420c5acee92b Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Sun, 3 Dec 2023 19:46:20 +0200 Subject: [PATCH 26/29] policy-endpoint-device: handle filters only if we have endpoints Otherwise the filters are handled both in policy-node and here and everything is messed up Fixes: #536 --- src/scripts/policy-endpoint-device.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/scripts/policy-endpoint-device.lua b/src/scripts/policy-endpoint-device.lua index 102e2368..8fb45b1f 100644 --- a/src/scripts/policy-endpoint-device.lua +++ b/src/scripts/policy-endpoint-device.lua @@ -22,10 +22,13 @@ function rescan () handleLinkable(si_ep) end - for filter in streams_om:iterate { - Constraint { "node.link-group", "+" }, - } do - handleFilter(filter) + -- handle filters only if we have endpoints + if endpoints_om:get_n_objects () > 0 then + for filter in streams_om:iterate { + Constraint { "node.link-group", "+" }, + } do + handleFilter(filter) + end end end From d3eb77b292655cef333a8f4cab4e861415bc37c2 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Sun, 3 Dec 2023 20:05:19 +0200 Subject: [PATCH 27/29] release 0.4.17 --- NEWS.rst | 22 ++++++++++++++++++---- meson.build | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index f8b48281..10925def 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,6 +1,23 @@ -WirePlumber 0.4.16 +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 @@ -51,9 +68,6 @@ Fixes: - Used an idle callback instead of pw_core_sync() in the object manager to expose tmp globals -Past releases -~~~~~~~~~~~~~ - WirePlumber 0.4.15 .................. diff --git a/meson.build b/meson.build index 7e6721e8..ed352e6c 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wireplumber', ['c'], - version : '0.4.16', + version : '0.4.17', license : 'MIT', meson_version : '>= 0.59.0', default_options : [ From bae3381c760d86278e0a61e6f819c0ee17575bfd Mon Sep 17 00:00:00 2001 From: "Tom A. Wagner" Date: Tue, 12 Dec 2023 12:08:20 +0100 Subject: [PATCH 28/29] meson: Set correct package version for generated .gir file for gobject introspection Previously the package version used for the gir file was hardcoded and did not change when the `wireplumber_api_version` definition was changed, which has now been fixed to use that definition. --- docs/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/meson.build b/docs/meson.build index 6b04bcb1..b49de49b 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -140,7 +140,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], From 1e03b5bbe1f3a273c66035581e81d5bb2ddb4ead Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Wed, 13 Dec 2023 09:24:38 +0000 Subject: [PATCH 29/29] Added Hebrew translation. --- po/he.po | 74 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 11 deletions(-) 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 "מצלמה אחורית מובנית"