From 96273dae5a01d6d3dd344833ab910d8225860c39 Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 22 Mar 2026 00:28:37 -0700 Subject: [PATCH] add wireplumber config --- .config/wireplumber/wireplumber.conf | 1009 +++++++++++++++++ .../wireplumber.conf.d/90-airpods.conf | 23 + 2 files changed, 1032 insertions(+) create mode 100644 .config/wireplumber/wireplumber.conf create mode 100644 .config/wireplumber/wireplumber.conf.d/90-airpods.conf diff --git a/.config/wireplumber/wireplumber.conf b/.config/wireplumber/wireplumber.conf new file mode 100644 index 0000000..ee1aa1d --- /dev/null +++ b/.config/wireplumber/wireplumber.conf @@ -0,0 +1,1009 @@ +## The WirePlumber configuration + +context.spa-libs = { + ## SPA factory name to library mappings + ## Used to find SPA factory names. It maps a SPA factory name regular + ## expression to a library name that should contain that factory. + ## + ## Syntax: + ## = + + api.alsa.* = alsa/libspa-alsa + api.bluez5.* = bluez5/libspa-bluez5 + api.v4l2.* = v4l2/libspa-v4l2 + api.libcamera.* = libcamera/libspa-libcamera + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + ## PipeWire modules to load. + ## These modules are loaded before a connection to pipewire is attempted. + ## This section should be kept minimal and load only the modules that are + ## necessary for the protocol to work. + ## + ## If ifexists is given, the module is ignored when it is not found. + ## If nofail is given, module initialization failures are ignored. + ## + ## Syntax: + ## { + ## name = + ## [ args = { = ... } ] + ## [ flags = [ ifexists | nofail ] ] + ## } + + # Uses RTKit to boost the data thread priority. Also allows clamping + # of utilisation when using the Completely Fair Scheduler on Linux. + { + name = libpipewire-module-rt + args = { + nice.level = -11 + # rt.prio = 88 + # rt.time.soft = -1 + # rt.time.hard = -1 + # uclamp.min = 0 + # uclamp.max = 1024 + } + flags = [ ifexists, nofail ] + } + + ## The native communication protocol. + { name = libpipewire-module-protocol-native } + + ## Support for metadata objects + { name = libpipewire-module-metadata } +] + +wireplumber.profiles = { + ## Syntax: + ## = { + ## inherits = [ other, profile, names ] # optional + ## # optional is the default + ## = [ required | optional | disabled ] + ## ... + ## } + + # The default profile + main = { + inherits = [ base ] + + metadata.sm-settings = required + metadata.sm-objects = required + + policy.standard = required + + hardware.audio = required + hardware.bluetooth = required + hardware.video-capture = required + } + + # Profile for running on a systemwide level + main-systemwide = { + inherits = [ main, mixin.systemwide-session ] + } + + # Typical profile for embedded use cases, systemwide without maintaining state + main-embedded = { + inherits = [ main, mixin.systemwide-session, mixin.stateless ] + } + + # Profile for video-only use cases (camera & screen sharing) + video-only = { + inherits = [ main ] + hardware.audio = disabled + hardware.bluetooth = disabled + } + + ## + ## Profiles for multi-instance setup + ## + + policy = { + inherits = [ base ] + metadata.sm-settings = required + metadata.sm-objects = required + policy.standard = required + } + + audio = { + inherits = [ base ] + hardware.audio = required + } + + bluetooth = { + inherits = [ base ] + hardware.bluetooth = required + } + + video-capture = { + inherits = [ base ] + hardware.video-capture = required + } + + ## + ## The following are not complete profiles, but blocks that can be inherited + ## + + # The bare minimum that all instances should have by default + base = { + check.no-media-session = required + support.settings = required + support.log-settings = required + support.session-services = required + } + + # Disable features that are meant only for user sessions + mixin.systemwide-session = { + support.reserve-device = disabled + monitor.alsa.reserve-device = disabled + + support.portal-permissionstore = disabled + script.client.access-portal = disabled + + support.logind = disabled + monitor.bluez.seat-monitoring = disabled + } + + # Disable storing/restoring runtime state information regarding + # default nodes selection, profiles, routes and stream volume / properties + # This makes sense for embedded systems that should always reboot to a default + # state, not remembering any of the user's runtime changes + mixin.stateless = { + hooks.device.profile.state = disabled + hooks.device.routes.state = disabled + hooks.default-nodes.state = disabled + hooks.stream.state = disabled + } +} + +wireplumber.components = [ + ## WirePlumber components to load. + ## These components are loaded after a connection to pipewire is established. + ## type is mandatory; rest of the tags are optional + ## + ## Syntax: + ## { + ## name = + ## type = + ## arguments = { } + ## + ## # Feature that this component provides + ## provides = + ## + ## # List of features that must be provided before this component is loaded + ## requires = [ ] + ## + ## # List of features that would offer additional functionality if provided + ## # but are not strictly required + ## wants = [ ] + ## } + + ## Check to avoid loading together with media-session + { + name = ensure-no-media-session, type = built-in + provides = check.no-media-session + } + + ## Makes a secondary connection to PipeWire for exporting objects + { + name = export-core, type = built-in + provides = support.export-core + } + + ## Enables creating local nodes that are exported to pipewire + ## This is needed for LocalNode() / WpImplNode + ## This should be used with the export-core to avoid protocol deadlocks, + ## unless you know what you are doing + { + name = libpipewire-module-client-node, type = pw-module + provides = pw.client-node + wants = [ support.export-core ] + } + + ## Enables creating local devices that are exported to pipewire + ## This is needed for SpaDevice() / WpSpaDevice + ## This should be used with the export-core to avoid protocol deadlocks, + ## unless you know what you are doing + { + name = libpipewire-module-client-device, type = pw-module + provides = pw.client-device + wants = [ support.export-core ] + } + + # Provides a node factory to create SPA nodes + # You need this to use LocalNode("spa-node-factory", ...) + { + name = libpipewire-module-spa-node-factory, type = pw-module + provides = pw.node-factory.spa + requires = [ pw.client-node ] + } + + ## Provides a node factory to create SPA nodes wrapped in an adapter + ## You need this to use LocalNode("adapter", ...) + { + name = libpipewire-module-adapter, type = pw-module + provides = pw.node-factory.adapter + requires = [ pw.client-node ] + } + + ## Provides the "sm-settings" metadata object + { + name = libwireplumber-module-settings, type = module + arguments = { metadata.name = sm-settings } + provides = metadata.sm-settings + } + + ## Activates a global WpSettings instance, providing settings from + ## the sm-settings metadata object. Note that this blocks and waits for the + ## sm-settings metadata object to become available, so one instance must + ## provide that, while others should only load this to access settings + { + name = settings-instance, type = built-in + arguments = { metadata.name = sm-settings } + provides = support.settings + after = [ metadata.sm-settings ] + } + + ## Log level settings + { + name = libwireplumber-module-log-settings, type = module + provides = support.log-settings + } + + ## The lua scripting engine + { + name = libwireplumber-module-lua-scripting, type = module + provides = support.lua-scripting + } + + ## Module listening for pipewire objects to push events + { + name = libwireplumber-module-standard-event-source, type = module + provides = support.standard-event-source + } + + ## The shared D-Bus connection + { + name = libwireplumber-module-dbus-connection, type = module + arguments = { plugin.name = dbus-connection, bus.system = false } + provides = support.dbus + } + + ## The shared system D-Bus connection + { + name = libwireplumber-module-dbus-connection, type = module + arguments = { plugin.name = system-dbus-connection, bus.system = true } + provides = support.system-dbus + } + + ## Module managing the portal permissions + { + name = libwireplumber-module-portal-permissionstore, type = module + provides = support.portal-permissionstore + requires = [ support.dbus ] + } + + ## Needed for device reservation to work + { + name = libwireplumber-module-reserve-device, type = module + provides = support.reserve-device + requires = [ support.dbus ] + } + + ## logind integration to enable certain functionality only on the active seat + { + name = libwireplumber-module-logind, type = module + provides = support.logind + } + + ## ModemManager integration to control call audio during a call + { + name = libwireplumber-module-modem-manager, type = module + provides = support.modem-manager + requires = [ support.system-dbus ] + } + + ## Session item factories + { + name = libwireplumber-module-si-node, type = module + provides = si.node + } + { + name = libwireplumber-module-si-audio-adapter, type = module + provides = si.audio-adapter + } + { + name = libwireplumber-module-si-standard-link, type = module + provides = si.standard-link + } + + ## API to access default nodes from scripts + { + name = libwireplumber-module-default-nodes-api, type = module + provides = api.default-nodes + } + + ## API to access mixer controls + { + name = libwireplumber-module-mixer-api, type = module + provides = api.mixer + } + + ## API to get notified about file changes + { + name = libwireplumber-module-file-monitor-api, type = module + provides = api.file-monitor + } + + ## API to send D-Bus notifications + { + name = libwireplumber-module-notifications-api, type = module + provides = api.notifications + requires = [ support.dbus ] + } + + + ## Provide the "default" pw_metadata + { + name = metadata.lua, type = script/lua + arguments = { metadata.name = default } + provides = metadata.default + } + + ## Provide the "filters" pw_metadata + { + name = metadata.lua, type = script/lua + arguments = { metadata.name = filters } + provides = metadata.filters + } + + ## Provide the "sm-objects" pw_metadata, supporting dynamic loadable objects + { + name = sm-objects.lua, type = script/lua + provides = metadata.sm-objects + } + + ## Populates the "session.services" property on the WirePlumber client object + { + name = session-services.lua, type = script/lua + provides = support.session-services + } + + ## Provides support for MPRIS + { + name = libwireplumber-module-mpris, type = module + provides = support.mpris + requires = [ support.dbus ] + } + + ## Device monitors' optional features + { + type = virtual, provides = monitor.alsa.reserve-device, + requires = [ support.reserve-device ] + } + { + type = virtual, provides = monitor.alsa-midi.monitoring, + requires = [ api.file-monitor ] + } + { + type = virtual, provides = monitor.bluez.seat-monitoring, + requires = [ support.logind ] + } + + ## Device monitors + { + name = monitors/alsa.lua, type = script/lua + provides = monitor.alsa + requires = [ support.export-core, pw.client-device ] + wants = [ monitor.alsa.reserve-device ] + } + { + name = monitors/bluez.lua, type = script/lua + provides = monitor.bluez + requires = [ support.export-core, + pw.client-device, + pw.client-node, + pw.node-factory.adapter ] + wants = [ monitor.bluez.seat-monitoring ] + } + { + name = monitors/bluez-midi.lua, type = script/lua + provides = monitor.bluez-midi + requires = [ support.export-core, + pw.client-device, + pw.client-node, + pw.node-factory.spa ] + wants = [ monitor.bluez.seat-monitoring ] + } + { + name = monitors/alsa-midi.lua, type = script/lua + provides = monitor.alsa-midi + wants = [ monitor.alsa-midi.monitoring ] + } + ## v4l2 monitor + { + name = monitors/v4l2/name-device.lua, type = script/lua + provides = hooks.monitor.v4l2-name-device + } + { + name = monitors/v4l2/create-device.lua, type = script/lua + provides = hooks.monitor.v4l2-create-device + requires = [ support.export-core, + pw.client-device, + support.standard-event-source ] + } + { + name = monitors/v4l2/name-node.lua, type = script/lua + provides = hooks.monitor.v4l2-name-node + } + { + name = monitors/v4l2/create-node.lua, type = script/lua + provides = hooks.monitor.v4l2-create-node + } + { + name = monitors/v4l2/enumerate-device.lua, type = script/lua + provides = monitor.v4l2 + requires = [ support.export-core, + pw.client-device, + support.standard-event-source, + hooks.monitor.v4l2-create-device, + hooks.monitor.v4l2-create-node ] + wants = [ hooks.monitor.v4l2-name-device, + hooks.monitor.v4l2-name-node ] + } + ## libcamera monitor + { + name = monitors/libcamera/name-device.lua, type = script/lua + provides = hooks.monitor.libcamera-name-device + } + { + name = monitors/libcamera/create-device.lua, type = script/lua + provides = hooks.monitor.libcamera-create-device + requires = [ support.export-core, + pw.client-device, + support.standard-event-source ] + } + { + name = monitors/libcamera/name-node.lua, type = script/lua + provides = hooks.monitor.libcamera-name-node + } + { + name = monitors/libcamera/create-node.lua, type = script/lua + provides = hooks.monitor.libcamera-create-node + } + { + name = monitors/libcamera/enumerate-device.lua, type = script/lua + provides = monitor.libcamera + requires = [ support.export-core, + pw.client-device, + support.standard-event-source, + hooks.monitor.libcamera-create-device, + hooks.monitor.libcamera-create-node ] + wants = [ hooks.monitor.libcamera-name-device, + hooks.monitor.libcamera-name-node ] + } + + ## Client access configuration hooks + { + name = client/access-default.lua, type = script/lua + provides = script.client.access-default + } + { + name = client/access-portal.lua, type = script/lua + provides = script.client.access-portal + requires = [ support.portal-permissionstore ] + } + { + name = client/access-snap.lua, type = script/lua + provides = script.client.access-snap + } + { + type = virtual, provides = policy.client.access + wants = [ script.client.access-default, + script.client.access-portal, + script.client.access-snap ] + } + + ## Device profile selection hooks + { + name = device/select-profile.lua, type = script/lua + provides = hooks.device.profile.select + } + { + name = device/find-voice-call-profile.lua, type = script/lua + provides = hooks.device.profile.find-voice-call + requires = [ support.modem-manager ] + } + { + name = device/find-preferred-profile.lua, type = script/lua + provides = hooks.device.profile.find-preferred + } + { + name = device/find-best-profile.lua, type = script/lua + provides = hooks.device.profile.find-best + } + { + name = device/state-profile.lua, type = script/lua + provides = hooks.device.profile.state + } + { + name = device/apply-profile.lua, type = script/lua + provides = hooks.device.profile.apply + } + { + name = device/autoswitch-bluetooth-profile.lua, type = script/lua + provides = hooks.device.profile.autoswitch-bluetooth + } + { + type = virtual, provides = policy.device.profile + requires = [ hooks.device.profile.select, + hooks.device.profile.autoswitch-bluetooth, + hooks.device.profile.apply ] + wants = [ hooks.device.profile.find-voice-call, + hooks.device.profile.find-best, + hooks.device.profile.find-preferred, + hooks.device.profile.state ] + } + + # Device route selection hooks + { + name = device/select-routes.lua, type = script/lua + provides = hooks.device.routes.select + } + { + name = device/find-best-routes.lua, type = script/lua + provides = hooks.device.routes.find-best + } + { + name = device/state-routes.lua, type = script/lua + provides = hooks.device.routes.state + } + { + name = device/apply-routes.lua, type = script/lua + provides = hooks.device.routes.apply + } + { + name = device/automute-alsa-routes.lua, type = script/lua + provides = hooks.device.routes.automute-alsa + wants = [ api.notifications ] + } + { + type = virtual, provides = policy.device.routes + requires = [ hooks.device.routes.select, + hooks.device.routes.apply ] + wants = [ hooks.device.routes.find-best, + hooks.device.routes.state, + hooks.device.routes.automute-alsa ] + } + + ## Default nodes selection hooks + { + name = default-nodes/rescan.lua, type = script/lua + provides = hooks.default-nodes.rescan + } + { + name = default-nodes/find-selected-default-node.lua, type = script/lua + provides = hooks.default-nodes.find-selected + requires = [ metadata.default ] + } + { + name = default-nodes/find-best-default-node.lua, type = script/lua + provides = hooks.default-nodes.find-best + } + { + name = default-nodes/state-default-nodes.lua, type = script/lua + provides = hooks.default-nodes.state + requires = [ metadata.default ] + } + { + name = default-nodes/apply-default-node.lua, type = script/lua, + provides = hooks.default-nodes.apply + requires = [ metadata.default ] + } + { + type = virtual, provides = policy.default-nodes + requires = [ hooks.default-nodes.rescan, + hooks.default-nodes.apply ] + wants = [ hooks.default-nodes.find-selected, + hooks.default-nodes.find-best, + hooks.default-nodes.state ] + } + + ## Node configuration hooks + { + name = node/create-item.lua, type = script/lua + provides = hooks.node.create-session-item + requires = [ si.audio-adapter, si.node ] + } + { + name = node/suspend-node.lua, type = script/lua + provides = hooks.node.suspend + } + { + name = node/state-stream.lua, type = script/lua + provides = hooks.stream.state + } + { + name = node/filter-forward-format.lua, type = script/lua + provides = hooks.filter.forward-format + } + { + name = node/filter-graph.lua, type = script/lua + provides = hooks.filter.graph + } + { + type = virtual, provides = policy.node + requires = [ hooks.node.create-session-item ] + wants = [ hooks.node.suspend + hooks.stream.state + hooks.filter.forward-format + hooks.filter.graph ] + } + { + name = node/software-dsp.lua, type = script/lua + provides = node.software-dsp + } + { + name = node/audio-group.lua, type = script/lua + provides = node.audio-group + } + + ## Linking hooks + { + name = linking/rescan.lua, type = script/lua + provides = hooks.linking.rescan + } + { + name = linking/find-media-role-target.lua, type = script/lua + provides = hooks.linking.target.find-media-role + } + { + name = linking/find-defined-target.lua, type = script/lua + provides = hooks.linking.target.find-defined + } + { + name = linking/find-audio-group-target.lua, type = script/lua + provides = hooks.linking.target.find-audio-group + requires = [ node.audio-group ] + } + { + name = linking/find-filter-target.lua, type = script/lua + provides = hooks.linking.target.find-filter + requires = [ metadata.filters ] + } + { + name = linking/find-default-target.lua, type = script/lua + provides = hooks.linking.target.find-default + requires = [ api.default-nodes ] + } + { + name = linking/find-best-target.lua, type = script/lua + provides = hooks.linking.target.find-best + requires = [ metadata.filters ] + } + { + name = linking/get-filter-from-target.lua, type = script/lua + provides = hooks.linking.target.get-filter-from + requires = [ metadata.filters ] + } + { + name = linking/prepare-link.lua, type = script/lua + provides = hooks.linking.target.prepare-link + requires = [ api.default-nodes ] + } + { + name = linking/link-target.lua, type = script/lua + provides = hooks.linking.target.link + requires = [ si.standard-link ] + } + { + name = linking/mpris-pause.lua, type = script/lua + provides = hooks.linking.pause-playback + requires = [ support.mpris ] + } + { + type = virtual, provides = policy.linking.standard + requires = [ hooks.linking.rescan, + hooks.linking.target.prepare-link, + hooks.linking.target.link ] + wants = [ hooks.linking.target.find-media-role, + hooks.linking.target.find-defined, + hooks.linking.target.find-audio-group, + hooks.linking.target.find-filter, + hooks.linking.target.find-default, + hooks.linking.target.find-best, + hooks.linking.target.get-filter-from, + hooks.linking.pause-playback ] + } + + ## Linking: Role-based priority system + { + name = linking/rescan-media-role-links.lua, type = script/lua + provides = hooks.linking.role-based.rescan + requires = [ api.mixer ] + } + { + name = node/find-media-role-default-volume.lua, type = script/lua + provides = hooks.node.role-based.default-volume + requires = [ hooks.linking.role-based.rescan ] + } + { + name = linking/find-media-role-sink-target.lua, type = script/lua + provides = hooks.linking.target.find-media-role-sink + } + { + type = virtual, provides = policy.linking.role-based + requires = [ policy.linking.standard, + hooks.linking.role-based.rescan, + hooks.node.role-based.default-volume, + hooks.linking.target.find-media-role-sink ] + } + + ## Standard policy definition + { + type = virtual, provides = policy.standard + requires = [ policy.client.access + policy.device.profile + policy.device.routes + policy.default-nodes + policy.linking.standard + policy.linking.role-based + policy.node + support.standard-event-source ] + } + + ## Load targets + { + type = virtual, provides = hardware.audio + wants = [ monitor.alsa, monitor.alsa-midi ] + } + { + type = virtual, provides = hardware.bluetooth + wants = [ monitor.bluez, monitor.bluez-midi ] + } + { + type = virtual, provides = hardware.video-capture + wants = [ monitor.v4l2, monitor.libcamera ] + } +] + +wireplumber.components.rules = [ + ## Rules to apply on top of wireplumber.components + ## Syntax: + ## { + ## matches = [ + ## { + ## [ = ... ] + ## } + ## ... + ## ] + ## actions = { + ## = { + ## [ = ... ] + ## } + ## ... + ## } + ## } + + { + matches = [ + { + type = "script/lua" + } + ] + actions = { + merge = { + requires = [ support.lua-scripting ] + } + } + } + { + matches = [ + { + provides = "~hooks.*" + name = "!~monitors/.*" + } + ] + actions = { + merge = { + before = [ support.standard-event-source ] + } + } + } + { + matches = [ + { provides = "~monitor.*" } + ] + actions = { + merge = { + after = [ support.standard-event-source ] + } + } + } + # session-services.lua must execute at the very end + { + matches = [ + { name = "!session-services.lua" } + ] + actions = { + merge = { + before = [ support.session-services ] + } + } + } +] + +wireplumber.settings.schema = { + ## Bluetooth + bluetooth.use-persistent-storage = { + name = "Persistent storage" + description = "Remember and restore Bluetooth headset mode status" + type = "bool" + default = true + } + bluetooth.autoswitch-to-headset-profile = { + name = "Auto-switch to headset profile" + description = "Always show microphone for Bluetooth headsets, and switch to headset mode when recording" + type = "bool" + default = true + } + + ## Device + device.restore-profile = { + name = "Restore profile" + description = "Remember and restore device profiles" + type = "bool" + default = true + } + device.restore-routes = { + name = "Restore routes" + description = "Remember and restore device routes" + type = "bool" + default = true + } + device.routes.default-sink-volume = { + name = "Default sink volume" + description = "The default volume for audio sinks" + type = "float" + default = 0.064 + min = 0.0 + max = 1.0 + } + device.routes.default-source-volume = { + name = "Default source volume" + description = "The default volume for audio sources" + type = "float" + default = 1.0 + min = 0.0 + max = 1.0 + } + device.routes.mute-on-alsa-playback-removed = { + name = "Auto-mute on wired audio disconnect" + description = "Automatically mute all audio devices when active wired headphones/speakers are disconnected to prevent unintended sound output" + type = "bool" + default = false + } + device.routes.mute-on-bluetooth-playback-removed = { + name = "Auto-mute on Bluetooth audio disconnect" + description = "Automatically mute all audio devices when active Bluetooth headphones/speakers are disconnected to prevent unintended sound output" + type = "bool" + default = false + } + + + ## Linking + linking.role-based.duck-level = { + name = "Ducking level" + description = "The volume level to apply when ducking (= reducing volume for a higher priority stream to be audible) in the role-based linking policy" + type = "float" + default = 0.3 + min = 0.0 + max = 1.0 + } + linking.allow-moving-streams = { + name = "Allow moving streams" + description = "Streams may be moved by adding PipeWire metadata at runtime" + type = "bool" + default = true + } + linking.follow-default-target = { + name = "Follow default target" + description = "Streams connected to the default device follow when default changes" + type = "bool" + default = true + } + linking.pause-playback = { + name = "Pause playback if output removed" + description = "Pause media players if their target sink is removed" + type = "bool" + default = true + } + + ## Monitor + monitor.camera-discovery-timeout = { + name = "Discovery timeout" + description = "The camera discovery timeout in milliseconds" + type = "int" + default = 1000 + min = 0 + max = 60000 + } + monitor.alsa.autodetect-hdmi-channels = { + name = "Automatically detect HDMI channels (experimental)" + description = "Automatically detect channel count and positions for HDMI devices (experimental)" + type = "bool" + default = false + } + + ## Node + node.features.audio.no-dsp = { + name = "No DSP" + description = "Do not convert audio to F32 format" + type = "bool" + default = false + } + node.features.audio.monitor-ports = { + name = "Monitor ports" + description = "Enable monitor ports on audio nodes" + type = "bool" + default = true + } + node.features.audio.control-port = { + name = "Control ports" + description = "Enable control ports on audio nodes" + type = "bool" + default = false + } + node.features.audio.mono = { + name = "Mono" + description = "Configure all audio device sink nodes in MONO" + type = "bool" + default = false + } + node.stream.restore-props = { + name = "Restore properties" + description = "Remember and restore properties of streams" + type = "bool" + default = true + } + node.stream.restore-target = { + name = "Restore target" + description = "Remember and restore stream targets" + type = "bool" + default = true + } + node.stream.default-playback-volume = { + name = "Default playback volume" + description = "The default volume for playback nodes" + type = "float" + default = 1.0 + min = 0.0 + max = 1.0 + } + node.stream.default-capture-volume = { + name = "Default capture volume" + description = "The default volume for capture nodes" + type = "float" + default = 1.0 + min = 0.0 + max = 1.0 + } + node.stream.default-media-role = { + name = "Default media role" + description = "Default media.role to assign on streams that do not specify it" + type = "string" + default = null + } + node.filter.forward-format = { + name = "Forward format" + description = "Forward format on filter nodes or not" + type = "bool" + default = false + } + node.restore-default-targets = { + name = "Restore default target" + description = "Remember and restore default audio/video input/output devices" + type = "bool" + default = true + } +} diff --git a/.config/wireplumber/wireplumber.conf.d/90-airpods.conf b/.config/wireplumber/wireplumber.conf.d/90-airpods.conf new file mode 100644 index 0000000..0ba0f86 --- /dev/null +++ b/.config/wireplumber/wireplumber.conf.d/90-airpods.conf @@ -0,0 +1,23 @@ +# AirPods-specific Bluetooth policy override. +# Keep the device on A2DP/AAC and avoid restoring stale route state. + +wireplumber.settings = { + device.restore-routes = false +} + +monitor.bluez.rules = [ + { + matches = [ + { + device.name = "bluez_card.18_3F_70_4E_02_CC" + } + ] + actions = { + update-props = { + device.profile = "a2dp-sink" + bluez5.auto-connect = [ a2dp_sink ] + bluez5.a2dp.aac.bitratemode = 0 + } + } + } +]