mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-10-29 05:40:27 -04:00 
			
		
		
		
	backend-native: report battery status to BlueZ
This commit is contained in:
		
							parent
							
								
									a1ed8aec68
								
							
						
					
					
						commit
						2683c1ef5e
					
				
					 2 changed files with 239 additions and 3 deletions
				
			
		|  | @ -47,6 +47,17 @@ | |||
| 
 | ||||
| #define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles" | ||||
| 
 | ||||
| #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" | ||||
| #define DBUS_SIGNAL_INTERFACES_ADDED "InterfacesAdded" | ||||
| #define DBUS_SIGNAL_INTERFACES_REMOVED "InterfacesRemoved" | ||||
| #define DBUS_SIGNAL_PROPERTIES_CHANGED "PropertiesChanged" | ||||
| 
 | ||||
| #define BLUEZ_INTERFACE_BATTERY_PROVIDER "org.bluez.BatteryProvider1" | ||||
| #define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER "org.bluez.BatteryProviderManager1" | ||||
| 
 | ||||
| #define PIPEWIRE_BATTERY_PROVIDER "/org/freedesktop/pipewire/battery" | ||||
| #define BATTERY_PROVIDER_UNAVAILABLE 255 | ||||
| 
 | ||||
| struct spa_bt_backend { | ||||
| 	struct spa_bt_monitor *monitor; | ||||
| 
 | ||||
|  | @ -62,6 +73,8 @@ struct spa_bt_backend { | |||
| 
 | ||||
| 	struct spa_list rfcomm_list; | ||||
| 	unsigned int msbc_support_enabled_in_config:1; | ||||
| 	unsigned int has_battery_provider; | ||||
| 	unsigned int battery_provider_unavailable; | ||||
| }; | ||||
| 
 | ||||
| struct transport_data { | ||||
|  | @ -154,6 +167,205 @@ finish: | |||
| 	return t; | ||||
| } | ||||
| 
 | ||||
| // Working with BlueZ Battery Provider. Developed using https://github.com/dgreid/adhd/commit/655b58f as example of DBus calls.
 | ||||
| static char *battery_name(struct spa_bt_device *d) | ||||
| { | ||||
| 	char *path = malloc(strlen(PIPEWIRE_BATTERY_PROVIDER) + strlen(d->path) + 1); | ||||
| 	sprintf(path, PIPEWIRE_BATTERY_PROVIDER"%s", d->path); | ||||
| 
 | ||||
| 	// /org/freedesktop/pipewire/battery/org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX
 | ||||
| 	return path; | ||||
| } | ||||
| 
 | ||||
| static void write_battery(DBusMessageIter *iter, struct spa_bt_device *device) | ||||
| { | ||||
| 	DBusMessageIter dict, entry, variant; | ||||
| 
 | ||||
| 	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); | ||||
| 
 | ||||
| 	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, | ||||
| 					 &entry); | ||||
| 	const char *prop_percentage = "Percentage"; | ||||
| 	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &prop_percentage); | ||||
| 	dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, | ||||
| 					 DBUS_TYPE_BYTE_AS_STRING, &variant); | ||||
| 	dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &device->battery); | ||||
| 	dbus_message_iter_close_container(&entry, &variant); | ||||
| 	dbus_message_iter_close_container(&dict, &entry); | ||||
| 
 | ||||
| 	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); | ||||
| 	const char *prop_device = "Device"; | ||||
| 	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &prop_device); | ||||
| 	dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, | ||||
| 					 DBUS_TYPE_OBJECT_PATH_AS_STRING, | ||||
| 					 &variant); | ||||
| 	dbus_message_iter_append_basic(&variant, DBUS_TYPE_OBJECT_PATH, &device->path); | ||||
| 	dbus_message_iter_close_container(&entry, &variant); | ||||
| 	dbus_message_iter_close_container(&dict, &entry); | ||||
| 
 | ||||
| 	dbus_message_iter_close_container(iter, &dict); | ||||
| } | ||||
| 
 | ||||
| static void update_battery(struct spa_bt_device *device) | ||||
| { | ||||
| 	const char *name = battery_name(device); | ||||
| 	spa_log_debug(device->monitor->log, NAME": updating battery: %s", name); | ||||
| 
 | ||||
| 	DBusMessage *msg; | ||||
| 	DBusMessageIter iter; | ||||
| 
 | ||||
| 	msg = dbus_message_new_signal(name, | ||||
| 				      DBUS_INTERFACE_PROPERTIES, | ||||
| 				      DBUS_SIGNAL_PROPERTIES_CHANGED); | ||||
| 
 | ||||
| 	dbus_message_iter_init_append(msg, &iter); | ||||
| 	const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; | ||||
| 	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, | ||||
| 				       &interface); | ||||
| 
 | ||||
| 	write_battery(&iter, device); | ||||
| 
 | ||||
