mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-05 19:28:01 +02:00
scripts: add bluez-midi monitor and its configuration
Add support for BLE MIDI devices and local endpoints. Disabled by default for now, as the feature currently faces some DBus/SELinux policy issues e.g. on Fedora.
This commit is contained in:
parent
6b32ef5e64
commit
0978c224dc
6 changed files with 227 additions and 0 deletions
|
|
@ -68,6 +68,9 @@ context.modules = [
|
|||
|
||||
# Provides factories to make session manager objects.
|
||||
{ name = libpipewire-module-session-manager }
|
||||
|
||||
# Provides factories to make SPA node objects.
|
||||
{ name = libpipewire-module-spa-node-factory }
|
||||
]
|
||||
|
||||
wireplumber.components = [
|
||||
|
|
|
|||
18
src/config/bluetooth.lua.d/30-bluez-midi-monitor.lua
Normal file
18
src/config/bluetooth.lua.d/30-bluez-midi-monitor.lua
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
bluez_midi_monitor = {}
|
||||
bluez_midi_monitor.properties = {}
|
||||
bluez_midi_monitor.rules = {}
|
||||
|
||||
function bluez_midi_monitor.enable()
|
||||
if bluez_midi_monitor.enabled == false then
|
||||
return
|
||||
end
|
||||
|
||||
load_monitor("bluez-midi", {
|
||||
properties = bluez_midi_monitor.properties,
|
||||
rules = bluez_midi_monitor.rules,
|
||||
})
|
||||
|
||||
if bluez_midi_monitor.properties["with-logind"] then
|
||||
load_optional_module("logind")
|
||||
end
|
||||
end
|
||||
41
src/config/bluetooth.lua.d/50-bluez-midi-config.lua
Normal file
41
src/config/bluetooth.lua.d/50-bluez-midi-config.lua
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
-- BLE MIDI is currently disabled by default, because it conflicts with
|
||||
-- the SELinux policy on Fedora 37 and potentially other systems using
|
||||
-- SELinux. For a workaround, see
|
||||
-- https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/spa/plugins/bluez5/README-MIDI.md
|
||||
bluez_midi_monitor.enabled = false
|
||||
|
||||
bluez_midi_monitor.properties = {
|
||||
-- Enable the logind module, which arbitrates which user will be allowed
|
||||
-- to have bluetooth audio enabled at any given time (particularly useful
|
||||
-- if you are using GDM as a display manager, as the gdm user also launches
|
||||
-- pipewire and wireplumber).
|
||||
-- This requires access to the D-Bus user session; disable if you are running
|
||||
-- a system-wide instance of wireplumber.
|
||||
["with-logind"] = true,
|
||||
|
||||
-- List of MIDI server node names. Each node name given will create a new instance
|
||||
-- of a BLE MIDI service. Typical BLE MIDI instruments have on service instance,
|
||||
-- so adding more than one here may confuse some clients. The node property matching
|
||||
-- rules below apply also to these servers.
|
||||
--["servers"] = { "bluez_midi.server" },
|
||||
}
|
||||
|
||||
bluez_midi_monitor.rules = {
|
||||
-- An array of matches/actions to evaluate.
|
||||
{
|
||||
matches = {
|
||||
{
|
||||
-- Matches all nodes.
|
||||
{ "node.name", "matches", "bluez_midi.*" },
|
||||
},
|
||||
},
|
||||
apply_properties = {
|
||||
--["node.nick"] = "My Node",
|
||||
--["priority.driver"] = 100,
|
||||
--["priority.session"] = 100,
|
||||
--["node.pause-on-idle"] = false,
|
||||
--["session.suspend-timeout-seconds"] = 5, -- 0 disables suspend
|
||||
--["monitor.channel-volumes"] = false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
bluez_monitor.enable()
|
||||
bluez_midi_monitor.enable()
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ context.modules = [
|
|||
|
||||
# Provides factories to make session manager objects.
|
||||
{ name = libpipewire-module-session-manager }
|
||||
|
||||
# Provides factories to make SPA node objects.
|
||||
{ name = libpipewire-module-spa-node-factory }
|
||||
]
|
||||
|
||||
wireplumber.components = [
|
||||
|
|
|
|||
161
src/scripts/monitors/bluez-midi.lua
Normal file
161
src/scripts/monitors/bluez-midi.lua
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Pauli Virtanen
|
||||
-- @author Pauli Virtanen
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
local config = ... or {}
|
||||
|
||||
-- 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 createNode(parent, id, type, factory, properties)
|
||||
properties["factory.name"] = factory
|
||||
|
||||
-- set the node description
|
||||
local desc = properties["node.description"]
|
||||
-- sanitize description, replace ':' with ' '
|
||||
properties["node.description"] = desc:gsub("(:)", " ")
|
||||
|
||||
-- set the node name
|
||||
local name =
|
||||
"bluez_midi." .. properties["api.bluez5.address"]
|
||||
-- sanitize name
|
||||
name = name:gsub("([^%w_%-%.])", "_")
|
||||
-- deduplicate nodes with the same name
|
||||
properties["node.name"] = name
|
||||
for counter = 2, 99, 1 do
|
||||
if node_names_table[properties["node.name"]] ~= true then
|
||||
node_names_table[properties["node.name"]] = true
|
||||
break
|
||||
end
|
||||
properties["node.name"] = name .. "." .. counter
|
||||
end
|
||||
|
||||
-- apply properties from config.rules
|
||||
rulesApplyProperties(properties)
|
||||
|
||||
-- create the node
|
||||
-- it doesn't necessarily need to be a local node,
|
||||
-- the other Bluetooth parts run in the local process,
|
||||
-- so it's consistent to have also this here
|
||||
local node = LocalNode("spa-node-factory", properties)
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
parent:store_managed_object(id, node)
|
||||
id_to_name_table[id] = properties["node.name"]
|
||||
end
|
||||
|
||||
function createMonitor()
|
||||
local monitor_props = {}
|
||||
for k, v in pairs(config.properties or {}) do
|
||||
monitor_props[k] = v
|
||||
end
|
||||
monitor_props["server"] = nil
|
||||
|
||||
local monitor = SpaDevice("api.bluez5.midi.enum", monitor_props)
|
||||
if monitor then
|
||||
monitor:connect("create-object", createNode)
|
||||
monitor:connect("object-removed", function (parent, id)
|
||||
node_names_table[id_to_name_table[id]] = nil
|
||||
id_to_name_table[id] = nil
|
||||
end)
|
||||
else
|
||||
Log.message("PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported.")
|
||||
return nil
|
||||
end
|
||||
|
||||
-- reset the name tables to make sure names are recycled
|
||||
node_names_table = {}
|
||||
id_to_name_table = {}
|
||||
|
||||
monitor:activate(Feature.SpaDevice.ENABLED)
|
||||
return monitor
|
||||
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
|
||||
local node_props = {
|
||||
["node.name"] = v,
|
||||
["node.description"] = string.format(I18n.gettext("BLE MIDI %d"), i),
|
||||
["api.bluez5.role"] = "server",
|
||||
["factory.name"] = "api.bluez5.midi.node"
|
||||
}
|
||||
rulesApplyProperties(node_props)
|
||||
|
||||
local node = LocalNode("spa-node-factory", node_props)
|
||||
if node then
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
table.insert(servers, node)
|
||||
else
|
||||
Log.message("Failed to create BLE MIDI server.")
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return servers
|
||||
end
|
||||
|
||||
logind_plugin = Plugin.find("logind")
|
||||
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)
|
||||
|
||||
if seat_state == "active" then
|
||||
monitor = createMonitor()
|
||||
servers = createServers()
|
||||
elseif monitor then
|
||||
monitor:deactivate(Feature.SpaDevice.ENABLED)
|
||||
monitor = nil
|
||||
servers = nil
|
||||
end
|
||||
end
|
||||
|
||||
logind_plugin:connect("state-changed", function(p, s) startStopMonitor(s) end)
|
||||
startStopMonitor(logind_plugin:call("get-state"))
|
||||
else
|
||||
monitor = createMonitor()
|
||||
servers = createServers()
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue