bluez5: different icon for A2DP & HFP output routes

Set different icons for A2DP & HFP output routes, so that they look
different (in Gnome).

Don't call the non-HFP output route as "headset" or "handsfree" in this
case, to be less ambiguous about microphone availability.

Also set device.icon-name for the device too.
This commit is contained in:
Pauli Virtanen 2025-05-03 18:19:22 +03:00 committed by Wim Taymans
parent 9586ef891e
commit 270eda63a9
3 changed files with 71 additions and 6 deletions

View file

@ -1721,8 +1721,9 @@ static void emit_device_info(struct spa_bt_monitor *monitor,
{ {
struct spa_device_object_info info; struct spa_device_object_info info;
char dev[32], name[128], class[16], vendor_id[64], product_id[64], product_id_tot[67]; char dev[32], name[128], class[16], vendor_id[64], product_id[64], product_id_tot[67];
struct spa_dict_item items[23]; struct spa_dict_item items[24];
uint32_t n_items = 0; uint32_t n_items = 0;
enum spa_bt_form_factor ff;
info = SPA_DEVICE_OBJECT_INFO_INIT(); info = SPA_DEVICE_OBJECT_INFO_INIT();
info.type = SPA_TYPE_INTERFACE_Device; info.type = SPA_TYPE_INTERFACE_Device;
@ -1731,6 +1732,8 @@ static void emit_device_info(struct spa_bt_monitor *monitor,
SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
info.flags = 0; info.flags = 0;
ff = spa_bt_form_factor_from_class(device->bluetooth_class);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "bluez5"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "bluez5");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, "bluetooth"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, "bluetooth");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device");
@ -1745,9 +1748,8 @@ static void emit_device_info(struct spa_bt_monitor *monitor,
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, vendor_id); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, vendor_id);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, product_id_tot); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, product_id_tot);
} }
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, spa_bt_form_factor_name(ff));
spa_bt_form_factor_name( items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ICON_NAME, spa_bt_form_factor_icon_name(ff));
spa_bt_form_factor_from_class(device->bluetooth_class)));
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_STRING, device->address); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_STRING, device->address);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ICON, device->icon); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ICON, device->icon);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PATH, device->path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PATH, device->path);

View file

