diff --git a/contrib/ci/build_windows.sh b/contrib/ci/build_windows.sh index f9f185114..839410508 100755 --- a/contrib/ci/build_windows.sh +++ b/contrib/ci/build_windows.sh @@ -47,6 +47,7 @@ meson .. \ -Dgusb:docs=false \ -Dgusb:introspection=false \ -Dgusb:vapi=false \ + -Dbluez=false \ -Dgudev=false $@ VERSION=$(meson introspect . --projectinfo | jq -r .version) ninja -v diff --git a/libfwupdplugin/fu-bluez-device.c b/libfwupdplugin/fu-bluez-device.c new file mode 100644 index 000000000..d043c233e --- /dev/null +++ b/libfwupdplugin/fu-bluez-device.c @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "FuBluezDevice" + +#include "config.h" + +#include + +#include "fu-bluez-device.h" +#include "fu-common.h" +#include "fu-device-private.h" +#include "fu-firmware-common.h" + +#define DEFAULT_PROXY_TIMEOUT 5000 + +/** + * SECTION:fu-bluez-device + * @short_description: a Bluez Bluetooth LE device + * + * An object that represents a Bluez Bluetooth LE device. + * + * See also: #FuBluezDevice + */ + +typedef struct { + FuBluezDevice parent_instance; + GDBusObjectManager *object_manager; + GDBusProxy *proxy; + GHashTable *uuid_paths; /* utf8 : utf8 */ +} FuBluezDevicePrivate; + +enum { + PROP_0, + PROP_OBJECT_MANAGER, + PROP_PROXY, + PROP_LAST +}; + +G_DEFINE_TYPE_WITH_PRIVATE (FuBluezDevice, fu_bluez_device, FU_TYPE_DEVICE) + +#define GET_PRIVATE(o) (fu_bluez_device_get_instance_private (o)) + +static void +fu_bluez_device_add_uuid_path (FuBluezDevice *self, const gchar *uuid, const gchar *path) +{ + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + g_return_if_fail (FU_IS_BLUEZ_DEVICE (self)); + g_return_if_fail (uuid != NULL); + g_return_if_fail (path != NULL); + g_hash_table_insert (priv->uuid_paths, + g_strdup (uuid), + g_strdup (path)); +} + +static void +fu_bluez_device_set_modalias (FuBluezDevice *self, const gchar *modalias) +{ + gsize modaliaslen = strlen (modalias); + guint16 vid = 0x0; + guint16 pid = 0x0; + guint16 rev = 0x0; + + g_return_if_fail (modalias != NULL); + + /* usb:v0461p4EEFd0001 */ + if (g_str_has_prefix (modalias, "usb:")) { + fu_firmware_strparse_uint16_safe (modalias, modaliaslen, 5, &vid, NULL); + fu_firmware_strparse_uint16_safe (modalias, modaliaslen, 10, &pid, NULL); + fu_firmware_strparse_uint16_safe (modalias, modaliaslen, 15, &rev, NULL); + + /* bluetooth:v000ApFFFFdFFFF */ + } else if (g_str_has_prefix (modalias, "bluetooth:")) { + fu_firmware_strparse_uint16_safe (modalias, modaliaslen, 11, &vid, NULL); + fu_firmware_strparse_uint16_safe (modalias, modaliaslen, 16, &pid, NULL); + fu_firmware_strparse_uint16_safe (modalias, modaliaslen, 21, &rev, NULL); + } + + if (vid != 0x0 && pid != 0x0 && rev != 0x0) { + g_autofree gchar *devid = NULL; + devid = g_strdup_printf ("BLUETOOTH\\VID_%04X&PID_%04X&REV_%04X", + vid, pid, rev); + fu_device_add_instance_id (FU_DEVICE (self), devid); + } + if (vid != 0x0 && pid != 0x0) { + g_autofree gchar *devid = NULL; + devid = g_strdup_printf ("BLUETOOTH\\VID_%04X&PID_%04X", vid, pid); + fu_device_add_instance_id (FU_DEVICE (self), devid); + } + if (vid != 0x0) { + g_autofree gchar *devid = NULL; + g_autofree gchar *vendor_id = NULL; + devid = g_strdup_printf ("BLUETOOTH\\VID_%04X", vid); + fu_device_add_instance_id_full (FU_DEVICE (self), devid, + FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); + vendor_id = g_strdup_printf ("BLUETOOTH:%04X", vid); + fu_device_add_vendor_id (FU_DEVICE (self), vendor_id); + } +} + +static void +fu_bluez_device_to_string (FuDevice *device, guint idt, GString *str) +{ + FuBluezDevice *self = FU_BLUEZ_DEVICE (device); + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + + if (priv->uuid_paths != NULL) { + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, priv->uuid_paths); + while (g_hash_table_iter_next (&iter, &key, &value)) { + fu_common_string_append_kv (str, idt + 1, + (const gchar *) key, + (const gchar *) value); + } + } +} + +/* + * Returns the value of a property of an object specified by its path as + * a GVariant, or NULL if the property wasn't found. + */ +static GVariant * +fu_bluez_device_get_ble_property (const gchar *obj_path, + const gchar *iface, + const gchar *prop_name, + GError **error) +{ + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GVariant) val = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.bluez", + obj_path, iface, NULL, error); + if (proxy == NULL) { + g_prefix_error (error, "failed to connect to %s: ", iface); + return NULL; + } + val = g_dbus_proxy_get_cached_property (proxy, prop_name); + if (val == NULL) { + g_prefix_error (error, "property %s not found in %s: ", + prop_name, obj_path); + return NULL; + } + + return g_steal_pointer (&val); +} + +/* + * Returns the value of the string property of an object specified by + * its path, or NULL if the property wasn't found. + * + * The returned string must be freed using g_free(). + */ +static gchar * +fu_bluez_device_get_ble_string_property (const gchar *obj_path, + const gchar *iface, + const gchar *prop_name, + GError **error) +{ + g_autoptr(GVariant) val = NULL; + val = fu_bluez_device_get_ble_property (obj_path, iface, prop_name, error); + if (val == NULL) + return NULL; + return g_variant_dup_string (val, NULL); +} + +/* + * Populates the {uuid : object_path} entries of a device for all its + * characteristics. + * + * TODO: Extend to services and descriptors too? + */ +static void +fu_bluez_device_add_device_uuids (FuBluezDevice *self) +{ + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + g_autolist(GDBusObject) obj_list = NULL; + + obj_list = g_dbus_object_manager_get_objects (priv->object_manager); + for (GList *l = obj_list; l != NULL; l = l->next) { + GDBusObject *obj = G_DBUS_OBJECT (l->data); + g_autoptr(GDBusInterface) iface = NULL; + g_autoptr(GError) error_local = NULL; + g_autofree gchar *obj_uuid = NULL; + const gchar *obj_path = g_dbus_object_get_object_path (obj); + + /* not us */ + if (!g_str_has_prefix (g_dbus_object_get_object_path (obj), + g_dbus_proxy_get_object_path (priv->proxy))) + continue; + + /* + * TODO: Currently restricted to getting UUIDs for + * characteristics only, as the only use case we're + * going to need for now is reading/writing + * characteristics. + */ + iface = g_dbus_object_get_interface (obj, "org.bluez.GattCharacteristic1"); + if (iface == NULL) + continue; + obj_uuid = fu_bluez_device_get_ble_string_property (obj_path, + "org.bluez.GattCharacteristic1", + "UUID", + &error_local); + if (obj_uuid == NULL) { + g_debug ("failed to get property: %s", error_local->message); + continue; + } + fu_bluez_device_add_uuid_path (self, obj_uuid, obj_path); + } +} + +static gboolean +fu_bluez_device_setup (FuDevice *device, GError **error) +{ + FuBluezDevice *self = FU_BLUEZ_DEVICE (device); + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + + fu_bluez_device_add_device_uuids (self); + + return TRUE; +} + +static gboolean +fu_bluez_device_probe (FuDevice *device, GError **error) +{ + FuBluezDevice *self = FU_BLUEZ_DEVICE (device); + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + g_autoptr(GVariant) val_adapter = NULL; + g_autoptr(GVariant) val_address = NULL; + g_autoptr(GVariant) val_icon = NULL; + g_autoptr(GVariant) val_modalias = NULL; + g_autoptr(GVariant) val_name = NULL; + + val_address = g_dbus_proxy_get_cached_property (priv->proxy, "Address"); + if (val_address == NULL) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "No required BLE address"); + return FALSE; + } + fu_device_set_logical_id (device, g_variant_get_string (val_address, NULL)); + val_adapter = g_dbus_proxy_get_cached_property (priv->proxy, "Adapter"); + if (val_adapter != NULL) + fu_device_set_physical_id (device, g_variant_get_string (val_adapter, NULL)); + val_name = g_dbus_proxy_get_cached_property (priv->proxy, "Name"); + if (val_name != NULL) + fu_device_set_name (device, g_variant_get_string (val_name, NULL)); + val_icon = g_dbus_proxy_get_cached_property (priv->proxy, "Icon"); + if (val_icon != NULL) + fu_device_add_icon (device, g_variant_get_string (val_name, NULL)); + val_modalias = g_dbus_proxy_get_cached_property (priv->proxy, "Modalias"); + if (val_modalias != NULL) + fu_bluez_device_set_modalias (self, g_variant_get_string (val_modalias, NULL)); + + /* success */ + return TRUE; +} + +/** + * fu_bluez_device_read: + * @self: A #FuBluezDevice + * @uuid: The UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` + * @error: A #GError, or %NULL + * + * Reads from a UUID on the device. + * + * Returns: (transfer full): data, or %NULL for error + * + * Since: 1.5.7 + **/ +GByteArray * +fu_bluez_device_read (FuBluezDevice *self, const gchar *uuid, GError **error) +{ + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + guint8 byte; + const gchar *path; + g_autoptr(GByteArray) buf = g_byte_array_new (); + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GVariantBuilder) builder = NULL; + g_autoptr(GVariantIter) iter = NULL; + g_autoptr(GVariant) val = NULL; + + path = g_hash_table_lookup (priv->uuid_paths, uuid); + if (path == NULL) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "UUID %s not supported", uuid); + return NULL; + } + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.bluez", + path, + "org.bluez.GattCharacteristic1", + NULL, error); + if (proxy == NULL) { + g_prefix_error (error, "Failed to connect GattCharacteristic1: "); + return NULL; + } + g_dbus_proxy_set_default_timeout (proxy, DEFAULT_PROXY_TIMEOUT); + + /* + * Call the "ReadValue" method through the proxy synchronously. + * + * The method takes one argument: an array of dicts of + * {string:value} specifing the options (here the option is + * "offset":0. + * The result is a byte array. + */ + builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (builder, "{sv}", "offset", + g_variant_new("q", 0)); + + val = g_dbus_proxy_call_sync (proxy, + "ReadValue", + g_variant_new ("(a{sv})", builder), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, error); + if (val == NULL) { + g_prefix_error (error, "Failed to read GattCharacteristic1: "); + return NULL; + } + g_variant_get(val, "(ay)", &iter); + while (g_variant_iter_loop (iter, "y", &byte)) + g_byte_array_append (buf, &byte, 1); + + /* success */ + return g_steal_pointer (&buf); +} + +/** + * fu_bluez_device_read_string: + * @self: A #FuBluezDevice + * @uuid: The UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` + * @error: A #GError, or %NULL + * + * Reads a string from a UUID on the device. + * + * Returns: (transfer full): data, or %NULL for error + * + * Since: 1.5.7 + **/ +gchar * +fu_bluez_device_read_string (FuBluezDevice *self, const gchar *uuid, GError **error) +{ + GByteArray *buf = fu_bluez_device_read (self, uuid, error); + if (buf == NULL) + return NULL; + return (gchar *) g_byte_array_free (buf, FALSE); +} + +/** + * fu_bluez_device_write: + * @self: A #FuBluezDevice + * @uuid: The UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` + * @error: A #GError, or %NULL + * + * Writes to a UUID on the device. + * + * Returns: (transfer full): data, or %NULL for error + * + * Since: 1.5.7 + **/ +gboolean +fu_bluez_device_write (FuBluezDevice *self, + const gchar *uuid, + GByteArray *buf, + GError **error) +{ + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + const gchar *path; + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GVariantBuilder) opt_builder = NULL; + g_autoptr(GVariantBuilder) val_builder = NULL; + g_autoptr(GVariant) opt_variant = NULL; + g_autoptr(GVariant) ret = NULL; + g_autoptr(GVariant) val_variant = NULL; + + path = g_hash_table_lookup (priv->uuid_paths, uuid); + if (path == NULL) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "UUID %s not supported", uuid); + return FALSE; + } + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.bluez", + path, + "org.bluez.GattCharacteristic1", + NULL, error); + if (proxy == NULL) { + g_prefix_error (error, "Failed to connect GattCharacteristic1: "); + return FALSE; + } + + /* build the value variant */ + val_builder = g_variant_builder_new (G_VARIANT_TYPE ("ay")); + for (gsize i = 0; i < buf->len; i++) + g_variant_builder_add (val_builder, "y", buf->data[i]); + val_variant = g_variant_new ("ay", val_builder); + + /* build the options variant (offset = 0) */ + opt_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add (opt_builder, "{sv}", "offset", + g_variant_new_uint16 (0)); + opt_variant = g_variant_new("a{sv}", opt_builder); + + ret = g_dbus_proxy_call_sync (proxy, + "WriteValue", + g_variant_new ("(@ay@a{sv})", + val_variant, + opt_variant), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, error); + if (ret == NULL) { + g_prefix_error (error, "Failed to write GattCharacteristic1: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static void +fu_bluez_device_incorporate (FuDevice *self, FuDevice *donor) +{ + FuBluezDevice *uself = FU_BLUEZ_DEVICE (self); + FuBluezDevice *udonor = FU_BLUEZ_DEVICE (donor); + FuBluezDevicePrivate *priv = GET_PRIVATE (uself); + FuBluezDevicePrivate *privdonor = GET_PRIVATE (udonor); + + if (g_hash_table_size (priv->uuid_paths) == 0) { + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, privdonor->uuid_paths); + while (g_hash_table_iter_next (&iter, &key, &value)) { + fu_bluez_device_add_uuid_path (uself, + (const gchar *) key, + (const gchar *) value); + } + } + if (priv->object_manager == NULL) + priv->object_manager = g_object_ref (privdonor->object_manager); + if (priv->proxy == NULL) + priv->proxy = g_object_ref (privdonor->proxy); +} + +static void +fu_bluez_device_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + FuBluezDevice *self = FU_BLUEZ_DEVICE (object); + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + switch (prop_id) { + case PROP_OBJECT_MANAGER: + g_value_set_object (value, priv->object_manager); + break; + case PROP_PROXY: + g_value_set_object (value, priv->proxy); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +fu_bluez_device_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + FuBluezDevice *self = FU_BLUEZ_DEVICE (object); + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + switch (prop_id) { + case PROP_OBJECT_MANAGER: + priv->object_manager = g_value_dup_object (value); + break; + case PROP_PROXY: + priv->proxy = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +fu_bluez_device_finalize (GObject *object) +{ + FuBluezDevice *self = FU_BLUEZ_DEVICE (object); + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + + g_hash_table_unref (priv->uuid_paths); + g_object_unref (priv->proxy); + g_object_unref (priv->object_manager); + G_OBJECT_CLASS (fu_bluez_device_parent_class)->finalize (object); +} + +static void +fu_bluez_device_init (FuBluezDevice *self) +{ + FuBluezDevicePrivate *priv = GET_PRIVATE (self); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NO_GUID_MATCHING); + priv->uuid_paths = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); +} + +static void +fu_bluez_device_class_init (FuBluezDeviceClass *klass) +{ + FuDeviceClass *device_class = FU_DEVICE_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + object_class->get_property = fu_bluez_device_get_property; + object_class->set_property = fu_bluez_device_set_property; + object_class->finalize = fu_bluez_device_finalize; + device_class->probe = fu_bluez_device_probe; + device_class->setup = fu_bluez_device_setup; + device_class->to_string = fu_bluez_device_to_string; + device_class->incorporate = fu_bluez_device_incorporate; + + pspec = g_param_spec_object ("object-manager", NULL, NULL, + G_TYPE_DBUS_OBJECT_MANAGER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_NAME); + g_object_class_install_property (object_class, PROP_OBJECT_MANAGER, pspec); + + pspec = g_param_spec_object ("proxy", NULL, NULL, + G_TYPE_DBUS_PROXY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_NAME); + g_object_class_install_property (object_class, PROP_PROXY, pspec); +} diff --git a/libfwupdplugin/fu-bluez-device.h b/libfwupdplugin/fu-bluez-device.h new file mode 100644 index 000000000..938c83596 --- /dev/null +++ b/libfwupdplugin/fu-bluez-device.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-device.h" + +#define FU_TYPE_BLUEZ_DEVICE (fu_bluez_device_get_type ()) +G_DECLARE_DERIVABLE_TYPE (FuBluezDevice, fu_bluez_device, FU, BLUEZ_DEVICE, FuDevice) + +struct _FuBluezDeviceClass +{ + FuDeviceClass parent_class; + gpointer __reserved[31]; +}; + +GByteArray *fu_bluez_device_read (FuBluezDevice *self, + const gchar *uuid, + GError **error); +gchar *fu_bluez_device_read_string (FuBluezDevice *self, + const gchar *uuid, + GError **error); +gboolean fu_bluez_device_write (FuBluezDevice *self, + const gchar *uuid, + GByteArray *buf, + GError **error); diff --git a/libfwupdplugin/fu-plugin.h b/libfwupdplugin/fu-plugin.h index a357e62a9..86b3c36b0 100644 --- a/libfwupdplugin/fu-plugin.h +++ b/libfwupdplugin/fu-plugin.h @@ -11,6 +11,7 @@ #include #endif +#include "fu-bluez-device.h" #include "fu-common.h" #include "fu-common-guid.h" #include "fu-common-version.h" diff --git a/libfwupdplugin/fwupdplugin.h b/libfwupdplugin/fwupdplugin.h index 5bc5e3e46..0f0685466 100644 --- a/libfwupdplugin/fwupdplugin.h +++ b/libfwupdplugin/fwupdplugin.h @@ -14,6 +14,7 @@ #define __FWUPDPLUGIN_H_INSIDE__ #include +#include #include #include #include diff --git a/libfwupdplugin/fwupdplugin.map b/libfwupdplugin/fwupdplugin.map index b8dd39dd9..550ba01c3 100644 --- a/libfwupdplugin/fwupdplugin.map +++ b/libfwupdplugin/fwupdplugin.map @@ -762,6 +762,10 @@ LIBFWUPDPLUGIN_1.5.6 { LIBFWUPDPLUGIN_1.5.7 { global: + fu_bluez_device_get_type; + fu_bluez_device_read; + fu_bluez_device_read_string; + fu_bluez_device_write; fu_firmware_get_version_raw; fu_firmware_set_version_raw; local: *; diff --git a/libfwupdplugin/meson.build b/libfwupdplugin/meson.build index 15461719c..2225725bc 100644 --- a/libfwupdplugin/meson.build +++ b/libfwupdplugin/meson.build @@ -1,5 +1,6 @@ fwupdplugin_src = [ 'fu-archive.c', + 'fu-bluez-device.c', 'fu-cabinet.c', 'fu-chunk.c', # fuzzing 'fu-common.c', # fuzzing @@ -33,6 +34,7 @@ fwupdplugin_src = [ fwupdplugin_headers = [ 'fu-archive.h', + 'fu-bluez-device.h', 'fu-cabinet.h', 'fu-chunk.h', 'fu-common.h', diff --git a/meson.build b/meson.build index b43605dcf..bbf811398 100644 --- a/meson.build +++ b/meson.build @@ -206,6 +206,9 @@ if get_option('gudev') else gudev = dependency('', required : false) endif +if get_option('bluez') + conf.set('HAVE_BLUEZ', '1') +endif libxmlb = dependency('xmlb', version : '>= 0.1.13', fallback : ['libxmlb', 'libxmlb_dep']) if get_option('gusb') gusb = dependency('gusb', version : '>= 0.3.5', fallback : ['gusb', 'gusb_dep']) diff --git a/meson_options.txt b/meson_options.txt index e4a013a72..05eb06191 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -9,6 +9,7 @@ option('man', type : 'boolean', value : true, description : 'enable man pages') option('libarchive', type : 'boolean', value : true, description : 'enable libarchive support') option('gudev', type : 'boolean', value : true, description : 'enable GUdev support') option('gusb', type : 'boolean', value : true, description : 'enable GUsb support') +option('bluez', type : 'boolean', value : true, description : 'enable BlueZ support') option('polkit', type: 'boolean', value : true, description : 'enable PolKit support in daemon') option('gnutls', type: 'boolean', value : true, description : 'enable GnuTLS support') option('plugin_altos', type : 'boolean', value : true, description : 'enable altos support') diff --git a/src/fu-bluez-backend.c b/src/fu-bluez-backend.c new file mode 100644 index 000000000..3ff06afbc --- /dev/null +++ b/src/fu-bluez-backend.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "FuBackend" + +#include "config.h" + +#include "fu-bluez-device.h" +#include "fu-bluez-backend.h" + +struct _FuBluezBackend { + FuBackend parent_instance; + GDBusObjectManager *object_manager; + GHashTable *devices; /* object_path : * FuDevice */ +}; + +G_DEFINE_TYPE (FuBluezBackend, fu_bluez_backend, FU_TYPE_BACKEND) + +static void +fu_bluez_backend_object_properties_changed (FuBluezBackend *self, GDBusProxy *proxy) +{ + const gchar *path = g_dbus_proxy_get_object_path (proxy); + gboolean suitable; + FuDevice *device_tmp; + g_autoptr(FuBluezDevice) dev = NULL; + g_autoptr(GVariant) val_connected = NULL; + g_autoptr(GVariant) val_paired = NULL; + + /* device is suitable */ + val_connected = g_dbus_proxy_get_cached_property (proxy, "Connected"); + if (val_connected == NULL) + return; + val_paired = g_dbus_proxy_get_cached_property (proxy, "Paired"); + if (val_paired == NULL) + return; + suitable = g_variant_get_boolean (val_connected) && + g_variant_get_boolean (val_paired); + + /* is this an existing device we've previously added */ + device_tmp = g_hash_table_lookup (self->devices, path); + if (device_tmp != NULL) { + if (suitable) { + g_debug ("ignoring suitable changed Bluez device: %s", path); + return; + } + g_debug ("removing unsuitable Bluez device: %s", path); + fu_backend_device_removed (FU_BACKEND (self), device_tmp); + g_hash_table_remove (self->devices, path); + return; + } + + /* not paired and connected */ + if (!suitable) + return; + + /* create device */ + dev = g_object_new (FU_TYPE_BLUEZ_DEVICE, + "object-manager", self->object_manager, + "proxy", proxy, + NULL); + g_debug ("adding suitable Bluez device: %s", path); + g_hash_table_insert (self->devices, g_strdup (path), g_object_ref (dev)); + fu_backend_device_added (FU_BACKEND (self), FU_DEVICE (dev)); +} + +static void +fu_bluez_backend_object_properties_changed_cb (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + FuBluezBackend *self) +{ + fu_bluez_backend_object_properties_changed (self, proxy); +} + +static void +fu_bluez_backend_object_added (FuBluezBackend *self, GDBusObject *object) +{ + g_autoptr(GDBusInterface) iface = NULL; + g_auto(GStrv) names = NULL; + + iface = g_dbus_object_get_interface (object, "org.bluez.Device1"); + if (iface == NULL) + return; + g_signal_connect (iface, "g-properties-changed", + G_CALLBACK (fu_bluez_backend_object_properties_changed_cb), + self); + fu_bluez_backend_object_properties_changed (self, G_DBUS_PROXY (iface)); +} + +static void +fu_bluez_backend_object_added_cb (GDBusObjectManager *manager, + GDBusObject *object, + FuBluezBackend *self) +{ + fu_bluez_backend_object_added (self, object); +} + +static void +fu_bluez_backend_object_removed_cb (GDBusObjectManager *manager, + GDBusObject *object, + FuBluezBackend *self) +{ + const gchar *path = g_dbus_object_get_object_path (object); + FuDevice *device_tmp; + + device_tmp = g_hash_table_lookup (self->devices, path); + if (device_tmp == NULL) + return; + g_debug ("removing Bluez device: %s", path); + fu_backend_device_removed (FU_BACKEND (self), device_tmp); + g_hash_table_remove (self->devices, path); +} + +static gboolean +fu_bluez_backend_setup (FuBackend *backend, GError **error) +{ + FuBluezBackend *self = FU_BLUEZ_BACKEND (backend); + + self->object_manager = g_dbus_object_manager_client_new_for_bus_sync ( + G_BUS_TYPE_SYSTEM, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + "org.bluez", + "/", + NULL, NULL, NULL, + NULL, error); + if (self->object_manager == NULL) + return FALSE; + g_signal_connect (self->object_manager, "object-added", + G_CALLBACK (fu_bluez_backend_object_added_cb), self); + g_signal_connect (self->object_manager, "object-removed", + G_CALLBACK (fu_bluez_backend_object_removed_cb), self); + return TRUE; +} + +static gboolean +fu_bluez_backend_coldplug (FuBackend *backend, GError **error) +{ + FuBluezBackend *self = FU_BLUEZ_BACKEND (backend); + g_autolist(GDBusObject) objects = NULL; + + /* failed to set up */ + if (self->object_manager == NULL) + return TRUE; + objects = g_dbus_object_manager_get_objects (self->object_manager); + for (GList *l = objects; l != NULL; l = l->next) { + GDBusObject *object = G_DBUS_OBJECT (l->data); + fu_bluez_backend_object_added (self, object); + } + return TRUE; +} + +static void +fu_bluez_backend_finalize (GObject *object) +{ + FuBluezBackend *self = FU_BLUEZ_BACKEND (object); + if (self->object_manager != NULL) + g_object_unref (self->object_manager); + g_hash_table_unref (self->devices); + G_OBJECT_CLASS (fu_bluez_backend_parent_class)->finalize (object); +} + +static void +fu_bluez_backend_init (FuBluezBackend *self) +{ + self->devices = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + (GDestroyNotify) g_object_unref); +} + +static void +fu_bluez_backend_class_init (FuBluezBackendClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FuBackendClass *klass_backend = FU_BACKEND_CLASS (klass); + + object_class->finalize = fu_bluez_backend_finalize; + klass_backend->setup = fu_bluez_backend_setup; + klass_backend->coldplug = fu_bluez_backend_coldplug; + klass_backend->recoldplug = fu_bluez_backend_coldplug; +} + +FuBackend * +fu_bluez_backend_new (void) +{ + return FU_BACKEND (g_object_new (FU_TYPE_BLUEZ_BACKEND, "name", "bluez", NULL)); +} diff --git a/src/fu-bluez-backend.h b/src/fu-bluez-backend.h new file mode 100644 index 000000000..72c35ef07 --- /dev/null +++ b/src/fu-bluez-backend.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-backend.h" + +#define FU_TYPE_BLUEZ_BACKEND (fu_bluez_backend_get_type ()) +G_DECLARE_FINAL_TYPE (FuBluezBackend, fu_bluez_backend, FU, BLUEZ_BACKEND, FuBackend) + +FuBackend *fu_bluez_backend_new (void); diff --git a/src/fu-engine.c b/src/fu-engine.c index 4639671a7..439aa6032 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -59,6 +59,9 @@ #ifdef HAVE_GUSB #include "fu-usb-backend.h" #endif +#ifdef HAVE_BLUEZ +#include "fu-bluez-backend.h" +#endif #include "fu-dfu-firmware.h" #include "fu-dfuse-firmware.h" @@ -6591,6 +6594,9 @@ fu_engine_init (FuEngine *self) #ifdef HAVE_GUDEV g_ptr_array_add (self->backends, fu_udev_backend_new (self->udev_subsystems)); #endif +#ifdef HAVE_BLUEZ + g_ptr_array_add (self->backends, fu_bluez_backend_new ()); +#endif /* setup Jcat context */ self->jcat_context = jcat_context_new (); diff --git a/src/meson.build b/src/meson.build index ea3fd89ef..22d0d4c56 100644 --- a/src/meson.build +++ b/src/meson.build @@ -61,6 +61,9 @@ endif if get_option('gusb') daemon_src += 'fu-usb-backend.c' endif +if get_option('bluez') + daemon_src += 'fu-bluez-backend.c' +endif if build_daemon fwupdmgr = executable(