| 	if (!dbus_connection_send(device->monitor->conn, msg, NULL)) | ||||
| 		spa_log_error(device->monitor->log, NAME": Error updating battery"); | ||||
| 
 | ||||
| 	dbus_message_unref(msg); | ||||
| } | ||||
| 
 | ||||
| static void create_battery(struct spa_bt_device *device) { | ||||
| 	DBusMessage *msg; | ||||
| 	DBusMessageIter iter, entry, dict; | ||||
| 	msg = dbus_message_new_signal(PIPEWIRE_BATTERY_PROVIDER, | ||||
| 				      DBUS_INTERFACE_OBJECT_MANAGER, | ||||
| 				      DBUS_SIGNAL_INTERFACES_ADDED); | ||||
| 
 | ||||
| 	dbus_message_iter_init_append(msg, &iter); | ||||
| 	const char *bat_name = battery_name(device); | ||||
| 	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, | ||||
| 				       &bat_name); | ||||
| 	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); | ||||
| 	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); | ||||
| 	const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; | ||||
| 	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, | ||||
| 				       &interface); | ||||
| 
 | ||||
| 	write_battery(&entry, device); | ||||
| 
 | ||||
| 	dbus_message_iter_close_container(&dict, &entry); | ||||
| 	dbus_message_iter_close_container(&iter, &dict); | ||||
| 
 | ||||
| 	if (!dbus_connection_send(device->monitor->conn, msg, NULL)) { | ||||
| 		spa_log_error(device->monitor->backend_native->log, NAME": Failed to create virtual battery for %s", device->address); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	dbus_message_unref(msg); | ||||
| 
 | ||||
| 	spa_log_debug(device->monitor->backend_native->log, NAME": Created virtual battery for %s", device->address); | ||||
| 	device->has_battery = true; | ||||
| } | ||||
| 
 | ||||
| static void remove_battery(struct spa_bt_device *d) { | ||||
| 	if (!d->monitor->backend_native->has_battery_provider) return; | ||||
| 
 | ||||
| 	spa_log_debug(d->monitor->backend_native->log, NAME": Removing battery provider: %s", battery_name(d)); | ||||
| 
 | ||||
| 	DBusMessage *m = dbus_message_new_signal(PIPEWIRE_BATTERY_PROVIDER, | ||||
| 				      DBUS_INTERFACE_OBJECT_MANAGER, | ||||
| 				      DBUS_SIGNAL_INTERFACES_REMOVED); | ||||
| 
 | ||||
| 	DBusMessageIter i, entry; | ||||
| 
 | ||||
| 	dbus_message_iter_init_append(m, &i); | ||||
| 	const char *bat_name = battery_name(d); | ||||
| 	dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, | ||||
| 				       &bat_name); | ||||
| 	dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, | ||||
| 					 DBUS_TYPE_STRING_AS_STRING, &entry); | ||||
| 	const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; | ||||
| 	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, | ||||
| 				       &interface); | ||||
| 	dbus_message_iter_close_container(&i, &entry); | ||||
| 
 | ||||
| 	if (!dbus_connection_send(d->monitor->conn, m, NULL)) { | ||||
| 		spa_log_error(d->monitor->backend_native->log, NAME": sending " DBUS_SIGNAL_INTERFACES_REMOVED " failed"); | ||||
| 	} | ||||
| 
 | ||||
| 	dbus_message_unref(m); | ||||
| 
 | ||||
| 	d->has_battery = false; | ||||
| } | ||||
| 
 | ||||
