mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-08 15:08:04 +02:00
policy-node.lua: Second round of cleanup.
- WirePlumber Lua now facilitates Lua libraries/modules, utilize this and create modules. Add some tests around this functionality. - Create policy-hooks.lua containing all the hooks to find-target events - Create policy-utils.lua module and push all the policy utility functions to it. - Create common-utils.lua module and push the common utility functions to it. - Remove all the above functionality from policy-node.lua and clean it up.
This commit is contained in:
parent
3832e14c1c
commit
72536261e9
9 changed files with 1034 additions and 815 deletions
|
|
@ -30,8 +30,12 @@ typedef enum
|
|||
{
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_BASE = 0,
|
||||
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_TARGET_SI = WP_EVENT_HOOK_DEFAULT_PRIORITY_BASE,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_RESCAN_POLICY = WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_TARGET_SI + PRIORITY_STEP,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_LINK_TARGET_SI = WP_EVENT_HOOK_DEFAULT_PRIORITY_BASE,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_PREPARE_LINK_SI = WP_EVENT_HOOK_DEFAULT_PRIORITY_LINK_TARGET_SI + PRIORITY_STEP,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_BEST_TARGET_SI = WP_EVENT_HOOK_DEFAULT_PRIORITY_PREPARE_LINK_SI + PRIORITY_STEP,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_DEFAULT_TARGET_SI = WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_BEST_TARGET_SI + PRIORITY_STEP,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_DEFINED_TARGET_SI = WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_DEFAULT_TARGET_SI + PRIORITY_STEP,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_RESCAN_POLICY = WP_EVENT_HOOK_DEFAULT_PRIORITY_FIND_DEFINED_TARGET_SI + PRIORITY_STEP,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_AFTER_EVENTS_DEFAULT_NODES_STATE_SAVE = WP_EVENT_HOOK_DEFAULT_PRIORITY_RESCAN_POLICY + PRIORITY_STEP,
|
||||
WP_EVENT_HOOK_DEFAULT_PRIORITY_RESCAN_DEFAULT_NODES = WP_EVENT_HOOK_DEFAULT_PRIORITY_AFTER_EVENTS_DEFAULT_NODES_STATE_SAVE + PRIORITY_STEP,
|
||||
|
||||
|
|
|
|||
|
|
@ -1675,8 +1675,8 @@ event_dispatcher_push_event (lua_State *L)
|
|||
g_autoptr (WpEventDispatcher) dispatcher =
|
||||
wp_event_dispatcher_get_instance (get_wp_core (L));
|
||||
WpEvent *event = wp_event_new (type, priority, properties, source, subject);
|
||||
wp_event_dispatcher_push_event (dispatcher, event);
|
||||
wplua_pushboxed (L, WP_TYPE_EVENT, wp_event_ref (event));
|
||||
wp_event_dispatcher_push_event (dispatcher, wp_event_ref (event));
|
||||
wplua_pushboxed (L, WP_TYPE_EVENT, event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ wireplumber.components = [
|
|||
# Link nodes to each other to make media flow in the graph
|
||||
{ name = policy-node.lua , type = script/lua }
|
||||
|
||||
# policy hooks to link nodes to each other
|
||||
{ name = policy-hooks.lua , type = script/lua }
|
||||
|
||||
# Create endpoints statically at startup
|
||||
{ name = static-endpoints.lua , type = script/lua }
|
||||
|
||||
|
|
|
|||
37
src/scripts/lib/common-utils.lua
Normal file
37
src/scripts/lib/common-utils.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- WirePlumber
|
||||
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
|
||||
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- Script is a Lua Module of common Lua utility functions
|
||||
|
||||
local cutils = {}
|
||||
|
||||
function cutils.parseBool (var)
|
||||
return var and (var:lower () == "true" or var == "1")
|
||||
end
|
||||
|
||||
function cutils.getTargetDirection (properties)
|
||||
local target_direction = nil
|
||||
if properties ["item.node.direction"] == "output" or
|
||||
(properties ["item.node.direction"] == "input" and
|
||||
cutils.parseBool (properties ["stream.capture.sink"])) then
|
||||
target_direction = "input"
|
||||
else
|
||||
target_direction = "output"
|
||||
end
|
||||
return target_direction
|
||||
end
|
||||
|
||||
function cutils.getDefaultNode (properties, target_direction)
|
||||
local target_media_class =
|
||||
properties ["media.type"] ..
|
||||
(target_direction == "input" and "/Sink" or "/Source")
|
||||
return default_nodes:call ("get-default-node", target_media_class)
|
||||
end
|
||||
|
||||
default_nodes = Plugin.find ("default-nodes-api")
|
||||
|
||||
return cutils
|
||||
313
src/scripts/lib/policy-utils.lua
Normal file
313
src/scripts/lib/policy-utils.lua
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
-- WirePlumber
|
||||
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
|
||||
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- Script is a Lua Module of policy Lua utility functions
|
||||
|
||||
local cutils = require ("common-utils")
|
||||
|
||||
function parseBool (var)
|
||||
return cutils.parseBool (var)
|
||||
end
|
||||
|
||||
local putils = {}
|
||||
|
||||
putils.si_flags = {}
|
||||
|
||||
function putils.get_flags (si_id)
|
||||
if not putils.si_flags [si_id] then
|
||||
putils.si_flags [si_id] = {}
|
||||
end
|
||||
|
||||
return putils.si_flags [si_id]
|
||||
end
|
||||
|
||||
function putils.set_flags (si_id, si_flags)
|
||||
putils.si_flags [si_id] = si_flags
|
||||
end
|
||||
|
||||
function putils.canPassthrough (si, si_target)
|
||||
local props = si.properties
|
||||
local tprops = si_target.properties
|
||||
-- both nodes must support encoded formats
|
||||
if not parseBool (props ["item.node.supports-encoded-fmts"])
|
||||
or not parseBool (tprops ["item.node.supports-encoded-fmts"]) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- make sure that the nodes have at least one common non-raw format
|
||||
local n1 = si:get_associated_proxy ("node")
|
||||
local n2 = si_target:get_associated_proxy ("node")
|
||||
for p1 in n1:iterate_params ("EnumFormat") do
|
||||
local p1p = p1:parse ()
|
||||
if p1p.properties.mediaSubtype ~= "raw" then
|
||||
for p2 in n2:iterate_params ("EnumFormat") do
|
||||
if p1:filter (p2) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function putils.checkFollowDefault (si, si_target, has_node_defined_target)
|
||||
-- If it got linked to the default target that is defined by node
|
||||
-- props but not metadata, start ignoring the node prop from now on.
|
||||
-- This is what Pulseaudio does.
|
||||
--
|
||||
-- Pulseaudio skips here filter streams (i->origin_sink and
|
||||
-- o->destination_source set in PA). Pipewire does not have a flag
|
||||
-- explicitly for this, but we can use presence of node.link-group.
|
||||
if not has_node_defined_target then
|
||||
return
|
||||
end
|
||||
local si_props = si.properties
|
||||
local target_props = si_target.properties
|
||||
local reconnect = not parseBool (si_props ["node.dont-reconnect"])
|
||||
local is_filter = (si_props ["node.link-group"] ~= nil)
|
||||
|
||||
if follow and default_nodes ~= nil and reconnect and not is_filter then
|
||||
local def_id = getDefaultNode (si_props,
|
||||
cutils.getTargetDirection (si_props))
|
||||
|
||||
if target_props ["node.id"] == tostring (def_id) then
|
||||
local metadata = putils.get_default_metadata_object ()
|
||||
-- Set target.node, for backward compatibility
|
||||
metadata:set (tonumber
|
||||
(si_props ["node.id"]), "target.node", "Spa:Id", "-1")
|
||||
Log.info (si, "... set metadata to follow default")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function putils.lookupLink (si_id, si_target_id)
|
||||
local link = links_om:lookup {
|
||||
Constraint { "out.item.id", "=", si_id },
|
||||
Constraint { "in.item.id", "=", si_target_id }
|
||||
}
|
||||
if not link then
|
||||
link = links_om:lookup {
|
||||
Constraint { "in.item.id", "=", si_id },
|
||||
Constraint { "out.item.id", "=", si_target_id }
|
||||
}
|
||||
end
|
||||
return link
|
||||
end
|
||||
|
||||
function putils.isLinked (si_target)
|
||||
local target_id = si_target.id
|
||||
local linked = false
|
||||
local exclusive = false
|
||||
|
||||
for l in links_om:iterate () do
|
||||
local p = l.properties
|
||||
local out_id = tonumber (p ["out.item.id"])
|
||||
local in_id = tonumber (p ["in.item.id"])
|
||||
linked = (out_id == target_id) or (in_id == target_id)
|
||||
if linked then
|
||||
exclusive = parseBool (p ["exclusive"]) or parseBool (p ["passthrough"])
|
||||
break
|
||||
end
|
||||
end
|
||||
return linked, exclusive
|
||||
end
|
||||
|
||||
function putils.canLink (properties, si_target)
|
||||
local target_props = si_target.properties
|
||||
|
||||
-- nodes must have the same media type
|
||||
if properties ["media.type"] ~= target_props ["media.type"] then
|
||||
return false
|
||||
end
|
||||
|
||||
-- nodes must have opposite direction, or otherwise they must be both input
|
||||
-- and the target must have a monitor (so the target will be used as a source)
|
||||
local function isMonitor(properties)
|
||||
return properties ["item.node.direction"] == "input" and
|
||||
parseBool (properties ["item.features.monitor"]) and
|
||||
not parseBool (properties ["item.features.no-dsp"]) and
|
||||
properties ["item.factory.name"] == "si-audio-adapter"
|
||||
end
|
||||
|
||||
if properties ["item.node.direction"] == target_props ["item.node.direction"]
|
||||
and not isMonitor (target_props) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- check link group
|
||||
local function canLinkGroupCheck(link_group, si_target, hops)
|
||||
local target_props = si_target.properties
|
||||
local target_link_group = target_props ["node.link-group"]
|
||||
|
||||
if hops == 8 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- allow linking if target has no link-group property
|
||||
if not target_link_group then
|
||||
return true
|
||||
end
|
||||
|
||||
-- do not allow linking if target has the same link-group
|
||||
if link_group == target_link_group then
|
||||
return false
|
||||
end
|
||||
|
||||
-- make sure target is not linked with another node with same link group
|
||||
-- start by locating other nodes in the target's link-group, in opposite direction
|
||||
for n in linkables_om:iterate {
|
||||
Constraint { "id", "!", si_target.id, type = "gobject" },
|
||||
Constraint { "item.node.direction", "!", target_props ["item.node.direction"] },
|
||||
Constraint { "node.link-group", "=", target_link_group },
|
||||
} do
|
||||
-- iterate their peers and return false if one of them cannot link
|
||||
for silink in links_om:iterate () do
|
||||
local out_id = tonumber (silink.properties ["out.item.id"])
|
||||
local in_id = tonumber (silink.properties ["in.item.id"])
|
||||
if out_id == n.id or in_id == n.id then
|
||||
local peer_id = (out_id == n.id) and in_id or out_id
|
||||
local peer = linkables_om:lookup {
|
||||
Constraint { "id", "=", peer_id, type = "gobject" },
|
||||
}
|
||||
if peer and not canLinkGroupCheck (link_group, peer, hops + 1) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local link_group = properties ["node.link-group"]
|
||||
if link_group then
|
||||
return canLinkGroupCheck (link_group, si_target, 0)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function putils.findDefaultLinkable (si)
|
||||
local si_props = si.properties
|
||||
local target_direction = cutils.getTargetDirection (si_props)
|
||||
local def_node_id = cutils.getDefaultNode (si_props, target_direction)
|
||||
return linkables_om:lookup {
|
||||
Constraint { "node.id", "=", tostring (def_node_id) }
|
||||
}
|
||||
end
|
||||
|
||||
function putils.checkPassthroughCompatibility (si, si_target)
|
||||
local si_must_passthrough =
|
||||
parseBool (si.properties ["item.node.encoded-only"])
|
||||
local si_target_must_passthrough =
|
||||
parseBool (si_target.properties ["item.node.encoded-only"])
|
||||
local can_passthrough = putils.canPassthrough (si, si_target)
|
||||
if (si_must_passthrough or si_target_must_passthrough)
|
||||
and not can_passthrough then
|
||||
return false, can_passthrough
|
||||
end
|
||||
return true, can_passthrough
|
||||
end
|
||||
|
||||
-- Does the target device have any active/available paths/routes to
|
||||
-- the physical device(spkr/mic/cam)?
|
||||
function putils.haveAvailableRoutes (si_props)
|
||||
local card_profile_device = si_props ["card.profile.device"]
|
||||
local device_id = si_props ["device.id"]
|
||||
local device = device_id and devices_om:lookup {
|
||||
Constraint { "bound-id", "=", device_id, type = "gobject" },
|
||||
}
|
||||
|
||||
if not card_profile_device or not device then
|
||||
return true
|
||||
end
|
||||
|
||||
local found = 0
|
||||
local avail = 0
|
||||
|
||||
-- First check "SPA_PARAM_Route" if there are any active devices
|
||||
-- in an active profile.
|
||||
for p in device:iterate_params ("Route") do
|
||||
local route = parseParam (p, "Route")
|
||||
if not route then
|
||||
goto skip_route
|
||||
end
|
||||
|
||||
if (route.device ~= tonumber (card_profile_device)) then
|
||||
goto skip_route
|
||||
end
|
||||
|
||||
if (route.available == "no") then
|
||||
return false
|
||||
end
|
||||
|
||||
do return true end
|
||||
|
||||
::skip_route::
|
||||
end
|
||||
|
||||
-- Second check "SPA_PARAM_EnumRoute" if there is any route that
|
||||
-- is available if not active.
|
||||
for p in device:iterate_params ("EnumRoute") do
|
||||
local route = parseParam (p, "EnumRoute")
|
||||
if not route then
|
||||
goto skip_enum_route
|
||||
end
|
||||
|
||||
if not arrayContains (route.devices, tonumber (card_profile_device)) then
|
||||
goto skip_enum_route
|
||||
end
|
||||
found = found + 1;
|
||||
if (route.available ~= "no") then
|
||||
avail = avail + 1
|
||||
end
|
||||
::skip_enum_route::
|
||||
end
|
||||
|
||||
if found == 0 then
|
||||
return true
|
||||
end
|
||||
if avail > 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
linkables_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLinkable",
|
||||
-- only handle si-audio-adapter and si-node
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "active-features", "!", 0, type = "gobject" },
|
||||
}
|
||||
}
|
||||
|
||||
linkables_om:activate ()
|
||||
|
||||
metadata_om = ObjectManager {
|
||||
Interest {
|
||||
type = "metadata",
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
}
|
||||
}
|
||||
|
||||
metadata_om:activate ()
|
||||
|
||||
links_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLink",
|
||||
-- only handle links created by this policy
|
||||
Constraint { "is.policy.item.link", "=", true },
|
||||
}
|
||||
}
|
||||
|
||||
links_om:activate ()
|
||||
|
||||
function putils.get_default_metadata_object ()
|
||||
return metadata_om:lookup ()
|
||||
end
|
||||
|
||||
return putils
|
||||
572
src/scripts/policy-hooks.lua
Normal file
572
src/scripts/policy-hooks.lua
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
-- WirePlumber
|
||||
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
|
||||
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- Script registers hooks needed to perform policy rescan.
|
||||
|
||||
-- settings file: policy.conf
|
||||
|
||||
local putils = require ("policy-utils")
|
||||
local cutils = require ("common-utils")
|
||||
|
||||
local move = Settings.get ("default-policy-move"):parse () or false
|
||||
|
||||
function parseBool (var)
|
||||
return cutils.parseBool (var)
|
||||
end
|
||||
|
||||
-- check if target node is defined explicitly.
|
||||
-- This defination can be done in two ways.
|
||||
-- 1. "node.target"/"target.object" in the node properties
|
||||
-- 2. "target.node"/"target.object" in default metadata
|
||||
function findDefinedTarget (event)
|
||||
local si = event:get_subject ()
|
||||
local si_props = si.properties
|
||||
local si_id = si.id;
|
||||
local si_flags = putils.get_flags (si_id)
|
||||
local si_target = nil
|
||||
|
||||
Log.info (si, string.format ("handling item: %s (%s)",
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
local metadata = move and putils.get_default_metadata_object ()
|
||||
local target_key
|
||||
local target_value = nil
|
||||
local node_defined = false
|
||||
local target_picked = nil
|
||||
|
||||
if si_props ["target.object"] ~= nil then
|
||||
target_value = si_props ["target.object"]
|
||||
target_key = "object.serial"
|
||||
node_defined = true
|
||||
elseif si_props ["node.target"] ~= nil then
|
||||
target_value = si_props ["node.target"]
|
||||
target_key = "node.id"
|
||||
node_defined = true
|
||||
end
|
||||
|
||||
if metadata then
|
||||
local id = metadata:find (si_props ["node.id"], "target.object")
|
||||
if id ~= nil then
|
||||
target_value = id
|
||||
target_key = "object.serial"
|
||||
node_defined = false
|
||||
else
|
||||
id = metadata:find (si_props ["node.id"], "target.node")
|
||||
if id ~= nil then
|
||||
target_value = id
|
||||
target_key = "node.id"
|
||||
node_defined = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if target_value == "-1" then
|
||||
target_picked = false
|
||||
si_target = nil
|
||||
elseif target_value and tonumber (target_value) then
|
||||
si_target = linkables_om:lookup {
|
||||
Constraint { target_key, "=", target_value },
|
||||
}
|
||||
if si_target and putils.canLink (si_props, si_target) then
|
||||
target_picked = true
|
||||
end
|
||||
elseif target_value then
|
||||
for si_target in linkables_om:iterate () do
|
||||
local target_props = si_target.properties
|
||||
if (target_props ["node.name"] == target_value or
|
||||
target_props ["object.path"] == target_value) and
|
||||
target_props ["item.node.direction"] == cutils.getTargetDirection (si_props) and
|
||||
putils.canLink (si_props, si_target) then
|
||||
target_picked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local can_passthrough, passthrough_compatible
|
||||
if si_target then
|
||||
passthrough_compatible, can_passthrough =
|
||||
putils.checkPassthroughCompatibility (si, si_target)
|
||||
|
||||
if not passthrough_compatible then
|
||||
si_target = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- if the client has seen a target that we haven't yet prepared, stop the
|
||||
-- event and wait(for one time) for next rescan to happen and hope for the
|
||||
-- best.
|
||||
|
||||
if target_picked
|
||||
and not si_target
|
||||
and not si_flags.was_handled
|
||||
and not si_flags.done_waiting then
|
||||
Log.info (si, string.format ("... waiting for target %s (%s)",
|
||||
tostring (si_target.properties ["node.name"]),
|
||||
tostring (si_target.properties ["node.id"])))
|
||||
si_flags.done_waiting = true
|
||||
event:stop_processing ()
|
||||
|
||||
elseif target_picked then
|
||||
Log.info (si,
|
||||
string.format ("... defined target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (si_target.properties ["node.name"]),
|
||||
tostring (si_target.properties ["node.id"]),
|
||||
tostring (can_passthrough)))
|
||||
si_flags.si_target = si_target
|
||||
si_flags.has_node_defined_target = node_defined
|
||||
si_flags.can_passthrough = can_passthrough
|
||||
else
|
||||
si_flags.si_target = nil
|
||||
si_flags.can_passthrough = nil
|
||||
si_flags.has_node_defined_target = nil
|
||||
end
|
||||
|
||||
putils.set_flags (si_id, si_flags)
|
||||
end
|
||||
|
||||
-- check if default nodes can be picked up as target node.
|
||||
function findDefaultTarget (event)
|
||||
local si = event:get_subject ()
|
||||
local si_id = si.id
|
||||
local si_flags = putils.get_flags (si_id)
|
||||
local si_target = si_flags.si_target
|
||||
|
||||
if si_target or (default_nodes == nil) then
|
||||
-- bypass the hook as the target is already picked up.
|
||||
return
|
||||
end
|
||||
|
||||
local si_props = si.properties
|
||||
local target_picked = false
|
||||
|
||||
Log.info (si, string.format ("handling item: %s (%s)",
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
local si_target = putils.findDefaultLinkable (si)
|
||||
|
||||
local can_passthrough, passthrough_compatible
|
||||
if si_target then
|
||||
passthrough_compatible, can_passthrough =
|
||||
putils.checkPassthroughCompatibility (si, si_target)
|
||||
if putils.canLink (si_props, si_target) and passthrough_compatible then
|
||||
target_picked = true;
|
||||
end
|
||||
end
|
||||
|
||||
if target_picked then
|
||||
Log.info (si,
|
||||
string.format ("... default target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (si_target.properties ["node.name"]),
|
||||
tostring (si_target.properties ["node.id"]),
|
||||
tostring (can_passthrough)))
|
||||
si_flags.si_target = si_target
|
||||
si_flags.can_passthrough = can_passthrough
|
||||
else
|
||||
si_flags.si_target = nil
|
||||
si_flags.can_passthrough = nil
|
||||
end
|
||||
|
||||
putils.set_flags (si_id, si_flags)
|
||||
end
|
||||
|
||||
-- Traverse through all the possible targets to pick up target node.
|
||||
function findBestTarget (event)
|
||||
local si = event:get_subject ()
|
||||
local si_id = si.id
|
||||
local si_flags = putils.get_flags (si_id)
|
||||
local si_target = si_flags.si_target
|
||||
|
||||
if si_target then
|
||||
-- bypass the hook as the target is already picked up.
|
||||
return
|
||||
end
|
||||
|
||||
local si_props = si.properties
|
||||
local target_direction = putils.getTargetDirection (si_props)
|
||||
local target_picked = nil
|
||||
local target_can_passthrough = false
|
||||
local target_priority = 0
|
||||
local target_plugged = 0
|
||||
|
||||
for si_target in linkables_om:iterate {
|
||||
Constraint { "item.node.type", "=", "device" },
|
||||
Constraint { "item.node.direction", "=", target_direction },
|
||||
Constraint { "media.type", "=", si_props ["media.type"] },
|
||||
} do
|
||||
local si_target_props = si_target.properties
|
||||
local si_target_node_id = si_target_props ["node.id"]
|
||||
local priority = tonumber (si_target_props ["priority.session"]) or 0
|
||||
|
||||
Log.debug (string.format ("Looking at: %s (%s)",
|
||||
tostring (si_target_props ["node.name"]),
|
||||
tostring (si_target_node_id)))
|
||||
|
||||
if not putils.canLink (si_props, si_target) then
|
||||
Log.debug ("... cannot link, skip linkable")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
if not putils.haveAvailableRoutes (si_target_props) then
|
||||
Log.debug ("... does not have routes, skip linkable")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
local passthrough_compatible, can_passthrough =
|
||||
putils.checkPassthroughCompatibility (si, si_target)
|
||||
if not passthrough_compatible then
|
||||
Log.debug ("... passthrough is not compatible, skip linkable")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
local plugged = tonumber (si_target_props ["item.plugged.usec"]) or 0
|
||||
|
||||
Log.debug ("... priority:" .. tostring (priority) .. ", plugged:" .. tostring (plugged))
|
||||
|
||||
-- (target_picked == NULL) --> make sure atleast one target is picked.
|
||||
-- (priority > target_priority) --> pick the highest priority linkable(node)
|
||||
-- target.
|
||||
-- (priority == target_priority and plugged > target_plugged) --> pick the
|
||||
-- latest connected/plugged(in time) linkable(node) target.
|
||||
if (target_picked == nil or
|
||||
priority > target_priority or
|
||||
(priority == target_priority and plugged > target_plugged)) then
|
||||
Log.debug ("... picked")
|
||||
target_picked = si_target
|
||||
target_can_passthrough = can_passthrough
|
||||
target_priority = priority
|
||||
target_plugged = plugged
|
||||
end
|
||||
::skip_linkable::
|
||||
end
|
||||
|
||||
if target_picked then
|
||||
Log.info (si,
|
||||
string.format ("... best target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (target_picked.properties ["node.name"]),
|
||||
tostring (target_picked.properties ["node.id"]),
|
||||
tostring (target_can_passthrough)))
|
||||
si_flags.si_target = target_picked
|
||||
si_flags.can_passthrough = target_can_passthrough
|
||||
else
|
||||
si_flags.si_target = nil
|
||||
si_flags.can_passthrough = nil
|
||||
end
|
||||
|
||||
putils.set_flags (si_id, si_flags)
|
||||
end
|
||||
|
||||
-- remove the existing link if needed, check the properties of target, which
|
||||
-- indicate it is not available for linking. If no target is available, send
|
||||
-- down an error to the corresponding client.
|
||||
function prepareLink (event)
|
||||
local si = event:get_subject ()
|
||||
local si_id = si.id
|
||||
local si_flags = putils.get_flags (si_id)
|
||||
local si_target = si_flags.si_target
|
||||
local si_props = si.properties
|
||||
|
||||
local reconnect = not parseBool (si_props ["node.dont-reconnect"])
|
||||
local exclusive = parseBool (si_props ["node.exclusive"])
|
||||
local si_must_passthrough = parseBool (si_props ["item.node.encoded-only"])
|
||||
|
||||
Log.info(si, string.format("handling item: %s (%s)",
|
||||
tostring(si_props["node.name"]), tostring(si_props["node.id"])))
|
||||
|
||||
-- Check if item is linked to proper target, otherwise re-link
|
||||
if si_flags.peer_id then
|
||||
if si_target and si_flags.peer_id == si_target.id then
|
||||
Log.debug (si, "... already linked to proper target")
|
||||
-- Check this also here, in case in default targets changed
|
||||
putils.checkFollowDefault (si, si_target,
|
||||
si_flags.has_node_defined_target)
|
||||
si_target = nil
|
||||
goto done
|
||||
end
|
||||
|
||||
local link = putils.lookupLink (si_id, si_flags.peer_id)
|
||||
if reconnect then
|
||||
if link ~= nil then
|
||||
-- remove old link
|
||||
if ((link:get_active_features () & Feature.SessionItem.ACTIVE) == 0)
|
||||
then
|
||||
-- remove also not yet activated links: they might never become
|
||||
-- active, and we need not wait for it to become active
|
||||
Log.warning (link, "Link was not activated before removing")
|
||||
end
|
||||
si_flags.peer_id = nil
|
||||
link:remove ()
|
||||
Log.info (si, "... moving to new target")
|
||||
end
|
||||
else
|
||||
if link ~= nil then
|
||||
Log.info (si, "... dont-reconnect, not moving")
|
||||
goto done
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if the stream has dont-reconnect and was already linked before,
|
||||
-- don't link it to a new target
|
||||
if not reconnect and si_flags.was_handled then
|
||||
si_target = nil
|
||||
goto done
|
||||
end
|
||||
|
||||
-- check target's availability
|
||||
if si_target then
|
||||
local target_is_linked, target_is_exclusive = putils.isLinked (si_target)
|
||||
if target_is_exclusive then
|
||||
Log.info (si, "... target is linked exclusively")
|
||||
si_target = nil
|
||||
end
|
||||
|
||||
if target_is_linked then
|
||||
if exclusive or si_must_passthrough then
|
||||
Log.info (si, "... target is already linked, cannot link exclusively")
|
||||
si_target = nil
|
||||
else
|
||||
-- disable passthrough, we can live without it
|
||||
si_flags.can_passthrough = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not si_target then
|
||||
Log.info (si, "... target not found, reconnect:" .. tostring (reconnect))
|
||||
|
||||
local node = si:get_associated_proxy ("node")
|
||||
if not reconnect then
|
||||
Log.info (si, "... destroy node")
|
||||
node:request_destroy ()
|
||||
elseif si_flags.was_handled then
|
||||
Log.info (si, "... waiting reconnect")
|
||||
return
|
||||
end
|
||||
|
||||
local client_id = node.properties ["client.id"]
|
||||
if client_id then
|
||||
local client = clients_om:lookup {
|
||||
Constraint { "bound-id", "=", client_id, type = "gobject" }
|
||||
}
|
||||
if client then
|
||||
client:send_error (node ["bound-id"], -2, "no node available")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
::done::
|
||||
si_flags.si_target = si_target
|
||||
putils.set_flags (si_id, si_flags)
|
||||
end
|
||||
|
||||
function createLink (event)
|
||||
local si = event:get_subject ()
|
||||
local si_id = si.id
|
||||
local si_flags = putils.get_flags (si_id)
|
||||
local si_target = si_flags.si_target
|
||||
|
||||
if not si_target then
|
||||
-- bypass the hook, nothing to link to.
|
||||
return
|
||||
end
|
||||
|
||||
local si_props = si.properties
|
||||
local target_props = si_target.properties
|
||||
local out_item = nil
|
||||
local in_item = nil
|
||||
local si_link = nil
|
||||
local passthrough = si_flags.can_passthrough
|
||||
|
||||
local exclusive = parseBool (si_props ["node.exclusive"])
|
||||
local passive = parseBool (si_props ["node.passive"]) or
|
||||
parseBool (target_props ["node.passive"])
|
||||
|
||||
-- break rescan if tried more than 5 times with same target
|
||||
if si_flags.failed_peer_id ~= nil and
|
||||
si_flags.failed_peer_id == si_target.id and
|
||||
si_flags.failed_count ~= nil and
|
||||
si_flags.failed_count > 5 then
|
||||
Log.warning (si, "tried to link on last rescan, not retrying")
|
||||
goto done
|
||||
end
|
||||
|
||||
if si_props ["item.node.direction"] == "output" then
|
||||
-- playback
|
||||
out_item = si
|
||||
in_item = si_target
|
||||
else
|
||||
-- capture
|
||||
in_item = si
|
||||
out_item = si_target
|
||||
end
|
||||
|
||||
Log.info (si,
|
||||
string.format ("link %s <-> %s passive:%s, passthrough:%s, exclusive:%s",
|
||||
tostring (si_props ["node.name"]),
|
||||
tostring (target_props ["node.name"]),
|
||||
tostring (passive), tostring (passthrough), tostring (exclusive)))
|
||||
|
||||
-- create and configure link
|
||||
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",
|
||||
["in.item.port.context"] = "input",
|
||||
["is.policy.item.link"] = true,
|
||||
} then
|
||||
Log.warning (si_link, "failed to configure si-standard-link")
|
||||
goto done
|
||||
end
|
||||
|
||||
si_link:connect("link-error", function (_, error_msg)
|
||||
local ids = {si_id}
|
||||
if si_flags[si_id] ~= nil then
|
||||
table.insert (ids, si_flags[si_id].peer_id)
|
||||
end
|
||||
|
||||
for _, id in ipairs (ids) do
|
||||
local si = linkables_om:lookup {
|
||||
Constraint { "id", "=", id, type = "gobject" },
|
||||
}
|
||||
if si then
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local client_id = node.properties["client.id"]
|
||||
if client_id then
|
||||
local client = clients_om:lookup {
|
||||
Constraint { "bound-id", "=", client_id, type = "gobject" }
|
||||
}
|
||||
if client then
|
||||
Log.info (node, "sending client error: " .. error_msg)
|
||||
client:send_error (node["bound-id"], -32, error_msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- register
|
||||
si_flags.peer_id = si_target.id
|
||||
si_flags.failed_peer_id = si_target.id
|
||||
if si_flags.failed_count ~= nil then
|
||||
si_flags.failed_count = si_flags.failed_count + 1
|
||||
else
|
||||
si_flags.failed_count = 1
|
||||
end
|
||||
si_link:register ()
|
||||
|
||||
-- activate
|
||||
si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
|
||||
if e then
|
||||
Log.info(l, "failed to activate si-standard-link: "..tostring(si).." error:".. tostring(e))
|
||||
if si_flags ~= nil then
|
||||
si_flags.peer_id = nil
|
||||
end
|
||||
l:remove ()
|
||||
else
|
||||
if si_flags ~= nil then
|
||||
si_flags.failed_peer_id = nil
|
||||
si_flags.failed_count = 0
|
||||
end
|
||||
Log.info (l, "activated si-standard-link "..tostring(si))
|
||||
end
|
||||
putils.set_flags (si_id, si_flags)
|
||||
end)
|
||||
|
||||
::done::
|
||||
putils.set_flags (si_id, si_flags)
|
||||
end
|
||||
|
||||
linkables_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLinkable",
|
||||
-- only handle si-audio-adapter and si-node
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "active-features", "!", 0, type = "gobject" },
|
||||
}
|
||||
}
|
||||
|
||||
linkables_om:activate ()
|
||||
|
||||
clients_om = ObjectManager { Interest { type = "client" } }
|
||||
|
||||
clients_om:activate ()
|
||||
|
||||
default_nodes = Plugin.find ("default-nodes-api")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "link-target-si@policy-node",
|
||||
type = "after-events-with-event",
|
||||
priority = "link-target-si",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "find-target-si-and-link" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
createLink (event)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "prepare-link-si@policy-node",
|
||||
type = "after-events-with-event",
|
||||
priority = "prepare-link-si",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "find-target-si-and-link" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
prepareLink (event)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "find-best-target-si@policy-node",
|
||||
type = "after-events-with-event",
|
||||
priority = "find-best-target-si",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "find-target-si-and-link" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
findBestTarget (event)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "find-default-target-si@policy-node",
|
||||
type = "after-events-with-event",
|
||||
priority = "find-default-target-si",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "find-target-si-and-link" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
findDefaultTarget (event)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "find-defined-target@policy-node",
|
||||
type = "after-events-with-event",
|
||||
priority = "find-defined-target-si",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "find-target-si-and-link" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
findDefinedTarget (event)
|
||||
end
|
||||
}:register ()
|
||||
|
|
@ -20,152 +20,54 @@ local move = Settings.get ("default-policy-move"):parse() or false
|
|||
local follow = Settings.get ("default-policy-follow"):parse() or false
|
||||
local filter_forward_format = Settings.get ("filter.forward-format"):parse() or false
|
||||
|
||||
local self = {}
|
||||
self.scanning = false
|
||||
self.pending_rescan = false
|
||||
self.events_skipped = false
|
||||
self.pending_error_timer = nil
|
||||
local putils = require ("policy-utils")
|
||||
local cutils = require ("common-utils")
|
||||
|
||||
find_target_events = {}
|
||||
|
||||
function findTargetSiAndLink (si)
|
||||
function parseBool (var)
|
||||
return cutils.parseBool (var)
|
||||
end
|
||||
|
||||
si_props = si.properties
|
||||
|
||||
Log.info (si, string.format ("handling item: %s (%s)",
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
ensureSiFlags (si)
|
||||
|
||||
-- get other important node properties
|
||||
local reconnect = not parseBool (si_props ["node.dont-reconnect"])
|
||||
local exclusive = parseBool (si_props ["node.exclusive"])
|
||||
local si_must_passthrough = parseBool (si_props ["item.node.encoded-only"])
|
||||
|
||||
-- find defined target
|
||||
local si_target, has_defined_target, has_node_defined_target = findDefinedTarget (si_props)
|
||||
local can_passthrough = si_target and canPassthrough (si, si_target)
|
||||
|
||||
if si_target and si_must_passthrough and not can_passthrough then
|
||||
si_target = nil
|
||||
end
|
||||
|
||||
-- if the client has seen a target that we haven't yet prepared, schedule
|
||||
-- a rescan one more time and hope for the best
|
||||
local si_id = si.id
|
||||
if has_defined_target
|
||||
and not si_target
|
||||
and not si_flags [si_id].was_handled
|
||||
and not si_flags [si_id].done_waiting then
|
||||
Log.info (si, "... waiting for target")
|
||||
si_flags [si_id].done_waiting = true
|
||||
-- Event-Stack TBD: do we need to retain this call here?
|
||||
rescan ()
|
||||
function unhandleLinkable (si)
|
||||
local si_flags = putils.get_flags (si_id)
|
||||
local valid, si_props = checkLinkable (si, true)
|
||||
if not valid then
|
||||
return
|
||||
end
|
||||
|
||||
-- find fallback target
|
||||
if not si_target and (reconnect or not has_defined_target) then
|
||||
si_target, can_passthrough = findUndefinedTarget (si)
|
||||
end
|
||||
Log.info (si, string.format ("unhandling item: %s (%s)",
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
-- Check if item is linked to proper target, otherwise re-link
|
||||
if si_flags [si_id].peer_id then
|
||||
if si_target and si_flags [si_id].peer_id == si_target.id then
|
||||
Log.debug (si, "... already linked to proper target")
|
||||
-- Check this also here, in case in default targets changed
|
||||
checkFollowDefault (si, si_target, has_node_defined_target)
|
||||
return
|
||||
end
|
||||
local link = lookupLink (si_id, si_flags [si_id].peer_id)
|
||||
if reconnect then
|
||||
if link ~= nil then
|
||||
-- remove old link if active, otherwise schedule rescan
|
||||
if ((link:get_active_features () & Feature.SessionItem.ACTIVE) ~= 0) then
|
||||
si_flags [si_id].peer_id = nil
|
||||
link:remove ()
|
||||
Log.info (si, "... moving to new target")
|
||||
else
|
||||
-- Event-Stack TBD: do we need to retain this call here?
|
||||
rescan ()
|
||||
Log.info (si, "... scheduled rescan")
|
||||
return
|
||||
end
|
||||
end
|
||||
else
|
||||
if link ~= nil then
|
||||
Log.info (si, "... dont-reconnect, not moving")
|
||||
return
|
||||
-- remove any links associated with this item
|
||||
for silink in links_om:iterate () do
|
||||
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
|
||||
if out_id == si.id and
|
||||
si_flags and si_flags.peer_id == out_id then
|
||||
si_flags.peer_id = nil
|
||||
elseif in_id == si.id and
|
||||
si_flags and si_flags.peer_id == in_id then
|
||||
si_flags.peer_id = nil
|
||||
end
|
||||
silink:remove ()
|
||||
Log.info (silink, "... link removed")
|
||||
end
|
||||
end
|
||||
|
||||
-- if the stream has dont-reconnect and was already linked before,
|
||||
-- don't link it to a new target
|
||||
if not reconnect and si_flags [si.id].was_handled then
|
||||
si_target = nil
|
||||
end
|
||||
|
||||
-- check target's availability
|
||||
if si_target then
|
||||
local target_is_linked, target_is_exclusive = isLinked (si_target)
|
||||
if target_is_exclusive then
|
||||
Log.info (si, "... target is linked exclusively")
|
||||
si_target = nil
|
||||
end
|
||||
|
||||
if target_is_linked then
|
||||
if exclusive or si_must_passthrough then
|
||||
Log.info (si, "... target is already linked, cannot link exclusively")
|
||||
si_target = nil
|
||||
else
|
||||
-- disable passthrough, we can live without it
|
||||
can_passthrough = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not si_target then
|
||||
Log.info (si, "... target not found, reconnect:" .. tostring (reconnect))
|
||||
|
||||
local node = si:get_associated_proxy ("node")
|
||||
if not reconnect then
|
||||
Log.info (si, "... destroy node")
|
||||
node:request_destroy ()
|
||||
elseif si_flags [si.id].was_handled then
|
||||
Log.info (si, "... waiting reconnect")
|
||||
return
|
||||
end
|
||||
|
||||
local client_id = node.properties ["client.id"]
|
||||
if client_id then
|
||||
local client = clients_om:lookup {
|
||||
Constraint { "bound-id", "=", client_id, type = "gobject" }
|
||||
}
|
||||
if client then
|
||||
client:send_error (node ["bound-id"], -2, "no node available")
|
||||
end
|
||||
end
|
||||
else
|
||||
createLink (si, si_target, can_passthrough, exclusive)
|
||||
si_flags [si.id].was_handled = true
|
||||
|
||||
checkFollowDefault (si, si_target, has_node_defined_target)
|
||||
end
|
||||
si_flags = nil
|
||||
putils.set_flags (si_id, si_flags)
|
||||
end
|
||||
|
||||
function handleLinkable (si)
|
||||
if checkPending () then
|
||||
return
|
||||
end
|
||||
local si_id = si.id;
|
||||
|
||||
local valid, si_props = checkLinkable (si)
|
||||
if not valid then
|
||||
return
|
||||
end
|
||||
|
||||
props = {}
|
||||
props ["event.subject.type"] = "linkable"
|
||||
|
||||
-- check if we need to link this node at all
|
||||
local autoconnect = parseBool (si_props ["node.autoconnect"])
|
||||
if not autoconnect then
|
||||
|
|
@ -173,548 +75,27 @@ function handleLinkable (si)
|
|||
return
|
||||
end
|
||||
|
||||
if not find_target_events [si.id] then
|
||||
find_target_events [si.id] = {}
|
||||
else
|
||||
-- stop the processing of the old event, we are going to queue a new one any
|
||||
-- way
|
||||
find_target_events [si.id]:stop_processing ()
|
||||
if not find_target_events [si_id] then
|
||||
find_target_events [si_id] = nil
|
||||
end
|
||||
|
||||
find_target_events [si.id] = EventDispatcher.push_event {
|
||||
type = "find-target-si-and-link", priority = 10, properties = props,
|
||||
subject = si }
|
||||
if find_target_events [si_id] ~= nil then
|
||||
-- stop the processing of the old event, we are going to queue a new one any
|
||||
-- way
|
||||
find_target_events [si_id]:stop_processing ()
|
||||
end
|
||||
|
||||
find_target_events [si_id] = EventDispatcher.push_event {
|
||||
type = "find-target-si-and-link", priority = 10, subject = si }
|
||||
end
|
||||
|
||||
function rescan ()
|
||||
Log.info ("rescanning..")
|
||||
for si in linkables_om:iterate () do
|
||||
handleLinkable (si)
|
||||
handleLinkable (si)
|
||||
end
|
||||
end
|
||||
|
||||
function parseBool (var)
|
||||
return var and (var:lower () == "true" or var == "1")
|
||||
end
|
||||
|
||||
function createLink (si, si_target, passthrough, exclusive)
|
||||
local out_item = nil
|
||||
local in_item = nil
|
||||
local si_props = si.properties
|
||||
local target_props = si_target.properties
|
||||
local si_id = si.id
|
||||
|
||||
-- break rescan if tried more than 5 times with same target
|
||||
if si_flags [si_id].failed_peer_id ~= nil and
|
||||
si_flags [si_id].failed_peer_id == si_target.id and
|
||||
si_flags [si_id].failed_count ~= nil and
|
||||
si_flags [si_id].failed_count > 5 then
|
||||
Log.warning (si, "tried to link on last rescan, not retrying")
|
||||
return
|
||||
end
|
||||
|
||||
if si_props ["item.node.direction"] == "output" then
|
||||
-- playback
|
||||
out_item = si
|
||||
in_item = si_target
|
||||
else
|
||||
-- capture
|
||||
in_item = si
|
||||
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",
|
||||
tostring (si_props ["node.name"]),
|
||||
tostring (target_props ["node.name"]),
|
||||
tostring (passive), 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",
|
||||
["in.item.port.context"] = "input",
|
||||
["is.policy.item.link"] = true,
|
||||
} then
|
||||
Log.warning (si_link, "failed to configure si-standard-link")
|
||||
return
|
||||
end
|
||||
|
||||
si_link:connect("link-error", function (_, error_msg)
|
||||
local ids = {si_id}
|
||||
if si_flags[si_id] ~= nil then
|
||||
table.insert (ids, si_flags[si_id].peer_id)
|
||||
end
|
||||
|
||||
for _, id in ipairs (ids) do
|
||||
local si = linkables_om:lookup {
|
||||
Constraint { "id", "=", id, type = "gobject" },
|
||||
}
|
||||
if si then
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local client_id = node.properties["client.id"]
|
||||
if client_id then
|
||||
local client = clients_om:lookup {
|
||||
Constraint { "bound-id", "=", client_id, type = "gobject" }
|
||||
}
|
||||
if client then
|
||||
Log.info (node, "sending client error: " .. error_msg)
|
||||
client:send_error (node["bound-id"], -32, error_msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- register
|
||||
si_flags [si_id].peer_id = si_target.id
|
||||
si_flags [si_id].failed_peer_id = si_target.id
|
||||
if si_flags [si_id].failed_count ~= nil then
|
||||
si_flags [si_id].failed_count = si_flags [si_id].failed_count + 1
|
||||
else
|
||||
si_flags [si_id].failed_count = 1
|
||||
end
|
||||
si_link:register ()
|
||||
|
||||
-- activate
|
||||
si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
|
||||
if e then
|
||||
Log.info (l, "failed to activate si-standard-link: " .. tostring (e))
|
||||
if si_flags [si_id] ~= nil then
|
||||
si_flags [si_id].peer_id = nil
|
||||
end
|
||||
l:remove ()
|
||||
else
|
||||
if si_flags [si_id] ~= nil then
|
||||
si_flags [si_id].failed_peer_id = nil
|
||||
si_flags [si_id].failed_count = 0
|
||||
end
|
||||
Log.info (l, "activated si-standard-link")
|
||||
end
|
||||
scheduleRescan()
|
||||
end)
|
||||
end
|
||||
|
||||
function isLinked (si_target)
|
||||
local target_id = si_target.id
|
||||
local linked = false
|
||||
local exclusive = false
|
||||
|
||||
for l in links_om:iterate () do
|
||||
local p = l.properties
|
||||
local out_id = tonumber (p ["out.item.id"])
|
||||
local in_id = tonumber (p ["in.item.id"])
|
||||
linked = (out_id == target_id) or (in_id == target_id)
|
||||
if linked then
|
||||
exclusive = parseBool (p ["exclusive"]) or parseBool (p ["passthrough"])
|
||||
break
|
||||
end
|
||||
end
|
||||
return linked, exclusive
|
||||
end
|
||||
|
||||
function canPassthrough (si, si_target)
|
||||
-- both nodes must support encoded formats
|
||||
if not parseBool (si.properties ["item.node.supports-encoded-fmts"])
|
||||
or not parseBool (si_target.properties ["item.node.supports-encoded-fmts"]) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- make sure that the nodes have at least one common non-raw format
|
||||
local n1 = si:get_associated_proxy ("node")
|
||||
local n2 = si_target:get_associated_proxy ("node")
|
||||
for p1 in n1:iterate_params ("EnumFormat") do
|
||||
local p1p = p1:parse ()
|
||||
if p1p.properties.mediaSubtype ~= "raw" then
|
||||
for p2 in n2:iterate_params ("EnumFormat") do
|
||||
if p1:filter (p2) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function canLink (properties, si_target)
|
||||
local target_properties = si_target.properties
|
||||
|
||||
-- nodes must have the same media type
|
||||
if properties ["media.type"] ~= target_properties ["media.type"] then
|
||||
return false
|
||||
end
|
||||
|
||||
-- nodes must have opposite direction, or otherwise they must be both input
|
||||
-- and the target must have a monitor (so the target will be used as a source)
|
||||
local function isMonitor(properties)
|
||||
return properties ["item.node.direction"] == "input" and
|
||||
parseBool (properties ["item.features.monitor"]) and
|
||||
not parseBool (properties ["item.features.no-dsp"]) and
|
||||
properties ["item.factory.name"] == "si-audio-adapter"
|
||||
end
|
||||
|
||||
if properties ["item.node.direction"] == target_properties ["item.node.direction"]
|
||||
and not isMonitor (target_properties) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- check link group
|
||||
local function canLinkGroupCheck(link_group, si_target, hops)
|
||||
local target_props = si_target.properties
|
||||
local target_link_group = target_props ["node.link-group"]
|
||||
|
||||
if hops == 8 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- allow linking if target has no link-group property
|
||||
if not target_link_group then
|
||||
return true
|
||||
end
|
||||
|
||||
-- do not allow linking if target has the same link-group
|
||||
if link_group == target_link_group then
|
||||
return false
|
||||
end
|
||||
|
||||
-- make sure target is not linked with another node with same link group
|
||||
-- start by locating other nodes in the target's link-group, in opposite direction
|
||||
for n in linkables_om:iterate {
|
||||
Constraint { "id", "!", si_target.id, type = "gobject" },
|
||||
Constraint { "item.node.direction", "!", target_props ["item.node.direction"] },
|
||||
Constraint { "node.link-group", "=", target_link_group },
|
||||
} do
|
||||
-- iterate their peers and return false if one of them cannot link
|
||||
for silink in links_om:iterate () do
|
||||
local out_id = tonumber (silink.properties ["out.item.id"])
|
||||
local in_id = tonumber (silink.properties ["in.item.id"])
|
||||
if out_id == n.id or in_id == n.id then
|
||||
local peer_id = (out_id == n.id) and in_id or out_id
|
||||
local peer = linkables_om:lookup {
|
||||
Constraint { "id", "=", peer_id, type = "gobject" },
|
||||
}
|
||||
if peer and not canLinkGroupCheck (link_group, peer, hops + 1) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local link_group = properties ["node.link-group"]
|
||||
if link_group then
|
||||
return canLinkGroupCheck (link_group, si_target, 0)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function getTargetDirection (properties)
|
||||
local target_direction = nil
|
||||
if properties ["item.node.direction"] == "output" or
|
||||
(properties ["item.node.direction"] == "input" and
|
||||
parseBool (properties ["stream.capture.sink"])) then
|
||||
target_direction = "input"
|
||||
else
|
||||
target_direction = "output"
|
||||
end
|
||||
return target_direction
|
||||
end
|
||||
|
||||
function getDefaultNode (properties, target_direction)
|
||||
local target_media_class =
|
||||
properties ["media.type"] ..
|
||||
(target_direction == "input" and "/Sink" or "/Source")
|
||||
return default_nodes:call ("get-default-node", target_media_class)
|
||||
end
|
||||
|
||||
-- Try to locate a valid target node that was explicitly requsted by the
|
||||
-- client(node.target) or by the user(target.node)
|
||||
-- Use the target.node metadata, if "move" setting is enabled,
|
||||
-- then use the node.target property that was set on the node
|
||||
-- `properties` must be the properties dictionary of the session item
|
||||
-- that is currently being handled
|
||||
function findDefinedTarget (properties)
|
||||
local metadata = move and metadata_om:lookup ()
|
||||
local target_direction = getTargetDirection (properties)
|
||||
local target_key
|
||||
local target_value
|
||||
local node_defined = false
|
||||
|
||||
if properties ["target.object"] ~= nil then
|
||||
target_value = properties ["target.object"]
|
||||
target_key = "object.serial"
|
||||
node_defined = true
|
||||
elseif properties ["node.target"] ~= nil then
|
||||
target_value = properties ["node.target"]
|
||||
target_key = "node.id"
|
||||
node_defined = true
|
||||
end
|
||||
|
||||
if metadata then
|
||||
local id = metadata:find (properties ["node.id"], "target.object")
|
||||
if id ~= nil then
|
||||
target_value = id
|
||||
target_key = "object.serial"
|
||||
node_defined = false
|
||||
else
|
||||
id = metadata:find (properties ["node.id"], "target.node")
|
||||
if id ~= nil then
|
||||
target_value = id
|
||||
target_key = "node.id"
|
||||
node_defined = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if target_value == "-1" then
|
||||
return nil, false, node_defined
|
||||
end
|
||||
|
||||
if target_value and tonumber (target_value) then
|
||||
local si_target = linkables_om:lookup {
|
||||
Constraint { target_key, "=", target_value },
|
||||
}
|
||||
if si_target and canLink (properties, si_target) then
|
||||
return si_target, true, node_defined
|
||||
end
|
||||
end
|
||||
|
||||
if target_value then
|
||||
for si_target in linkables_om:iterate () do
|
||||
local target_props = si_target.properties
|
||||
if (target_props ["node.name"] == target_value or
|
||||
target_props ["object.path"] == target_value) and
|
||||
target_props ["item.node.direction"] == target_direction and
|
||||
canLink (properties, si_target) then
|
||||
return si_target, true, node_defined
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, (target_value ~= nil), node_defined
|
||||
end
|
||||
|
||||
function parseParam (param, id)
|
||||
local route = param:parse ()
|
||||
if route.pod_type == "Object" and route.object_id == id then
|
||||
return route.properties
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function arrayContains (a, value)
|
||||
for _, v in ipairs (a) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Does the target device have any active/available paths/routes to
|
||||
-- the physical device(spkr/mic/cam)?
|
||||
function haveAvailableRoutes (si_props)
|
||||
local card_profile_device = si_props ["card.profile.device"]
|
||||
local device_id = si_props ["device.id"]
|
||||
local device = device_id and devices_om:lookup {
|
||||
Constraint { "bound-id", "=", device_id, type = "gobject" },
|
||||
}
|
||||
|
||||
if not card_profile_device or not device then
|
||||
return true
|
||||
end
|
||||
|
||||
local found = 0
|
||||
local avail = 0
|
||||
|
||||
-- First check "SPA_PARAM_Route" if there are any active devices
|
||||
-- in an active profile.
|
||||
for p in device:iterate_params ("Route") do
|
||||
local route = parseParam (p, "Route")
|
||||
if not route then
|
||||
goto skip_route
|
||||
end
|
||||
|
||||
if (route.device ~= tonumber (card_profile_device)) then
|
||||
goto skip_route
|
||||
end
|
||||
|
||||
if (route.available == "no") then
|
||||
return false
|
||||
end
|
||||
|
||||
do return true end
|
||||
|
||||
::skip_route::
|
||||
end
|
||||
|
||||
-- Second check "SPA_PARAM_EnumRoute" if there is any route that
|
||||
-- is available if not active.
|
||||
for p in device:iterate_params ("EnumRoute") do
|
||||
local route = parseParam (p, "EnumRoute")
|
||||
if not route then
|
||||
goto skip_enum_route
|
||||
end
|
||||
|
||||
if not arrayContains (route.devices, tonumber (card_profile_device)) then
|
||||
goto skip_enum_route
|
||||
end
|
||||
found = found + 1;
|
||||
if (route.available ~= "no") then
|
||||
avail = avail + 1
|
||||
end
|
||||
::skip_enum_route::
|
||||
end
|
||||
|
||||
if found == 0 then
|
||||
return true
|
||||
end
|
||||
if avail > 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function findDefaultLinkable (si)
|
||||
local si_props = si.properties
|
||||
local target_direction = getTargetDirection (si_props)
|
||||
local def_node_id = getDefaultNode (si_props, target_direction)
|
||||
return linkables_om:lookup {
|
||||
Constraint { "node.id", "=", tostring (def_node_id) }
|
||||
}
|
||||
end
|
||||
|
||||
function checkPassthroughCompatibility (si, si_target)
|
||||
local si_must_passthrough = parseBool (si.properties ["item.node.encoded-only"])
|
||||
local si_target_must_passthrough = parseBool (si_target.properties ["item.node.encoded-only"])
|
||||
local can_passthrough = canPassthrough (si, si_target)
|
||||
if (si_must_passthrough or si_target_must_passthrough)
|
||||
and not can_passthrough then
|
||||
return false, can_passthrough
|
||||
end
|
||||
return true, can_passthrough
|
||||
end
|
||||
|
||||
function findBestLinkable (si)
|
||||
local si_props = si.properties
|
||||
local target_direction = getTargetDirection (si_props)
|
||||
local target_picked = nil
|
||||
local target_can_passthrough = false
|
||||
local target_priority = 0
|
||||
local target_plugged = 0
|
||||
|
||||
for si_target in linkables_om:iterate {
|
||||
Constraint { "item.node.type", "=", "device" },
|
||||
Constraint { "item.node.direction", "=", target_direction },
|
||||
Constraint { "media.type", "=", si_props ["media.type"] },
|
||||
} do
|
||||
local si_target_props = si_target.properties
|
||||
local si_target_node_id = si_target_props ["node.id"]
|
||||
local priority = tonumber (si_target_props ["priority.session"]) or 0
|
||||
|
||||
Log.debug (string.format ("Looking at: %s (%s)",
|
||||
tostring (si_target_props ["node.name"]),
|
||||
tostring (si_target_node_id)))
|
||||
|
||||
if not canLink (si_props, si_target) then
|
||||
Log.debug ("... cannot link, skip linkable")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
if not haveAvailableRoutes (si_target_props) then
|
||||
Log.debug ("... does not have routes, skip linkable")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
local passthrough_compatible, can_passthrough =
|
||||
checkPassthroughCompatibility (si, si_target)
|
||||
if not passthrough_compatible then
|
||||
Log.debug ("... passthrough is not compatible, skip linkable")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
local plugged = tonumber (si_target_props ["item.plugged.usec"]) or 0
|
||||
|
||||
Log.debug ("... priority:" .. tostring (priority) .. ", plugged:" .. tostring (plugged))
|
||||
|
||||
-- (target_picked == NULL) --> make sure atleast one target is picked.
|
||||
-- (priority > target_priority) --> pick the highest priority linkable(node)
|
||||
-- target.
|
||||
-- (priority == target_priority and plugged > target_plugged) --> pick the
|
||||
-- latest connected/plugged(in time) linkable(node) target.
|
||||
if (target_picked == nil or
|
||||
priority > target_priority or
|
||||
(priority == target_priority and plugged > target_plugged)) then
|
||||
Log.debug ("... picked")
|
||||
target_picked = si_target
|
||||
target_can_passthrough = can_passthrough
|
||||
target_priority = priority
|
||||
target_plugged = plugged
|
||||
end
|
||||
::skip_linkable::
|
||||
end
|
||||
|
||||
if target_picked then
|
||||
Log.info (string.format ("... best target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (target_picked.properties ["node.name"]),
|
||||
tostring (target_picked.properties ["node.id"]),
|
||||
tostring (target_can_passthrough)))
|
||||
return target_picked, target_can_passthrough
|
||||
else
|
||||
return nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
function findUndefinedTarget (si)
|
||||
-- Just find the best linkable if default nodes module is not loaded
|
||||
if default_nodes == nil then
|
||||
return findBestLinkable (si)
|
||||
end
|
||||
|
||||
-- Otherwise find the default linkable. If the default linkable is not
|
||||
-- compatible, we find the best one instead. We return nil if the default
|
||||
-- linkable does not exist.
|
||||
local si_target = findDefaultLinkable (si)
|
||||
if si_target then
|
||||
local passthrough_compatible, can_passthrough =
|
||||
checkPassthroughCompatibility (si, si_target)
|
||||
if canLink (si.properties, si_target) and passthrough_compatible then
|
||||
Log.info (string.format ("... default target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (si_target.properties ["node.name"]),
|
||||
tostring (si_target.properties ["node.id"]),
|
||||
tostring (can_passthrough)))
|
||||
return si_target, can_passthrough
|
||||
else
|
||||
return findBestLinkable (si)
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
function lookupLink (si_id, si_target_id)
|
||||
local link = links_om:lookup {
|
||||
Constraint { "out.item.id", "=", si_id },
|
||||
Constraint { "in.item.id", "=", si_target_id }
|
||||
}
|
||||
if not link then
|
||||
link = links_om:lookup {
|
||||
Constraint { "in.item.id", "=", si_id },
|
||||
Constraint { "out.item.id", "=", si_target_id }
|
||||
}
|
||||
end
|
||||
return link
|
||||
end
|
||||
|
||||
function checkLinkable (si, handle_nonstreams)
|
||||
-- only handle stream session items
|
||||
local si_props = si.properties
|
||||
|
|
@ -732,119 +113,6 @@ function checkLinkable (si, handle_nonstreams)
|
|||
return true, si_props
|
||||
end
|
||||
|
||||
si_flags = {}
|
||||
|
||||
function checkPending ()
|
||||
local pending_linkables = pending_linkables_om:get_n_objects ()
|
||||
|
||||
-- We cannot process linkables if some of them are pending activation,
|
||||
-- because linkables do not appear in the same order as nodes,
|
||||
-- and we cannot resolve target node references until all linkables
|
||||
-- have appeared.
|
||||
|
||||
if self.pending_error_timer then
|
||||
self.pending_error_timer:destroy ()
|
||||
self.pending_error_timer = nil
|
||||
end
|
||||
|
||||
if pending_linkables ~= 0 then
|
||||
-- Wait for linkables to get it sync
|
||||
Log.debug (string.format ("pending %d linkable not ready",
|
||||
pending_linkables))
|
||||
self.events_skipped = true
|
||||
|
||||
-- To make bugs in activation easier to debug, emit an error message
|
||||
-- if they occur. policy-node should never be suspended for 20sec.
|
||||
self.pending_error_timer = Core.timeout_add (20000, function ()
|
||||
self.pending_error_timer = nil
|
||||
if pending_linkables ~= 0 then
|
||||
Log.message (string.format ("%d pending linkable(s) not activated in 20sec. "
|
||||
.. "This should never happen.", pending_linkables))
|
||||
end
|
||||
end)
|
||||
|
||||
return true
|
||||
elseif self.events_skipped then
|
||||
Log.debug ("pending linkables ready")
|
||||
self.events_skipped = false
|
||||
-- Event-Stack TBD: do we need to retain this call here?
|
||||
rescan ()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function checkFollowDefault (si, si_target, has_node_defined_target)
|
||||
-- If it got linked to the default target that is defined by node
|
||||
-- props but not metadata, start ignoring the node prop from now on.
|
||||
-- This is what Pulseaudio does.
|
||||
--
|
||||
-- Pulseaudio skips here filter streams (i->origin_sink and
|
||||
-- o->destination_source set in PA). Pipewire does not have a flag
|
||||
-- explicitly for this, but we can use presence of node.link-group.
|
||||
if not has_node_defined_target then
|
||||
return
|
||||
end
|
||||
|
||||
local si_props = si.properties
|
||||
local target_props = si_target.properties
|
||||
local reconnect = not parseBool (si_props ["node.dont-reconnect"])
|
||||
local is_filter = (si_props ["node.link-group"] ~= nil)
|
||||
|
||||
if follow and default_nodes ~= nil and reconnect and not is_filter then
|
||||
local def_id = getDefaultNode (si_props, getTargetDirection (si_props))
|
||||
|
||||
if target_props ["node.id"] == tostring (def_id) then
|
||||
local metadata = metadata_om:lookup ()
|
||||
-- Set target.node, for backward compatibility
|
||||
metadata:set (tonumber (si_props ["node.id"]), "target.node", "Spa:Id", "-1")
|
||||
Log.info (si, "... set metadata to follow default")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
default_nodes = Plugin.find ("default-nodes-api")
|
||||
|
||||
metadata_om = ObjectManager {
|
||||
Interest {
|
||||
type = "metadata",
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
}
|
||||
}
|
||||
|
||||
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" } }
|
||||
|
||||
clients_om = ObjectManager { Interest { type = "client" } }
|
||||
|
||||
devices_om = ObjectManager { Interest { type = "device" } }
|
||||
|
||||
linkables_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLinkable",
|
||||
-- only handle si-audio-adapter and si-node
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "active-features", "!", 0, type = "gobject" },
|
||||
}
|
||||
}
|
||||
|
||||
pending_linkables_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLinkable",
|
||||
-- only handle si-audio-adapter and si-node
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "active-features", "=", 0, type = "gobject" },
|
||||
}
|
||||
}
|
||||
|
||||
links_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLink",
|
||||
-- only handle links created by this policy
|
||||
Constraint { "is.policy.item.link", "=", true },
|
||||
}
|
||||
}
|
||||
|
||||
function findAssociatedLinkGroupNode (si)
|
||||
local si_props = si.properties
|
||||
local node = si:get_associated_proxy ("node")
|
||||
|
|
@ -854,7 +122,7 @@ function findAssociatedLinkGroupNode (si)
|
|||
end
|
||||
|
||||
-- get the associated media class
|
||||
local assoc_direction = getTargetDirection (si_props)
|
||||
local assoc_direction = cutils.getTargetDirection (si_props)
|
||||
local assoc_media_class =
|
||||
si_props ["media.type"] ..
|
||||
(assoc_direction == "input" and "/Sink" or "/Source")
|
||||
|
|
@ -910,27 +178,21 @@ function onLinkGroupPortsStateChanged (si, old_state, new_state)
|
|||
end
|
||||
end
|
||||
|
||||
function ensureSiFlags (si)
|
||||
-- prepare flags table
|
||||
if not si_flags [si.id] then
|
||||
si_flags [si.id] = {}
|
||||
end
|
||||
end
|
||||
|
||||
function checkFiltersPortsState (si)
|
||||
local si_props = si.properties
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local link_group = node.properties ["node.link-group"]
|
||||
|
||||
ensureSiFlags (si)
|
||||
local si_id = si.id
|
||||
local si_flags = putils.get_flags (si_id)
|
||||
|
||||
-- only listen for ports state changed on audio filter streams
|
||||
if si_flags [si.id].ports_state_signal ~= true and
|
||||
if si_flags.ports_state_signal ~= true and
|
||||
si_props ["item.factory.name"] == "si-audio-adapter" and
|
||||
si_props ["item.node.type"] == "stream" and
|
||||
link_group ~= nil then
|
||||
si:connect ("adapter-ports-state-changed", onLinkGroupPortsStateChanged)
|
||||
si_flags [si.id].ports_state_signal = true
|
||||
si_flags.ports_state_signal = true
|
||||
putils.set_flags (si_id, si_flags)
|
||||
Log.info (si, "listening ports state changed on " .. si_props ["node.name"])
|
||||
end
|
||||
end
|
||||
|
|
@ -961,22 +223,6 @@ SimpleEventHook {
|
|||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "find-target-si-and-link@policy-node",
|
||||
type = "after-events-with-event",
|
||||
priority = "find-target-si",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "find-target-si-and-link" },
|
||||
Constraint { "event.subject.type", "=", "linkable" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local si = event:get_subject ()
|
||||
findTargetSiAndLink (si)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linkable-removed@policy-node",
|
||||
type = "on-event",
|
||||
|
|
@ -1087,14 +333,12 @@ if move then
|
|||
Constraint { "event.subject.type", "=", "metadata" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "=", "target.node" },
|
||||
Constraint { "event.subject.value", "!", "-1" },
|
||||
},
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "object-changed" },
|
||||
Constraint { "event.subject.type", "=", "metadata" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "=", "target.object" },
|
||||
Constraint { "event.subject.value", "!", "-1" },
|
||||
},
|
||||
},
|
||||
execute = function ()
|
||||
|
|
@ -1103,10 +347,28 @@ if move then
|
|||
}:register ()
|
||||
end
|
||||
|
||||
metadata_om:activate ()
|
||||
default_nodes = Plugin.find ("default-nodes-api")
|
||||
|
||||
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" } }
|
||||
|
||||
linkables_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLinkable",
|
||||
-- only handle si-audio-adapter and si-node
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "active-features", "!", 0, type = "gobject" },
|
||||
}
|
||||
}
|
||||
|
||||
pending_linkables_om = ObjectManager {
|
||||
Interest {
|
||||
type = "SiLinkable",
|
||||
-- only handle si-audio-adapter and si-node
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "active-features", "=", 0, type = "gobject" },
|
||||
}
|
||||
}
|
||||
|
||||
endpoints_om:activate ()
|
||||
clients_om:activate ()
|
||||
linkables_om:activate ()
|
||||
pending_linkables_om:activate ()
|
||||
links_om:activate ()
|
||||
devices_om:activate ()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,26 @@
|
|||
local testlib = {}
|
||||
|
||||
testlib.table1 = {}
|
||||
|
||||
testlib.table1 ["test-key"] = "test-value"
|
||||
|
||||
function testlib.test_add_ten (x)
|
||||
return x + 10
|
||||
end
|
||||
|
||||
Log.info("in testlib")
|
||||
function testlib.get_empty_table (key)
|
||||
testlib.table1 [key] = {}
|
||||
return testlib.table1 [key]
|
||||
end
|
||||
|
||||
function testlib.set_table (key, value)
|
||||
testlib.table1 [key] = value
|
||||
end
|
||||
|
||||
function testlib.get_table (key)
|
||||
return testlib.table1 [key]
|
||||
end
|
||||
|
||||
Log.info ("in testlib")
|
||||
|
||||
return testlib
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
local testlib = require("testlib")
|
||||
local testlib = require ("testlib")
|
||||
assert (package.loaded ["testlib"] == testlib)
|
||||
|
||||
assert(type(testlib) == "table")
|
||||
assert(package.loaded["testlib"] == testlib)
|
||||
assert (type (testlib) == "table")
|
||||
|
||||
local x = 1
|
||||
x = testlib.test_add_ten(x)
|
||||
assert(x == 11)
|
||||
x = testlib.test_add_ten (x)
|
||||
assert (x == 11)
|
||||
|
||||
local val_table = testlib.get_empty_table ("test-key")
|
||||
assert (type (val_table) == "table")
|
||||
val_table ["key"] = "value"
|
||||
val_table.id = 100
|
||||
|
||||
testlib.set_table ("test-key", val_table)
|
||||
|
||||
local val1 = testlib.get_table ("test-key")
|
||||
assert (val1 ["key"] == "value")
|
||||
assert (val1.id == 100)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue