Moving files:
This commit is contained in:
39
wireplumber/scripts/default-nodes/apply-default-node.lua
Normal file
39
wireplumber/scripts/default-nodes/apply-default-node.lua
Normal file
@@ -0,0 +1,39 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
log = Log.open_topic ("s-default-nodes")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "default-nodes/apply-default-node",
|
||||
after = { "default-nodes/find-best-default-node",
|
||||
"default-nodes/find-selected-default-node",
|
||||
"default-nodes/find-stored-default-node" },
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-default-node" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local props = event:get_properties ()
|
||||
local def_node_type = props ["default-node.type"]
|
||||
local selected_node = event:get_data ("selected-node")
|
||||
|
||||
local om = source:call ("get-object-manager", "metadata")
|
||||
local metadata = om:lookup { Constraint { "metadata.name", "=", "default" } }
|
||||
|
||||
if selected_node then
|
||||
local key = "default." .. def_node_type
|
||||
|
||||
log:info ("set default node for " .. key .. " " .. selected_node)
|
||||
|
||||
metadata:set (0, key, "Spa:String:JSON",
|
||||
Json.Object { ["name"] = selected_node }:to_string ())
|
||||
else
|
||||
metadata:set (0, "default." .. def_node_type, nil, nil)
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
41
wireplumber/scripts/default-nodes/find-best-default-node.lua
Normal file
41
wireplumber/scripts/default-nodes/find-best-default-node.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
log = Log.open_topic ("s-default-nodes")
|
||||
|
||||
nutils = require ("node-utils")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "default-nodes/find-best-default-node",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-default-node" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local available_nodes = event:get_data ("available-nodes")
|
||||
local selected_prio = event:get_data ("selected-node-priority") or 0
|
||||
local selected_node = event:get_data ("selected-node")
|
||||
|
||||
available_nodes = available_nodes and available_nodes:parse ()
|
||||
if not available_nodes then
|
||||
return
|
||||
end
|
||||
|
||||
for _, node_props in ipairs (available_nodes) do
|
||||
-- Highest priority node wins
|
||||
local priority = nutils.get_session_priority (node_props)
|
||||
|
||||
if priority > selected_prio or selected_node == nil then
|
||||
selected_prio = priority
|
||||
selected_node = node_props ["node.name"]
|
||||
end
|
||||
end
|
||||
|
||||
event:set_data ("selected-node-priority", selected_prio)
|
||||
event:set_data ("selected-node", selected_node)
|
||||
end
|
||||
}:register ()
|
||||
@@ -0,0 +1,70 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- hook to make sure the user prefered device(default.configured.*) in other
|
||||
-- words currently selected device is given higher priority
|
||||
|
||||
-- state-default-nodes.lua also does find out the default node out of the user
|
||||
-- preferences(current and past), however it doesnt give any higher priority to
|
||||
-- the currently selected device.
|
||||
|
||||
log = Log.open_topic ("s-default-nodes")
|
||||
|
||||
nutils = require ("node-utils")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "default-nodes/find-selected-default-node",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-default-node" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local available_nodes = event:get_data ("available-nodes")
|
||||
|
||||
available_nodes = available_nodes and available_nodes:parse ()
|
||||
if not available_nodes then
|
||||
return
|
||||
end
|
||||
|
||||
local selected_prio = event:get_data ("selected-node-priority") or 0
|
||||
local selected_node = event:get_data ("selected-node")
|
||||
|
||||
local source = event:get_source ()
|
||||
local props = event:get_properties ()
|
||||
local def_node_type = props ["default-node.type"]
|
||||
local metadata_om = source:call ("get-object-manager", "metadata")
|
||||
local metadata = metadata_om:lookup { Constraint { "metadata.name", "=", "default" } }
|
||||
local obj = metadata:find (0, "default.configured." .. def_node_type)
|
||||
|
||||
if not obj then
|
||||
return
|
||||
end
|
||||
|
||||
local json = Json.Raw (obj)
|
||||
local current_configured_node = json:parse ().name
|
||||
|
||||
for _, node_props in ipairs (available_nodes) do
|
||||
local name = node_props ["node.name"]
|
||||
local priority = nutils.get_session_priority (node_props)
|
||||
|
||||
if current_configured_node == name then
|
||||
priority = 30000 + priority
|
||||
|
||||
if priority > selected_prio then
|
||||
|
||||
selected_prio = priority
|
||||
selected_node = name
|
||||
|
||||
event:set_data ("selected-node-priority", selected_prio)
|
||||
event:set_data ("selected-node", selected_node)
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
179
wireplumber/scripts/default-nodes/rescan.lua
Normal file
179
wireplumber/scripts/default-nodes/rescan.lua
Normal file
@@ -0,0 +1,179 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- looks for changes in user-preferences and devices added/removed and schedules
|
||||
-- rescan and pushes "select-default-node" event for each of the media_classes
|
||||
|
||||
log = Log.open_topic ("s-default-nodes")
|
||||
|
||||
-- looks for changes in user-preferences and devices added/removed and schedules
|
||||
-- rescan
|
||||
SimpleEventHook {
|
||||
name = "default-nodes/rescan-trigger",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "c", "session-item-added", "session-item-removed" },
|
||||
Constraint { "event.session-item.interface", "=", "linkable" },
|
||||
Constraint { "media.class", "#", "Audio/*" },
|
||||
},
|
||||
EventInterest {
|
||||
Constraint { "event.type", "c", "session-item-added", "session-item-removed" },
|
||||
Constraint { "event.session-item.interface", "=", "linkable" },
|
||||
Constraint { "media.class", "#", "Video/*" },
|
||||
},
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "c", "default.configured.audio.sink",
|
||||
"default.configured.audio.source", "default.configured.video.source"
|
||||
},
|
||||
},
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "device-params-changed"},
|
||||
Constraint { "event.subject.param-id", "c", "Route", "EnumRoute"},
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
source:call ("schedule-rescan", "default-nodes")
|
||||
end
|
||||
}:register ()
|
||||
|
||||
-- pushes "select-default-node" event for each of the media_classes
|
||||
SimpleEventHook {
|
||||
name = "default-nodes/rescan",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "rescan-for-default-nodes" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local si_om = source:call ("get-object-manager", "session-item")
|
||||
local devices_om = source:call ("get-object-manager", "device")
|
||||
|
||||
log:trace ("re-evaluating default nodes")
|
||||
|
||||
-- Audio Sink
|
||||
pushSelectDefaultNodeEvent (source, si_om, devices_om, "audio.sink", "in", {
|
||||
"Audio/Sink", "Audio/Duplex"
|
||||
})
|
||||
|
||||
-- Audio Source
|
||||
pushSelectDefaultNodeEvent (source, si_om, devices_om, "audio.source", "out", {
|
||||
"Audio/Source", "Audio/Source/Virtual", "Audio/Duplex", "Audio/Sink"
|
||||
})
|
||||
|
||||
-- Video Source
|
||||
pushSelectDefaultNodeEvent (source, si_om, devices_om, "video.source", "out", {
|
||||
"Video/Source", "Video/Source/Virtual"
|
||||
})
|
||||
end
|
||||
}:register ()
|
||||
|
||||
function pushSelectDefaultNodeEvent (source, si_om, devices_om, def_node_type,
|
||||
port_direction, media_classes)
|
||||
local nodes =
|
||||
collectAvailableNodes (si_om, devices_om, port_direction, media_classes)
|
||||
local event = source:call ("create-event", "select-default-node", nil, {
|
||||
["default-node.type"] = def_node_type,
|
||||
})
|
||||
event:set_data ("available-nodes", Json.Array (nodes))
|
||||
EventDispatcher.push_event (event)
|
||||
end
|
||||
|
||||
-- Return an array table where each element is another table containing all the
|
||||
-- node properties of all the nodes that can be selected for a given media class
|
||||
-- set and direction
|
||||
function collectAvailableNodes (si_om, devices_om, port_direction, media_classes)
|
||||
local collected = {}
|
||||
|
||||
for linkable in si_om:iterate {
|
||||
type = "SiLinkable",
|
||||
Constraint { "media.class", "c", table.unpack (media_classes) },
|
||||
} do
|
||||
local linkable_props = linkable.properties
|
||||
local node = linkable:get_associated_proxy ("node")
|
||||
|
||||
-- check that the node has ports in the requested direction
|
||||
if not node:lookup_port {
|
||||
Constraint { "port.direction", "=", port_direction }
|
||||
} then
|
||||
goto next_linkable
|
||||
end
|
||||
|
||||
-- check that the node has available routes,
|
||||
-- if it is associated to a real device
|
||||
if not nodeHasAvailableRoutes (node, devices_om) then
|
||||
goto next_linkable
|
||||
end
|
||||
|
||||
table.insert (collected, Json.Object (node.properties))
|
||||
|
||||
::next_linkable::
|
||||
end
|
||||
|
||||
return collected
|
||||
end
|
||||
|
||||
-- If the node has an associated device, verify that it has an available
|
||||
-- route. Some UCM profiles expose all paths (headphones, HDMI, etc) as nodes,
|
||||
-- even though they may not be connected... See #145
|
||||
function nodeHasAvailableRoutes (node, devices_om)
|
||||
local properties = node.properties
|
||||
local device_id = properties ["device.id"]
|
||||
local cpd = properties ["card.profile.device"]
|
||||
|
||||
if not device_id or not cpd then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Get the device
|
||||
local device = devices_om:lookup {
|
||||
Constraint { "bound-id", "=", device_id, type = "gobject" }
|
||||
}
|
||||
if not device then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check if the current device route supports the node card device profile
|
||||
for r in device:iterate_params ("Route") do
|
||||
local route = r:parse ()
|
||||
local route_props = route.properties
|
||||
if route_props.device == tonumber (cpd) then
|
||||
if route_props.available == "no" then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if available routes support the node card device profile
|
||||
local found = 0
|
||||
for r in device:iterate_params ("EnumRoute") do
|
||||
local route = r:parse ()
|
||||
local route_props = route.properties
|
||||
if type (route_props.devices) == "table" then
|
||||
for _, i in ipairs (route_props.devices) do
|
||||
if i == tonumber (cpd) then
|
||||
found = found + 1
|
||||
if route_props.available ~= "no" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- The node is part of a profile without routes so we assume it
|
||||
-- is available. This can happen for Pro Audio profiles
|
||||
if found == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
199
wireplumber/scripts/default-nodes/state-default-nodes.lua
Normal file
199
wireplumber/scripts/default-nodes/state-default-nodes.lua
Normal file
@@ -0,0 +1,199 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- the script states the default nodes from the user preferences, it has hooks
|
||||
-- which stores the user preferences(it stores not just the current preference
|
||||
-- but all the previous preferences) in to the state file, retrives them from
|
||||
-- state file during the bootup, finally it has a hook which finds a default
|
||||
-- node out of the user preferences
|
||||
|
||||
log = Log.open_topic ("s-default-nodes")
|
||||
|
||||
nutils = require ("node-utils")
|
||||
|
||||
-- the state storage
|
||||
state = nil
|
||||
state_table = nil
|
||||
|
||||
find_stored_default_node_hook = SimpleEventHook {
|
||||
name = "default-nodes/find-stored-default-node",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-default-node" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local props = event:get_properties ()
|
||||
local available_nodes = event:get_data ("available-nodes")
|
||||
local selected_prio = event:get_data ("selected-node-priority") or 0
|
||||
local selected_node = event:get_data ("selected-node")
|
||||
|
||||
available_nodes = available_nodes and available_nodes:parse ()
|
||||
if not available_nodes then
|
||||
return
|
||||
end
|
||||
|
||||
local stored = collectStored (props ["default-node.type"])
|
||||
|
||||
-- Check if any of the available nodes matches any of the configured
|
||||
for _, node_props in ipairs (available_nodes) do
|
||||
local name = node_props ["node.name"]
|
||||
|
||||
for i, v in ipairs (stored) do
|
||||
if name == v then
|
||||
local priority = nutils.get_session_priority (node_props)
|
||||
priority = priority + 20001 - i
|
||||
|
||||
if priority > selected_prio then
|
||||
selected_prio = priority
|
||||
selected_node = name
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if selected_node then
|
||||
event:set_data ("selected-node-priority", selected_prio)
|
||||
event:set_data ("selected-node", selected_node)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
store_configured_default_nodes_hook = SimpleEventHook {
|
||||
name = "default-nodes/store-configured-default-nodes",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "c", "default.configured.audio.sink",
|
||||
"default.configured.audio.source", "default.configured.video.source"
|
||||
},
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local props = event:get_properties ()
|
||||
-- get the part after "default.configured." (= 19 chars)
|
||||
local def_node_type = props ["event.subject.key"]:sub (20)
|
||||
local new_value = props ["event.subject.value"]
|
||||
local new_stored = {}
|
||||
|
||||
if new_value then
|
||||
new_value = Json.Raw (new_value):parse () ["name"]
|
||||
end
|
||||
|
||||
if new_value then
|
||||
local stored = collectStored (def_node_type)
|
||||
local pos = #stored + 1
|
||||
|
||||
-- find if the curent configured value is already in the stack
|
||||
for i, v in ipairs (stored) do
|
||||
if v == new_value then
|
||||
pos = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- insert at the top and shift the remaining to fill the gap
|
||||
new_stored [1] = new_value
|
||||
if pos > 1 then
|
||||
table.move (stored, 1, pos-1, 2, new_stored)
|
||||
end
|
||||
if pos < #stored then
|
||||
table.move (stored, pos+1, #stored, pos+1, new_stored)
|
||||
end
|
||||
end
|
||||
|
||||
updateStored (def_node_type, new_stored)
|
||||
end
|
||||
}
|
||||
|
||||
-- set initial values
|
||||
metadata_added_hook = SimpleEventHook {
|
||||
name = "default-nodes/metadata-added",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "metadata-added" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local types = { "audio.sink", "audio.source", "video.source" }
|
||||
local source = event:get_source ()
|
||||
local om = source:call ("get-object-manager", "metadata")
|
||||
local metadata = om:lookup { Constraint { "metadata.name", "=", "default" } }
|
||||
|
||||
for _, t in ipairs (types) do
|
||||
local v = state_table ["default.configured." .. t]
|
||||
if v then
|
||||
metadata:set (0, "default.configured." .. t, "Spa:String:JSON",
|
||||
Json.Object { ["name"] = v }:to_string ())
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
-- Collect all the previously configured node names from the state file
|
||||
function collectStored (def_node_type)
|
||||
local stored = {}
|
||||
local key_base = "default.configured." .. def_node_type
|
||||
local key = key_base
|
||||
|
||||
local index = 0
|
||||
repeat
|
||||
local v = state_table [key]
|
||||
table.insert (stored, v)
|
||||
key = key_base .. "." .. tostring (index)
|
||||
index = index + 1
|
||||
until v == nil
|
||||
|
||||
return stored
|
||||
end
|
||||
|
||||
-- Store the given node names in the state file
|
||||
function updateStored (def_node_type, stored)
|
||||
local key_base = "default.configured." .. def_node_type
|
||||
local key = key_base
|
||||
|
||||
local index = 0
|
||||
for _, v in ipairs (stored) do
|
||||
state_table [key] = v
|
||||
key = key_base .. "." .. tostring (index)
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
-- erase the rest, if any
|
||||
repeat
|
||||
local v = state_table [key]
|
||||
state_table [key] = nil
|
||||
key = key_base .. "." .. tostring (index)
|
||||
index = index + 1
|
||||
until v == nil
|
||||
|
||||
state:save_after_timeout (state_table)
|
||||
end
|
||||
|
||||
function toggleState (enable)
|
||||
if enable and not state then
|
||||
state = State ("default-nodes")
|
||||
state_table = state:load ()
|
||||
find_stored_default_node_hook:register ()
|
||||
store_configured_default_nodes_hook:register ()
|
||||
metadata_added_hook:register ()
|
||||
elseif not enable and state then
|
||||
state = nil
|
||||
state_table = nil
|
||||
find_stored_default_node_hook:remove ()
|
||||
store_configured_default_nodes_hook:remove ()
|
||||
metadata_added_hook:remove ()
|
||||
end
|
||||
end
|
||||
|
||||
Settings.subscribe ("node.restore-default-targets", function ()
|
||||
toggleState (Settings.get_boolean ("node.restore-default-targets"))
|
||||
end)
|
||||
toggleState (Settings.get_boolean ("node.restore-default-targets"))
|
||||
Reference in New Issue
Block a user