New wireplumber config

This commit is contained in:
2024-07-26 06:23:40 -04:00
parent f9f2119beb
commit 9716a8df5a
99 changed files with 7572 additions and 5398 deletions

View File

@@ -5,11 +5,25 @@
--
-- SPDX-License-Identifier: MIT
-- Receive script arguments from config.lua
local config = ... or {}
cutils = require ("common-utils")
log = Log.open_topic ("s-monitors")
-- ensure config.properties is not nil
config.properties = config.properties or {}
defaults = {}
defaults.node_properties = { -- Midi bridge node properties
["factory.name"] = "api.alsa.seq.bridge",
-- Name set for the node with ALSA MIDI ports
["node.name"] = "Midi-Bridge",
-- Set priorities so that it can be used as a fallback driver (see pipewire#3562)
["priority.session"] = "100",
["priority.driver"] = "1",
}
config = {}
config.monitoring = Core.test_feature ("monitor.alsa-midi.monitoring")
config.node_properties = Conf.get_section_as_properties (
"monitor.alsa-midi.properties", defaults.node_properties)
SND_PATH = "/dev/snd"
SEQ_NAME = "seq"
@@ -19,18 +33,10 @@ midi_node = nil
fm_plugin = nil
function CreateMidiNode ()
-- Midi properties
local props = {}
if type(config.properties["alsa.midi.node-properties"]) == "table" then
props = config.properties["alsa.midi.node-properties"]
end
props["factory.name"] = "api.alsa.seq.bridge"
props["node.name"] = props["node.name"] or "Midi-Bridge"
-- create the midi node
local node = Node("spa-node-factory", props)
local node = Node("spa-node-factory", config.node_properties)
node:activate(Feature.Proxy.BOUND, function (n)
Log.info ("activated Midi bridge")
log:info ("activated Midi bridge")
end)
return node;
@@ -38,7 +44,7 @@ end
if GLib.access (SND_SEQ_PATH, "rw") then
midi_node = CreateMidiNode ()
elseif config.properties["alsa.midi.monitoring"] then
elseif config.monitoring then
fm_plugin = Plugin.find("file-monitor-api")
end

View File

@@ -5,50 +5,28 @@
--
-- SPDX-License-Identifier: MIT
-- Receive script arguments from config.lua
local config = ... or {}
cutils = require ("common-utils")
log = Log.open_topic ("s-monitors")
-- ensure config.properties is not nil
config.properties = config.properties or {}
config = {}
config.reserve_device = Core.test_feature ("monitor.alsa.reserve-device")
config.properties = Conf.get_section_as_properties ("monitor.alsa.properties")
config.rules = Conf.get_section_as_json ("monitor.alsa.rules", Json.Array {})
-- unique device/node name tables
device_names_table = nil
node_names_table = nil
-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
r.interests = {}
for _, i in ipairs(r.matches) do
local interest_desc = { type = "properties" }
for _, c in ipairs(i) do
c.type = "pw"
table.insert(interest_desc, Constraint(c))
end
local interest = Interest(interest_desc)
table.insert(r.interests, interest)
end
r.matches = nil
end
-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
for _, r in ipairs(config.rules or {}) do
if r.apply_properties then
for _, interest in ipairs(r.interests) do
if interest:matches(properties) then
for k, v in pairs(r.apply_properties) do
properties[k] = v
end
end
end
end
end
end
function nonempty(str)
return str ~= "" and str or nil
end
function applyDefaultDeviceProperties (properties)
properties["api.alsa.use-acp"] = true
properties["api.acp.auto-port"] = false
properties["api.dbus.ReserveDevice1.Priority"] = -20
end
function createNode(parent, id, obj_type, factory, properties)
local dev_props = parent.properties
@@ -119,6 +97,8 @@ function createNode(parent, id, obj_type, factory, properties)
properties["node.name"] = name
log:info ("Creating node " .. name)
-- deduplicate nodes with the same name
for counter = 2, 99, 1 do
if node_names_table[properties["node.name"]] ~= true then
@@ -126,6 +106,7 @@ function createNode(parent, id, obj_type, factory, properties)
break
end
properties["node.name"] = name .. "." .. counter
log:info ("deduplicating node name -> " .. properties["node.name"])
end
end
@@ -166,17 +147,17 @@ function createNode(parent, id, obj_type, factory, properties)
end
end
-- apply VM overrides
local vm_overrides = config.properties["vm.node.defaults"]
if nonempty(Core.get_vm_type()) and type(vm_overrides) == "table" then
for k, v in pairs(vm_overrides) do
properties[k] = v
end
-- add cpu.vm.name for rule matching purposes
local vm_type = Core.get_vm_type()
if nonempty(vm_type) then
properties["cpu.vm.name"] = vm_type
end
-- apply properties from config.rules
rulesApplyProperties(properties)
if properties["node.disabled"] then
-- apply properties from rules defined in JSON .conf file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
if cutils.parseBool (properties ["node.disabled"]) then
log:notice ("ALSA node " .. properties["node.name"] .. " disabled")
node_names_table [properties ["node.name"]] = nil
return
end
@@ -197,12 +178,14 @@ function createDevice(parent, id, factory, properties)
return
end
node_names_table[node.properties["node.name"]] = nil
local node_name = node.properties["node.name"]
log:info ("Removing node " .. node_name)
node_names_table[node_name] = nil
end)
device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
parent:store_managed_object(id, device)
else
Log.warning ("Failed to create '" .. factory .. "' device")
log:warning ("Failed to create '" .. factory .. "' device")
end
end
@@ -276,16 +259,19 @@ function prepareDevice(parent, id, obj_type, factory, properties)
properties["device.icon-name"] = icon .. "-analog" .. (b and ("-" .. b) or "")
end
-- apply properties from config.rules
rulesApplyProperties(properties)
if properties["device.disabled"] then
-- apply properties from rules defined in JSON .conf file
applyDefaultDeviceProperties (properties)
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
if cutils.parseBool (properties ["device.disabled"]) then
log:notice ("ALSA card/device " .. properties ["device.name"] .. " disabled")
device_names_table [properties ["device.name"]] = nil
return
end
-- override the device factory to use ACP
if properties["api.alsa.use-acp"] then
Log.info("Enabling the use of ACP on " .. properties["device.name"])
if cutils.parseBool (properties ["api.alsa.use-acp"]) then
log:info("Enabling the use of ACP on " .. properties["device.name"])
factory = "api.alsa.acp.device"
end
@@ -294,9 +280,9 @@ function prepareDevice(parent, id, obj_type, factory, properties)
local rd_name = "Audio" .. properties["api.alsa.card"]
local rd = rd_plugin:call("create-reservation",
rd_name,
config.properties["alsa.reserve.application-name"] or "WirePlumber",
cutils.get_application_name (),
properties["device.name"],
config.properties["alsa.reserve.priority"] or -20);
properties["api.dbus.ReserveDevice1.Priority"]);
properties["api.dbus.ReserveDevice1"] = rd_name
@@ -320,22 +306,11 @@ function prepareDevice(parent, id, obj_type, factory, properties)
end)
rd:connect("release-requested", function (rd)
Log.info("release requested")
log:info("release requested")
parent:store_managed_object(id, nil)
rd:call("release")
end)
if jack_device then
rd:connect("notify::owner-name-changed", function (rd, pspec)
if rd["state"] == "busy" and
rd["owner-application-name"] == "Jack audio server" then
-- TODO enable the jack device
else
-- TODO disable the jack device
end
end)
end
rd:call("acquire")
else
-- create the device
@@ -346,8 +321,8 @@ end
function createMonitor ()
local m = SpaDevice("api.alsa.enum.udev", config.properties)
if m == nil then
Log.message("PipeWire's SPA ALSA udev plugin(\"api.alsa.enum.udev\")"
.. "missing or broken. Sound Cards cannot be enumerated")
log:notice("PipeWire's ALSA SPA plugin is missing or broken. " ..
"Sound cards will not be supported")
return nil
end
@@ -378,30 +353,19 @@ function createMonitor ()
node_names_table = {}
-- activate monitor
Log.info("Activating ALSA monitor")
log:info("Activating ALSA monitor")
m:activate(Feature.SpaDevice.ENABLED)
return m
end
-- create the JACK device (for PipeWire to act as client to a JACK server)
if config.properties["alsa.jack-device"] then
jack_device = Device("spa-device-factory", {
["factory.name"] = "api.jack.device",
["node.name"] = "JACK-Device",
})
jack_device:activate(Feature.Proxy.BOUND)
end
-- enable device reservation if requested
if config.properties["alsa.reserve"] then
rd_plugin = Plugin.find("reserve-device")
end
-- if the reserve-device plugin is enabled, at the point of script execution
-- it is expected to be connected. if it is not, assume the d-bus connection
-- has failed and continue without it
if config.reserve_device then
rd_plugin = Plugin.find("reserve-device")
end
if rd_plugin and rd_plugin:call("get-dbus")["state"] ~= "connected" then
Log.message("reserve-device plugin is not connected to D-Bus, "
log:notice("reserve-device plugin is not connected to D-Bus, "
.. "disabling device reservation")
rd_plugin = nil
end
@@ -412,12 +376,12 @@ if rd_plugin then
local dbus = rd_plugin:call("get-dbus")
dbus:connect("notify::state", function (b, pspec)
local state = b["state"]
Log.info ("rd-plugin state changed to " .. state)
log:info ("rd-plugin state changed to " .. state)
if state == "connected" then
Log.info ("Creating ALSA monitor")
log:info ("Creating ALSA monitor")
monitor = createMonitor()
elseif state == "closed" then
Log.info ("Destroying ALSA monitor")
log:info ("Destroying ALSA monitor")
monitor = nil
end
end)

View File

@@ -5,42 +5,22 @@
--
-- SPDX-License-Identifier: MIT
local config = ... or {}
cutils = require ("common-utils")
log = Log.open_topic ("s-monitors")
defaults = {}
defaults.servers = { "bluez_midi.server" }
config = {}
config.seat_monitoring = Core.test_feature ("monitor.bluez.seat-monitoring")
config.properties = Conf.get_section_as_properties ("monitor.bluez-midi.properties")
config.servers = Conf.get_section_as_array ("monitor.bluez-midi.servers", defaults.servers)
config.rules = Conf.get_section_as_json ("monitor.bluez-midi.rules", Json.Array {})
-- unique device/node name tables
node_names_table = nil
id_to_name_table = nil
-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
r.interests = {}
for _, i in ipairs(r.matches) do
local interest_desc = { type = "properties" }
for _, c in ipairs(i) do
c.type = "pw"
table.insert(interest_desc, Constraint(c))
end
local interest = Interest(interest_desc)
table.insert(r.interests, interest)
end
r.matches = nil
end
-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
for _, r in ipairs(config.rules or {}) do
if r.apply_properties then
for _, interest in ipairs(r.interests) do
if interest:matches(properties) then
for k, v in pairs(r.apply_properties) do
properties[k] = v
end
end
end
end
end
end
function setLatencyOffset(node, offset_msec)
if not offset_msec then
return
@@ -50,7 +30,7 @@ function setLatencyOffset(node, offset_msec)
props.latencyOffsetNsec = tonumber(offset_msec) * 1000000
local param = Pod.Object(props)
Log.debug(param, "setting latency offset on " .. tostring(node))
log:debug(param, "setting latency offset on " .. tostring(node))
node:set_param("Props", param)
end
@@ -79,8 +59,8 @@ function createNode(parent, id, type, factory, properties)
properties["api.glib.mainloop"] = "true"
-- apply properties from config.rules
rulesApplyProperties(properties)
-- apply properties from the rules in the configuration file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
local latency_offset = properties["node.latency-offset-msec"]
properties["node.latency-offset-msec"] = nil
@@ -101,7 +81,6 @@ function createMonitor()
for k, v in pairs(config.properties or {}) do
monitor_props[k] = v
end
monitor_props["server"] = nil
monitor_props["api.glib.mainloop"] = "true"
@@ -113,7 +92,7 @@ function createMonitor()
id_to_name_table[id] = nil
end)
else
Log.message("PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported.")
log:notice("PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported.")
return nil
end
@@ -126,16 +105,10 @@ function createMonitor()
end
function createServers()
local props = config.properties or {}
if not props["servers"] then
return nil
end
local servers = {}
local i = 1
for k, v in pairs(props["servers"]) do
for k, v in pairs(config.servers) do
local node_props = {
["node.name"] = v,
["node.description"] = string.format(I18n.gettext("BLE MIDI %d"), i),
@@ -143,7 +116,7 @@ function createServers()
["factory.name"] = "api.bluez5.midi.node",
["api.glib.mainloop"] = "true",
}
rulesApplyProperties(node_props)
node_props = JsonUtils.match_rules_update_properties (config.rules, node_props)
local latency_offset = node_props["node.latency-offset-msec"]
node_props["node.latency-offset-msec"] = nil
@@ -154,7 +127,7 @@ function createServers()
table.insert(servers, node)
setLatencyOffset(node, latency_offset)
else
Log.message("Failed to create BLE MIDI server.")
log:notice("Failed to create BLE MIDI server.")
end
i = i + 1
end
@@ -162,12 +135,14 @@ function createServers()
return servers
end
logind_plugin = Plugin.find("logind")
if config.seat_monitoring then
logind_plugin = Plugin.find("logind")
end
if logind_plugin then
-- if logind support is enabled, activate
-- the monitor only when the seat is active
function startStopMonitor(seat_state)
Log.info(logind_plugin, "Seat state changed: " .. seat_state)
log:info(logind_plugin, "Seat state changed: " .. seat_state)
if seat_state == "active" then
monitor = createMonitor()

