linking: Add ALSA loopback policy scripts

This policy script is in charge of linking all nodes from all ALSA loopback
collections.
This commit is contained in:
Julian Bouzas 2025-12-17 09:38:15 -05:00
parent c81cd4cd96
commit dfeb57934d
3 changed files with 209 additions and 0 deletions

View file

@ -719,6 +719,14 @@ wireplumber.components = [
provides = hooks.linking.default.target.get-filter-from
requires = [ metadata.filters ]
}
{
name = linking/alsa-loopback/find-filter-target.lua, type = script/lua
provides = hooks.linking.alsa-loopback.target.find-filter
}
{
name = linking/alsa-loopback/find-alsa-target.lua, type = script/lua
provides = hooks.linking.alsa-loopback.target.find-alsa
}
{
name = linking/prepare-link.lua, type = script/lua
provides = hooks.linking.target.prepare-link
@ -747,6 +755,8 @@ wireplumber.components = [
hooks.linking.default.target.find-default,
hooks.linking.default.target.find-best,
hooks.linking.default.target.get-filter-from,
hooks.linking.alsa-loopback.target.find-filter,
hooks.linking.alsa-loopback.target.find-alsa,
hooks.linking.pause-playback ]
}

View file

@ -0,0 +1,102 @@
-- WirePlumber
--
-- Copyright © 2025 Collabora Ltd.
--
-- SPDX-License-Identifier: MIT
--
-- Finds the hardware target for a linkable in 'alsa_loopback.*' collection
lutils = require ("linking-utils")
cutils = require ("common-utils")
log = Log.open_topic ("s-linking")
SimpleEventHook {
name = "linking/alsa-loopback/find-alsa-target",
before = "linking/prepare-link",
interests = {
EventInterest {
Constraint { "event.type", "=", "select-target" },
Constraint { "collection.name", "#", "alsa_loopback.*" },
},
},
execute = function (event)
local source, om, si, si_props, si_flags, target =
lutils:unwrap_select_target_event (event)
-- Bypass the hook if the target is already picked up
if target then
return
end
-- Get the collection name
local collection_name = si_props["collection.name"]
assert (collection_name)
local target_direction = cutils.getTargetDirection (si_props)
local target_picked = nil
local target_can_passthrough = false
local target_priority = 0
log:info (string.format ("handling item %d: %s (%s)", si.id,
si_props ["node.name"], si_props ["node.id"]))
-- Find the hardware target (session items without node.link-group property)
-- matching the collection name
for target in om:iterate {
type = "SiLinkable",
Constraint { "item.node.type", "=", "device" },
Constraint { "item.node.direction", "=", target_direction },
Constraint { "media.type", "=", si_props ["media.type"] },
Constraint { "collection.name", "=", collection_name },
Constraint { "node.link-group", "-" },
} do
local target_props = target.properties
local priority = tonumber (target_props ["priority.session"]) or 0
log:debug (string.format ("Looking at: %s (%s)",
target_props ["node.name"], target_props ["node.id"]))
if not lutils.canLink (si_props, target) then
log:debug ("... cannot link, skip linkable")
goto skip_linkable
end
local passthrough_compatible, can_passthrough =
lutils.checkPassthroughCompatibility (si, target)
if not passthrough_compatible then
log:debug ("... passthrough is not compatible, skip linkable")
goto skip_linkable
end
if target_picked == nil or priority > target_priority then
log:debug ("... picked")
target_picked = target
target_can_passthrough = can_passthrough
target_priority = priority
end
::skip_linkable::
end
local can_passthrough, passthrough_compatible
if target then
passthrough_compatible, can_passthrough =
lutils.checkPassthroughCompatibility (si, target)
if lutils.canLink (si_props, target) and passthrough_compatible then
target_picked = true
end
end
if target_picked then
log:info (si,
string.format ("... hardware target picked: %s (%s), can_passthrough:%s",
target_picked.properties ["node.name"],
target_picked.properties ["node.id"],
tostring (target_can_passthrough)))
si_flags.can_passthrough = target_can_passthrough
event:set_data ("target", target_picked)
else
event:stop_processing ()
end
end
}:register ()

View file

@ -0,0 +1,97 @@
-- WirePlumber
--
-- Copyright © 2026 Collabora Ltd.
--
-- SPDX-License-Identifier: MIT
--
-- Finds the filter target for a linkable in 'alsa_loopback.*' collection
lutils = require ("linking-utils")
cutils = require ("common-utils")
log = Log.open_topic ("s-linking")
SimpleEventHook {
name = "linking/alsa-loopback/find-filter-target",
before = "linking/alsa-loopback/find-alsa-target",
interests = {
EventInterest {
Constraint { "event.type", "=", "select-target" },
Constraint { "collection.name", "#", "alsa_loopback.*" },
},
},
execute = function (event)
local source, om, si, si_props, si_flags, target =
lutils:unwrap_select_target_event (event)
-- Bypass the hook if the target is already picked up
if target then
return
end
-- Get the collection name
local collection_name = si_props["collection.name"]
assert (collection_name)
local target_direction = cutils.getTargetDirection (si_props)
local target_picked = nil
local target_can_passthrough = false
log:info (string.format ("handling item %d: %s (%s)", si.id,
si_props ["node.name"], si_props ["node.id"]))
-- Find the first filter target (session items with node.link-group property
-- that is not an alsa loopback item) matching the collection name
for target in om:iterate {
type = "SiLinkable",
Constraint { "item.node.type", "=", "device" },
Constraint { "item.node.direction", "=", target_direction },
Constraint { "media.type", "=", si_props ["media.type"] },
Constraint { "collection.name", "=", collection_name },
Constraint { "node.link-group", "+" },
Constraint { "alsa.loopback.id", "-" },
} do
local target_props = target.properties
log:debug (string.format ("Looking at: %s (%s)",
target_props ["node.name"], target_props ["node.id"]))
if not lutils.canLink (si_props, target) then
log:debug ("... cannot link, skip linkable")
goto skip_linkable
end
local passthrough_compatible, can_passthrough =
lutils.checkPassthroughCompatibility (si, target)
if not passthrough_compatible then
log:debug ("... passthrough is not compatible, skip linkable")
goto skip_linkable
end
log:debug ("... picked")
target_picked = target
target_can_passthrough = can_passthrough
break
::skip_linkable::
end
local can_passthrough, passthrough_compatible
if target then
passthrough_compatible, can_passthrough =
lutils.checkPassthroughCompatibility (si, target)
if lutils.canLink (si_props, target) and passthrough_compatible then
target_picked = true
end
end
if target_picked then
log:info (si,
string.format ("... filter target picked: %s (%s), can_passthrough:%s",
target_picked.properties ["node.name"],
target_picked.properties ["node.id"],
tostring (target_can_passthrough)))
si_flags.can_passthrough = target_can_passthrough
event:set_data ("target", target_picked)
end
end
}:register ()