-- 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 -- Auto connect the stereo null sink to the first two channels of the RME Fireface auto_connect_ports { output = Constraint { "object.path", "matches", "rme-nullsink:*" }, input = Constraint { "port.alias", "matches", "Fireface 802 (24240739):*" }, connect = { ["FL"] = "AUX12", ["FR"] = "AUX13" } } -- Auto connect the stereo null sink to the jack_sink for when the jack server gets started auto_connect_ports { output = Constraint { "object.path", "matches", "rme-nullsink:*" }, input = Constraint { "object.path", "matches", "jack_sink:*" }, connect = { ["FL"] = "FL", ["FR"] = "FR" } } auto_connect_ports { output = Constraint { "object.path", "matches", "rme-nullsink:*" }, input = Constraint { "object.path", "matches", "alsa:*" }, connect = { ["FL"] = "FL", ["FR"] = "FR" }, -- Don't connect to speakers if there are bluetooth headphones plugged in -- unless = Constraint { "object.path", "matches", "bluez_output.*" } } -- Auto connect the stereo null sink to bluetooth headphones -- auto_connect_ports { -- output = Constraint { "object.path", "matches", "rme-nullsink:*" }, -- input = Constraint { "object.path", "matches", "bluez_output.*" }, -- connect = { -- -- Connect to the correct channel or "MONO" if the output is in "headset" mode -- ["FL"] = { "FL", "MONO" }, -- ["FR"] = { "FR", "MONO" } -- } --}