@ -534,7 +534,7 @@ static void emit_device_set_node(struct impl *this, uint32_t id)
struct spa_bt_device *device = this->bt_dev; struct spa_bt_device *device = this->bt_dev;
struct node *node = &this->nodes[id]; struct node *node = &this->nodes[id];
struct spa_device_object_info info; struct spa_device_object_info info;
struct spa_dict_item items[8]; struct spa_dict_item items[9];
char str_id[32], members_json[8192], channels_json[512]; char str_id[32], members_json[8192], channels_json[512];
struct device_set_member *members; struct device_set_member *members;
uint32_t n_members; uint32_t n_members;
@ -2150,6 +2150,22 @@ static bool profile_has_route(uint32_t profile, uint32_t route)
return false; return false;
} }
static bool device_has_route(struct impl *this, uint32_t route)
{
bool found = false;
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_DUPLEX)
found = found || profile_has_route(DEVICE_PROFILE_A2DP, route);
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_BAP_AUDIO)
found = found || profile_has_route(DEVICE_PROFILE_BAP, route);
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)
found = found || profile_has_route(DEVICE_PROFILE_HSP_HFP, route);
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)
found = found || profile_has_route(DEVICE_PROFILE_ASHA, route);
return found;
}
static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
uint32_t id, uint32_t route, uint32_t profile) uint32_t id, uint32_t route, uint32_t profile)
{ {
@ -2157,6 +2173,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
struct spa_pod_frame f[2]; struct spa_pod_frame f[2];
enum spa_direction direction; enum spa_direction direction;
const char *name_prefix, *description, *hfp_description, *port_type; const char *name_prefix, *description, *hfp_description, *port_type;
const char *port_icon_name = NULL;
enum spa_bt_form_factor ff; enum spa_bt_form_factor ff;
enum spa_bluetooth_audio_codec codec; enum spa_bluetooth_audio_codec codec;
enum spa_param_availability available; enum spa_param_availability available;
@ -2250,6 +2267,20 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
dev = DEVICE_ID_SINK; dev = DEVICE_ID_SINK;
available = this->device_set.sink_enabled ? available = this->device_set.sink_enabled ?
SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes;
if (device_has_route(this, ROUTE_HF_OUTPUT)) {
/* Distinguish A2DP vs. HFP output routes */
switch (ff) {
case SPA_BT_FORM_FACTOR_HEADSET:
case SPA_BT_FORM_FACTOR_HANDSFREE:
port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADPHONE);
/* Don't call it "headset", the HF one has the mic */
description = _("Headphone");
break;
default:
break;
}
}
break; break;
case ROUTE_HF_OUTPUT: case ROUTE_HF_OUTPUT:
direction = SPA_DIRECTION_OUTPUT; direction = SPA_DIRECTION_OUTPUT;
@ -2257,6 +2288,8 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
description = hfp_description; description = hfp_description;
dev = DEVICE_ID_SINK; dev = DEVICE_ID_SINK;
available = SPA_PARAM_AVAILABILITY_yes; available = SPA_PARAM_AVAILABILITY_yes;
if (device_has_route(this, ROUTE_OUTPUT))
port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADSET);
break; break;
case ROUTE_SET_INPUT: case ROUTE_SET_INPUT:
if (!(this->device_set.source_enabled && this->device_set.leader)) if (!(this->device_set.source_enabled && this->device_set.leader))
@ -2292,11 +2325,16 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
0); 0);
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_info, 0); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_info, 0);
spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_push_struct(b, &f[1]);
spa_pod_builder_int(b, 1); spa_pod_builder_int(b, port_icon_name ? 2 : 1);
spa_pod_builder_add(b, spa_pod_builder_add(b,
SPA_POD_String("port.type"), SPA_POD_String("port.type"),
SPA_POD_String(port_type), SPA_POD_String(port_type),
NULL); NULL);
if (port_icon_name)
spa_pod_builder_add(b,
SPA_POD_String("device.icon-name"),
SPA_POD_String(port_icon_name),
NULL);
spa_pod_builder_pop(b, &f[1]); spa_pod_builder_pop(b, &f[1]);
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0);
spa_pod_builder_push_array(b, &f[1]); spa_pod_builder_push_array(b, &f[1]);

View file

@ -427,6 +427,31 @@ static inline const char *spa_bt_form_factor_name(enum spa_bt_form_factor ff)
} }
} }
static inline const char *spa_bt_form_factor_icon_name(enum spa_bt_form_factor ff)
{
switch (ff) {
case SPA_BT_FORM_FACTOR_HEADSET:
return "audio-headset-bluetooth";
case SPA_BT_FORM_FACTOR_HANDSFREE:
return "audio-handsfree-bluetooth";
case SPA_BT_FORM_FACTOR_MICROPHONE:
return "audio-input-microphone-bluetooth";
case SPA_BT_FORM_FACTOR_SPEAKER:
return "audio-speakers-bluetooth";
case SPA_BT_FORM_FACTOR_HEADPHONE:
return "audio-headphones-bluetooth";
case SPA_BT_FORM_FACTOR_PORTABLE:
return "multimedia-player-bluetooth";
case SPA_BT_FORM_FACTOR_PHONE:
return "phone-bluetooth";
case SPA_BT_FORM_FACTOR_CAR:
case SPA_BT_FORM_FACTOR_HIFI:
case SPA_BT_FORM_FACTOR_UNKNOWN:
default:
return "audio-card-bluetooth";
}
}
static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t bluetooth_class) static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t bluetooth_class)
{ {
uint32_t major, minor; uint32_t major, minor;