blob: 3a0448dbbff3e352333080d6e66432493ee6158d [file] [log] [blame]
/*
* Copyright 2021 Ricardo CaƱuelo <ricardo.canuelo@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#define G_LOG_DOMAIN "FuBackend"
#include "config.h"
#include "fu-bluez-backend.h"
#include "fu-bluez-device.h"
struct _FuBluezBackend {
FuBackend parent_instance;
GDBusObjectManager *object_manager;
};
G_DEFINE_TYPE(FuBluezBackend, fu_bluez_backend, FU_TYPE_BACKEND)
#define FU_BLUEZ_BACKEND_TIMEOUT 1500 /* ms */
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;
g_autoptr(GVariant) val_services_resolved = 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;
val_services_resolved = g_dbus_proxy_get_cached_property(proxy, "ServicesResolved");
if (val_services_resolved == NULL)
return;
suitable = g_variant_get_boolean(val_connected) && g_variant_get_boolean(val_paired) &&
g_variant_get_boolean(val_services_resolved);
/* is this an existing device we've previously added */
device_tmp = fu_backend_lookup_by_id(FU_BACKEND(self), 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);
return;
}
/* not paired and connected */
if (!suitable) {
g_debug("%s connected=%i, paired=%i, services resolved=%i, ignoring",
path,
g_variant_get_boolean(val_connected),
g_variant_get_boolean(val_paired),
g_variant_get_boolean(val_services_resolved));
return;
}
/* create device */
dev = g_object_new(FU_TYPE_BLUEZ_DEVICE,
"backend-id",
path,
"object-manager",
self->object_manager,
"proxy",
proxy,
NULL);
g_info("adding suitable BlueZ device: %s", path);
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;
iface = g_dbus_object_get_interface(object, "org.bluez.Device1");
if (iface == NULL)
return;
g_signal_connect(G_DBUS_INTERFACE(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 = fu_backend_lookup_by_id(FU_BACKEND(self), path);
if (device_tmp == NULL)
return;
g_info("removing BlueZ device: %s", path);
fu_backend_device_removed(FU_BACKEND(self), device_tmp);
}
typedef struct {
GDBusObjectManager *object_manager;
GMainLoop *loop;
GError **error;
GCancellable *cancellable;
guint timeout_id;
} FuBluezBackendHelper;
static void
fu_bluez_backend_helper_free(FuBluezBackendHelper *helper)
{
if (helper->object_manager != NULL)
g_object_unref(helper->object_manager);
if (helper->timeout_id != 0)
g_source_remove(helper->timeout_id);
g_cancellable_cancel(helper->cancellable);
g_main_loop_unref(helper->loop);
g_free(helper);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuBluezBackendHelper, fu_bluez_backend_helper_free)
static void
fu_bluez_backend_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
{
FuBluezBackendHelper *helper = (FuBluezBackendHelper *)user_data;
helper->object_manager =
g_dbus_object_manager_client_new_for_bus_finish(res, helper->error);
g_main_loop_quit(helper->loop);
}
static gboolean
fu_bluez_backend_timeout_cb(gpointer user_data)
{
FuBluezBackendHelper *helper = (FuBluezBackendHelper *)user_data;
g_cancellable_cancel(helper->cancellable);
helper->timeout_id = 0;
return G_SOURCE_REMOVE;
}
static gboolean
fu_bluez_backend_setup(FuBackend *backend,
FuBackendSetupFlags flags,
FuProgress *progress,
GError **error)
{
FuBluezBackend *self = FU_BLUEZ_BACKEND(backend);
g_autoptr(FuBluezBackendHelper) helper = g_new0(FuBluezBackendHelper, 1);
/* in some circumstances the bluez daemon will just hang... do not wait
* forever and make fwupd startup also fail */
helper->error = error;
helper->loop = g_main_loop_new(NULL, FALSE);
helper->cancellable = g_cancellable_new();
helper->timeout_id =
g_timeout_add(FU_BLUEZ_BACKEND_TIMEOUT, fu_bluez_backend_timeout_cb, helper);
g_dbus_object_manager_client_new_for_bus(G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
"org.bluez",
"/",
NULL,
NULL,
NULL,
helper->cancellable,
fu_bluez_backend_connect_cb,
helper);
g_main_loop_run(helper->loop);
if (helper->object_manager == NULL)
return FALSE;
self->object_manager = g_steal_pointer(&helper->object_manager);
if (flags & FU_BACKEND_SETUP_FLAG_USE_HOTPLUG) {
g_signal_connect(G_DBUS_OBJECT_MANAGER(self->object_manager),
"object-added",
G_CALLBACK(fu_bluez_backend_object_added_cb),
self);
g_signal_connect(G_DBUS_OBJECT_MANAGER(self->object_manager),
"object-removed",
G_CALLBACK(fu_bluez_backend_object_removed_cb),
self);
}
return TRUE;
}
static gboolean
fu_bluez_backend_coldplug(FuBackend *backend, FuProgress *progress, 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_OBJECT_CLASS(fu_bluez_backend_parent_class)->finalize(object);
}
static void
fu_bluez_backend_init(FuBluezBackend *self)
{
}
static void
fu_bluez_backend_class_init(FuBluezBackendClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
FuBackendClass *backend_class = FU_BACKEND_CLASS(klass);
object_class->finalize = fu_bluez_backend_finalize;
backend_class->setup = fu_bluez_backend_setup;
backend_class->coldplug = fu_bluez_backend_coldplug;
}
FuBackend *
fu_bluez_backend_new(FuContext *ctx)
{
return FU_BACKEND(g_object_new(FU_TYPE_BLUEZ_BACKEND,
"name",
"bluez",
"context",
ctx,
"device-gtype",
FU_TYPE_BLUEZ_DEVICE,
NULL));
}