View File

@@ -5,7 +5,20 @@
--
-- SPDX-License-Identifier: MIT
local config = ... or {}
COMBINE_OFFSET = 64
LOOPBACK_SOURCE_ID = 128
DEVICE_SOURCE_ID = 0
cutils = require ("common-utils")
log = Log.open_topic ("s-monitors")
config = {}
config.seat_monitoring = Core.test_feature ("monitor.bluez.seat-monitoring")
config.properties = Conf.get_section_as_properties ("monitor.bluez.properties")
config.rules = Conf.get_section_as_json ("monitor.bluez.rules", Json.Array {})
-- This is not a setting, it must always be enabled
config.properties["api.bluez5.connection-info"] = true
devices_om = ObjectManager {
Interest {
@@ -21,36 +34,6 @@ nodes_om = ObjectManager {
}
}
-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
r.interests = {}
for _, i in ipairs(r.matches) do
local interest_desc = { type = "properties" }
for _, c in ipairs(i) do
c.type = "pw"
table.insert(interest_desc, Constraint(c))
end
local interest = Interest(interest_desc)
table.insert(r.interests, interest)
end
r.matches = nil
end
-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
for _, r in ipairs(config.rules or {}) do
if r.apply_properties then
for _, interest in ipairs(r.interests) do
if interest:matches(properties) then
for k, v in pairs(r.apply_properties) do
properties[k] = v
end
end
end
end
end
end
function setOffloadActive(device, value)
local pod = Pod.Object {
"Spa:Pod:Object:Param:Props", "Props", bluetoothOffloadActive = value
@@ -126,7 +109,7 @@ function createOffloadScoNode(parent, id, type, factory, properties)
}
args["playback.props"] = Json.Object(playback_args)
else
Log.warning(parent, "Unsupported factory: " .. factory)
log:warning(parent, "Unsupported factory: " .. factory)
return
end
@@ -142,16 +125,120 @@ function createOffloadScoNode(parent, id, type, factory, properties)
parent:store_managed_object(id, loopback)
end
device_set_nodes_om = ObjectManager {
Interest {
type = "node",
Constraint { "api.bluez5.set.leader", "+", type = "pw" },
}
}
device_set_nodes_om:connect ("object-added", function(_, node)
-- Connect ObjectConfig events to the right node
if not monitor then
return
end
local interest = Interest {
type = "device",
Constraint { "object.id", "=", node.properties["device.id"] }
}
log:info("Device set node found: " .. tostring (node["bound-id"]))
for device in devices_om:iterate (interest) do
local device_id = device.properties["api.bluez5.id"]
if not device_id then
goto next_device
end
local spa_device = monitor:get_managed_object (tonumber (device_id))
if not spa_device then
goto next_device
end
local id = node.properties["card.profile.device"]
if id ~= nil then
log:info(".. assign to device: " .. tostring (device["bound-id"]) .. " node " .. tostring (id))
spa_device:store_managed_object (id, node)
-- set routes again to update volumes etc.
for route in device:iterate_params ("Route") do
device:set_param ("Route", route)
end
end
::next_device::
end
end)
function createSetNode(parent, id, type, factory, properties)
local args = {}
local target_class
local stream_class
local rules = {}
local members_json = Json.Raw (properties["api.bluez5.set.members"])
local channels_json = Json.Raw (properties["api.bluez5.set.channels"])
local members = members_json:parse ()
local channels = channels_json:parse ()
if properties["media.class"] == "Audio/Sink" then
args["combine.mode"] = "sink"
target_class = "Audio/Sink/Internal"
stream_class = "Stream/Output/Audio/Internal"
else
args["combine.mode"] = "source"
target_class = "Audio/Source/Internal"
stream_class = "Stream/Input/Audio/Internal"
end
log:info("Device set: " .. properties["node.name"])
for _, member in pairs(members) do
log:info("Device set member:" .. member["object.path"])
table.insert(rules,
Json.Object {
["matches"] = Json.Array {
Json.Object {
["object.path"] = member["object.path"],
["media.class"] = target_class,
},
},
["actions"] = Json.Object {
["create-stream"] = Json.Object {
["media.class"] = stream_class,
["audio.position"] = Json.Array (member["channels"]),
}
},
}
)
end
properties["node.virtual"] = false
properties["device.api"] = "bluez5"
properties["api.bluez5.set.members"] = nil
properties["api.bluez5.set.channels"] = nil
properties["api.bluez5.set.leader"] = true
properties["audio.position"] = Json.Array (channels)
args["combine.props"] = Json.Object (properties)
args["stream.props"] = Json.Object {}
args["stream.rules"] = Json.Array (rules)
local args_json = Json.Object(args)
local args_string = args_json:get_data()
local combine_properties = {}
log:info("Device set node: " .. args_string)
return LocalModule("libpipewire-module-combine-stream", args_string, combine_properties)
end
function createNode(parent, id, type, factory, properties)
local dev_props = parent.properties
local parent_id = parent["bound-id"]
if config.properties["bluez5.hw-offload-sco"] and factory:find("sco") then
if cutils.parseBool (config.properties ["bluez5.hw-offload-sco"]) and factory:find("sco") then
createOffloadScoNode(parent, id, type, factory, properties)
return
end
-- set the device id and spa factory name; REQUIRED, do not change
properties["device.id"] = parent["bound-id"]
properties["device.id"] = parent_id
properties["factory.name"] = factory
-- set the default pause-on-idle setting
@@ -167,10 +254,21 @@ function createNode(parent, id, type, factory, properties)
-- sanitize description, replace ':' with ' '
properties["node.description"] = desc:gsub("(:)", " ")
local name_prefix = ((factory:find("sink") and "bluez_output") or
(factory:find("source") and "bluez_input" or factory))
-- hide the source node because we use the loopback source instead
if parent:get_managed_object (LOOPBACK_SOURCE_ID) ~= nil and
(factory == "api.bluez5.sco.source" or
(factory == "api.bluez5.a2dp.source" and cutils.parseBool (properties["api.bluez5.a2dp-duplex"]))) then
properties["bluez5.loopback-target"] = true
properties["api.bluez5.internal"] = true
-- add 'internal' to name prefix to not be confused with loopback node
name_prefix = name_prefix .. "_internal"
end
-- set the node name
local name =
((factory:find("sink") and "bluez_output") or
(factory:find("source") and "bluez_input" or factory)) .. "." ..
local name = name_prefix .. "." ..
(properties["api.bluez5.address"] or dev_props["device.name"]) .. "." ..
tostring(id)
-- sanitize name
@@ -190,14 +288,26 @@ function createNode(parent, id, type, factory, properties)
properties["node.autoconnect"] = true
end
-- apply properties from config.rules
rulesApplyProperties(properties)
-- apply properties from the rules in the configuration file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
-- create the node; bluez requires "local" nodes, i.e. ones that run in
-- the same process as the spa device, for several reasons
local node = LocalNode("adapter", properties)
node:activate(Feature.Proxy.BOUND)
parent:store_managed_object(id, node)
if properties["api.bluez5.set.leader"] then
local combine = createSetNode(parent, id, type, factory, properties)
parent:store_managed_object(id + COMBINE_OFFSET, combine)
else
properties["bluez5.loopback"] = false
local node = LocalNode("adapter", properties)
node:activate(Feature.Proxy.BOUND)
parent:store_managed_object(id, node)
end
end
function removeNode(parent, id)
-- Clear also the device set module, if any
parent:store_managed_object(id + COMBINE_OFFSET, nil)
end
function createDevice(parent, id, type, factory, properties)
@@ -239,22 +349,24 @@ function createDevice(parent, id, type, factory, properties)
-- initial profile is to be set by policy-device-profile.lua, not spa-bluez5
properties["bluez5.profile"] = "off"
properties["api.bluez5.id"] = id
-- apply properties from config.rules
rulesApplyProperties(properties)
-- apply properties from the rules in the configuration file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
-- create the device
device = SpaDevice(factory, properties)
if device then
device:connect("create-object", createNode)
device:connect("object-removed", removeNode)
parent:store_managed_object(id, device)
else
Log.warning ("Failed to create '" .. factory .. "' device")
log:warning ("Failed to create '" .. factory .. "' device")
return
end
end
Log.info(parent, string.format("%d, %s (%s): %s",
log:info(parent, string.format("%d, %s (%s): %s",
id, properties["device.description"],
properties["api.bluez5.address"], properties["api.bluez5.connection"]))
@@ -267,14 +379,12 @@ function createDevice(parent, id, type, factory, properties)
end
function createMonitor()
local monitor_props = config.properties or {}
monitor_props["api.bluez5.connection-info"] = true
local monitor = SpaDevice("api.bluez5.enum.dbus", monitor_props)
local monitor = SpaDevice("api.bluez5.enum.dbus", config.properties)
if monitor then
monitor:connect("create-object", createDevice)
else
Log.message("PipeWire's BlueZ SPA missing or broken. Bluetooth not supported.")
log:notice("PipeWire's BlueZ SPA plugin is missing or broken. " ..
"Bluetooth devices will not be supported.")
return nil
end
monitor:activate(Feature.SpaDevice.ENABLED)
@@ -282,12 +392,111 @@ function createMonitor()
return monitor
end
logind_plugin = Plugin.find("logind")
function CreateDeviceLoopbackSource (dev_name, dec_desc, dev_id)
local args = Json.Object {
["capture.props"] = Json.Object {
["node.name"] = string.format ("bluez_capture_internal.%s", dev_name),
["media.class"] = "Stream/Input/Audio/Internal",
["node.description"] =
string.format ("Bluetooth internal capture stream for %s", dec_desc),
["audio.channels"] = 1,
["audio.position"] = "[MONO]",
["bluez5.loopback"] = true,
["stream.dont-remix"] = true,
["node.passive"] = true,
["node.dont-fallback"] = true,
["node.linger"] = true
},
["playback.props"] = Json.Object {
["node.name"] = string.format ("bluez_input.%s", dev_name),
["node.description"] = string.format ("%s", dec_desc),
["audio.position"] = "[MONO]",
["media.class"] = "Audio/Source",
["device.id"] = dev_id,
["card.profile.device"] = DEVICE_SOURCE_ID,
["priority.driver"] = 2010,
["priority.session"] = 2010,
["bluez5.loopback"] = true,
["filter.smart"] = true,
["filter.smart.target"] = Json.Object {
["bluez5.loopback-target"] = true,
["bluez5.loopback"] = false,
["device.id"] = dev_id
}
}
}
return LocalModule("libpipewire-module-loopback", args:get_data(), {})
end
function checkProfiles (dev)
local device_id = dev["bound-id"]
local props = dev.properties
-- Don't create loopback source device if autoswitch is disabled
if not Settings.get_boolean ("bluetooth.autoswitch-to-headset-profile") then
return
end
-- Get the associated BT SpaDevice
local internal_id = tostring (props["api.bluez5.id"])
local spa_device = monitor:get_managed_object (internal_id)
if spa_device == nil then
return
end
-- Ignore devices that don't support both A2DP sink and HSP/HFP profiles
local has_a2dpsink_profile = false
local has_headset_profile = false
for p in dev:iterate_params("EnumProfile") do
local profile = cutils.parseParam (p, "EnumProfile")
if profile.name:find ("a2dp") and profile.name:find ("sink") then
has_a2dpsink_profile = true
elseif profile.name:find ("headset") then
has_headset_profile = true
end
end
if not has_a2dpsink_profile or not has_headset_profile then
return
end
-- Create the loopback device if never created before
local loopback = spa_device:get_managed_object (LOOPBACK_SOURCE_ID)
if loopback == nil then
local dev_name = props["api.bluez5.address"] or props["device.name"]
local dec_desc = props["device.description"] or props["device.name"]
or props["device.nick"] or props["device.alias"] or "bluetooth-device"
-- sanitize description, replace ':' with ' '
dec_desc = dec_desc:gsub("(:)", " ")
loopback = CreateDeviceLoopbackSource (dev_name, dec_desc, device_id)
spa_device:store_managed_object(LOOPBACK_SOURCE_ID, loopback)
end
end
function onDeviceParamsChanged (dev, param_name)
if param_name == "EnumProfile" then
checkProfiles (dev)
end
end
devices_om:connect("object-added", function(_, dev)
-- Ignore all devices that are not BT devices
if dev.properties["device.api"] ~= "bluez5" then
return
end
-- check available profiles
dev:connect ("params-changed", onDeviceParamsChanged)
checkProfiles (dev)
end)
if config.seat_monitoring then
logind_plugin = Plugin.find("logind")
end
if logind_plugin then
-- if logind support is enabled, activate
-- the monitor only when the seat is active
function startStopMonitor(seat_state)
Log.info(logind_plugin, "Seat state changed: " .. seat_state)
log:info(logind_plugin, "Seat state changed: " .. seat_state)
if seat_state == "active" then
monitor = createMonitor()
@@ -305,3 +514,4 @@ end
nodes_om:activate()
devices_om:activate()
device_set_nodes_om:activate()

View File

@@ -1,174 +0,0 @@
-- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
-- @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT
local config = ... or {}
-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
r.interests = {}
for _, i in ipairs(r.matches) do
local interest_desc = { type = "properties" }
for _, c in ipairs(i) do
c.type = "pw"
table.insert(interest_desc, Constraint(c))
end
local interest = Interest(interest_desc)
table.insert(r.interests, interest)
end
r.matches = nil
end
-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
for _, r in ipairs(config.rules or {}) do
if r.apply_properties then
for _, interest in ipairs(r.interests) do
if interest:matches(properties) then
for k, v in pairs(r.apply_properties) do
properties[k] = v
end
end
end
end
end
end
function findDuplicate(parent, id, property, value)
for i = 0, id - 1, 1 do
local obj = parent:get_managed_object(i)
if obj and obj.properties[property] == value then
return true
end
end
return false
end
function createNode(parent, id, type, factory, properties)
local dev_props = parent.properties
local location = properties["api.libcamera.location"]
-- set the device id and spa factory name; REQUIRED, do not change
properties["device.id"] = parent["bound-id"]
properties["factory.name"] = factory
-- set the default pause-on-idle setting
properties["node.pause-on-idle"] = false
-- set the node name
local name =
(factory:find("sink") and "libcamera_output") or
(factory:find("source") and "libcamera_input" or factory)
.. "." ..
(dev_props["device.name"]:gsub("^libcamera_device%.(.+)", "%1") or
dev_props["device.name"] or
dev_props["device.nick"] or
dev_props["device.alias"] or
"libcamera-device")
-- sanitize name
name = name:gsub("([^%w_%-%.])", "_")
properties["node.name"] = name
-- deduplicate nodes with the same name
for counter = 2, 99, 1 do
if findDuplicate(parent, id, "node.name", properties["node.name"]) then
properties["node.name"] = name .. "." .. counter
else
break
end
end
-- set the node description
local desc = dev_props["device.description"] or "libcamera-device"
if location == "front" then
desc = I18n.gettext("Built-in Front Camera")
elseif location == "back" then
desc = I18n.gettext("Built-in Back Camera")
end
-- sanitize description, replace ':' with ' '
properties["node.description"] = desc:gsub("(:)", " ")
-- set the node nick
local nick = properties["node.nick"] or
dev_props["device.product.name"] or
dev_props["device.description"] or
dev_props["device.nick"]
properties["node.nick"] = nick:gsub("(:)", " ")
-- set priority
if not properties["priority.session"] then
local priority = 700
if location == "external" then
priority = priority + 150
elseif location == "front" then
priority = priority + 100
elseif location == "back" then
priority = priority + 50
end
properties["priority.session"] = priority
end
-- apply properties from config.rules
rulesApplyProperties(properties)
if properties ["node.disabled"] then
return
end
-- create the node
local node = Node("spa-node-factory", properties)
node:activate(Feature.Proxy.BOUND)
parent:store_managed_object(id, node)
end
function createDevice(parent, id, type, factory, properties)
-- ensure the device has an appropriate name
local name = "libcamera_device." ..
(properties["device.name"] or
properties["device.bus-id"] or
properties["device.bus-path"] or
tostring(id)):gsub("([^%w_%-%.])", "_")
properties["device.name"] = name
-- deduplicate devices with the same name
for counter = 2, 99, 1 do
if findDuplicate(parent, id, "device.name", properties["device.name"]) then
properties["device.name"] = name .. "." .. counter
else
break
end
end
-- ensure the device has a description
properties["device.description"] =
properties["device.description"]
or properties["device.product.name"]
or "Unknown device"
-- apply properties from config.rules
rulesApplyProperties(properties)
if properties ["device.disabled"] then
return
end
-- create the device
local device = SpaDevice(factory, properties)
if device then
device:connect("create-object", createNode)
device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
parent:store_managed_object(id, device)
else
Log.warning ("Failed to create '" .. factory .. "' device")
end
end
monitor = SpaDevice("api.libcamera.enum.manager", config.properties or {})
if monitor then
monitor:connect("create-object", createDevice)
monitor:activate(Feature.SpaDevice.ENABLED)
else
Log.message("PipeWire's libcamera SPA missing or broken. libcamera not supported.")
end

View File

@@ -0,0 +1,61 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
cutils = require ("common-utils")
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-libcamera")
config = {}
config.rules = Conf.get_section_as_json ("monitor.libcamera.rules", Json.Array {})
function createLibcamNode (parent, id, type, factory, properties)
local registered = mutils:register_cam_node (parent, id, factory, properties)
if not registered then
source = source or Plugin.find ("standard-event-source")
local e = source:call ("create-event", "create-libcamera-device-node",
parent, nil)
e:set_data ("factory", factory)
e:set_data ("node-properties", properties)
e:set_data ("node-sub-id", id)
EventDispatcher.push_event (e)
end
end
SimpleEventHook {
name = "monitor/libcamera/create-device",
after = "monitor/libcamera/name-device",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-libcamera-device" },
},
},
execute = function(event)
local properties = event:get_data ("device-properties")
local factory = event:get_data ("factory")
local parent = event:get_subject ()
local id = event:get_data ("device-sub-id")
-- apply properties from rules defined in JSON .conf file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
if cutils.parseBool (properties ["device.disabled"]) then
log:notice ("libcam device " .. properties["device.name"] .. " disabled")
return
end
local device = SpaDevice (factory, properties)
if device then
device:connect ("create-object", createLibcamNode)
device:activate (Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
parent:store_managed_object (id, device)
else
log:warning ("Failed to create '" .. factory .. "' device")
end
end
}:register ()

View File

@@ -0,0 +1,41 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
cutils = require ("common-utils")
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-libcamera")
config = {}
config.rules = Conf.get_section_as_json ("monitor.libcamera.rules", Json.Array {})
SimpleEventHook {
name = "monitor/libcamera/create-node",
after = "monitor/libcamera/name-node",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-libcamera-device-node" },
},
},
execute = function(event)
local properties = event:get_data ("node-properties")
local parent = event:get_subject ()
local id = event:get_data ("node-sub-id")
-- apply properties from rules defined in JSON .conf file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
if cutils.parseBool (properties["node.disabled"]) then
log:notice ("libcam node" .. properties ["node.name"] .. " disabled")
return
end
-- create the node
local node = Node ("spa-node-factory", properties)
node:activate (Feature.Proxy.BOUND)
parent:store_managed_object (id, node)
end
}:register ()

