/* Copyright (C) 2018 Red Hat, Inc * * 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 . * * Authors: Christian J. Kellner * */ #include #include #include #include "bolt-device.h" #include "bolt-error.h" #include "bolt-time.h" #include "cc-thunderbolt-resources.h" #include "cc-bolt-device-dialog.h" #include "cc-bolt-device-entry.h" struct _CcBoltDeviceDialog { GtkDialog parent; BoltClient *client; BoltDevice *device; GCancellable *cancel; /* main ui */ GtkHeaderBar *header_bar; /* notifications */ GtkLabel *notify_label; GtkRevealer *notify_revealer; /* device details */ GtkLabel *name_label; GtkLabel *status_label; GtkLabel *uuid_label; GtkLabel *time_title; GtkLabel *time_label; /* parents */ GtkExpander *parents_expander; GtkLabel *parents_label; GtkListBox *parents_devices; /* actions */ GtkWidget *button_box; GtkSpinner *spinner; GtkButton *connect_button; GtkButton *forget_button; }; static void on_notify_button_clicked_cb (GtkButton *button, CcBoltDeviceDialog *panel); static void on_forget_button_clicked_cb (CcBoltDeviceDialog *dialog); static void on_connect_button_clicked_cb (CcBoltDeviceDialog *dialog); G_DEFINE_TYPE (CcBoltDeviceDialog, cc_bolt_device_dialog, GTK_TYPE_DIALOG); #define RESOURCE_UI "/org/gnome/control-center/thunderbolt/cc-bolt-device-dialog.ui" static const char * status_to_string_for_ui (BoltDevice *dev) { BoltStatus status; BoltAuthFlags aflags; gboolean nopcie; status = bolt_device_get_status (dev); aflags = bolt_device_get_authflags(dev); nopcie = bolt_flag_isset (aflags, BOLT_AUTH_NOPCIE); switch (status) { case BOLT_STATUS_DISCONNECTED: return C_("Thunderbolt Device Status", "Disconnected"); case BOLT_STATUS_CONNECTING: return C_("Thunderbolt Device Status", "Connecting"); case BOLT_STATUS_CONNECTED: return C_("Thunderbolt Device Status", "Connected"); case BOLT_STATUS_AUTH_ERROR: return C_("Thunderbolt Device Status", "Authorization Error"); case BOLT_STATUS_AUTHORIZING: return C_("Thunderbolt Device Status", "Authorizing"); case BOLT_STATUS_AUTHORIZED: case BOLT_STATUS_AUTHORIZED_NEWKEY: case BOLT_STATUS_AUTHORIZED_SECURE: case BOLT_STATUS_AUTHORIZED_DPONLY: if (nopcie) return C_("Thunderbolt Device Status", "Reduced Functionality"); else return C_("Thunderbolt Device Status", "Connected & Authorized"); case BOLT_STATUS_UNKNOWN: break; /* use default return value, i.e. Unknown */ } return C_("Thunderbolt Device Status", "Unknown"); } static void dialog_update_from_device (CcBoltDeviceDialog *dialog) { g_autofree char *generated = NULL; g_autofree char *timestr = NULL; const char *label; const char *uuid; const char *status_brief; BoltStatus status; gboolean stored; BoltDevice *dev; guint timestamp; if (gtk_widget_in_destruction (GTK_WIDGET (dialog))) return; dev = dialog->device; uuid = bolt_device_get_uid (dev); label = bolt_device_get_label (dev); stored = bolt_device_is_stored (dev); status = bolt_device_get_status (dev); if (label == NULL) { const char *name = bolt_device_get_name (dev); const char *vendor = bolt_device_get_vendor (dev); generated = g_strdup_printf ("%s %s", name, vendor); label = generated; } gtk_label_set_label (dialog->name_label, label); gtk_header_bar_set_title (dialog->header_bar, label); status_brief = status_to_string_for_ui (dev); gtk_label_set_label (dialog->status_label, status_brief); gtk_widget_set_visible (GTK_WIDGET (dialog->forget_button), stored); /* while we are having an ongoing operation we are setting the buttons * to be in-sensitive. In that case, if the button was visible * before it will be hidden when the operation is finished by the * dialog_operation_done() function */ if (gtk_widget_is_sensitive (GTK_WIDGET (dialog->connect_button))) gtk_widget_set_visible (GTK_WIDGET (dialog->connect_button), status == BOLT_STATUS_CONNECTED); gtk_label_set_label (dialog->uuid_label, uuid); if (bolt_status_is_authorized (status)) { /* Translators: The time point the device was authorized. */ gtk_label_set_label (dialog->time_title, _("Authorized at:")); timestamp = bolt_device_get_authtime (dev); } else if (bolt_status_is_connected (status)) { /* Translators: The time point the device was connected. */ gtk_label_set_label (dialog->time_title, _("Connected at:")); timestamp = bolt_device_get_conntime (dev); } else { /* Translators: The time point the device was enrolled, * i.e. authorized and stored in the device database. */ gtk_label_set_label (dialog->time_title, _("Enrolled at:")); timestamp = bolt_device_get_storetime (dev); } timestr = bolt_epoch_format (timestamp, "%c"); gtk_label_set_label (dialog->time_label, timestr); } static void on_device_notify_cb (GObject *gobject, GParamSpec *pspec, gpointer user_data) { CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data); dialog_update_from_device (dialog); } static void dialog_operation_start (CcBoltDeviceDialog *dialog) { gtk_widget_set_sensitive (GTK_WIDGET (dialog->connect_button), FALSE); gtk_widget_set_sensitive (GTK_WIDGET (dialog->forget_button), FALSE); gtk_spinner_start (dialog->spinner); } static void dialog_operation_done (CcBoltDeviceDialog *dialog, GtkWidget *sender, GError *error) { GtkWidget *cb = GTK_WIDGET (dialog->connect_button); GtkWidget *fb = GTK_WIDGET (dialog->forget_button); /* don' do anything if we are being destroyed */ if (gtk_widget_in_destruction (GTK_WIDGET (dialog))) return; /* also don't do anything if the op was canceled */ if (error != NULL && bolt_err_cancelled (error)) return; gtk_spinner_stop (dialog->spinner); if (error != NULL) { gtk_label_set_label (dialog->notify_label, error->message); gtk_revealer_set_reveal_child (dialog->notify_revealer, TRUE); /* set the *other* button to sensitive */ gtk_widget_set_sensitive (cb, cb != sender); gtk_widget_set_sensitive (fb, fb != sender); } else { gtk_widget_set_visible (sender, FALSE); gtk_widget_set_sensitive (cb, TRUE); gtk_widget_set_sensitive (fb, TRUE); } } static void on_connect_all_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) err = NULL; CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data); gboolean ok; ok = bolt_client_connect_all_finish (dialog->client, res, &err); if (!ok) g_prefix_error (&err, _("Failed to authorize device: ")); dialog_operation_done (dialog, GTK_WIDGET (dialog->connect_button), err); } static void on_connect_button_clicked_cb (CcBoltDeviceDialog *dialog) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GList) entries = NULL; BoltDevice *device = dialog->device; GList *iter; g_return_if_fail (device != NULL); dialog_operation_start (dialog); entries = gtk_container_get_children (GTK_CONTAINER (dialog->parents_devices)); devices = g_ptr_array_new (); /* reverse the order, so to start with the devices closest to the host */ entries = g_list_reverse (entries); for (iter = entries; iter; iter = iter->next) { CcBoltDeviceEntry *entry; BoltDevice *dev; BoltStatus status; entry = (CcBoltDeviceEntry *) iter->data; dev = cc_bolt_device_entry_get_device (entry); status = bolt_device_get_status (dev); /* skip any devices down in the chain that are already authorized * NB: it is not possible to have gaps of non-authorized devices * in the chain, i.e. once we encounter a non-authorized device, * all following device (down the chain, towards the target) will * also be not authorized. */ if (!bolt_status_is_pending (status)) continue; /* device is now either !stored || pending */ g_ptr_array_add (devices, dev); } /* finally the actual device of the dialog */ g_ptr_array_add (devices, device); bolt_client_connect_all_async (dialog->client, devices, BOLT_POLICY_DEFAULT, BOLT_AUTHCTRL_NONE, dialog->cancel, on_connect_all_done, dialog); } static void on_forget_device_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) err = NULL; CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data); gboolean ok; ok = bolt_client_forget_device_finish (dialog->client, res, &err); if (!ok) g_prefix_error (&err, _("Failed to forget device: ")); dialog_operation_done (dialog, GTK_WIDGET (dialog->forget_button), err); } static void on_forget_button_clicked_cb (CcBoltDeviceDialog *dialog) { const char *uid = NULL; g_return_if_fail (dialog->device != NULL); uid = bolt_device_get_uid (dialog->device); dialog_operation_start (dialog); bolt_client_forget_device_async (dialog->client, uid, dialog->cancel, on_forget_device_done, dialog); } static void on_notify_button_clicked_cb (GtkButton *button, CcBoltDeviceDialog *dialog) { gtk_revealer_set_reveal_child (dialog->notify_revealer, FALSE); } static void cc_bolt_device_dialog_finalize (GObject *object) { CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (object); g_clear_object (&dialog->device); g_cancellable_cancel (dialog->cancel); g_clear_object (&dialog->cancel); g_clear_object (&dialog->client); G_OBJECT_CLASS (cc_bolt_device_dialog_parent_class)->finalize (object); } static void cc_bolt_device_dialog_class_init (CcBoltDeviceDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = cc_bolt_device_dialog_finalize; gtk_widget_class_set_template_from_resource (widget_class, RESOURCE_UI); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, header_bar); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, notify_label); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, notify_revealer); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, name_label); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, status_label); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, uuid_label); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, time_title); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, time_label); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, parents_expander); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, parents_label); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, parents_devices); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, button_box); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, spinner); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, connect_button); gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, forget_button); gtk_widget_class_bind_template_callback (widget_class, on_notify_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, on_forget_button_clicked_cb); } static void cc_bolt_device_dialog_init (CcBoltDeviceDialog *dialog) { g_resources_register (cc_thunderbolt_get_resource ()); gtk_widget_init_template (GTK_WIDGET (dialog)); gtk_list_box_set_header_func (dialog->parents_devices, cc_list_box_update_header_func, NULL, NULL); } /* public functions */ CcBoltDeviceDialog * cc_bolt_device_dialog_new (void) { CcBoltDeviceDialog *dialog; dialog = g_object_new (CC_TYPE_BOLT_DEVICE_DIALOG, "use-header-bar", TRUE, NULL); return dialog; } void cc_bolt_device_dialog_set_client (CcBoltDeviceDialog *dialog, BoltClient *client) { g_clear_object (&dialog->client); dialog->client = g_object_ref (client); } void cc_bolt_device_dialog_set_device (CcBoltDeviceDialog *dialog, BoltDevice *device, GPtrArray *parents) { g_autofree char *msg = NULL; guint i; if (device == dialog->device) return; if (dialog->device) { g_cancellable_cancel (dialog->cancel); g_clear_object (&dialog->cancel); dialog->cancel = g_cancellable_new (); g_signal_handlers_disconnect_by_func (dialog->device, G_CALLBACK (on_device_notify_cb), dialog); g_clear_object (&dialog->device); gtk_container_foreach (GTK_CONTAINER (dialog->parents_devices), (GtkCallback) gtk_widget_destroy, NULL); gtk_widget_hide (GTK_WIDGET (dialog->parents_expander)); } if (device == NULL) return; dialog->device = g_object_ref (device); g_signal_connect_object (dialog->device, "notify", G_CALLBACK (on_device_notify_cb), dialog, 0); /* reset the sensitivity of the buttons, because * dialog_update_from_device, because it can't know */ gtk_widget_set_sensitive (GTK_WIDGET (dialog->connect_button), TRUE); gtk_widget_set_sensitive (GTK_WIDGET (dialog->forget_button), TRUE); dialog_update_from_device (dialog); /* no parents, we are done here */ if (!parents || parents->len == 0) return; msg = g_strdup_printf (ngettext ("Depends on %u other device", "Depends on %u other devices", parents->len), parents->len); gtk_label_set_label (dialog->parents_label, msg); gtk_widget_show (GTK_WIDGET (dialog->parents_expander)); for (i = 0; i < parents->len; i++) { CcBoltDeviceEntry *entry; BoltDevice *parent; parent = g_ptr_array_index (parents, i); entry = cc_bolt_device_entry_new (parent, TRUE); gtk_widget_show (GTK_WIDGET (entry)); gtk_container_add (GTK_CONTAINER (dialog->parents_devices), GTK_WIDGET (entry)); } } BoltDevice * cc_bolt_device_dialog_peek_device (CcBoltDeviceDialog *dialog) { return dialog->device; } gboolean cc_bolt_device_dialog_device_equal (CcBoltDeviceDialog *dialog, BoltDevice *device) { return dialog->device != NULL && device == dialog->device; }