Update
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Traverse through all the possible targets to pick up target node.
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
futils = require ("filter-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/find-best-target",
|
||||
after = { "linking/find-defined-target",
|
||||
"linking/find-filter-target",
|
||||
"linking/find-media-role-target",
|
||||
"linking/find-default-target" },
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
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
|
||||
|
||||
local target_direction = cutils.getTargetDirection (si_props)
|
||||
local target_picked = nil
|
||||
local target_can_passthrough = false
|
||||
local target_priority = 0
|
||||
local target_plugged = 0
|
||||
|
||||
log:info (si, string.format ("handling item: %s (%s)",
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
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"] },
|
||||
} do
|
||||
local target_props = target.properties
|
||||
local target_node_id = target_props ["node.id"]
|
||||
local si_target_node = target:get_associated_proxy ("node")
|
||||
local si_target_link_group = si_target_node.properties ["node.link-group"]
|
||||
local priority = tonumber (target_props ["priority.session"]) or 0
|
||||
|
||||
log:debug (string.format ("Looking at: %s (%s)",
|
||||
tostring (target_props ["node.name"]),
|
||||
tostring (target_node_id)))
|
||||
|
||||
-- Skip smart filters as best target
|
||||
if si_target_link_group ~= nil and
|
||||
futils.is_filter_smart (target_direction, si_target_link_group) then
|
||||
Log.debug ("... ignoring smart filter as best target")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
if not lutils.canLink (si_props, target) then
|
||||
log:debug ("... cannot link, skip linkable")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
if not lutils.haveAvailableRoutes (target_props) then
|
||||
log:debug ("... does not have routes, 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
|
||||
|
||||
local plugged = tonumber (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 = 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.can_passthrough = target_can_passthrough
|
||||
event:set_data ("target", target_picked)
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
@@ -0,0 +1,58 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Check if default nodes can be picked up as target node.
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/find-default-target",
|
||||
after = { "linking/find-defined-target",
|
||||
"linking/find-filter-target",
|
||||
"linking/find-media-role-target" },
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
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
|
||||
|
||||
local target_picked = false
|
||||
|
||||
log:info (si, string.format ("handling item: %s (%s)",
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
target = lutils.findDefaultLinkable (si)
|
||||
|
||||
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 ("... default target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (target.properties ["node.name"]),
|
||||
tostring (target.properties ["node.id"]),
|
||||
tostring (can_passthrough)))
|
||||
si_flags.can_passthrough = can_passthrough
|
||||
event:set_data ("target", target)
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
@@ -0,0 +1,131 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Check if the 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 the default metadata
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/find-defined-target",
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
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
|
||||
|
||||
log:info (si, string.format ("handling item %d: %s (%s)", si.id,
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
local metadata = Settings.get_boolean ("linking.allow-moving-streams") and
|
||||
cutils.get_default_metadata_object ()
|
||||
local dont_fallback = cutils.parseBool (si_props ["node.dont-fallback"])
|
||||
local dont_move = cutils.parseBool (si_props ["node.dont-move"])
|
||||
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 and not dont_move 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
|
||||
target = nil
|
||||
elseif target_value and tonumber (target_value) then
|
||||
target = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { target_key, "=", target_value },
|
||||
}
|
||||
if target and lutils.canLink (si_props, target) then
|
||||
target_picked = true
|
||||
end
|
||||
elseif target_value then
|
||||
for lnkbl in om:iterate { type = "SiLinkable" } do
|
||||
local target_props = lnkbl.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
|
||||
lutils.canLink (si_props, lnkbl) then
|
||||
target_picked = true
|
||||
target = lnkbl
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local can_passthrough, passthrough_compatible
|
||||
if target then
|
||||
passthrough_compatible, can_passthrough =
|
||||
lutils.checkPassthroughCompatibility (si, target)
|
||||
if not passthrough_compatible then
|
||||
target = nil
|
||||
end
|
||||
end
|
||||
|
||||
si_flags.has_defined_target = false
|
||||
if target_picked and target then
|
||||
log:info (si,
|
||||
string.format ("... defined target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (target.properties ["node.name"]),
|
||||
tostring (target.properties ["node.id"]),
|
||||
tostring (can_passthrough)))
|
||||
si_flags.has_node_defined_target = node_defined
|
||||
si_flags.can_passthrough = can_passthrough
|
||||
si_flags.has_defined_target = true
|
||||
event:set_data ("target", target)
|
||||
elseif target_value and dont_fallback then
|
||||
-- send error to client and destroy node if linger is not set
|
||||
local linger = cutils.parseBool (si_props ["node.linger"])
|
||||
if not linger then
|
||||
local node = si:get_associated_proxy ("node")
|
||||
lutils.sendClientError (event, node, -2, "defined target not found")
|
||||
node:request_destroy ()
|
||||
log:info(si, "... destroyed node as defined target was not found")
|
||||
else
|
||||
log:info(si, "... waiting for defined target as dont-fallback is set")
|
||||
end
|
||||
event:stop_processing ()
|
||||
end
|
||||
|
||||
end
|
||||
}:register ()
|
||||
@@ -0,0 +1,92 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2023 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Check if the target node is a filter target.
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
futils = require ("filter-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
function findFilterTarget (si, om)
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local link_group = node.properties ["node.link-group"]
|
||||
local target_id = -1
|
||||
|
||||
-- return nil if session item is not a filter node
|
||||
if link_group == nil then
|
||||
return nil, false
|
||||
end
|
||||
|
||||
-- return nil if filter is not smart
|
||||
local direction = cutils.getTargetDirection (si.properties)
|
||||
if not futils.is_filter_smart (direction, link_group) then
|
||||
return nil, false
|
||||
end
|
||||
|
||||
-- get the filter target
|
||||
return futils.get_filter_target (direction, link_group), true
|
||||
end
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/find-filter-target",
|
||||
after = "linking/find-defined-target",
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
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
|
||||
|
||||
local dont_fallback = cutils.parseBool (si_props ["node.dont-fallback"])
|
||||
local target_picked = false
|
||||
local allow_fallback
|
||||
|
||||
log:info (si, string.format ("handling item %d: %s (%s)", si.id,
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
target, is_smart_filter = findFilterTarget (si, om)
|
||||
|
||||
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 and target then
|
||||
log:info (si,
|
||||
string.format ("... filter target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (target.properties ["node.name"]),
|
||||
tostring (target.properties ["node.id"]),
|
||||
tostring (can_passthrough)))
|
||||
si_flags.can_passthrough = can_passthrough
|
||||
event:set_data ("target", target)
|
||||
elseif is_smart_filter and dont_fallback then
|
||||
-- send error to client and destroy node if linger is not set
|
||||
local linger = cutils.parseBool (si_props ["node.linger"])
|
||||
if not linger then
|
||||
local node = si:get_associated_proxy ("node")
|
||||
lutils.sendClientError (event, node, -2, "smart filter defined target not found")
|
||||
node:request_destroy ()
|
||||
log:info(si, "... destroyed node as smart filter defined target was not found")
|
||||
else
|
||||
log:info(si, "... waiting for smart filter defined target as dont-fallback is set")
|
||||
end
|
||||
event:stop_processing ()
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
@@ -0,0 +1,70 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2024 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Select the media role target
|
||||
|
||||
cutils = require("common-utils")
|
||||
lutils = require("linking-utils")
|
||||
log = Log.open_topic("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/find-media-role-target",
|
||||
after = { "linking/find-defined-target",
|
||||
"linking/find-filter-target" },
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local _, om, si, si_props, _, target =
|
||||
lutils:unwrap_select_target_event (event)
|
||||
|
||||
local target_direction = cutils.getTargetDirection (si_props)
|
||||
local media_role = si_props["media.role"]
|
||||
|
||||
-- bypass the hook if the target is already picked up or if the role is not
|
||||
-- defined
|
||||
if target or media_role == nil then
|
||||
return
|
||||
end
|
||||
|
||||
log:info (si, string.format ("handling item %d: %s (%s) role (%s)", si.id,
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"]), media_role))
|
||||
|
||||
for si_target in om:iterate {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.node.direction", "=", target_direction },
|
||||
Constraint { "device.intended-roles", "+" },
|
||||
Constraint { "media.type", "=", si_props["media.type"] },
|
||||
} do
|
||||
|
||||
local roles_json = si_target.properties["device.intended-roles"]
|
||||
local roles_table = Json.Raw(roles_json):parse()
|
||||
|
||||
for _, target_role in ipairs(roles_table) do
|
||||
if target_role == media_role then
|
||||
target = si_target
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if target then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- set target
|
||||
if target ~= nil then
|
||||
log:info(si,
|
||||
string.format("... media role target picked: %s (%s)",
|
||||
tostring(target.properties["node.name"]),
|
||||
tostring(target.properties["node.id"])))
|
||||
event:set_data("target", target)
|
||||
end
|
||||
end
|
||||
}:register()
|
||||
@@ -0,0 +1,37 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- example of a user injectible hook to link a node to a custom target
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/sample-find-user-target",
|
||||
before = "linking/find-defined-target",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
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
|
||||
|
||||
log:info (si, "in find-user-target")
|
||||
|
||||
-- implement logic here to find a suitable target
|
||||
|
||||
-- store the found target on the event,
|
||||
-- the next hooks will take care of linking
|
||||
event:set_data ("target", target)
|
||||
end
|
||||
}:register ()
|
||||
@@ -0,0 +1,92 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2023 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Check if the target node is a filter target.
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
futils = require ("filter-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/get-filter-from-target",
|
||||
after = { "linking/find-defined-target",
|
||||
"linking/find-filter-target",
|
||||
"linking/find-media-role-target",
|
||||
"linking/find-default-target",
|
||||
"linking/find-best-target" },
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source, om, si, si_props, si_flags, target =
|
||||
lutils:unwrap_select_target_event (event)
|
||||
|
||||
-- bypass the hook if the target was not found or if it is a role-based policy target
|
||||
if target == nil or lutils.is_role_policy_target (si_props, target.properties) then
|
||||
return
|
||||
end
|
||||
|
||||
-- bypass the hook if the session item is a smart filter
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local node_props = node.properties
|
||||
local link_group = node_props ["node.link-group"]
|
||||
local target_direction = cutils.getTargetDirection (si.properties)
|
||||
if link_group ~= nil and
|
||||
futils.is_filter_smart (target_direction, link_group) then
|
||||
return
|
||||
end
|
||||
|
||||
-- bypass the hook if target is defined, is a filter and is targetable
|
||||
local target_node = target:get_associated_proxy ("node")
|
||||
local target_node_props = target_node.properties
|
||||
local target_link_group = target_node_props ["node.link-group"]
|
||||
if target_link_group ~= nil and si_flags.has_defined_target then
|
||||
if futils.is_filter_smart (target_direction, target_link_group) and
|
||||
not futils.is_filter_disabled (target_direction, target_link_group) and
|
||||
futils.is_filter_targetable (target_direction, target_link_group) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Get the filter from the given target if it exists, otherwise get the
|
||||
-- default filter, but only if target was not defined
|
||||
local media_type = si_props["media.type"]
|
||||
local filter_target = futils.get_filter_from_target (target_direction, media_type, target)
|
||||
if filter_target ~= nil then
|
||||
target = filter_target
|
||||
log:info (si, "... got filter for given target")
|
||||
elseif filter_target == nil and not si_flags.has_defined_target then
|
||||
filter_target = futils.get_filter_from_target (target_direction, media_type, nil)
|
||||
if filter_target ~= nil then
|
||||
target = filter_target
|
||||
log:info (si, "... got default filter for given target")
|
||||
end
|
||||
end
|
||||
|
||||
local can_passthrough, passthrough_compatible
|
||||
if target ~= nil 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 ("... target picked: %s (%s), can_passthrough:%s",
|
||||
tostring (target.properties ["node.name"]),
|
||||
tostring (target.properties ["node.id"]),
|
||||
tostring (can_passthrough)))
|
||||
si_flags.can_passthrough = can_passthrough
|
||||
event:set_data ("target", target)
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
157
pipewire/.config/wireplumber/scripts/linking/link-target.lua
Normal file
157
pipewire/.config/wireplumber/scripts/linking/link-target.lua
Normal file
@@ -0,0 +1,157 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Links a session item to the target that has been previously selected.
|
||||
-- This is meant to be the last hook in the select-target chain.
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
AsyncEventHook {
|
||||
name = "linking/link-target",
|
||||
after = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
steps = {
|
||||
start = {
|
||||
next = "none",
|
||||
execute = function (event, transition)
|
||||
local source, om, si, si_props, si_flags, target =
|
||||
lutils:unwrap_select_target_event (event)
|
||||
|
||||
if not target then
|
||||
-- bypass the hook, nothing to link to.
|
||||
transition:advance ()
|
||||
return
|
||||
end
|
||||
|
||||
local target_props = target.properties
|
||||
local out_item = nil
|
||||
local in_item = nil
|
||||
local si_link = nil
|
||||
local passthrough = si_flags.can_passthrough
|
||||
|
||||
log:info (si, string.format ("handling item %d: %s (%s)", si.id,
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
local exclusive = cutils.parseBool (si_props ["node.exclusive"])
|
||||
|
||||
-- break rescan if tried more than 5 times with same target
|
||||
if si_flags.failed_peer_id ~= nil and
|
||||
si_flags.failed_peer_id == target.id and
|
||||
si_flags.failed_count ~= nil and
|
||||
si_flags.failed_count > 5 then
|
||||
transition:return_error ("tried to link on last rescan, not retrying "
|
||||
.. tostring (si_link))
|
||||
return
|
||||
end
|
||||
|
||||
if si_props["item.node.direction"] == "output" then
|
||||
-- playback
|
||||
out_item = si
|
||||
in_item = target
|
||||
else
|
||||
-- capture
|
||||
in_item = si
|
||||
out_item = target
|
||||
end
|
||||
|
||||
local is_role_policy_link = lutils.is_role_policy_target (si_props, target_props)
|
||||
|
||||
log:info (si,
|
||||
string.format ("link %s <-> %s passthrough:%s, exclusive:%s, media role link:%s",
|
||||
tostring (si_props ["node.name"]),
|
||||
tostring (target_props ["node.name"]),
|
||||
tostring (passthrough),
|
||||
tostring (exclusive),
|
||||
tostring (is_role_policy_link)))
|
||||
|
||||
-- create and configure link
|
||||
si_link = SessionItem ("si-standard-link")
|
||||
if not si_link:configure {
|
||||
["out.item"] = out_item,
|
||||
["in.item"] = in_item,
|
||||
["passthrough"] = passthrough,
|
||||
["exclusive"] = exclusive,
|
||||
["out.item.port.context"] = "output",
|
||||
["in.item.port.context"] = "input",
|
||||
["media.role"] = si_props["media.role"],
|
||||
["target.media.class"] = target_props["media.class"],
|
||||
["policy.role-based.priority"] = target_props["policy.role-based.priority"],
|
||||
["policy.role-based.action.same-priority"] = target_props["policy.role-based.action.same-priority"],
|
||||
["policy.role-based.action.lower-priority"] = target_props["policy.role-based.action.lower-priority"],
|
||||
["is.role.policy.link"] = is_role_policy_link,
|
||||
["main.item.id"] = si.id,
|
||||
["target.item.id"] = target.id,
|
||||
} then
|
||||
transition:return_error ("failed to configure si-standard-link "
|
||||
.. tostring (si_link))
|
||||
return
|
||||
end
|
||||
|
||||
local ids = {si.id, target.id}
|
||||
si_link:connect("link-error", function (_, error_msg)
|
||||
for _, id in ipairs (ids) do
|
||||
local si = om:lookup {
|
||||
Constraint { "id", "=", id, type = "gobject" },
|
||||
}
|
||||
if si then
|
||||
local node = si:get_associated_proxy ("node")
|
||||
lutils.sendClientError(event, node, -32, error_msg)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- register
|
||||
si_flags.was_handled = true
|
||||
si_flags.peer_id = target.id
|
||||
si_flags.failed_peer_id = 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 ()
|
||||
|
||||
log:debug (si_link, "registered link between "
|
||||
.. tostring (si) .. " and " .. tostring (target))
|
||||
|
||||
-- only activate non role-based policy links because their activation is
|
||||
-- handled by rescan-media-role-links.lua
|
||||
if not is_role_policy_link then
|
||||
si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
|
||||
if e then
|
||||
transition:return_error (tostring (l) .. " link failed: "
|
||||
.. tostring (e))
|
||||
if si_flags ~= nil then
|
||||
si_flags.peer_id = nil
|
||||
end
|
||||
l:remove ()
|
||||
else
|
||||
si_flags.failed_peer_id = nil
|
||||
if si_flags.peer_id == nil then
|
||||
si_flags.peer_id = target.id
|
||||
end
|
||||
si_flags.failed_count = 0
|
||||
|
||||
log:debug (l, "activated link between "
|
||||
.. tostring (si) .. " and " .. tostring (target))
|
||||
|
||||
transition:advance ()
|
||||
end
|
||||
end)
|
||||
else
|
||||
lutils.updatePriorityMediaRoleLink(si_link)
|
||||
transition:advance ()
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
}:register ()
|
||||
125
pipewire/.config/wireplumber/scripts/linking/prepare-link.lua
Normal file
125
pipewire/.config/wireplumber/scripts/linking/prepare-link.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- 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.
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source, _, si, si_props, si_flags, target =
|
||||
lutils:unwrap_select_target_event (event)
|
||||
|
||||
local si_id = si.id
|
||||
local reconnect = not cutils.parseBool (si_props ["node.dont-reconnect"])
|
||||
local exclusive = cutils.parseBool (si_props ["node.exclusive"])
|
||||
local si_must_passthrough = cutils.parseBool (si_props ["item.node.encoded-only"])
|
||||
|
||||
log:info (si, string.format ("handling item %d: %s (%s)", si_id,
|
||||
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 target and si_flags.peer_id == target.id then
|
||||
log:info (si, "... already linked to proper target")
|
||||
|
||||
-- Check this also here, in case in default targets changed
|
||||
if Settings.get_boolean ("linking.follow-default-target") and
|
||||
si_flags.has_node_defined_target then
|
||||
lutils.checkFollowDefault (si, target)
|
||||
end
|
||||
|
||||
target = nil
|
||||
goto done
|
||||
end
|
||||
|
||||
local link = lutils.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
|
||||
target = nil
|
||||
goto done
|
||||
end
|
||||
|
||||
-- check target's availability
|
||||
if target then
|
||||
local target_is_linked, target_is_exclusive = lutils.isLinked (target)
|
||||
if target_is_exclusive then
|
||||
log:info (si, "... target is linked exclusively")
|
||||
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")
|
||||
target = nil
|
||||
else
|
||||
-- disable passthrough, we can live without it
|
||||
si_flags.can_passthrough = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not target then
|
||||
log:info (si, "... target not found, reconnect:" .. tostring (reconnect))
|
||||
|
||||
local node = si:get_associated_proxy ("node")
|
||||
if reconnect and si_flags.was_handled then
|
||||
log:info (si, "... waiting reconnect")
|
||||
return
|
||||
end
|
||||
|
||||
local linger = cutils.parseBool (si_props ["node.linger"])
|
||||
|
||||
if linger then
|
||||
log:info (si, "... node linger")
|
||||
return
|
||||
end
|
||||
|
||||
lutils.sendClientError (event, node, -2,
|
||||
reconnect and "no target node available" or "target not found")
|
||||
|
||||
if not reconnect then
|
||||
log:info (si, "... destroy node")
|
||||
node:request_destroy ()
|
||||
end
|
||||
end
|
||||
|
||||
::done::
|
||||
event:set_data ("target", target)
|
||||
end
|
||||
}:register ()
|
||||
@@ -0,0 +1,203 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2024 Collabora Ltd.
|
||||
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
lutils = require("linking-utils")
|
||||
cutils = require("common-utils")
|
||||
log = Log.open_topic("s-linking")
|
||||
|
||||
function restoreVolume (om, link)
|
||||
setVolume(om, link, 1.0)
|
||||
end
|
||||
|
||||
function duckVolume (om, link)
|
||||
setVolume(om, link, Settings.get_float("linking.role-based.duck-level"))
|
||||
end
|
||||
|
||||
function setVolume (om, link, level)
|
||||
local lprops = link.properties
|
||||
local media_role_si_id = nil
|
||||
local dir = lprops ["item.node.direction"]
|
||||
|
||||
if dir == "output" then
|
||||
media_role_si_id = lprops ["out.item.id"]
|
||||
else
|
||||
media_role_si_id = lprops ["in.item.id"]
|
||||
end
|
||||
|
||||
local media_role_lnkbl = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "id", "=", media_role_si_id, type = "gobject" },
|
||||
}
|
||||
|
||||
-- apply volume control on the stream node of the loopback module, instead of
|
||||
-- the sink/source node as it simplyfies the volume ducking and
|
||||
-- restoration.
|
||||
local media_role_other_lnkbl = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "node.link-group", "=", media_role_lnkbl.properties ["node.link-group"] },
|
||||
Constraint { "id", "!", media_role_lnkbl.id, type = "gobject" },
|
||||
}
|
||||
|
||||
if media_role_other_lnkbl then
|
||||
local n = media_role_other_lnkbl:get_associated_proxy("node")
|
||||
if n then
|
||||
log:info(string.format(".. %s volume of media role node \"%s(%d)\" to %f",
|
||||
level < 1.0 and "duck" or "restore", n.properties ["node.name"],
|
||||
n ["bound-id"], level))
|
||||
|
||||
local props = {
|
||||
"Spa:Pod:Object:Param:Props",
|
||||
"Props",
|
||||
volume = level,
|
||||
}
|
||||
|
||||
local param = Pod.Object(props)
|
||||
n:set_param("Props", param)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function getSuspendPlaybackFromMetadata (om)
|
||||
local suspend = false
|
||||
local metadata = om:lookup {
|
||||
type = "metadata",
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
}
|
||||
if metadata then
|
||||
local value = metadata:find(0, "suspend.playback")
|
||||
if value then
|
||||
suspend = value == "1" and true or false
|
||||
end
|
||||
end
|
||||
return suspend
|
||||
end
|
||||
|
||||
AsyncEventHook {
|
||||
name = "linking/rescan-media-role-links",
|
||||
interests = {
|
||||
EventInterest {
|
||||
-- on media client link added and removed
|
||||
Constraint { "event.type", "c", "session-item-added", "session-item-removed" },
|
||||
Constraint { "event.session-item.interface", "=", "link" },
|
||||
Constraint { "is.role.policy.link", "=", true },
|
||||
},
|
||||
EventInterest {
|
||||
-- on default metadata suspend.playback changed
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "=", "suspend.playback" },
|
||||
}
|
||||
},
|
||||
steps = {
|
||||
start = {
|
||||
next = "none",
|
||||
execute = function(event, transition)
|
||||
local source, om, _, si_props, _, _ =
|
||||
lutils:unwrap_select_target_event(event)
|
||||
|
||||
local metadata_om = source:call("get-object-manager", "metadata")
|
||||
local suspend = getSuspendPlaybackFromMetadata(metadata_om)
|
||||
local pending_activations = 0
|
||||
local mc = si_props ["target.media.class"]
|
||||
local pmrl_active = nil
|
||||
pmrl = lutils.getPriorityMediaRoleLink(mc)
|
||||
|
||||
log:debug("Rescanning media role links...")
|
||||
|
||||
local function onMediaRoleLinkActivated (l, e)
|
||||
local si_id = tonumber(l.properties ["main.item.id"])
|
||||
local target_id = tonumber(l.properties ["target.item.id"])
|
||||
local si_flags = lutils:get_flags(si_id)
|
||||
|
||||
if e then
|
||||
log:warning(l, "failed to activate media role link: " .. e)
|
||||
if si_flags ~= nil then
|
||||
si_flags.peer_id = nil
|
||||
end
|
||||
l:remove()
|
||||
else
|
||||
log:info(l, "media role link activated successfully")
|
||||
si_flags.failed_peer_id = nil
|
||||
if si_flags.peer_id == nil then
|
||||
si_flags.peer_id = target_id
|
||||
end
|
||||
si_flags.failed_count = 0
|
||||
end
|
||||
|
||||
-- advance only when all pending activations are completed
|
||||
pending_activations = pending_activations - 1
|
||||
if pending_activations <= 0 then
|
||||
log:info("All media role links activated")
|
||||
transition:advance()
|
||||
end
|
||||
end
|
||||
|
||||
for link in om:iterate {
|
||||
type = "SiLink",
|
||||
Constraint { "is.role.policy.link", "=", true },
|
||||
Constraint { "target.media.class", "=", mc },
|
||||
} do
|
||||
-- deactivate all links if suspend playback metadata is present
|
||||
if suspend then
|
||||
link:deactivate(Feature.SessionItem.ACTIVE)
|
||||
end
|
||||
|
||||
local active = ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0)
|
||||
|
||||
log:debug(string.format(" .. looking at link(%d) active %s pmrl %s", link.id, tostring(active),
|
||||
tostring(link == pmrl)))
|
||||
|
||||
if link == pmrl then
|
||||
pmrl_active = active
|
||||
restoreVolume(om, pmrl)
|
||||
goto continue
|
||||
end
|
||||
|
||||
local action = lutils.getAction(pmrl, link)
|
||||
|
||||
log:debug(string.format(" .. apply action(%s) on link(%d)", action, link.id, tostring(active)))
|
||||
|
||||
if action == "cork" then
|
||||
if active then
|
||||
link:deactivate(Feature.SessionItem.ACTIVE)
|
||||
end
|
||||
elseif action == "mix" then
|
||||
if not active and not suspend then
|
||||
pending_activations = pending_activations + 1
|
||||
link:activate(Feature.SessionItem.ACTIVE, onMediaRoleLinkActivated)
|
||||
end
|
||||
restoreVolume(om, link)
|
||||
elseif action == "duck" then
|
||||
if not active and not suspend then
|
||||
pending_activations = pending_activations + 1
|
||||
link:activate(Feature.SessionItem.ACTIVE, onMediaRoleLinkActivated)
|
||||
end
|
||||
duckVolume(om, link)
|
||||
else
|
||||
log:warning("Unknown action: " .. action)
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
if pmrl and not pmrl_active then
|
||||
pending_activations = pending_activations + 1
|
||||
pmrl:activate(Feature.SessionItem.ACTIVE, onMediaRoleLinkActivated)
|
||||
restoreVolume(om, pmrl)
|
||||
end
|
||||
|
||||
-- just advance transition if no pending activations are needed
|
||||
if pending_activations <= 0 then
|
||||
log:debug("All media role links rescanned")
|
||||
transition:advance()
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
}:register()
|
||||
235
pipewire/.config/wireplumber/scripts/linking/rescan.lua
Normal file
235
pipewire/.config/wireplumber/scripts/linking/rescan.lua
Normal file
@@ -0,0 +1,235 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2020-2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Handle new linkables and trigger rescanning of the graph.
|
||||
-- Rescan the graph by pushing new select-target events for
|
||||
-- all linkables that need to be linked
|
||||
-- Cleanup links when the linkables they are associated with are removed.
|
||||
-- Also, cleanup flags attached to linkables.
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
futils = require ("filter-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
handles = {}
|
||||
|
||||
function checkFilter (si, om, handle_nonstreams)
|
||||
-- always handle filters if handle_nonstreams is true, even if it is disabled
|
||||
if handle_nonstreams then
|
||||
return true
|
||||
end
|
||||
|
||||
-- always return true if this is not a filter
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local link_group = node.properties["node.link-group"]
|
||||
if link_group == nil then
|
||||
return true
|
||||
end
|
||||
|
||||
local direction = cutils.getTargetDirection (si.properties)
|
||||
|
||||
-- always handle filters that are not smart
|
||||
if not futils.is_filter_smart (direction, link_group) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- dont handle smart filters that are disabled
|
||||
return not futils.is_filter_disabled (direction, link_group)
|
||||
end
|
||||
|
||||
function checkLinkable (si, om, handle_nonstreams)
|
||||
local si_props = si.properties
|
||||
|
||||
-- For the rest of them, only handle stream session items
|
||||
if not si_props or (si_props ["item.node.type"] ~= "stream"
|
||||
and not handle_nonstreams) then
|
||||
return false, si_props
|
||||
end
|
||||
|
||||
-- check filters
|
||||
if not checkFilter (si, om, handle_nonstreams) then
|
||||
return false, si_props
|
||||
end
|
||||
|
||||
return true, si_props
|
||||
end
|
||||
|
||||
function unhandleLinkable (si, om)
|
||||
local si_id = si.id
|
||||
local valid, si_props = checkLinkable (si, om, true)
|
||||
if not valid then
|
||||
return
|
||||
end
|
||||
|
||||
log:info (si, string.format ("unhandling item %d", si_id))
|
||||
|
||||
-- iterate over all the links in the graph and
|
||||
-- remove any links associated with this item
|
||||
for silink in om:iterate { type = "SiLink" } 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
|
||||
local in_flags = lutils:get_flags (in_id)
|
||||
local out_flags = lutils:get_flags (out_id)
|
||||
|
||||
if out_id == si_id and in_flags.peer_id == out_id then
|
||||
in_flags.peer_id = nil
|
||||
elseif in_id == si_id and out_flags.peer_id == in_id then
|
||||
out_flags.peer_id = nil
|
||||
end
|
||||
|
||||
if cutils.parseBool (silink.properties["is.role.policy.link"]) then
|
||||
lutils.clearPriorityMediaRoleLink(silink)
|
||||
end
|
||||
|
||||
silink:remove ()
|
||||
log:info (silink, "... link removed")
|
||||
end
|
||||
end
|
||||
|
||||
lutils:clear_flags (si_id)
|
||||
end
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/linkable-removed",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "session-item-removed" },
|
||||
Constraint { "event.session-item.interface", "=", "linkable" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local si = event:get_subject ()
|
||||
local source = event:get_source ()
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
|
||||
unhandleLinkable (si, om)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
function handleLinkables (source)
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
|
||||
for si in om:iterate { type = "SiLinkable" } do
|
||||
local valid, si_props = checkLinkable (si, om)
|
||||
if not valid then
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
-- check if we need to link this node at all
|
||||
local autoconnect = cutils.parseBool (si_props ["node.autoconnect"])
|
||||
if not autoconnect then
|
||||
log:debug (si, tostring (si_props ["node.name"]) .. " does not need to be autoconnected")
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
-- push event to find target and link
|
||||
source:call ("push-event", "select-target", si, nil)
|
||||
|
||||
::skip_linkable::
|
||||
end
|
||||
end
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/rescan",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "rescan-for-linking" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
|
||||
log:info ("rescanning...")
|
||||
|
||||
-- always unlink all filters that are smart and disabled
|
||||
for si in om:iterate {
|
||||
type = "SiLinkable",
|
||||
Constraint { "node.link-group", "+" },
|
||||
} do
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local link_group = node.properties["node.link-group"]
|
||||
local direction = cutils.getTargetDirection (si.properties)
|
||||
if futils.is_filter_smart (direction, link_group) and
|
||||
futils.is_filter_disabled (direction, link_group) then
|
||||
unhandleLinkable (si, om)
|
||||
end
|
||||
end
|
||||
|
||||
handleLinkables (source)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/rescan-trigger",
|
||||
interests = {
|
||||
-- on linkable added or removed, where linkable is adapter or plain node
|
||||
EventInterest {
|
||||
Constraint { "event.type", "c", "session-item-added", "session-item-removed" },
|
||||
Constraint { "event.session-item.interface", "=", "linkable" },
|
||||
},
|
||||
-- on device Routes changed
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "device-params-changed" },
|
||||
Constraint { "event.subject.param-id", "c", "Route", "EnumRoute" },
|
||||
},
|
||||
-- on any "default" target changed
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "c", "default.audio.source",
|
||||
"default.audio.sink", "default.video.source" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
source:call ("schedule-rescan", "linking")
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/rescan-trigger-on-filters-metadata-changed",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "filters" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
source:call ("schedule-rescan", "linking")
|
||||
end
|
||||
}:register ()
|
||||
|
||||
function handleMoveSetting (enable)
|
||||
if (not handles.move_hook) and (enable == true) then
|
||||
handles.move_hook = SimpleEventHook {
|
||||
name = "linking/rescan-trigger-on-target-metadata-changed",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "c", "target.object", "target.node" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
source:call ("schedule-rescan", "linking")
|
||||
end
|
||||
}
|
||||
handles.move_hook:register()
|
||||
elseif (handles.move_hook) and (enable == false) then
|
||||
handles.move_hook:remove ()
|
||||
handles.move_hook = nil
|
||||
end
|
||||
end
|
||||
|
||||
Settings.subscribe ("linking.allow-moving-streams", function ()
|
||||
handleMoveSetting (Settings.get_boolean ("linking.allow-moving-streams"))
|
||||
end)
|
||||
handleMoveSetting (Settings.get_boolean ("linking.allow-moving-streams"))
|
||||
Reference in New Issue
Block a user