View File

@@ -0,0 +1,32 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
cutils = require ("common-utils")
log = Log.open_topic ("s-monitors-libcamera")
config = {}
config.properties = Conf.get_section_as_properties ("monitor.libcamera.properties")
function createCamDevice (parent, id, type, factory, properties)
source = source or Plugin.find ("standard-event-source")
local e = source:call ("create-event", "create-libcamera-device", parent, nil)
e:set_data ("device-properties", properties)
e:set_data ("factory", factory)
e:set_data ("device-sub-id", id)
EventDispatcher.push_event (e)
end
monitor = SpaDevice ("api.libcamera.enum.manager", config.properties)
if monitor then
monitor:connect ("create-object", createCamDevice)
monitor:activate (Feature.SpaDevice.ENABLED)
else
log:notice ("PipeWire's libcamera SPA plugin is missing or broken. " ..
"Some camera types may not be supported.")
end

View File

@@ -0,0 +1,49 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-libcamera")
SimpleEventHook {
name = "monitor/libcamera/name-device",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-libcamera-device" },
},
},
execute = function(event)
local parent = event:get_subject ()
local properties = event:get_data ("device-properties")
local id = event:get_data ("device-sub-id")
local name = "libcamera_device." ..
(properties["device.name"] or
properties["device.bus-id"] or
properties["device.bus-path"] or
tostring (id)):gsub ("([^%w_%-%.])", "_")
properties["device.name"] = name
-- deduplicate devices with the same name
for counter = 2, 99, 1 do
if mutils.find_duplicate (parent, id, "device.name", properties["node.name"]) then
properties["device.name"] = name .. "." .. counter
else
break
end
end
-- ensure the device has a description
properties["device.description"] =
properties["device.description"]
or properties["device.product.name"]
or "Unknown device"
event:set_data ("device-properties", properties)
end
}:register ()

