From 4c1a6f58404d9093df3c359d20182b7e022c5b0a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 4 Dec 2024 19:49:23 +0200 Subject: [PATCH] monitors/bluez: recreate SCO source when loopback is emitted If loopback is emitted after the SCO node, e.g. when A2DP profile connects late, recreate the SCO node. This ensures the underlying SCO node is hidden when the loopback is present. --- src/scripts/monitors/bluez.lua | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/scripts/monitors/bluez.lua b/src/scripts/monitors/bluez.lua index e8da0952..009714d4 100644 --- a/src/scripts/monitors/bluez.lua +++ b/src/scripts/monitors/bluez.lua @@ -20,6 +20,10 @@ config.rules = Conf.get_section_as_json ("monitor.bluez.rules", Json.Array {}) -- This is not a setting, it must always be enabled config.properties["api.bluez5.connection-info"] = true +-- Properties used for previously creating a SCO source node. key: SPA device id +sco_source_node_properties = {} + + devices_om = ObjectManager { Interest { type = "device", @@ -231,6 +235,7 @@ end function createNode(parent, id, type, factory, properties) local dev_props = parent.properties local parent_id = parent["bound-id"] + local parent_spa_id = tonumber(dev_props["api.bluez5.id"]) if cutils.parseBool (config.properties ["bluez5.hw-offload-sco"]) and factory:find("sco") then createOffloadScoNode(parent, id, type, factory, properties) @@ -240,6 +245,7 @@ function createNode(parent, id, type, factory, properties) -- set the device id and spa factory name; REQUIRED, do not change properties["device.id"] = parent_id properties["factory.name"] = factory + properties["api.bluez5.id"] = id -- set the default pause-on-idle setting properties["node.pause-on-idle"] = false @@ -298,7 +304,11 @@ function createNode(parent, id, type, factory, properties) local combine = createSetNode(parent, id, type, factory, properties) parent:store_managed_object(id + COMBINE_OFFSET, combine) else + log:info("Create node: " .. properties["node.name"] .. ": " .. factory .. " " .. tostring (id)) properties["bluez5.loopback"] = false + if factory == "api.bluez5.sco.source" then + sco_source_node_properties[parent_spa_id] = properties + end local node = LocalNode("adapter", properties) node:activate(Feature.Proxy.BOUND) parent:store_managed_object(id, node) @@ -306,6 +316,17 @@ function createNode(parent, id, type, factory, properties) end function removeNode(parent, id) + local dev_props = parent.properties + local parent_spa_id = tonumber(dev_props["api.bluez5.id"]) + local src_properties = sco_source_node_properties[parent_spa_id] + + log:debug("Remove node: " .. tostring (id)) + + if src_properties ~= nil and id == tonumber(src_properties["api.bluez5.id"]) then + log:debug("Clear old SCO properties") + sco_source_node_properties[parent_spa_id] = nil + end + -- Clear also the device set module, if any parent:store_managed_object(id + COMBINE_OFFSET, nil) end @@ -378,10 +399,15 @@ function createDevice(parent, id, type, factory, properties) end end +function removeDevice(parent, id) + sco_source_node_properties[id] = nil +end + function createMonitor() local monitor = SpaDevice("api.bluez5.enum.dbus", config.properties) if monitor then monitor:connect("create-object", createDevice) + monitor:connect("object-removed", removeDevice) else log:notice("PipeWire's BlueZ SPA plugin is missing or broken. " .. "Bluetooth devices will not be supported.") @@ -431,6 +457,7 @@ end function checkProfiles (dev) local device_id = dev["bound-id"] local props = dev.properties + local device_spa_id = tonumber(props["api.bluez5.id"]) -- Don't create loopback source device if autoswitch is disabled if not Settings.get_boolean ("bluetooth.autoswitch-to-headset-profile") then @@ -465,10 +492,32 @@ function checkProfiles (dev) local dev_name = props["api.bluez5.address"] or props["device.name"] local dec_desc = props["device.description"] or props["device.name"] or props["device.nick"] or props["device.alias"] or "bluetooth-device" + + log:info("create SCO loopback node: " .. dev_name) + -- sanitize description, replace ':' with ' ' dec_desc = dec_desc:gsub("(:)", " ") loopback = CreateDeviceLoopbackSource (dev_name, dec_desc, device_id) spa_device:store_managed_object(LOOPBACK_SOURCE_ID, loopback) + + -- recreate any sco source node + local properties = sco_source_node_properties[device_spa_id] + if properties ~= nil then + local node_id = tonumber(properties["api.bluez5.id"]) + local node = spa_device:get_managed_object (node_id) + if node ~= nil then + log:info("Recreate node: " .. properties["node.name"] .. ": " .. + properties["factory.name"] .. " " .. tostring (node_id)) + + spa_device:store_managed_object(node_id, nil) + + properties["bluez5.loopback-target"] = true + properties["api.bluez5.internal"] = true + node = LocalNode("adapter", properties) + node:activate(Feature.Proxy.BOUND) + spa_device:store_managed_object(node_id, node) + end + end end end