2021-03-23 15:08:03 -04:00
|
|
|
-- WirePlumber
|
|
|
|
|
--
|
|
|
|
|
-- Copyright © 2021 Collabora Ltd.
|
|
|
|
|
-- @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
|
|
|
--
|
|
|
|
|
-- SPDX-License-Identifier: MIT
|
|
|
|
|
|
2022-07-13 06:25:51 +05:30
|
|
|
-- create-item.lua script takes pipewire nodes and creates session items (a.k.a
|
2022-06-20 18:15:04 +05:30
|
|
|
-- linkable) objects out of them.
|
|
|
|
|
|
2023-11-15 18:16:52 +02:00
|
|
|
cutils = require ("common-utils")
|
2023-05-19 20:12:08 +03:00
|
|
|
log = Log.open_topic ("s-node")
|
2022-09-08 12:37:13 -04:00
|
|
|
|
2021-03-23 15:08:03 -04:00
|
|
|
items = {}
|
|
|
|
|
|
2022-07-13 06:25:51 +05:30
|
|
|
function configProperties (node)
|
2024-03-26 11:43:58 +02:00
|
|
|
local properties = node.properties
|
2022-07-13 06:25:51 +05:30
|
|
|
local media_class = properties ["media.class"] or ""
|
2025-12-08 09:24:07 -05:00
|
|
|
local factory_name = properties ["factory.name"] or ""
|
2021-10-08 00:09:42 +03:00
|
|
|
|
2024-03-26 11:43:58 +02:00
|
|
|
-- ensure a media.type is set
|
2022-07-13 06:25:51 +05:30
|
|
|
if not properties ["media.type"] then
|
|
|
|
|
for _, i in ipairs ({ "Audio", "Video", "Midi" }) do
|
|
|
|
|
if media_class:find (i) then
|
|
|
|
|
properties ["media.type"] = i
|
2021-10-08 00:09:42 +03:00
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-03-26 11:43:58 +02:00
|
|
|
properties ["item.node"] = node
|
|
|
|
|
properties ["item.node.direction"] =
|
|
|
|
|
cutils.mediaClassToDirection (media_class)
|
2022-07-13 06:25:51 +05:30
|
|
|
properties ["item.node.type"] =
|
|
|
|
|
media_class:find ("^Stream/") and "stream" or "device"
|
2024-03-26 11:43:58 +02:00
|
|
|
properties ["item.plugged.usec"] = GLib.get_monotonic_time ()
|
|
|
|
|
properties ["item.features.no-dsp"] =
|
|
|
|
|
Settings.get_boolean ("node.features.audio.no-dsp")
|
|
|
|
|
properties ["item.features.monitor"] =
|
|
|
|
|
Settings.get_boolean ("node.features.audio.monitor-ports")
|
|
|
|
|
properties ["item.features.control-port"] =
|
|
|
|
|
Settings.get_boolean ("node.features.audio.control-port")
|
2025-07-03 10:46:28 -04:00
|
|
|
properties ["item.features.mono"] =
|
2025-12-08 09:24:07 -05:00
|
|
|
(factory_name == "api.alsa.pcm.sink" or factory_name == "api.bluez5.a2dp.sink") and
|
2025-07-03 10:46:28 -04:00
|
|
|
Settings.get_boolean ("node.features.audio.mono")
|
2024-03-26 11:43:58 +02:00
|
|
|
properties ["node.id"] = node ["bound-id"]
|
2021-10-08 00:09:42 +03:00
|
|
|
|
2024-06-25 15:53:11 +03:00
|
|
|
-- set the default media.role, if configured
|
|
|
|
|
-- avoid Settings.get_string(), as it will parse the default "null" value
|
|
|
|
|
-- as a string instead of returning nil
|
|
|
|
|
local default_role = Settings.get ("node.stream.default-media-role")
|
|
|
|
|
if default_role then
|
|
|
|
|
default_role = default_role:parse()
|
|
|
|
|
properties ["media.role"] = properties ["media.role"] or default_role
|
|
|
|
|
end
|
|
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
return properties
|
|
|
|
|
end
|
|
|
|
|
|
2022-05-20 17:43:43 +03:00
|
|
|
AsyncEventHook {
|
2023-01-04 20:49:39 +02:00
|
|
|
name = "node/create-item",
|
2022-05-20 17:43:43 +03:00
|
|
|
interests = {
|
|
|
|
|
EventInterest {
|
2022-11-24 13:33:30 +02:00
|
|
|
Constraint { "event.type", "=", "node-added" },
|
2022-05-20 17:43:43 +03:00
|
|
|
Constraint { "media.class", "#", "Stream/*", type = "pw-global" },
|
|
|
|
|
},
|
|
|
|
|
EventInterest {
|
2022-11-24 13:33:30 +02:00
|
|
|
Constraint { "event.type", "=", "node-added" },
|
2022-05-20 17:43:43 +03:00
|
|
|
Constraint { "media.class", "#", "Video/*", type = "pw-global" },
|
|
|
|
|
},
|
|
|
|
|
EventInterest {
|
2022-11-24 13:33:30 +02:00
|
|
|
Constraint { "event.type", "=", "node-added" },
|
2022-05-20 17:43:43 +03:00
|
|
|
Constraint { "media.class", "#", "Audio/*", type = "pw-global" },
|
2022-12-11 18:45:43 -05:00
|
|
|
Constraint { "wireplumber.is-virtual", "-", type = "pw" },
|
2022-05-20 17:43:43 +03:00
|
|
|
},
|
2021-06-10 16:38:34 +03:00
|
|
|
},
|
2022-05-20 17:43:43 +03:00
|
|
|
steps = {
|
|
|
|
|
start = {
|
|
|
|
|
next = "register",
|
|
|
|
|
execute = function (event, transition)
|
2022-07-13 06:25:51 +05:30
|
|
|
local node = event:get_subject ()
|
2022-08-23 08:25:16 -04:00
|
|
|
local id = node.id
|
2022-05-20 17:43:43 +03:00
|
|
|
local item
|
|
|
|
|
local item_type
|
|
|
|
|
|
2022-07-13 06:25:51 +05:30
|
|
|
local media_class = node.properties ['media.class']
|
2022-05-20 17:43:43 +03:00
|
|
|
if string.find (media_class, "Audio") then
|
|
|
|
|
item_type = "si-audio-adapter"
|
|
|
|
|
else
|
|
|
|
|
item_type = "si-node"
|
|
|
|
|
end
|
|
|
|
|
|
2023-11-07 12:11:43 +02:00
|
|
|
log:info (node, "creating item for node -> " .. item_type)
|
|
|
|
|
|
2022-05-20 17:43:43 +03:00
|
|
|
-- create item
|
|
|
|
|
item = SessionItem (item_type)
|
2022-07-13 06:25:51 +05:30
|
|
|
items [id] = item
|
2022-05-20 17:43:43 +03:00
|
|
|
|
|
|
|
|
-- configure item
|
2022-07-13 06:25:51 +05:30
|
|
|
if not item:configure (configProperties (node)) then
|
|
|
|
|
transition:return_error ("failed to configure item for node "
|
|
|
|
|
.. tostring (id))
|
2022-05-20 17:43:43 +03:00
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- activate item
|
|
|
|
|
item:activate (Features.ALL, function (_, e)
|
|
|
|
|
if e then
|
2022-07-13 06:25:51 +05:30
|
|
|
transition:return_error ("failed to activate item: "
|
|
|
|
|
.. tostring (e));
|
2022-05-20 17:43:43 +03:00
|
|
|
else
|
2022-07-13 06:25:51 +05:30
|
|
|
transition:advance ()
|
2022-05-20 17:43:43 +03:00
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
end,
|
|
|
|
|
},
|
|
|
|
|
register = {
|
|
|
|
|
next = "none",
|
|
|
|
|
execute = function (event, transition)
|
2022-07-13 06:25:51 +05:30
|
|
|
local node = event:get_subject ()
|
2022-08-23 08:25:16 -04:00
|
|
|
local bound_id = node ["bound-id"]
|
|
|
|
|
local item = items [node.id]
|
2022-05-20 17:43:43 +03:00
|
|
|
|
2023-05-19 20:12:08 +03:00
|
|
|
log:info (item, "activated item for node " .. tostring (bound_id))
|
2022-05-20 17:43:43 +03:00
|
|
|
item:register ()
|
2022-07-13 06:25:51 +05:30
|
|
|
transition:advance ()
|
2022-05-20 17:43:43 +03:00
|
|
|
end,
|
|
|
|
|
},
|
2021-06-10 16:38:34 +03:00
|
|
|
},
|
2022-07-13 06:25:51 +05:30
|
|
|
}:register ()
|
2022-05-20 17:43:43 +03:00
|
|
|
|
|
|
|
|
SimpleEventHook {
|
2023-01-04 20:49:39 +02:00
|
|
|
name = "node/destroy-item",
|
2022-05-20 17:43:43 +03:00
|
|
|
interests = {
|
|
|
|
|
EventInterest {
|
2022-11-24 13:33:30 +02:00
|
|
|
Constraint { "event.type", "=", "node-removed" },
|
2022-05-20 17:43:43 +03:00
|
|
|
Constraint { "media.class", "#", "Stream/*", type = "pw-global" },
|
|
|
|
|
},
|
|
|
|
|
EventInterest {
|
2022-11-24 13:33:30 +02:00
|
|
|
Constraint { "event.type", "=", "node-removed" },
|
2022-05-20 17:43:43 +03:00
|
|
|
Constraint { "media.class", "#", "Video/*", type = "pw-global" },
|
|
|
|
|
},
|
|
|
|
|
EventInterest {
|
2022-11-24 13:33:30 +02:00
|
|
|
Constraint { "event.type", "=", "node-removed" },
|
2022-05-20 17:43:43 +03:00
|
|
|
Constraint { "media.class", "#", "Audio/*", type = "pw-global" },
|
2022-12-11 18:45:43 -05:00
|
|
|
Constraint { "wireplumber.is-virtual", "-", type = "pw" },
|
2022-05-20 17:43:43 +03:00
|
|
|
},
|
2021-06-10 16:38:34 +03:00
|
|
|
},
|
2022-07-13 06:27:08 +05:30
|
|
|
execute = function (event)
|
2022-07-13 06:25:51 +05:30
|
|
|
local node = event:get_subject ()
|
2022-08-23 08:25:16 -04:00
|
|
|
local id = node.id
|
2022-07-13 06:25:51 +05:30
|
|
|
if items [id] then
|
|
|
|
|
items [id]:remove ()
|
|
|
|
|
items [id] = nil
|
2022-05-20 17:43:43 +03:00
|
|
|
end
|
2022-05-20 17:43:43 +03:00
|
|
|
|
2021-03-23 15:08:03 -04:00
|
|
|
end
|
2022-07-13 06:25:51 +05:30
|
|
|
}:register ()
|
2025-07-03 10:39:02 -04:00
|
|
|
|
|
|
|
|
function reconfigureAudioAdapters ()
|
|
|
|
|
local ids = {}
|
|
|
|
|
|
|
|
|
|
-- Get the Id of all session items that are audio adapters
|
|
|
|
|
for id, item in pairs(items) do
|
|
|
|
|
local si_props = item.properties
|
|
|
|
|
if si_props ["item.factory.name"] == "si-audio-adapter" then
|
|
|
|
|
table.insert (ids, id)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Re-configure all audio adapters
|
|
|
|
|
for _, id in pairs (ids) do
|
|
|
|
|
local item = items[id]
|
|
|
|
|
local node = item:get_associated_proxy ("node")
|
|
|
|
|
|
|
|
|
|
log:info (item, "Started re-configuring audio adapter")
|
|
|
|
|
|
|
|
|
|
-- Remove the session item so that it is unlinked
|
|
|
|
|
items[id] = nil
|
|
|
|
|
item:remove()
|
|
|
|
|
|
|
|
|
|
-- Configure the session item
|
|
|
|
|
if not item:configure (configProperties (node)) then
|
|
|
|
|
log:warning (item, "Could not re-configure audio adapter")
|
|
|
|
|
goto skip_item
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Activate the session item so that it is linked again
|
|
|
|
|
items[id] = item
|
|
|
|
|
item:activate (Features.ALL, function (si, e)
|
|
|
|
|
if e then
|
|
|
|
|
log:warning (si, "Could not re-activate audio adapter")
|
|
|
|
|
else
|
|
|
|
|
log:info (si, "Successfully re-activated audio adapter")
|
|
|
|
|
si:register ()
|
|
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
::skip_item::
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Settings.subscribe ("node.features.audio.*", function ()
|
|
|
|
|
reconfigureAudioAdapters ()
|
|
|
|
|
end)
|