View File

@@ -0,0 +1,90 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-libcamera")
SimpleEventHook {
name = "monitor/libcamera/name-node",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-libcamera-device-node" },
},
},
execute = function(event)
local properties = event:get_data ("node-properties")
local parent = event:get_subject ()
local dev_props = parent.properties
local factory = event:get_data ("factory")
local id = event:get_data ("node-sub-id")
local location = properties ["api.libcamera.location"]
-- set the device id and spa factory name; REQUIRED, do not change
properties ["device.id"] = parent ["bound-id"]
properties ["factory.name"] = factory
-- set the default pause-on-idle setting
properties ["node.pause-on-idle"] = false
-- set the node name
local name =
(factory:find ("sink") and "libcamera_output") or
(factory:find ("source") and "libcamera_input" or factory)
.. "." ..
(dev_props ["device.name"]:gsub ("^libcamera_device%.(.+)", "%1") or
dev_props ["device.name"] or
dev_props ["device.nick"] or
dev_props ["device.alias"] or
"libcamera-device")
-- sanitize name
name = name:gsub ("([^%w_%-%.])", "_")
properties ["node.name"] = name
-- deduplicate nodes with the same name
for counter = 2, 99, 1 do
if mutils.find_duplicate (parent, id, "node.name", properties ["node.name"]) then
properties ["node.name"] = name .. "." .. counter
else
break
end
end
-- set the node description
local desc = dev_props ["device.description"] or "libcamera-device"
if location == "front" then
desc = I18n.gettext ("Built-in Front Camera")
elseif location == "back" then
desc = I18n.gettext ("Built-in Back Camera")
end
-- sanitize description, replace ':' with ' '
properties ["node.description"] = desc:gsub ("(:)", " ")
-- set the node nick
local nick = properties ["node.nick"] or
dev_props ["device.product.name"] or
dev_props ["device.description"] or
dev_props ["device.nick"]
properties ["node.nick"] = nick:gsub ("(:)", " ")
-- set priority
if not properties ["priority.session"] then
local priority = 700
if location == "external" then
priority = priority + 150
elseif location == "front" then
priority = priority + 100
elseif location == "back" then
priority = priority + 50
end
properties ["priority.session"] = priority
end
event:set_data ("node-properties", properties)
end
}:register ()

