From 5ba6a047864ed668ef2e74cb4133adf4750c4ba8 Mon Sep 17 00:00:00 2001 From: Christian Colglazier Date: Sun, 9 Nov 2025 09:21:00 -0500 Subject: [PATCH] feat(wireplumber): add config --- .../wireplumber.conf.d/99-user-scripts.conf | 13 ++ .../scripts/auto-connect-ports.lua | 165 ++++++++++++++++++ .../wireplumber/scripts/dump-ports.lua | 26 +++ 3 files changed, 204 insertions(+) create mode 100644 home/dot_config/wireplumber/wireplumber.conf.d/99-user-scripts.conf create mode 100644 home/private_dot_local/private_share/wireplumber/scripts/auto-connect-ports.lua create mode 100644 home/private_dot_local/private_share/wireplumber/scripts/dump-ports.lua diff --git a/home/dot_config/wireplumber/wireplumber.conf.d/99-user-scripts.conf b/home/dot_config/wireplumber/wireplumber.conf.d/99-user-scripts.conf new file mode 100644 index 0000000..c1244c5 --- /dev/null +++ b/home/dot_config/wireplumber/wireplumber.conf.d/99-user-scripts.conf @@ -0,0 +1,13 @@ +wireplumber.components = [ + { + name = auto-connect-ports.lua, type = script/lua + provides = custom.auto-connect-ports + } +] + +wireplumber.profiles = { + main = { + custom.auto-connect-ports = required + } +} + diff --git a/home/private_dot_local/private_share/wireplumber/scripts/auto-connect-ports.lua b/home/private_dot_local/private_share/wireplumber/scripts/auto-connect-ports.lua new file mode 100644 index 0000000..8617449 --- /dev/null +++ b/home/private_dot_local/private_share/wireplumber/scripts/auto-connect-ports.lua @@ -0,0 +1,165 @@ +-- As explained on: https://bennett.dev/auto-link-pipewire-ports-wireplumber/ +-- +-- This script keeps my stereo-null-sink connected to whatever output I'm currently using. +-- I do this so Pulseaudio (and Wine) always sees a stereo output plus I can swap the output +-- without needing to reconnect everything. + +-- Link two ports together +function link_port(output_port, input_port) + if not input_port or not output_port then + return nil + end + + local link_args = { + ["link.input.node"] = input_port.properties["node.id"], + ["link.input.port"] = input_port.properties["object.id"], + + ["link.output.node"] = output_port.properties["node.id"], + ["link.output.port"] = output_port.properties["object.id"], + + -- The node never got created if it didn't have this field set to something + ["object.id"] = nil, + + -- I was running into issues when I didn't have this set + ["object.linger"] = true, + + ["node.description"] = "Link created by auto_connect_ports" + } + + local link = Link("link-factory", link_args) + link:activate(1) + + return link +end + +-- Automatically link ports together by their specific audio channels. +-- +-- ┌──────────────────┐ ┌───────────────────┐ +-- │ │ │ │ +-- │ FL ├────────►│ AUX0 │ +-- │ OUTPUT │ │ │ +-- │ FR ├────────►│ AUX1 INPUT │ +-- │ │ │ │ +-- └──────────────────┘ │ AUX2 │ +-- │ │ +-- └───────────────────┘ +-- +-- -- Call this method inside a script in global scope +-- +-- auto_connect_ports { +-- +-- -- A constraint for all the required ports of the output device +-- output = Constraint { "node.name"} +-- +-- -- A constraint for all the required ports of the input device +-- input = Constraint { .. } +-- +-- -- A mapping of output audio channels to input audio channels +-- +-- connections = { +-- ["FL"] = "AUX0" +-- ["FR"] = "AUX1" +-- } +-- +-- } +-- +function auto_connect_ports(args) + local output_om = ObjectManager { + Interest { + type = "port", + args["output"], + Constraint { "port.direction", "equals", "out" } + } + } + + local links = {} + + local input_om = ObjectManager { + Interest { + type = "port", + args["input"], + Constraint { "port.direction", "equals", "in" } + } + } + + local all_links = ObjectManager { + Interest { + type = "link", + } + } + + local unless = nil + + if args["unless"] then + unless = ObjectManager { + Interest { + type = "port", + args["unless"], + Constraint { "port.direction", "equals", "in" } + } + } + end + + function _connect() + local delete_links = unless and unless:get_n_objects() > 0 + + if delete_links then + for _i, link in pairs(links) do + link:request_destroy() + end + + links = {} + + return + end + + for output_name, input_names in pairs(args.connect) do + local input_names = input_names[1] == nil and { input_names } or input_names + + if delete_links then + else + -- Iterate through all the output ports with the correct channel name + for output in output_om:iterate { Constraint { "audio.channel", "equals", output_name } } do + for _i, input_name in pairs(input_names) do + -- Iterate through all the input ports with the correct channel name + for input in input_om:iterate { Constraint { "audio.channel", "equals", input_name } } do + -- Link all the nodes + local link = link_port(output, input) + + if link then + table.insert(links, link) + end + end + end + end + end + end + end + + output_om:connect("object-added", _connect) + input_om:connect("object-added", _connect) + all_links:connect("object-added", _connect) + + output_om:activate() + input_om:activate() + all_links:activate() + + if unless then + unless:connect("object-added", _connect) + unless:connect("object-removed", _connect) + unless:activate() + end +end + +-- pw-cli list-objects | grep object.path + +-- Connect to speakers +auto_connect_ports { + output = Constraint { "object.path", "matches", "speakers:*" }, + input = Constraint { "object.path", "matches", "alsa:acp:C8Pre:0:playback:*" }, + connect = { + ["FL"] = "AUX0", + ["FR"] = "AUX1" + } +} + diff --git a/home/private_dot_local/private_share/wireplumber/scripts/dump-ports.lua b/home/private_dot_local/private_share/wireplumber/scripts/dump-ports.lua new file mode 100644 index 0000000..b929e5a --- /dev/null +++ b/home/private_dot_local/private_share/wireplumber/scripts/dump-ports.lua @@ -0,0 +1,26 @@ +-- Dump all Wireplumber ports + +function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. dump(v) .. ',\n' + end + return s .. '} ' + else + return tostring(o) + end +end + +local port_om = ObjectManager { + Interest { + type = "port", + } +} + +port_om:connect("object-added", function (om, port) + print(dump(port.properties) .. '\n\n') +end) + +port_om:activate() \ No newline at end of file