scripts: Fix bluetooth profile autoswitch
This patch updates the deprecated policy-bluetooth.lua script so that it works with the current version. The script has been moved into the device sub-folder, and renamed to autoswitch-blueooth-profile.lua. The settings-manager is also used for the configuration, and the actual configuration has been moved from linkind.conf to bluetooth.conf.
This commit is contained in:
@@ -388,9 +388,15 @@ wireplumber.components = [
|
|||||||
provides = hooks.device.profile.apply
|
provides = hooks.device.profile.apply
|
||||||
requires = [ support.lua-scripting ]
|
requires = [ support.lua-scripting ]
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name = device/autoswitch-bluetooth-profile.lua, type = script/lua
|
||||||
|
provides = hooks.device.profile.autoswitch-bluetooth
|
||||||
|
requires = [ support.lua-scripting ]
|
||||||
|
}
|
||||||
{
|
{
|
||||||
type = virtual, provides = policy.device.profile
|
type = virtual, provides = policy.device.profile
|
||||||
requires = [ hooks.device.profile.select,
|
requires = [ hooks.device.profile.select,
|
||||||
|
hooks.device.profile.autoswitch-bluetooth,
|
||||||
hooks.device.profile.apply ]
|
hooks.device.profile.apply ]
|
||||||
wants = [ hooks.device.profile.find-best,
|
wants = [ hooks.device.profile.find-best,
|
||||||
hooks.device.profile.state ]
|
hooks.device.profile.state ]
|
||||||
|
@@ -1,13 +1,21 @@
|
|||||||
## The WirePlumber BLUEZ configuration
|
## The WirePlumber BLUEZ configuration
|
||||||
|
|
||||||
wireplumber.settings = {
|
wireplumber.settings = {
|
||||||
## Enables the logind module, which arbitrates which user will be allowed
|
## Whether to store state on the filesystem.
|
||||||
## to have bluetooth audio enabled at any given time (particularly useful
|
# bluetooth.use-persistent-storage = true
|
||||||
## if you are using GDM as a display manager, as the gdm user also launches
|
|
||||||
## pipewire and wireplumber).
|
## Whether to use headset profile in the presence of an input stream.
|
||||||
## This requires access to the D-Bus user session; disable if you are running
|
# bluetooth.autoswitch-to-headset-profile = true
|
||||||
## a system-wide instance of wireplumber.
|
|
||||||
# monitor.bluetooth.enable-logind = true
|
## Application names correspond to application.name in stream properties.
|
||||||
|
## Applications which do not set media.role but which should be considered
|
||||||
|
## for role based profile switching can be specified here.
|
||||||
|
# bluetooth.autoswitch-applications = [
|
||||||
|
# "Firefox", "Chromium input", "Google Chrome input", "Brave input",
|
||||||
|
# "Microsoft Edge input", "Vivaldi input", "ZOOM VoiceEngine",
|
||||||
|
# "Telegram Desktop", "telegram-desktop", "linphone", "Mumble",
|
||||||
|
# "WEBRTC VoiceEngine", "Skype"
|
||||||
|
# ]
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor.bluetooth.properties = {
|
monitor.bluetooth.properties = {
|
||||||
|
@@ -21,20 +21,4 @@ wireplumber.settings = {
|
|||||||
## How much to lower the volume of lower priority streams when ducking
|
## How much to lower the volume of lower priority streams when ducking
|
||||||
## note that this is a linear volume modifier (not cubic as in pulseaudio)
|
## note that this is a linear volume modifier (not cubic as in pulseaudio)
|
||||||
# linking.default.duck-level = 0.3
|
# linking.default.duck-level = 0.3
|
||||||
|
|
||||||
## Whether to store state on the filesystem.
|
|
||||||
# linking.bluetooth.use-persistent-storage = true
|
|
||||||
|
|
||||||
## Whether to use headset profile in the presence of an input stream.
|
|
||||||
# linking.bluetooth.media-role.use-headset-profile = true
|
|
||||||
|
|
||||||
## Application names correspond to application.name in stream properties.
|
|
||||||
## Applications which do not set media.role but which should be considered
|
|
||||||
## for role based profile switching can be specified here.
|
|
||||||
linking.bluetooth.media-role.applications = [
|
|
||||||
"Firefox", "Chromium input", "Google Chrome input", "Brave input",
|
|
||||||
"Microsoft Edge input", "Vivaldi input", "ZOOM VoiceEngine",
|
|
||||||
"Telegram Desktop", "telegram-desktop", "linphone", "Mumble",
|
|
||||||
"WEBRTC VoiceEngine", "Skype"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@@ -24,59 +24,14 @@
|
|||||||
-- When a stream goes away if the list with which we track the streams above
|
-- When a stream goes away if the list with which we track the streams above
|
||||||
-- is empty, then we revert back to the old profile.
|
-- is empty, then we revert back to the old profile.
|
||||||
|
|
||||||
-- settings file: linking.conf
|
-- settings file: bluetooth.conf
|
||||||
|
|
||||||
cutils = require ("common-utils")
|
cutils = require ("common-utils")
|
||||||
|
config = require ("bluetooth-config")
|
||||||
defaults = {}
|
|
||||||
defaults.use_persistent_storage = true
|
|
||||||
defaults.use_headset_profile = true
|
|
||||||
defaults.app_settings = Json.Array {}
|
|
||||||
|
|
||||||
config = {}
|
|
||||||
config.use_persistent_storage = Conf.get_value_boolean ("wireplumber.settings",
|
|
||||||
"linking.bluetooth.use-persistent-storage", defaults.use_persistent_storage)
|
|
||||||
config.use_headset_profile = Conf.get_value_boolean ("wireplumber.settings",
|
|
||||||
"linking.bluetooth.media-role.use-headset-profile", defaults.use_headset_profile)
|
|
||||||
config.apps_setting = Conf.get_value ("wireplumber.settings",
|
|
||||||
"linking.bluetooth.media-role.applications", defaults.app_settings): parse ()
|
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
headset_profiles = nil
|
headset_profiles = nil
|
||||||
|
|
||||||
function handlePersistantSetting (enable)
|
|
||||||
if enable and state == nil then
|
|
||||||
-- the state storage
|
|
||||||
state = config.use_persistent_storage and State ("linking-bluetooth") or nil
|
|
||||||
headset_profiles = state and state:load () or {}
|
|
||||||
else
|
|
||||||
state = nil
|
|
||||||
headset_profiles = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function settingsChangedCallback (_, setting, json)
|
|
||||||
if setting == "linking.bluetooth.use-persistent-storage" and
|
|
||||||
json:is_boolean () then
|
|
||||||
config.use_persistent_storage = json:parse ()
|
|
||||||
handlePersistantSetting (config.use_persistent_storage)
|
|
||||||
elseif setting == "linking.bluetooth.media-role.use-headset-profile" and
|
|
||||||
json:is_boolean () then
|
|
||||||
config.use_headset_profile = json:parse ()
|
|
||||||
elseif setting == "linking.bluetooth.media-role.applications"
|
|
||||||
and json:is_array () then
|
|
||||||
local new_apps_setting = json:parse ()
|
|
||||||
if #new_apps_setting > 0 then
|
|
||||||
config.apps_setting = new_apps_setting
|
|
||||||
loadAppNames (config.apps_setting)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Settings.subscribe ("linking.bluetooth*", settingsChangedCallback)
|
|
||||||
|
|
||||||
handlePersistantSetting (config.use_persistent_storage)
|
|
||||||
|
|
||||||
local applications = {}
|
local applications = {}
|
||||||
local profile_restore_timeout_msec = 2000
|
local profile_restore_timeout_msec = 2000
|
||||||
|
|
||||||
@@ -89,13 +44,29 @@ local last_profiles = {}
|
|||||||
local active_streams = {}
|
local active_streams = {}
|
||||||
local previous_streams = {}
|
local previous_streams = {}
|
||||||
|
|
||||||
|
function handlePersistantSetting (enable)
|
||||||
|
if enable and state == nil then
|
||||||
|
-- the state storage
|
||||||
|
state = config.autoswitch_to_headset_profile and State ("bluetooth-autoswitch") or nil
|
||||||
|
headset_profiles = state and state:load () or {}
|
||||||
|
else
|
||||||
|
state = nil
|
||||||
|
headset_profiles = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function loadAppNames (appNames)
|
function loadAppNames (appNames)
|
||||||
|
applications = {}
|
||||||
for i = 1, #appNames do
|
for i = 1, #appNames do
|
||||||
applications [appNames [i]] = true
|
applications [appNames [i]] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
loadAppNames (config.apps_setting)
|
handlePersistantSetting (config.use_persistent_storage)
|
||||||
|
loadAppNames (config.autoswitch_applications)
|
||||||
|
|
||||||
|
config:subscribe ("use-persistent-storage", handlePersistentSetting)
|
||||||
|
config:subscribe ("autoswitch-applications", loadAppNames)
|
||||||
|
|
||||||
devices_om = ObjectManager {
|
devices_om = ObjectManager {
|
||||||
Interest {
|
Interest {
|
||||||
@@ -132,7 +103,7 @@ local function getSavedLastProfile (device)
|
|||||||
return last_profiles [device.properties ["device.name"]]
|
return last_profiles [device.properties ["device.name"]]
|
||||||
end
|
end
|
||||||
|
|
||||||
local function isSwitched (device)
|
local function isSwitchedToHeadsetProfile (device)
|
||||||
return getSavedLastProfile (device) ~= nil
|
return getSavedLastProfile (device) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -144,7 +115,7 @@ local function isBluez5AudioSink (sink_name)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function isBluez5DefaultAudioSink ()
|
local function isBluez5DefaultAudioSink ()
|
||||||
local metadata = cutils.default_metadata_om:lookup ()
|
local metadata = cutils.get_default_metadata_object ()
|
||||||
local default_audio_sink = metadata:find (0, "default.audio.sink")
|
local default_audio_sink = metadata:find (0, "default.audio.sink")
|
||||||
return isBluez5AudioSink (default_audio_sink)
|
return isBluez5AudioSink (default_audio_sink)
|
||||||
end
|
end
|
||||||
@@ -232,22 +203,22 @@ local function hasProfileInputRoute (device, profile_index)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function switchProfile ()
|
local function switchDevicesToHeadsetProfile ()
|
||||||
local index
|
local index
|
||||||
local name
|
local name
|
||||||
|
|
||||||
|
-- clear restore callback, if any
|
||||||
if restore_timeout_source then
|
if restore_timeout_source then
|
||||||
restore_timeout_source:destroy ()
|
restore_timeout_source:destroy ()
|
||||||
restore_timeout_source = nil
|
restore_timeout_source = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
for device in devices_om:iterate () do
|
for device in devices_om:iterate () do
|
||||||
if isSwitched (device) then
|
if isSwitchedToHeadsetProfile (device) then
|
||||||
goto skip_device
|
goto skip_device
|
||||||
end
|
end
|
||||||
|
|
||||||
local cur_profile_name = getCurrentProfile (device)
|
local cur_profile_name = getCurrentProfile (device)
|
||||||
saveLastProfile (device, cur_profile_name)
|
|
||||||
|
|
||||||
_, index, name = findProfile (device, nil, cur_profile_name)
|
_, index, name = findProfile (device, nil, cur_profile_name)
|
||||||
if hasProfileInputRoute (device, index) then
|
if hasProfileInputRoute (device, index) then
|
||||||
@@ -270,6 +241,10 @@ local function switchProfile ()
|
|||||||
index = index
|
index = index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- store the current profile (needed when restoring)
|
||||||
|
saveLastProfile (device, cur_profile_name)
|
||||||
|
|
||||||
|
-- switch to headset profile
|
||||||
Log.info ("Setting profile of '"
|
Log.info ("Setting profile of '"
|
||||||
.. device.properties ["device.description"]
|
.. device.properties ["device.description"]
|
||||||
.. "' from: " .. cur_profile_name
|
.. "' from: " .. cur_profile_name
|
||||||
@@ -285,12 +260,10 @@ end
|
|||||||
|
|
||||||
local function restoreProfile ()
|
local function restoreProfile ()
|
||||||
for device in devices_om:iterate () do
|
for device in devices_om:iterate () do
|
||||||
if isSwitched (device) then
|
if isSwitchedToHeadsetProfile (device) then
|
||||||
local profile_name = getSavedLastProfile (device)
|
local profile_name = getSavedLastProfile (device)
|
||||||
local cur_profile_name = getCurrentProfile (device)
|
local cur_profile_name = getCurrentProfile (device)
|
||||||
|
|
||||||
saveLastProfile (device, nil)
|
|
||||||
|
|
||||||
if cur_profile_name then
|
if cur_profile_name then
|
||||||
Log.info ("Setting saved headset profile to: " .. cur_profile_name)
|
Log.info ("Setting saved headset profile to: " .. cur_profile_name)
|
||||||
saveHeadsetProfile (device, cur_profile_name)
|
saveHeadsetProfile (device, cur_profile_name)
|
||||||
@@ -305,6 +278,10 @@ local function restoreProfile ()
|
|||||||
index = index
|
index = index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- clear last profile as we will restore it now
|
||||||
|
saveLastProfile (device, nil)
|
||||||
|
|
||||||
|
-- restore previous profile
|
||||||
Log.info ("Restoring profile of '"
|
Log.info ("Restoring profile of '"
|
||||||
.. device.properties ["device.description"]
|
.. device.properties ["device.description"]
|
||||||
.. "' from: " .. cur_profile_name
|
.. "' from: " .. cur_profile_name
|
||||||
@@ -322,9 +299,12 @@ local function triggerRestoreProfile ()
|
|||||||
if restore_timeout_source then
|
if restore_timeout_source then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- we never restore the device profiles if there are active streams
|
||||||
if next (active_streams) ~= nil then
|
if next (active_streams) ~= nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
restore_timeout_source = Core.timeout_add (profile_restore_timeout_msec, function ()
|
restore_timeout_source = Core.timeout_add (profile_restore_timeout_msec, function ()
|
||||||
restore_timeout_source = nil
|
restore_timeout_source = nil
|
||||||
restoreProfile ()
|
restoreProfile ()
|
||||||
@@ -357,14 +337,14 @@ local function checkStreamStatus (stream)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function handleStream (stream)
|
local function handleStream (stream)
|
||||||
if not config.use_headset_profile then
|
if not config.autoswitch_to_headset_profile then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if checkStreamStatus (stream) then
|
if checkStreamStatus (stream) then
|
||||||
active_streams [stream.id] = true
|
active_streams [stream.id] = true
|
||||||
previous_streams [stream.id] = true
|
previous_streams [stream.id] = true
|
||||||
switchProfile ()
|
switchDevicesToHeadsetProfile ()
|
||||||
else
|
else
|
||||||
active_streams [stream.id] = nil
|
active_streams [stream.id] = nil
|
||||||
triggerRestoreProfile ()
|
triggerRestoreProfile ()
|
||||||
@@ -381,7 +361,7 @@ local function handleAllStreams ()
|
|||||||
end
|
end
|
||||||
|
|
||||||
SimpleEventHook {
|
SimpleEventHook {
|
||||||
name = "input-stream-removed@linking-bluetooth",
|
name = "input-stream-removed@autoswitch-bluetooth-profile",
|
||||||
interests = {
|
interests = {
|
||||||
EventInterest {
|
EventInterest {
|
||||||
Constraint { "event.type", "=", "node-removed" },
|
Constraint { "event.type", "=", "node-removed" },
|
||||||
@@ -397,7 +377,7 @@ SimpleEventHook {
|
|||||||
}:register ()
|
}:register ()
|
||||||
|
|
||||||
SimpleEventHook {
|
SimpleEventHook {
|
||||||
name = "input-stream-changed@linking-bluetooth",
|
name = "input-stream-changed@autoswitch-bluetooth-profile",
|
||||||
interests = {
|
interests = {
|
||||||
EventInterest {
|
EventInterest {
|
||||||
Constraint { "event.type", "=", "node-state-changed" },
|
Constraint { "event.type", "=", "node-state-changed" },
|
||||||
@@ -418,7 +398,7 @@ SimpleEventHook {
|
|||||||
}:register ()
|
}:register ()
|
||||||
|
|
||||||
SimpleEventHook {
|
SimpleEventHook {
|
||||||
name = "bluez-device-added@linking-bluetooth",
|
name = "bluez-device-added@autoswitch-bluetooth-profile",
|
||||||
interests = {
|
interests = {
|
||||||
EventInterest {
|
EventInterest {
|
||||||
Constraint { "event.type", "=", "device-added" },
|
Constraint { "event.type", "=", "device-added" },
|
||||||
@@ -428,15 +408,14 @@ SimpleEventHook {
|
|||||||
execute = function (event)
|
execute = function (event)
|
||||||
-- Devices are unswitched initially
|
-- Devices are unswitched initially
|
||||||
device = event:get_subject ()
|
device = event:get_subject ()
|
||||||
if isSwitched (device) then
|
|
||||||
saveLastProfile (device, nil)
|
saveLastProfile (device, nil)
|
||||||
end
|
|
||||||
handleAllStreams ()
|
handleAllStreams ()
|
||||||
end
|
end
|
||||||
}:register ()
|
}:register ()
|
||||||
|
|
||||||
SimpleEventHook {
|
SimpleEventHook {
|
||||||
name = "metadata-changed@linking-bluetooth",
|
name = "metadata-changed@autoswitch-bluetooth-profile",
|
||||||
interests = {
|
interests = {
|
||||||
EventInterest {
|
EventInterest {
|
||||||
Constraint { "event.type", "=", "metadata-changed" },
|
Constraint { "event.type", "=", "metadata-changed" },
|
||||||
@@ -447,7 +426,7 @@ SimpleEventHook {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
execute = function (event)
|
execute = function (event)
|
||||||
if (config.use_headset_profile) then
|
if (config.autoswitch_to_headset_profile) then
|
||||||
-- If bluez sink is set as default, rescan for active input streams
|
-- If bluez sink is set as default, rescan for active input streams
|
||||||
handleAllStreams ()
|
handleAllStreams ()
|
||||||
end
|
end
|
22
src/scripts/lib/bluetooth-config.lua
Normal file
22
src/scripts/lib/bluetooth-config.lua
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-- WirePlumber
|
||||||
|
--
|
||||||
|
-- Copyright © 2022 Collabora Ltd.
|
||||||
|
--
|
||||||
|
-- SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
-- Bluetooth settings manager
|
||||||
|
|
||||||
|
local settings_manager = require ("settings-manager")
|
||||||
|
|
||||||
|
local defaults = {
|
||||||
|
["use-persistent-storage"] = true,
|
||||||
|
["autoswitch-to-headset-profile"] = true,
|
||||||
|
["autoswitch-applications"] = {
|
||||||
|
"Firefox", "Chromium input", "Google Chrome input", "Brave input",
|
||||||
|
"Microsoft Edge input", "Vivaldi input", "ZOOM VoiceEngine",
|
||||||
|
"Telegram Desktop", "telegram-desktop", "linphone", "Mumble",
|
||||||
|
"WEBRTC VoiceEngine", "Skype"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings_manager.new ("bluetooth.", defaults)
|
Reference in New Issue
Block a user