View File

@@ -1,165 +0,0 @@
-- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
-- @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- SPDX-License-Identifier: MIT
local config = ... or {}
-- preprocess rules and create Interest objects
for _, r in ipairs(config.rules or {}) do
r.interests = {}
for _, i in ipairs(r.matches) do
local interest_desc = { type = "properties" }
for _, c in ipairs(i) do
c.type = "pw"
table.insert(interest_desc, Constraint(c))
end
local interest = Interest(interest_desc)
table.insert(r.interests, interest)
end
r.matches = nil
end
-- applies properties from config.rules when asked to
function rulesApplyProperties(properties)
for _, r in ipairs(config.rules or {}) do
if r.apply_properties then
for _, interest in ipairs(r.interests) do
if interest:matches(properties) then
for k, v in pairs(r.apply_properties) do
properties[k] = v
end
end
end
end
end
end
function findDuplicate(parent, id, property, value)
for i = 0, id - 1, 1 do
local obj = parent:get_managed_object(i)
if obj and obj.properties[property] == value then
return true
end
end
return false
end
function createNode(parent, id, type, factory, properties)
local dev_props = parent.properties
-- set the device id and spa factory name; REQUIRED, do not change
properties["device.id"] = parent["bound-id"]
properties["factory.name"] = factory
-- set the default pause-on-idle setting
properties["node.pause-on-idle"] = false
-- set the node name
local name =
(factory:find("sink") and "v4l2_output") or
(factory:find("source") and "v4l2_input" or factory)
.. "." ..
(dev_props["device.name"]:gsub("^v4l2_device%.(.+)", "%1") or
dev_props["device.name"] or
dev_props["device.nick"] or
dev_props["device.alias"] or
"v4l2-device")
-- sanitize name
name = name:gsub("([^%w_%-%.])", "_")
properties["node.name"] = name
-- deduplicate nodes with the same name
for counter = 2, 99, 1 do
if findDuplicate(parent, id, "node.name", properties["node.name"]) then
properties["node.name"] = name .. "." .. counter
else
break
end
end
-- set the node description
local desc = dev_props["device.description"] or "v4l2-device"
desc = desc .. " (V4L2)"
-- sanitize description, replace ':' with ' '
properties["node.description"] = desc:gsub("(:)", " ")
-- set the node nick
local nick = properties["node.nick"] or
dev_props["device.product.name"] or
dev_props["api.v4l2.cap.card"] or
dev_props["device.description"] or
dev_props["device.nick"]
properties["node.nick"] = nick:gsub("(:)", " ")
-- set priority
if not properties["priority.session"] then
local path = properties["api.v4l2.path"] or "/dev/video100"
local dev = path:gsub("/dev/video(%d+)", "%1")
properties["priority.session"] = 1000 - (tonumber(dev) * 10)
end
-- apply properties from config.rules
rulesApplyProperties(properties)
if properties["node.disabled"] then
return
end
-- create the node
local node = Node("spa-node-factory", properties)
node:activate(Feature.Proxy.BOUND)
parent:store_managed_object(id, node)
end
function createDevice(parent, id, type, factory, properties)
-- ensure the device has an appropriate name
local name = "v4l2_device." ..
(properties["device.name"] or
properties["device.bus-id"] or
properties["device.bus-path"] or
tostring(id)):gsub("([^%w_%-%.])", "_")
properties["device.name"] = name
-- deduplicate devices with the same name
for counter = 2, 99, 1 do
if findDuplicate(parent, id, "device.name", properties["device.name"]) then
properties["device.name"] = name .. "." .. counter
else
break
end
end
-- ensure the device has a description
properties["device.description"] =
properties["device.description"]
or properties["device.product.name"]
or "Unknown device"
-- apply properties from config.rules
rulesApplyProperties(properties)
if properties["device.disabled"] then
return
end
-- create the device
local device = SpaDevice(factory, properties)
if device then
device:connect("create-object", createNode)
device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
parent:store_managed_object(id, device)
else
Log.warning ("Failed to create '" .. factory .. "' device")
end
end
monitor = SpaDevice("api.v4l2.enum.udev", config.properties or {})
if monitor then
monitor:connect("create-object", createDevice)
monitor:activate(Feature.SpaDevice.ENABLED)
else
Log.message("PipeWire's V4L SPA missing or broken. Video4Linux not supported.")
end

