bluez5: move bluez-hardware.conf loading to the plugin

It's not really the responsibility of the session manager to load the
bluez5 device quirks, and it's easier for eg. Wireplumber if it doesn't
need to do it.

Move loading bluez-hardware.conf to be the responsibility of the bluez5
spa plugin, similarly as the alsa plugin deals with the ACP database.

Put the configuration to share/spa-0.2/bluez5, mirroring the plugin
directory structure in lib/spa-0.2/bluez5.
This commit is contained in:
Pauli Virtanen 2021-09-18 12:32:29 +03:00 committed by Wim Taymans
parent 6168067cb2
commit cae1554449
6 changed files with 84 additions and 28 deletions

View file

@ -0,0 +1,95 @@
# List of hardware/kernel features, which cannot be detected generically.
#
# The `feature` is enabled only if all three of adapter, device, and
# kernel have it.
#
# For each of the adapter/device/kernel, the match rules are processed
# one at a time, and the first one that matches is used.
#
# Features and tags:
# msbc "standard" mSBC (60 byte tx packet)
# msbc-alt1 USB adapters with mSBC in ALT1 setting (24 byte tx packet)
# msbc-alt1-rtl Realtek USB adapters with mSBC in ALT1 setting (24 byte tx packet)
# hw-volume AVRCP and HSP/HFP hardware volume support
# hw-volume-mic Functional HSP/HFP microphone volume support
# sbc-xq "nonstandard" SBC codec setting with better sound quality
#
# Features are disabled with the key "no-features" whose value is an
# array of strings in the match rule.
bluez5.features.device = [
# properties:
# - name
# - address ("ff:ff:ff:ff:ff:ff")
# - vendor-id ("bluetooth:ffff", "usb:ffff")
# - product-id
# - version-id
{ name = "Air 1 Plus", no-features = [ hw-volume-mic ] },
{ name = "AirPods", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
{ name = "AirPods Pro", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
{ name = "AXLOIE Goin", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
{ name = "BAA 100", no-features = [ hw-volume ] }, # Buxton BAA 100, doesn't remember volume, #pipewire-1449
{ name = "D50s", address = "~^00:13:ef:", no-features = [ hw-volume ] }, # volume has no effect, #pipewire-1562
{ name = "JBL Endurance RUN BT", no-features = [ msbc-alt1, msbc-alt1-rtl, sbc-xq ] },
{ name = "JBL LIVE650BTNC" },
{ name = "Motorola DC800", no-features = [ sbc-xq ] }, # #pipewire-1590
{ name = "Motorola S305", no-features = [ sbc-xq ] }, # #pipewire-1590
{ name = "Soundcore Life P2-L", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
{ name = "Urbanista Stockholm Plus", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
{ address = "~^94:16:25:", no-features = [ hw-volume ]}, # AirPods 2
{ address = "~^9c:64:8b:", no-features = [ hw-volume ]}, # AirPods 2
{ address = "~^a0:e9:db:", no-features = [ hw-volume ]}, # Ausdom M05
{ address = "~^0c:a6:94:", no-features = [ hw-volume ]}, # deepblue2
{ address = "~^00:14:02:", no-features = [ hw-volume ]}, # iKross IKBT83B HS
{ address = "~^44:5e:f3:", no-features = [ hw-volume ]}, # JayBird BlueBuds X
{ address = "~^d4:9c:28:", no-features = [ hw-volume ]}, # JayBird BlueBuds X
{ address = "~^00:18:6b:", no-features = [ hw-volume ]}, # LG Tone HBS-730
{ address = "~^b8:ad:3e:", no-features = [ hw-volume ]}, # LG Tone HBS-730
{ address = "~^a0:e9:db:", no-features = [ hw-volume ]}, # LG Tone HV-800
{ address = "~^00:24:1c:", no-features = [ hw-volume ]}, # Motorola Roadster
{ address = "~^00:11:b1:", no-features = [ hw-volume ]}, # Mpow Cheetah
{ address = "~^a4:15:66:", no-features = [ hw-volume ]}, # SOL REPUBLIC Tracks Air
{ address = "~^00:14:f1:", no-features = [ hw-volume ]}, # Swage Rokitboost HS
{ address = "~^00:26:7e:", no-features = [ hw-volume ]}, # VW Car Kit
{ address = "~^90:03:b7:", no-features = [ hw-volume ]}, # VW Car Kit
# All features are enabled by default; it's simpler to block non-working devices one by one.
]
bluez5.features.adapter = [
# properties:
# - address ("ff:ff:ff:ff:ff:ff")
# - bus-type ("usb", "other")
# - vendor-id ("usb:ffff")
# - product-id ("ffff")
# Realtek Semiconductor Corp.
{ bus-type = "usb", vendor-id = "usb:0bda" },
# Generic USB adapters
{ bus-type = "usb", no-features = [ msbc-alt1-rtl ] },
# Other adapters
{ no-features = [ msbc-alt1-rtl ] },
]
bluez5.features.kernel = [
# properties (as in uname):
# - sysname
# - release
# - version
# See https://lore.kernel.org/linux-bluetooth/20201210012003.133000-1-tpiepho@gmail.com/
# https://lore.kernel.org/linux-bluetooth/b86543908684cc6cd9afaf4de10fac7af1a49665.camel@iki.fi/
{ sysname = "Linux", release = "~^[0-4]\\.", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
{ sysname = "Linux", release = "~^5\\.[1-7]\\.", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
{ sysname = "Linux", release = "~^5\\.(8|9)\\.", no-features = [ msbc-alt1 ] },
{ sysname = "Linux", release = "~^5\\.10\\.(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|51|52|53|54|55|56|57|58|59|60|61)($|[^0-9])", no-features = [ msbc-alt1 ] },
{ sysname = "Linux", release = "~^5\\.12\\.(18|19)($|[^0-9])", no-features = [ msbc-alt1 ] },
{ sysname = "Linux", release = "~^5\\.13\\.(3|4|5|6|7|8|9|10|11|12|13)($|[^0-9])", no-features = [ msbc-alt1 ] },
{ sysname = "Linux", release = "~^5\\.14($|[^0-9])", no-features = [ msbc-alt1 ] },
{ no-features = [] },
]

View file

@ -32,6 +32,10 @@ bluez5_sources = ['plugin.c',
'bluez5-device.c',
'bluez5-dbus.c']
bluez5_data = ['bluez-hardware.conf']
install_data(bluez5_data, install_dir : spa_datadir / 'bluez5')
if not get_option('bluez5-backend-hsp-native').disabled() or not get_option('bluez5-backend-hfp-native').disabled()
if libusb_dep.found()
bluez5_deps += libusb_dep

View file

@ -31,7 +31,12 @@
#include <sys/socket.h>
#include <fcntl.h>
#include <regex.h>
#include <limits.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
@ -163,6 +168,63 @@ static int parse_force_flag(const struct spa_dict *info, const char *key)
return (strcmp(str, "true") == 0 || atoi(str)) ? 1 : 0;
}
static void load_quirks(struct spa_bt_quirks *this, const char *str, size_t len)
{
struct spa_json data = SPA_JSON_INIT(str, len);
struct spa_json rules;
char key[1024];
if (spa_json_enter_object(&data, &rules) <= 0)
spa_json_init(&rules, str, len);
while (spa_json_get_string(&rules, key, sizeof(key)-1)) {
int sz;
const char *value;
if ((sz = spa_json_next(&rules, &value)) <= 0)
break;
if (!spa_json_is_container(value, sz))
continue;
sz = spa_json_container_len(&rules, value, sz);
if (spa_streq(key, "bluez5.features.kernel") && !this->kernel_rules)
this->kernel_rules = strndup(value, sz);
else if (spa_streq(key, "bluez5.features.adapter") && !this->adapter_rules)
this->adapter_rules = strndup(value, sz);
else if (spa_streq(key, "bluez5.features.device") && !this->device_rules)
this->device_rules = strndup(value, sz);
}
}
static int load_conf(struct spa_bt_quirks *this, const char *path)
{
char *data;
struct stat sbuf;
int fd = -1;
spa_log_debug(this->log, NAME ": loading %s", path);
if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0)
goto fail;
if (fstat(fd, &sbuf) < 0)
goto fail;
if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
goto fail;
close(fd);
load_quirks(this, data, sbuf.st_size);
munmap(data, sbuf.st_size);
return 0;
fail:
if (fd >= 0)
close(fd);
return -errno;
}
struct spa_bt_quirks *spa_bt_quirks_create(const struct spa_dict *info, struct spa_log *log)
{
struct spa_bt_quirks *this;
@ -183,15 +245,22 @@ struct spa_bt_quirks *spa_bt_quirks_create(const struct spa_dict *info, struct s
this->force_msbc = parse_force_flag(info, "bluez5.enable-msbc");
this->force_hw_volume = parse_force_flag(info, "bluez5.enable-hw-volume");
str = spa_dict_lookup(info, "bluez5.features.kernel");
this->kernel_rules = str ? strdup(str) : NULL;
str = spa_dict_lookup(info, "bluez5.features.adapter");
this->adapter_rules = str ? strdup(str) : NULL;
str = spa_dict_lookup(info, "bluez5.features.device");
this->device_rules = str ? strdup(str) : NULL;
if ((str = spa_dict_lookup(info, "bluez5.hardware-database")) != NULL) {
spa_log_debug(this->log, NAME ": loading session manager provided data");
load_quirks(this, str, strlen(str));
} else {
char path[PATH_MAX];
const char *dir = getenv("SPA_DATA_DIR");
if (dir == NULL)
dir = SPADATADIR;
if (spa_scnprintf(path, sizeof(path), "%s/bluez5/bluez-hardware.conf", dir) >= 0)
load_conf(this, path);
}
if (!(this->kernel_rules && this->adapter_rules && this->device_rules))
spa_log_info(this->log, NAME " failed to find data from bluez-hardware.conf");
spa_log_warn(this->log, NAME ": failed to load bluez-hardware.conf");
return this;
}