2021-04-08 14:43:38 -04:00
|
|
|
-- WirePlumber
|
|
|
|
|
--
|
|
|
|
|
-- Copyright © 2021 Collabora Ltd.
|
|
|
|
|
-- @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
|
|
|
--
|
|
|
|
|
-- SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
-- Receive script arguments from config.lua
|
2022-01-13 11:08:34 +02:00
|
|
|
local config = ... or {}
|
2021-04-15 18:14:25 +03:00
|
|
|
config.roles = config.roles or {}
|
|
|
|
|
|
2021-12-12 12:28:42 -05:00
|
|
|
local self = {}
|
|
|
|
|
self.scanning = false
|
|
|
|
|
self.pending_rescan = false
|
|
|
|
|
|
|
|
|
|
function rescan ()
|
|
|
|
|
for si in linkables_om:iterate() do
|
|
|
|
|
handleLinkable (si)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function scheduleRescan ()
|
|
|
|
|
if self.scanning then
|
|
|
|
|
self.pending_rescan = true
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
self.scanning = true
|
|
|
|
|
rescan ()
|
|
|
|
|
self.scanning = false
|
|
|
|
|
|
|
|
|
|
if self.pending_rescan then
|
|
|
|
|
self.pending_rescan = false
|
|
|
|
|
Core.sync(function ()
|
|
|
|
|
scheduleRescan ()
|
|
|
|
|
end)
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-10-19 14:02:18 -04:00
|
|
|
|
2022-01-11 15:31:40 +05:30
|
|
|
function findRole(role, tmc)
|
2021-04-15 18:14:25 +03:00
|
|
|
if role and not config.roles[role] then
|
|
|
|
|
for r, p in pairs(config.roles) do
|
2022-01-11 15:31:40 +05:30
|
|
|
-- default media class can be overridden in the role config data
|
|
|
|
|
mc = p["media.class"] or "Audio/Sink"
|
|
|
|
|
if (type(p.alias) == "table" and tmc == mc) then
|
2021-04-15 18:14:25 +03:00
|
|
|
for i = 1, #(p.alias), 1 do
|
|
|
|
|
if role == p.alias[i] then
|
|
|
|
|
return r
|
|
|
|
|
end
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-04-15 18:14:25 +03:00
|
|
|
return role
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
|
2021-05-10 15:17:57 -04:00
|
|
|
function findTargetEndpoint (node, media_class)
|
|
|
|
|
local target_class_assoc = {
|
|
|
|
|
["Stream/Input/Audio"] = "Audio/Source",
|
|
|
|
|
["Stream/Output/Audio"] = "Audio/Sink",
|
|
|
|
|
["Stream/Input/Video"] = "Video/Source",
|
|
|
|
|
}
|
|
|
|
|
local media_role = nil
|
2021-04-08 14:43:38 -04:00
|
|
|
local highest_priority = -1
|
|
|
|
|
local target = nil
|
|
|
|
|
|
2021-05-10 15:17:57 -04:00
|
|
|
-- get target media class
|
|
|
|
|
local target_media_class = target_class_assoc[media_class]
|
|
|
|
|
if not target_media_class then
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-08 14:43:38 -04:00
|
|
|
-- find highest priority endpoint by role
|
2022-01-11 15:31:40 +05:30
|
|
|
media_role = findRole(node.properties["media.role"], target_media_class)
|
2021-10-19 14:02:18 -04:00
|
|
|
for si_target_ep in endpoints_om:iterate {
|
2021-04-15 18:14:25 +03:00
|
|
|
Constraint { "role", "=", media_role, type = "pw-global" },
|
|
|
|
|
Constraint { "media.class", "=", target_media_class, type = "pw-global" },
|
|
|
|
|
} do
|
|
|
|
|
local priority = tonumber(si_target_ep.properties["priority"])
|
|
|
|
|
if priority > highest_priority then
|
|
|
|
|
highest_priority = priority
|
|
|
|
|
target = si_target_ep
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return target
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function createLink (si, si_target_ep)
|
|
|
|
|
local out_item = nil
|
|
|
|
|
local in_item = nil
|
2021-10-19 14:02:18 -04:00
|
|
|
local si_props = si.properties
|
|
|
|
|
local target_ep_props = si_target_ep.properties
|
2021-04-08 14:43:38 -04:00
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
if si_props["item.node.direction"] == "output" then
|
2021-04-08 14:43:38 -04:00
|
|
|
-- playback
|
|
|
|
|
out_item = si
|
|
|
|
|
in_item = si_target_ep
|
2021-10-19 14:02:18 -04:00
|
|
|
else
|
|
|
|
|
-- capture
|
|
|
|
|
out_item = si_target_ep
|
|
|
|
|
in_item = si
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Log.info (string.format("link %s <-> %s",
|
2021-10-19 14:02:18 -04:00
|
|
|
tostring(si_props["node.name"]),
|
|
|
|
|
tostring(target_ep_props["name"])))
|
2021-04-08 14:43:38 -04:00
|
|
|
|
|
|
|
|
-- create and configure link
|
|
|
|
|
local si_link = SessionItem ( "si-standard-link" )
|
|
|
|
|
if not si_link:configure {
|
|
|
|
|
["out.item"] = out_item,
|
|
|
|
|
["in.item"] = in_item,
|
2021-10-08 00:09:42 +03:00
|
|
|
["out.item.port.context"] = "output",
|
|
|
|
|
["in.item.port.context"] = "input",
|
2021-04-09 12:03:06 -04:00
|
|
|
["is.policy.endpoint.client.link"] = true,
|
2021-10-19 14:02:18 -04:00
|
|
|
["media.role"] = target_ep_props["role"],
|
|
|
|
|
["target.media.class"] = target_ep_props["media.class"],
|
|
|
|
|
["item.plugged.usec"] = si_props["item.plugged.usec"],
|
2021-04-08 14:43:38 -04:00
|
|
|
} then
|
|
|
|
|
Log.warning (si_link, "failed to configure si-standard-link")
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-13 19:35:10 +03:00
|
|
|
-- register
|
|
|
|
|
si_link:register()
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
function checkLinkable (si)
|
2021-04-08 14:43:38 -04:00
|
|
|
-- only handle session items that has a node associated proxy
|
|
|
|
|
local node = si:get_associated_proxy ("node")
|
|
|
|
|
if not node or not node.properties then
|
2021-05-10 15:17:57 -04:00
|
|
|
return false
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
|
2021-05-10 15:17:57 -04:00
|
|
|
-- only handle stream session items
|
2021-04-08 14:43:38 -04:00
|
|
|
local media_class = node.properties["media.class"]
|
2021-05-28 18:44:11 +03:00
|
|
|
if not media_class or not string.find (media_class, "Stream") then
|
2021-05-10 15:17:57 -04:00
|
|
|
return false
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Determine if we can handle item by this policy
|
|
|
|
|
local media_role = node.properties["media.role"]
|
2021-10-19 14:02:18 -04:00
|
|
|
if endpoints_om:get_n_objects () == 0 or media_role == nil then
|
2021-10-08 00:25:55 +03:00
|
|
|
Log.debug (si, "item won't be handled by this policy")
|
2021-05-10 15:17:57 -04:00
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
function handleLinkable (si)
|
|
|
|
|
if not checkLinkable (si) then
|
2021-04-08 14:43:38 -04:00
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
2021-05-10 15:17:57 -04:00
|
|
|
local node = si:get_associated_proxy ("node")
|
2021-06-04 16:49:05 +03:00
|
|
|
local media_class = node.properties["media.class"] or ""
|
|
|
|
|
local media_role = node.properties["media.role"] or ""
|
|
|
|
|
Log.info (si, "handling item " .. tostring(node.properties["node.name"]) ..
|
2021-04-08 14:43:38 -04:00
|
|
|
" with role " .. media_role)
|
|
|
|
|
|
|
|
|
|
-- find proper target endpoint
|
2021-05-10 15:17:57 -04:00
|
|
|
local si_target_ep = findTargetEndpoint (node, media_class)
|
2021-04-08 14:43:38 -04:00
|
|
|
if not si_target_ep then
|
2021-10-19 14:02:18 -04:00
|
|
|
Log.info (si, "... target endpoint not found")
|
2021-04-08 14:43:38 -04:00
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
-- Check if item is linked to proper target, otherwise re-link
|
|
|
|
|
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.id or in_id == si.id then
|
|
|
|
|
local is_out = out_id == si.id and true or false
|
|
|
|
|
for peer_ep in endpoints_om:iterate() do
|
|
|
|
|
if peer_ep.id == (is_out and in_id or out_id) then
|
|
|
|
|
|
|
|
|
|
if peer_ep.id == si_target_ep.id then
|
|
|
|
|
Log.info (si, "... already linked to proper target endpoint")
|
|
|
|
|
return
|
|
|
|
|
end
|
2021-04-08 14:43:38 -04:00
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
-- remove old link if active, otherwise schedule rescan
|
|
|
|
|
if ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then
|
|
|
|
|
link:remove ()
|
|
|
|
|
Log.info (si, "... moving to new target")
|
|
|
|
|
else
|
2021-12-12 12:28:42 -05:00
|
|
|
scheduleRescan ()
|
2021-10-19 14:02:18 -04:00
|
|
|
Log.info (si, "... scheduled rescan")
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- create new link
|
|
|
|
|
createLink (si, si_target_ep)
|
|
|
|
|
end
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
function unhandleLinkable (si)
|
|
|
|
|
if not checkLinkable (si) then
|
2021-05-10 15:17:57 -04:00
|
|
|
return
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
|
2021-05-10 15:17:57 -04:00
|
|
|
local node = si:get_associated_proxy ("node")
|
2021-06-04 16:49:05 +03:00
|
|
|
Log.info (si, "unhandling item " .. tostring(node.properties["node.name"]))
|
2021-05-10 15:17:57 -04:00
|
|
|
|
|
|
|
|
-- remove any links associated with this item
|
2021-10-19 14:02:18 -04:00
|
|
|
for silink in links_om:iterate() do
|
2021-05-13 07:25:43 -04:00
|
|
|
local out_id = tonumber (silink.properties["out.item.id"])
|
|
|
|
|
local in_id = tonumber (silink.properties["in.item.id"])
|
|
|
|
|
if out_id == si.id or in_id == si.id then
|
|
|
|
|
silink:remove ()
|
2021-10-19 14:02:18 -04:00
|
|
|
Log.info (silink, "... link removed")
|
2021-04-08 14:43:38 -04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
|
|
|
|
|
linkables_om = ObjectManager { Interest { type = "SiLinkable",
|
2021-04-08 14:43:38 -04:00
|
|
|
-- only handle si-audio-adapter and si-node
|
|
|
|
|
Constraint {
|
2021-10-08 00:09:42 +03:00
|
|
|
"item.factory.name", "c", "si-audio-adapter", "si-node", type = "pw-global" },
|
2021-04-08 14:43:38 -04:00
|
|
|
}
|
|
|
|
|
}
|
2021-10-19 14:02:18 -04:00
|
|
|
links_om = ObjectManager { Interest { type = "SiLink",
|
2021-04-08 14:43:38 -04:00
|
|
|
-- only handle links created by this policy
|
2021-04-09 12:03:06 -04:00
|
|
|
Constraint { "is.policy.endpoint.client.link", "=", true, type = "pw-global" },
|
2021-04-08 14:43:38 -04:00
|
|
|
} }
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
linkables_om:connect("objects-changed", function (om)
|
2021-12-12 12:28:42 -05:00
|
|
|
scheduleRescan ()
|
2021-05-10 15:17:57 -04:00
|
|
|
end)
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
linkables_om:connect("object-removed", function (om, si)
|
|
|
|
|
unhandleLinkable (si)
|
2021-04-08 14:43:38 -04:00
|
|
|
end)
|
|
|
|
|
|
2021-10-19 14:02:18 -04:00
|
|
|
endpoints_om:activate()
|
|
|
|
|
linkables_om:activate()
|
|
|
|
|
links_om:activate()
|