View File

@@ -0,0 +1,61 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
cutils = require ("common-utils")
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-v4l2")
config = {}
config.rules = Conf.get_section_as_json ("monitor.v4l2.rules", Json.Array {})
function createV4l2camNode (parent, id, type, factory, properties)
local registered = mutils:register_cam_node (parent, id, factory, properties)
if not registered then
source = source or Plugin.find ("standard-event-source")
local e = source:call ("create-event", "create-v4l2-device-node",
parent, nil)
e:set_data ("factory", factory)
e:set_data ("node-properties", properties)
e:set_data ("node-sub-id", id)
EventDispatcher.push_event (e)
end
end
SimpleEventHook {
name = "monitor/v4l2/create-device",
after = "monitor/v4l2/name-device",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-v4l2-device" },
},
},
execute = function(event)
local properties = event:get_data ("device-properties")
local factory = event:get_data ("factory")
local parent = event:get_subject ()
local id = event:get_data ("device-sub-id")
-- apply properties from rules defined in JSON .conf file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
if cutils.parseBool (properties ["device.disabled"]) then
log:notice ("V4L2 device " .. properties["device.name"] .. " disabled")
return
end
local device = SpaDevice (factory, properties)
if device then
device:connect ("create-object", createV4l2camNode)
device:activate (Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND)
parent:store_managed_object (id, device)
else
log:warning ("Failed to create '" .. factory .. "' device")
end
end
}:register ()