| static void on_battery_provider_registered(DBusPendingCall *pending_call, | ||||
| 				       void *data) | ||||
| { | ||||
| 	DBusMessage *reply; | ||||
| 	struct spa_bt_device *device = data; | ||||
| 
 | ||||
| 	reply = dbus_pending_call_steal_reply(pending_call); | ||||
| 	dbus_pending_call_unref(pending_call); | ||||
| 
 | ||||
| 	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { | ||||
| 		spa_log_error(device->monitor->log, NAME": Failed to register battery provider. Error: %s", dbus_message_get_error_name(reply)); | ||||
| 		spa_log_error(device->monitor->log, NAME": BlueZ battery provider is not available, won't retry to register it"); | ||||
| 		device->monitor->backend_native->battery_provider_unavailable = true; | ||||
| 		dbus_message_unref(reply); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	spa_log_debug(device->monitor->log, NAME": Registered battery provider"); | ||||
| 
 | ||||
| 	device->monitor->backend_native->has_battery_provider = true; | ||||
| 
 | ||||
| 	if (!device->has_battery) | ||||
| 		create_battery(device); | ||||
| 
 | ||||
| 	dbus_message_unref(reply); | ||||
| } | ||||
| 
 | ||||
| static void register_battery_provider(DBusConnection *conn, struct spa_bt_device *device) | ||||
| { | ||||
| 	DBusMessage *method_call; | ||||
| 	DBusMessageIter message_iter; | ||||
| 	DBusPendingCall *pending_call; | ||||
| 
 | ||||
| 	method_call = dbus_message_new_method_call( | ||||
| 		BLUEZ_SERVICE, device->adapter_path, | ||||
| 		BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER, | ||||
| 		"RegisterBatteryProvider"); | ||||
| 
 | ||||
| 	if (!method_call) { | ||||
| 		spa_log_error(device->monitor->log, NAME": Failed to register battery provider"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	dbus_message_iter_init_append(method_call, &message_iter); | ||||
| 	const char *object_path = PIPEWIRE_BATTERY_PROVIDER; | ||||
| 	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH, | ||||
| 				       &object_path); | ||||
| 
 | ||||
| 	if (!dbus_connection_send_with_reply(conn, method_call, &pending_call, | ||||
| 					     DBUS_TIMEOUT_USE_DEFAULT)) { | ||||
| 		dbus_message_unref(method_call); | ||||
| 		spa_log_error(device->monitor->log, NAME": Failed to register battery provider"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	dbus_message_unref(method_call); | ||||
| 
 | ||||
| 	if (!pending_call) { | ||||
| 		spa_log_error(device->monitor->log, NAME": Failed to register battery provider"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!dbus_pending_call_set_notify( | ||||
| 		    pending_call, on_battery_provider_registered, | ||||
| 		    device, NULL)) { | ||||
| 		spa_log_error(device->monitor->log, "Failed to register battery provider"); | ||||
| 		dbus_pending_call_cancel(pending_call); | ||||
| 		dbus_pending_call_unref(pending_call); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void rfcomm_free(struct rfcomm *rfcomm) | ||||
| { | ||||
| 	spa_list_remove(&rfcomm->link); | ||||
|  | @ -477,8 +689,29 @@ static bool rfcomm_hfp_ag(struct spa_source *source, char* buf) | |||
| 
 | ||||
| 			if (k == SPA_BT_HFP_HF_IPHONEACCEV_KEY_BATTERY) { | ||||
| 				// Battery level is reported in range of 0-9, convert to 0-100%
 | ||||
| 				rfcomm->transport->battery = (v + 1) * 10; | ||||
| 				spa_log_debug(backend->log, NAME": battery level: %d%%", rfcomm->transport->battery); | ||||
| 				uint8_t level = (v + 1) * 10; | ||||
| 				spa_log_debug(backend->log, NAME": battery level: %d%%", level); | ||||
| 
 | ||||
| 				// TODO: report without Battery Provider (using props)
 | ||||
| 
 | ||||
| 				// BlueZ likely is running without battery provider support, don't try to report battery
 | ||||
| 				if (backend->battery_provider_unavailable) return true; | ||||
| 
 | ||||
| 				// If everything is initialized and battery level has not changed we don't need to send anything to BlueZ
 | ||||
| 				if (backend->has_battery_provider && rfcomm->device->has_battery && rfcomm->device->battery == level) return true; | ||||
| 
 | ||||
| 				rfcomm->device->battery = level; | ||||
| 
 | ||||
| 				if (!backend->has_battery_provider) { | ||||
| 					// No provider: register it, create battery in hook
 | ||||
| 					register_battery_provider(backend->conn, rfcomm->device); | ||||
| 				} else if (!rfcomm->device->has_battery) { | ||||
| 					// Have provider but no battery: create battery, it will already contain percentage
 | ||||
| 					create_battery(rfcomm->device); | ||||
| 				} else { | ||||
| 					// Just update existing battery percentage
 | ||||
| 					update_battery(rfcomm->device); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} else if (strncmp(buf, "AT+APLSIRI?", 11) == 0) { | ||||
|  | @ -1141,6 +1374,8 @@ static DBusHandlerResult profile_request_disconnection(DBusConnection *conn, DBu | |||
| 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; | ||||
| 	} | ||||
| 
 | ||||
| 	remove_battery(d); | ||||
| 
 | ||||
| 	spa_list_for_each_safe(rfcomm, rfcomm_tmp, &backend->rfcomm_list, link) { | ||||
| 		if (rfcomm->device == d && rfcomm->profile == profile) { | ||||
| 			if (rfcomm->source.loop) | ||||
|  |  | |||
|  | @ -433,6 +433,8 @@ struct spa_bt_device { | |||
| 	struct spa_list remote_endpoint_list; | ||||
| 	struct spa_list transport_list; | ||||
| 	struct spa_list codec_switch_list; | ||||
| 	uint8_t battery; | ||||
| 	int has_battery; | ||||
| 
 | ||||
| 	struct spa_hook_list listener_list; | ||||
| 	bool added; | ||||
|  | @ -503,7 +505,6 @@ struct spa_bt_transport { | |||
| 	enum spa_bt_transport_state state; | ||||
| 	const struct a2dp_codec *a2dp_codec; | ||||
| 	unsigned int codec; | ||||
| 	uint8_t battery; | ||||
| 	void *configuration; | ||||
| 	int configuration_len; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dmitry Sharshakov
						Dmitry Sharshakov