diff options
Diffstat (limited to 'panels/user-accounts/cc-fingerprint-manager.c')
-rw-r--r-- | panels/user-accounts/cc-fingerprint-manager.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/panels/user-accounts/cc-fingerprint-manager.c b/panels/user-accounts/cc-fingerprint-manager.c new file mode 100644 index 0000000..07a50e7 --- /dev/null +++ b/panels/user-accounts/cc-fingerprint-manager.c @@ -0,0 +1,597 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authors: Marco Trevisan <marco.trevisan@canonical.com> + */ + +#include "cc-fingerprint-manager.h" + +#include "cc-fprintd-generated.h" +#include "cc-user-accounts-enum-types.h" + +#define CC_FPRINTD_NAME "net.reactivated.Fprint" +#define CC_FPRINTD_MANAGER_PATH "/net/reactivated/Fprint/Manager" + +struct _CcFingerprintManager +{ + GObject parent_instance; +}; + +typedef struct +{ + ActUser *user; + GTask *current_task; + CcFingerprintState state; + GList *cached_devices; +} CcFingerprintManagerPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (CcFingerprintManager, cc_fingerprint_manager, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_USER, + PROP_STATE, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void cleanup_cached_devices (CcFingerprintManager *self); + +CcFingerprintManager * +cc_fingerprint_manager_new (ActUser *user) +{ + return g_object_new (CC_TYPE_FINGERPRINT_MANAGER, "user", user, NULL); +} + +static void +cc_fingerprint_manager_dispose (GObject *object) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + if (priv->current_task) + { + g_cancellable_cancel (g_task_get_cancellable (priv->current_task)); + priv->current_task = NULL; + } + + g_clear_object (&priv->user); + cleanup_cached_devices (self); + + G_OBJECT_CLASS (cc_fingerprint_manager_parent_class)->dispose (object); +} + +static void +cc_fingerprint_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + switch (prop_id) + { + case PROP_STATE: + g_value_set_enum (value, priv->state); + break; + + case PROP_USER: + g_value_set_object (value, priv->user); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_fingerprint_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + switch (prop_id) + { + case PROP_USER: + g_set_object (&priv->user, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_fingerprint_manager_constructed (GObject *object) +{ + cc_fingerprint_manager_update_state (CC_FINGERPRINT_MANAGER (object), NULL, NULL); +} + +static void +cc_fingerprint_manager_class_init (CcFingerprintManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = cc_fingerprint_manager_constructed; + object_class->dispose = cc_fingerprint_manager_dispose; + object_class->get_property = cc_fingerprint_manager_get_property; + object_class->set_property = cc_fingerprint_manager_set_property; + + properties[PROP_USER] = + g_param_spec_object ("user", + "User", + "The user account we manage the fingerprint for", + ACT_TYPE_USER, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + properties[PROP_STATE] = + g_param_spec_enum ("state", + "State", + "The state of the fingerprint for the user", + CC_TYPE_FINGERPRINT_STATE, CC_FINGERPRINT_STATE_NONE, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +cc_fingerprint_manager_init (CcFingerprintManager *self) +{ +} + +typedef struct +{ + guint waiting_devices; + GList *devices; +} DeviceListData; + +static void +object_list_destroy_notify (gpointer data) +{ + GList *list = data; + g_list_free_full (list, g_object_unref); +} + +static void +on_device_owner_changed (CcFingerprintManager *self, + GParamSpec *spec, + CcFprintdDevice *device) +{ + g_autofree char *name_owner = NULL; + + name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (device)); + + if (!name_owner) + { + g_debug ("Fprintd daemon disappeared, cleaning cache..."); + cleanup_cached_devices (self); + } +} + +static void +cleanup_cached_devices (CcFingerprintManager *self) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + CcFprintdDevice *target_device; + + if (!priv->cached_devices) + return; + + g_return_if_fail (CC_FPRINTD_IS_DEVICE (priv->cached_devices->data)); + + target_device = CC_FPRINTD_DEVICE (priv->cached_devices->data); + + g_signal_handlers_disconnect_by_func (target_device, on_device_owner_changed, self); + g_list_free_full (g_steal_pointer (&priv->cached_devices), g_object_unref); +} + +static void +cache_devices (CcFingerprintManager *self, + GList *devices) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + CcFprintdDevice *target_device; + + g_return_if_fail (devices && CC_FPRINTD_IS_DEVICE (devices->data)); + + cleanup_cached_devices (self); + priv->cached_devices = g_list_copy_deep (devices, (GCopyFunc) g_object_ref, NULL); + + /* We can monitor just the first device name, as the owner is just the same */ + target_device = CC_FPRINTD_DEVICE (priv->cached_devices->data); + + g_signal_connect_object (target_device, "notify::g-name-owner", + G_CALLBACK (on_device_owner_changed), self, + G_CONNECT_SWAPPED); +} + +static void +on_device_proxy (GObject *object, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(CcFprintdDevice) fprintd_device = NULL; + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + CcFingerprintManager *self = g_task_get_source_object (task); + DeviceListData *list_data = g_task_get_task_data (task); + + fprintd_device = cc_fprintd_device_proxy_new_for_bus_finish (res, &error); + list_data->waiting_devices--; + + if (error) + { + if (list_data->waiting_devices == 0) + g_task_return_error (task, g_steal_pointer (&error)); + else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Impossible to ge the device proxy: %s", error->message); + + return; + } + + g_debug ("Got fingerprint device %s", cc_fprintd_device_get_name (fprintd_device)); + + list_data->devices = g_list_append (list_data->devices, g_steal_pointer (&fprintd_device)); + + if (list_data->waiting_devices == 0) + { + cache_devices (self, list_data->devices); + g_task_return_pointer (task, g_steal_pointer (&list_data->devices), object_list_destroy_notify); + } +} + +static void +on_devices_list (GObject *object, GAsyncResult *res, gpointer user_data) +{ + CcFprintdManager *fprintd_manager = CC_FPRINTD_MANAGER (object); + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + g_auto(GStrv) devices_list = NULL; + DeviceListData *list_data; + guint i; + + cc_fprintd_manager_call_get_devices_finish (fprintd_manager, &devices_list, res, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (!devices_list || !devices_list[0]) + { + g_task_return_pointer (task, NULL, NULL); + return; + } + + list_data = g_new0 (DeviceListData, 1); + g_task_set_task_data (task, list_data, g_free); + + g_debug ("Fprintd replied with %u device(s)", g_strv_length (devices_list)); + + for (i = 0; devices_list[i] != NULL; ++i) + { + const char *device_path = devices_list[i]; + + list_data->waiting_devices++; + + cc_fprintd_device_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + CC_FPRINTD_NAME, + device_path, + g_task_get_cancellable (task), + on_device_proxy, + g_object_ref (task)); + } +} + +static void +on_manager_proxy (GObject *object, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(CcFprintdManager) fprintd_manager = NULL; + g_autoptr(GError) error = NULL; + + fprintd_manager = cc_fprintd_manager_proxy_new_for_bus_finish (res, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + g_debug ("Fprintd manager connected"); + + cc_fprintd_manager_call_get_devices (fprintd_manager, + g_task_get_cancellable (task), + on_devices_list, + g_object_ref (task)); +} + +static void +fprintd_manager_connect (CcFingerprintManager *self, + GAsyncReadyCallback callback, + GTask *task) +{ + g_assert (G_IS_TASK (task)); + + cc_fprintd_manager_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + CC_FPRINTD_NAME, CC_FPRINTD_MANAGER_PATH, + g_task_get_cancellable (task), + callback, + task); +} + +void +cc_fingerprint_manager_get_devices (CcFingerprintManager *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autoptr(GTask) task = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, cc_fingerprint_manager_get_devices); + + if (priv->cached_devices) + { + GList *devices; + + devices = g_list_copy_deep (priv->cached_devices, (GCopyFunc) g_object_ref, NULL); + g_task_return_pointer (task, devices, object_list_destroy_notify); + return; + } + + fprintd_manager_connect (self, on_manager_proxy, g_steal_pointer (&task)); +} + +/** + * cc_fingerprint_manager_get_devices_finish: + * @self: The #CcFingerprintManager + * @result: A #GAsyncResult + * @error: Return location for errors, or %NULL to ignore + * + * Finish an asynchronous operation to list all devices. + * + * Returns: (element-type CcFprintdDevice) (transfer full): List of prints or %NULL on error + */ +GList * +cc_fingerprint_manager_get_devices_finish (CcFingerprintManager *self, + GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (res, self), NULL); + + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +set_state (CcFingerprintManager *self, + CcFingerprintState state) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + if (priv->state == state) + return; + + g_debug ("Fingerprint manager state changed to %d", state); + + priv->state = state; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATE]); +} + +typedef struct +{ + guint waiting_devices; + CcFingerprintStateUpdated callback; + gpointer user_data; +} UpdateStateData; + +static void +update_state_callback (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autoptr(GError) error = NULL; + CcFingerprintState state; + UpdateStateData *data; + GTask *task; + + g_return_if_fail (g_task_is_valid (res, self)); + + task = G_TASK (res); + g_assert (g_steal_pointer (&priv->current_task) == task); + + state = g_task_propagate_int (task, &error); + data = g_task_get_task_data (task); + + if (error) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_warning ("Impossible to update fingerprint manager state: %s", + error->message); + + state = CC_FINGERPRINT_STATE_NONE; + } + + set_state (self, state); + + if (data->callback) + data->callback (self, state, data->user_data, error); +} + +static void +on_device_list_enrolled (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + g_auto(GStrv) enrolled_fingers = NULL; + UpdateStateData *data = g_task_get_task_data (task); + guint num_enrolled_fingers; + + cc_fprintd_device_call_list_enrolled_fingers_finish (fprintd_device, + &enrolled_fingers, + res, &error); + + if (data->waiting_devices == 0) + return; + + data->waiting_devices--; + + if (error) + { + g_autofree char *dbus_error = g_dbus_error_get_remote_error (error); + + if (!g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.NoEnrolledPrints")) + { + if (data->waiting_devices == 0) + g_task_return_error (task, g_steal_pointer (&error)); + else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Impossible to list enrolled fingers: %s", error->message); + + return; + } + } + + num_enrolled_fingers = enrolled_fingers ? g_strv_length (enrolled_fingers) : 0; + + g_debug ("Device %s has %u enrolled fingers", + cc_fprintd_device_get_name (fprintd_device), + num_enrolled_fingers); + + if (num_enrolled_fingers > 0) + { + data->waiting_devices = 0; + g_task_return_int (task, CC_FINGERPRINT_STATE_ENABLED); + } + else if (data->waiting_devices == 0) + { + g_task_return_int (task, CC_FINGERPRINT_STATE_DISABLED); + } +} + +static void +on_manager_devices_list (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autolist(CcFprintdDevice) fprintd_devices = NULL; + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + UpdateStateData *data = g_task_get_task_data (task); + const char *user_name; + GList *l; + + fprintd_devices = cc_fingerprint_manager_get_devices_finish (self, res, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (fprintd_devices == NULL) + { + g_debug ("No fingerprint devices found"); + g_task_return_int (task, CC_FINGERPRINT_STATE_NONE); + return; + } + + user_name = act_user_get_user_name (priv->user); + + for (l = fprintd_devices; l; l = l->next) + { + CcFprintdDevice *device = l->data; + + g_debug ("Connected to device %s, looking for enrolled fingers", + cc_fprintd_device_get_name (device)); + + data->waiting_devices++; + cc_fprintd_device_call_list_enrolled_fingers (device, user_name, + g_task_get_cancellable (task), + on_device_list_enrolled, + g_object_ref (task)); + } +} + +void +cc_fingerprint_manager_update_state (CcFingerprintManager *self, + CcFingerprintStateUpdated callback, + gpointer user_data) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autoptr(GCancellable) cancellable = NULL; + UpdateStateData *data; + + g_return_if_fail (priv->current_task == NULL); + + if (act_user_get_uid (priv->user) != getuid () || + !act_user_is_local_account (priv->user)) + { + set_state (self, CC_FINGERPRINT_STATE_NONE); + return; + } + + cancellable = g_cancellable_new (); + data = g_new0 (UpdateStateData, 1); + data->callback = callback; + data->user_data = user_data; + + priv->current_task = g_task_new (self, cancellable, update_state_callback, NULL); + g_task_set_source_tag (priv->current_task, cc_fingerprint_manager_update_state); + g_task_set_task_data (priv->current_task, data, g_free); + + set_state (self, CC_FINGERPRINT_STATE_UPDATING); + + cc_fingerprint_manager_get_devices (self, cancellable, on_manager_devices_list, + priv->current_task); +} + +CcFingerprintState +cc_fingerprint_manager_get_state (CcFingerprintManager *self) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + g_return_val_if_fail (CC_IS_FINGERPRINT_MANAGER (self), CC_FINGERPRINT_STATE_NONE); + + return priv->state; +} + +ActUser * +cc_fingerprint_manager_get_user (CcFingerprintManager *self) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + g_return_val_if_fail (CC_IS_FINGERPRINT_MANAGER (self), NULL); + + return priv->user; +} |