View File

@@ -0,0 +1,42 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
cutils = require ("common-utils")
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-v4l2")
config = {}
config.rules = Conf.get_section_as_json ("monitor.v4l2.rules", Json.Array {})
SimpleEventHook {
name = "monitor/v4l2/create-node",
after = "monitor/v4l2/name-node",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-v4l2-device-node" },
},
},
execute = function(event)
local properties = event:get_data ("node-properties")
local parent = event:get_subject ()
local id = event:get_data ("node-sub-id")
local factory = event:get_data ("factory")
-- apply properties from rules defined in JSON .conf file
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
if cutils.parseBool (properties ["node.disabled"]) then
log:notice ("V4L2 node" .. properties ["node.name"] .. " disabled")
return
end
-- create the node
local node = Node ("spa-node-factory", properties)
node:activate (Feature.Proxy.BOUND)
parent:store_managed_object (id, node)
end
}:register ()

View File

@@ -0,0 +1,32 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
cutils = require ("common-utils")
log = Log.open_topic ("s-monitors-v4l2")
config = {}
config.properties = Conf.get_section_as_properties ("monitor.v4l2.properties")
function createCamDevice (parent, id, type, factory, properties)
source = source or Plugin.find ("standard-event-source")
local e = source:call ("create-event", "create-v4l2-device", parent, nil)
e:set_data ("device-properties", properties)
e:set_data ("factory", factory)
e:set_data ("device-sub-id", id)
EventDispatcher.push_event (e)
end
monitor = SpaDevice ("api.v4l2.enum.udev", config.properties)
if monitor then
monitor:connect ("create-object", createCamDevice)
monitor:activate (Feature.SpaDevice.ENABLED)
else
log:notice ("PipeWire's V4L2 SPA plugin is missing or broken. " ..
"Some camera types may not be supported.")
end

View File

@@ -0,0 +1,49 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-v4l2")
SimpleEventHook {
name = "monitor/v4l2/name-device",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-v4l2-device" },
},
},
execute = function(event)
local properties = event:get_data ("device-properties")
local parent = event:get_subject ()
local id = event:get_data ("device-sub-id")
local name = "v4l2_device." ..
(properties["device.name"] or
properties["device.bus-id"] or
properties["device.bus-path"] or
tostring (id)):gsub ("([^%w_%-%.])", "_")
properties["device.name"] = name
-- deduplicate devices with the same name
for counter = 2, 99, 1 do
if mutils.find_duplicate (parent, id, "device.name", properties["node.name"]) then
properties["device.name"] = name .. "." .. counter
else
break
end
end
-- ensure the device has a description
properties["device.description"] =
properties["device.description"]
or properties["device.product.name"]
or "Unknown device"
event:set_data ("device-properties", properties)
end
}:register ()

View File

@@ -0,0 +1,80 @@
-- WirePlumber
--
-- Copyright © 2023 Collabora Ltd.
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
--
-- SPDX-License-Identifier: MIT
mutils = require ("monitor-utils")
log = Log.open_topic ("s-monitors-v4l2")
SimpleEventHook {
name = "monitor/v4l2/name-node",
interests = {
EventInterest {
Constraint { "event.type", "=", "create-v4l2-device-node" },
},
},
execute = function(event)
local properties = event:get_data ("node-properties")
local parent = event:get_subject ()
local dev_props = parent.properties
local factory = event:get_data ("factory")
local id = event:get_data ("node-sub-id")
-- set the device id and spa factory name; REQUIRED, do not change
properties["device.id"] = parent["bound-id"]
properties["factory.name"] = factory
-- set the default pause-on-idle setting
properties["node.pause-on-idle"] = false
-- set the node name
local name =
(factory:find ("sink") and "v4l2_output") or
(factory:find ("source") and "v4l2_input" or factory)
.. "." ..
(dev_props["device.name"]:gsub ("^v4l2_device%.(.+)", "%1") or
dev_props["device.name"] or
dev_props["device.nick"] or
dev_props["device.alias"] or
"v4l2-device")
-- sanitize name
name = name:gsub ("([^%w_%-%.])", "_")
properties["node.name"] = name
-- deduplicate nodes with the same name
for counter = 2, 99, 1 do
if mutils.find_duplicate (parent, id, "node.name", properties["node.name"]) then
properties["node.name"] = name .. "." .. counter
else
break
end
end
-- set the node description
local desc = dev_props["device.description"] or "v4l2-device"
desc = desc .. " (V4L2)"
-- sanitize description, replace ':' with ' '
properties["node.description"] = desc:gsub ("(:)", " ")
-- set the node nick
local nick = properties["node.nick"] or
dev_props["device.product.name"] or
dev_props["api.v4l2.cap.card"] or
dev_props["device.description"] or
dev_props["device.nick"]
properties["node.nick"] = nick:gsub ("(:)", " ")
-- set priority
if not properties["priority.session"] then
local path = properties["api.v4l2.path"] or "/dev/video100"
local dev = path:gsub ("/dev/video(%d+)", "%1")
properties["priority.session"] = 1000 - (tonumber (dev) * 10)
end
event:set_data ("node-properties", properties)
end
}:register ()