summaryrefslogtreecommitdiffstats
path: root/panels/thunderbolt/cc-bolt-panel.c
diff options
context:
space:
mode:
Diffstat (limited to 'panels/thunderbolt/cc-bolt-panel.c')
-rw-r--r--panels/thunderbolt/cc-bolt-panel.c994
1 files changed, 994 insertions, 0 deletions
diff --git a/panels/thunderbolt/cc-bolt-panel.c b/panels/thunderbolt/cc-bolt-panel.c
new file mode 100644
index 0000000..9004c3c
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-panel.c
@@ -0,0 +1,994 @@
+/* Copyright © 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Christian J. Kellner <ckellner@redhat.com>
+ *
+ */
+
+#include <config.h>
+
+#include <shell/cc-panel.h>
+#include <list-box-helper.h>
+
+#include <glib/gi18n.h>
+#include <polkit/polkit.h>
+
+#include "cc-bolt-device-dialog.h"
+#include "cc-bolt-device-entry.h"
+
+#include "bolt-client.h"
+#include "bolt-names.h"
+#include "bolt-str.h"
+
+#include "cc-bolt-panel.h"
+#include "cc-thunderbolt-resources.h"
+
+struct _CcBoltPanel
+{
+ CcPanel parent;
+
+ BoltClient *client;
+
+ /* headerbar menu */
+ GtkBox *headerbar_box;
+ GtkLockButton *lock_button;
+
+ /* main ui */
+ GtkStack *container;
+
+ /* empty state */
+ GtkLabel *notb_caption;
+ GtkLabel *notb_details;
+
+ /* notifications */
+ GtkLabel *notification_label;
+ GtkRevealer *notification_revealer;
+
+ /* authmode */
+ GtkSwitch *authmode_switch;
+ GtkSpinner *authmode_spinner;
+ GtkStack *authmode_mode;
+
+ /* device list */
+ GHashTable *devices;
+
+ GtkStack *devices_stack;
+ GtkBox *devices_box;
+ GtkBox *pending_box;
+
+ GtkListBox *devices_list;
+ GtkListBox *pending_list;
+
+ /* device details dialog */
+ CcBoltDeviceDialog *device_dialog;
+
+ /* polkit integration */
+ GPermission *permission;
+};
+
+/* initialization */
+static void bolt_client_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data);
+
+/* panel functions */
+static void cc_bolt_panel_set_no_thunderbolt (CcBoltPanel *panel,
+ const char *custom_msg);
+
+static void cc_bolt_panel_name_owner_changed (CcBoltPanel *panel);
+
+static CcBoltDeviceEntry * cc_bolt_panel_add_device (CcBoltPanel *panel,
+ BoltDevice *dev);
+
+static void cc_bolt_panel_del_device_entry (CcBoltPanel *panel,
+ CcBoltDeviceEntry *entry);
+
+static void cc_bolt_panel_authmode_sync (CcBoltPanel *panel);
+
+static void cc_panel_list_box_migrate (CcBoltPanel *panel,
+ GtkListBox *from,
+ GtkListBox *to,
+ CcBoltDeviceEntry *entry);
+
+/* bolt client signals */
+static void on_bolt_name_owner_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+static void on_bolt_device_added_cb (BoltClient *cli,
+ const char *path,
+ CcBoltPanel *panel);
+
+static void on_bolt_device_removed_cb (BoltClient *cli,
+ const char *opath,
+ CcBoltPanel *panel);
+
+static void on_bolt_notify_authmode_cb (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+/* panel signals */
+static gboolean on_authmode_state_set_cb (CcBoltPanel *panel,
+ gboolean state,
+ GtkSwitch *toggle);
+
+static void on_device_entry_row_activated_cb (CcBoltPanel *panel,
+ GtkListBoxRow *row);
+
+static gboolean on_device_dialog_delete_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ CcBoltPanel *panel);
+
+static void on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
+ BoltStatus new_status,
+ CcBoltPanel *panel);
+
+static void on_notification_button_clicked_cb (GtkButton *button,
+ CcBoltPanel *panel);
+
+
+/* polkit */
+static void on_permission_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void on_permission_notify_cb (GPermission *permission,
+ GParamSpec *pspec,
+ CcBoltPanel *panel);
+
+CC_PANEL_REGISTER (CcBoltPanel, cc_bolt_panel);
+
+static void
+bolt_client_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(CcBoltPanel) panel = NULL;
+ BoltClient *client;
+
+ panel = CC_BOLT_PANEL (user_data);
+ client = bolt_client_new_finish (res, &err);
+
+ if (client == NULL)
+ {
+ const char *text;
+
+ /* operation got cancelled because the panel got destroyed */
+ if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
+ g_error_matches (err, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED))
+ return;
+
+ g_warning ("Could not create client: %s", err->message);
+ text = _("The Thunderbolt subsystem (boltd) is not installed or "
+ "not set up properly.");
+
+ gtk_label_set_label (panel->notb_details, text);
+ gtk_stack_set_visible_child_name (panel->container, "no-thunderbolt");
+
+ return;
+ }
+
+ g_signal_connect_object (client,
+ "notify::g-name-owner",
+ G_CALLBACK (on_bolt_name_owner_changed_cb),
+ panel,
+ 0);
+
+ g_signal_connect_object (client,
+ "device-added",
+ G_CALLBACK (on_bolt_device_added_cb),
+ panel,
+ 0);
+
+ g_signal_connect_object (client,
+ "device-removed",
+ G_CALLBACK (on_bolt_device_removed_cb),
+ panel,
+ 0);
+
+ g_signal_connect_object (client,
+ "notify::auth-mode",
+ G_CALLBACK (on_bolt_notify_authmode_cb),
+ panel,
+ 0);
+
+ /* Treat security-level changes, which should rarely happen, as
+ * if the name owner changed, i.e. as if boltd got restarted */
+ g_signal_connect_object (client,
+ "notify::security-level",
+ G_CALLBACK (on_bolt_name_owner_changed_cb),
+ panel,
+ 0);
+
+ panel->client = client;
+
+ cc_bolt_device_dialog_set_client (panel->device_dialog, client);
+
+ cc_bolt_panel_authmode_sync (panel);
+
+ g_object_bind_property (panel->authmode_switch,
+ "active",
+ panel->devices_box,
+ "sensitive",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (panel->authmode_switch,
+ "active",
+ panel->pending_box,
+ "sensitive",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ gtk_stack_set_visible_child_name (panel->devices_stack, "no-devices");
+ cc_bolt_panel_name_owner_changed (panel);
+}
+
+static gboolean
+devices_table_transfer_entry (GHashTable *from,
+ GHashTable *to,
+ gconstpointer key)
+{
+ gpointer k, v;
+ gboolean found;
+
+ found = g_hash_table_lookup_extended (from, key, &k, &v);
+
+ if (found)
+ {
+ g_hash_table_steal (from, key);
+ g_hash_table_insert (to, k, v);
+ }
+
+ return found;
+}
+
+static void
+devices_table_clear_entries (GHashTable *table,
+ CcBoltPanel *panel)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, table);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ CcBoltDeviceEntry *entry = value;
+
+ cc_bolt_panel_del_device_entry (panel, entry);
+ g_hash_table_iter_remove (&iter);
+ }
+}
+
+static void
+devices_table_synchronize (CcBoltPanel *panel)
+{
+ g_autoptr(GHashTable) old = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+ g_autoptr(GError) err = NULL;
+ guint i;
+
+ devices = bolt_client_list_devices (panel->client, cc_panel_get_cancellable (CC_PANEL (panel)), &err);
+
+ if (!devices)
+ {
+ g_warning ("Could not list devices: %s", err->message);
+ devices = g_ptr_array_new_with_free_func (g_object_unref);
+ }
+
+ old = panel->devices;
+ panel->devices = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < devices->len; i++)
+ {
+ BoltDevice *dev = g_ptr_array_index (devices, i);
+ const char *path;
+ gboolean found;
+
+ path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
+ found = devices_table_transfer_entry (old, panel->devices, path);
+
+ if (found)
+ continue;
+
+ cc_bolt_panel_add_device (panel, dev);
+ }
+
+ devices_table_clear_entries (old, panel);
+ gtk_stack_set_visible_child_name (panel->container, "devices-listing");
+}
+
+static gboolean
+list_box_sync_visible (GtkListBox *lstbox)
+{
+ g_autoptr(GList) children = NULL;
+ gboolean show;
+
+ children = gtk_container_get_children (GTK_CONTAINER (lstbox));
+ show = g_list_length (children) > 0;
+
+ gtk_widget_set_visible (GTK_WIDGET (lstbox), show);
+
+ return show;
+}
+
+static GtkWidget *
+cc_bolt_panel_box_for_listbox (CcBoltPanel *panel,
+ GtkListBox *lstbox)
+{
+ if ((gpointer) lstbox == panel->devices_list)
+ return GTK_WIDGET (panel->devices_box);
+ else if ((gpointer) lstbox == panel->pending_list)
+ return GTK_WIDGET (panel->pending_box);
+
+ g_return_val_if_reached (NULL);
+}
+
+static CcBoltDeviceEntry *
+cc_bolt_panel_add_device (CcBoltPanel *panel,
+ BoltDevice *dev)
+{
+ CcBoltDeviceEntry *entry;
+ BoltDeviceType type;
+ BoltStatus status;
+ const char *path;
+
+ type = bolt_device_get_device_type (dev);
+
+ if (type != BOLT_DEVICE_PERIPHERAL)
+ return FALSE;
+
+ entry = cc_bolt_device_entry_new (dev, FALSE);
+ path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
+
+ /* add to the list box */
+ gtk_widget_show (GTK_WIDGET (entry));
+
+ status = bolt_device_get_status (dev);
+
+ if (bolt_status_is_pending (status))
+ {
+ gtk_container_add (GTK_CONTAINER (panel->pending_list), GTK_WIDGET (entry));
+ gtk_widget_show (GTK_WIDGET (panel->pending_list));
+ gtk_widget_show (GTK_WIDGET (panel->pending_box));
+ }
+ else
+ {
+ gtk_container_add (GTK_CONTAINER (panel->devices_list), GTK_WIDGET (entry));
+ gtk_widget_show (GTK_WIDGET (panel->devices_list));
+ gtk_widget_show (GTK_WIDGET (panel->devices_box));
+ }
+
+ g_signal_connect_object (entry,
+ "status-changed",
+ G_CALLBACK (on_device_entry_status_changed_cb),
+ panel,
+ 0);
+
+ gtk_stack_set_visible_child_name (panel->devices_stack, "have-devices");
+ g_hash_table_insert (panel->devices, (gpointer) path, entry);
+
+ return entry;
+}
+
+static void
+cc_bolt_panel_del_device_entry (CcBoltPanel *panel,
+ CcBoltDeviceEntry *entry)
+{
+ BoltDevice *dev;
+ GtkWidget *box;
+ GtkWidget *p;
+ gboolean show;
+
+ dev = cc_bolt_device_entry_get_device (entry);
+ if (cc_bolt_device_dialog_device_equal (panel->device_dialog, dev))
+ {
+ gtk_widget_hide (GTK_WIDGET (panel->device_dialog));
+ cc_bolt_device_dialog_set_device (panel->device_dialog, NULL, NULL);
+ }
+
+ p = gtk_widget_get_parent (GTK_WIDGET (entry));
+ gtk_widget_destroy (GTK_WIDGET (entry));
+
+ box = cc_bolt_panel_box_for_listbox (panel, GTK_LIST_BOX (p));
+ show = list_box_sync_visible (GTK_LIST_BOX (p));
+ gtk_widget_set_visible (box, show);
+
+ if (!gtk_widget_is_visible (GTK_WIDGET (panel->pending_list)) &&
+ !gtk_widget_is_visible (GTK_WIDGET (panel->devices_list)))
+ {
+ gtk_stack_set_visible_child_name (panel->devices_stack, "no-devices");
+ }
+}
+
+static void
+cc_bolt_panel_authmode_sync (CcBoltPanel *panel)
+{
+ BoltClient *client = panel->client;
+ BoltAuthMode mode;
+ gboolean enabled;
+ const char *name;
+
+ mode = bolt_client_get_authmode (client);
+ enabled = (mode & BOLT_AUTH_ENABLED) != 0;
+
+ g_signal_handlers_block_by_func (panel->authmode_switch, on_authmode_state_set_cb, panel);
+
+ gtk_switch_set_state (panel->authmode_switch, enabled);
+
+ g_signal_handlers_unblock_by_func (panel->authmode_switch, on_authmode_state_set_cb, panel);
+
+ name = enabled ? "enabled" : "disabled";
+ gtk_stack_set_visible_child_name (panel->authmode_mode, name);
+}
+
+static void
+cc_panel_list_box_migrate (CcBoltPanel *panel,
+ GtkListBox *from,
+ GtkListBox *to,
+ CcBoltDeviceEntry *entry)
+{
+ GtkWidget *from_box;
+ GtkWidget *to_box;
+ gboolean show;
+ GtkWidget *target;
+
+ target = GTK_WIDGET (entry);
+
+ gtk_container_remove (GTK_CONTAINER (from), target);
+ gtk_container_add (GTK_CONTAINER (to), target);
+ gtk_widget_show (GTK_WIDGET (to));
+
+ from_box = cc_bolt_panel_box_for_listbox (panel, from);
+ to_box = cc_bolt_panel_box_for_listbox (panel, to);
+
+ show = list_box_sync_visible (from);
+ gtk_widget_set_visible (from_box, show);
+ gtk_widget_set_visible (to_box, TRUE);
+}
+
+/* bolt client signals */
+static void
+cc_bolt_panel_set_no_thunderbolt (CcBoltPanel *panel,
+ const char *msg)
+{
+ if (!msg)
+ {
+ msg = _("Thunderbolt could not be detected.\n"
+ "Either the system lacks Thunderbolt support, "
+ "it has been disabled in the BIOS or is set to "
+ "an unsupported security level in the BIOS.");
+ }
+
+ gtk_label_set_label (panel->notb_details, msg);
+ gtk_stack_set_visible_child_name (panel->container, "no-thunderbolt");
+}
+
+static void
+cc_bolt_panel_name_owner_changed (CcBoltPanel *panel)
+{
+ g_autofree char *name_owner = NULL;
+ BoltClient *client = panel->client;
+ BoltSecurity sl;
+ gboolean notb = TRUE;
+ const char *text = NULL;
+
+ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (panel->client));
+
+ if (name_owner == NULL)
+ {
+ cc_bolt_panel_set_no_thunderbolt (panel, NULL);
+ devices_table_clear_entries (panel->devices, panel);
+ gtk_widget_hide (GTK_WIDGET (panel->headerbar_box));
+ return;
+ }
+
+ gtk_stack_set_visible_child_name (panel->container, "loading");
+
+ sl = bolt_client_get_security (client);
+
+ switch (sl)
+ {
+ case BOLT_SECURITY_NONE:
+ case BOLT_SECURITY_SECURE:
+ case BOLT_SECURITY_USER:
+ /* we fetch the device list and show them here */
+ notb = FALSE;
+ break;
+
+ case BOLT_SECURITY_DPONLY:
+ case BOLT_SECURITY_USBONLY:
+ text = _("Thunderbolt support has been disabled in the BIOS.");
+ break;
+
+ case BOLT_SECURITY_UNKNOWN:
+ text = _("Thunderbolt security level could not be determined.");;
+ break;
+ }
+
+ if (notb)
+ {
+ /* security level is unknown or un-handled */
+ cc_bolt_panel_set_no_thunderbolt (panel, text);
+ return;
+ }
+
+ if (panel->permission)
+ {
+ gtk_widget_show (GTK_WIDGET (panel->headerbar_box));
+ }
+ else
+ {
+ polkit_permission_new ("org.freedesktop.bolt.manage",
+ NULL,
+ cc_panel_get_cancellable (CC_PANEL (panel)),
+ on_permission_ready,
+ g_object_ref (panel));
+ }
+
+ devices_table_synchronize (panel);
+}
+
+/* bolt client signals */
+static void
+on_bolt_name_owner_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ cc_bolt_panel_name_owner_changed (CC_BOLT_PANEL (user_data));
+}
+
+static void
+on_bolt_device_added_cb (BoltClient *cli,
+ const char *path,
+ CcBoltPanel *panel)
+{
+ g_autoptr(GError) err = NULL;
+ GDBusConnection *bus;
+ BoltDevice *dev;
+ gboolean found;
+
+ found = g_hash_table_contains (panel->devices, path);
+
+ if (found)
+ return;
+
+ bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (panel->client));
+ dev = bolt_device_new_for_object_path (bus, path, cc_panel_get_cancellable (CC_PANEL (panel)), &err);
+
+ if (!dev)
+ {
+ g_warning ("Could not create proxy for %s", path);
+ return;
+ }
+
+ cc_bolt_panel_add_device (panel, dev);
+}
+
+static void
+on_bolt_device_removed_cb (BoltClient *cli,
+ const char *path,
+ CcBoltPanel *panel)
+{
+ CcBoltDeviceEntry *entry;
+
+ entry = g_hash_table_lookup (panel->devices, path);
+
+ if (!entry)
+ return;
+
+ cc_bolt_panel_del_device_entry (panel, entry);
+ g_hash_table_remove (panel->devices, path);
+}
+
+static void
+on_bolt_notify_authmode_cb (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ cc_bolt_panel_authmode_sync (CC_BOLT_PANEL (user_data));
+}
+
+/* panel signals */
+
+static void
+on_authmode_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ CcBoltPanel *panel = CC_BOLT_PANEL (user_data);
+ gboolean ok;
+
+ ok = bolt_client_set_authmode_finish (BOLT_CLIENT (source_object), res, &error);
+ if (!ok)
+ {
+ g_autofree char *text = NULL;
+
+ g_warning ("Could not set authmode: %s", error->message);
+
+ text = g_strdup_printf (_("Error switching direct mode: %s"), error->message);
+ gtk_label_set_markup (panel->notification_label, text);
+ gtk_revealer_set_reveal_child (panel->notification_revealer, TRUE);
+
+ /* make sure we are reflecting the correct state */
+ cc_bolt_panel_authmode_sync (panel);
+ }
+
+ gtk_spinner_stop (panel->authmode_spinner);
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), TRUE);
+}
+
+static gboolean
+on_authmode_state_set_cb (CcBoltPanel *panel,
+ gboolean enable,
+ GtkSwitch *toggle)
+{
+ BoltClient *client = panel->client;
+ BoltAuthMode mode;
+
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), FALSE);
+ gtk_spinner_start (panel->authmode_spinner);
+
+ mode = bolt_client_get_authmode (client);
+
+ if (enable)
+ mode = mode | BOLT_AUTH_ENABLED;
+ else
+ mode = mode & ~BOLT_AUTH_ENABLED;
+
+ bolt_client_set_authmode_async (client, mode, NULL, on_authmode_ready, panel);
+
+ return TRUE;
+}
+
+static void
+on_device_entry_row_activated_cb (CcBoltPanel *panel,
+ GtkListBoxRow *row)
+{
+ g_autoptr(GPtrArray) parents = NULL;
+ CcBoltDeviceEntry *entry;
+ BoltDevice *device;
+ BoltDevice *iter;
+ const char *parent;
+
+ if (!CC_IS_BOLT_DEVICE_ENTRY (row))
+ return;
+
+ entry = CC_BOLT_DEVICE_ENTRY (row);
+ device = cc_bolt_device_entry_get_device (entry);
+
+ /* walk up the chain and collect all parents */
+ parents = g_ptr_array_new_with_free_func (g_object_unref);
+ iter = device;
+
+ parent = bolt_device_get_parent (iter);
+ while (parent != NULL)
+ {
+ g_autofree char *path = NULL;
+ CcBoltDeviceEntry *child;
+ BoltDevice *dev;
+
+ path = bolt_gen_object_path (BOLT_DBUS_PATH_DEVICES, parent);
+
+ /* NB: the host device is not a peripheral and thus not
+ * in the hash table; therefore when get a NULL back, we
+ * should have reached the end of the chain */
+ child = g_hash_table_lookup (panel->devices, path);
+ if (!child)
+ break;
+
+ dev = cc_bolt_device_entry_get_device (child);
+ g_ptr_array_add (parents, g_object_ref (dev));
+ iter = dev;
+
+ parent = bolt_device_get_parent (iter);
+ }
+
+ cc_bolt_device_dialog_set_device (panel->device_dialog, device, parents);
+
+ gtk_window_resize (GTK_WINDOW (panel->device_dialog), 1, 1);
+ gtk_widget_show (GTK_WIDGET (panel->device_dialog));
+}
+
+static gboolean
+on_device_dialog_delete_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ CcBoltPanel *panel)
+{
+ CcBoltDeviceDialog *dialog;
+
+ dialog = CC_BOLT_DEVICE_DIALOG (widget);
+
+ cc_bolt_device_dialog_set_device (dialog, NULL, NULL);
+ gtk_widget_hide (widget);
+
+ return TRUE;
+}
+
+static void
+on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
+ BoltStatus new_status,
+ CcBoltPanel *panel)
+{
+ GtkListBox *from = NULL;
+ GtkListBox *to = NULL;
+ GtkWidget *p;
+ gboolean is_pending;
+ gboolean parent_pending;
+
+ /* if we are doing some active work, then lets not change
+ * the list the entry is in; otherwise we might just hop
+ * from one box to the other and back again.
+ */
+ if (new_status == BOLT_STATUS_CONNECTING || new_status == BOLT_STATUS_AUTHORIZING)
+ return;
+
+ is_pending = bolt_status_is_pending (new_status);
+
+ p = gtk_widget_get_parent (GTK_WIDGET (entry));
+ parent_pending = (gpointer) p == panel->pending_list;
+
+ /* */
+ if (is_pending && !parent_pending)
+ {
+ from = panel->devices_list;
+ to = panel->pending_list;
+ }
+ else if (!is_pending && parent_pending)
+ {
+ from = panel->pending_list;
+ to = panel->devices_list;
+ }
+
+ if (from && to)
+ cc_panel_list_box_migrate (panel, from, to, entry);
+}
+
+
+static void
+on_notification_button_clicked_cb (GtkButton *button,
+ CcBoltPanel *panel)
+{
+ gtk_revealer_set_reveal_child (panel->notification_revealer, FALSE);
+}
+
+/* polkit */
+
+static void
+on_permission_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(CcBoltPanel) panel = user_data;
+ g_autoptr(GError) err = NULL;
+ GPermission *permission;
+ gboolean is_allowed;
+ const char *name;
+
+ permission = polkit_permission_new_finish (res, &err);
+ panel->permission = permission;
+
+ if (!panel->permission)
+ {
+ g_warning ("Could not get polkit permissions: %s", err->message);
+ return;
+ }
+
+ g_signal_connect_object (permission,
+ "notify",
+ G_CALLBACK (on_permission_notify_cb),
+ panel,
+ G_CONNECT_AFTER);
+
+ is_allowed = g_permission_get_allowed (permission);
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), is_allowed);
+ gtk_lock_button_set_permission (panel->lock_button, permission);
+
+ name = gtk_stack_get_visible_child_name (panel->container);
+
+ gtk_widget_set_visible (GTK_WIDGET (panel->headerbar_box),
+ bolt_streq (name, "devices-listing"));
+}
+
+static void
+on_permission_notify_cb (GPermission *permission,
+ GParamSpec *pspec,
+ CcBoltPanel *panel)
+{
+ gboolean is_allowed = g_permission_get_allowed (permission);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), is_allowed);
+}
+
+static gint
+device_entries_sort_by_recency_cb (GtkListBoxRow *a_row,
+ GtkListBoxRow *b_row,
+ gpointer user_data)
+{
+ CcBoltDeviceEntry *a_entry = CC_BOLT_DEVICE_ENTRY (a_row);
+ CcBoltDeviceEntry *b_entry = CC_BOLT_DEVICE_ENTRY (b_row);
+ BoltDevice *a = cc_bolt_device_entry_get_device (a_entry);
+ BoltDevice *b = cc_bolt_device_entry_get_device (b_entry);
+ BoltStatus status;
+ gint64 a_ts, b_ts;
+ gint64 score;
+
+ a_ts = (gint64) bolt_device_get_timestamp (a);
+ b_ts = (gint64) bolt_device_get_timestamp (b);
+
+ score = b_ts - a_ts;
+
+ if (score != 0)
+ return score;
+
+ status = bolt_device_get_status (a);
+
+ if (bolt_status_is_connected (status))
+ {
+ const char *a_path;
+ const char *b_path;
+
+ a_path = bolt_device_get_syspath (a);
+ b_path = bolt_device_get_syspath (b);
+
+ return g_strcmp0 (a_path, b_path);
+ }
+ else
+ {
+ const char *a_name;
+ const char *b_name;
+
+ a_name = bolt_device_get_name (a);
+ b_name = bolt_device_get_name (b);
+
+ return g_strcmp0 (a_name, b_name);
+ }
+
+ return 0;
+}
+
+static gint
+device_entries_sort_by_syspath_cb (GtkListBoxRow *a_row,
+ GtkListBoxRow *b_row,
+ gpointer user_data)
+{
+ CcBoltDeviceEntry *a_entry = CC_BOLT_DEVICE_ENTRY (a_row);
+ CcBoltDeviceEntry *b_entry = CC_BOLT_DEVICE_ENTRY (b_row);
+ BoltDevice *a = cc_bolt_device_entry_get_device (a_entry);
+ BoltDevice *b = cc_bolt_device_entry_get_device (b_entry);
+
+ const char *a_path;
+ const char *b_path;
+
+ a_path = bolt_device_get_syspath (a);
+ b_path = bolt_device_get_syspath (b);
+
+ return g_strcmp0 (a_path, b_path);
+}
+
+/* GObject overrides */
+
+static void
+cc_bolt_panel_finalize (GObject *object)
+{
+ CcBoltPanel *panel = CC_BOLT_PANEL (object);
+
+ g_clear_object (&panel->client);
+ g_clear_pointer (&panel->devices, g_hash_table_unref);
+ g_clear_object (&panel->permission);
+
+ G_OBJECT_CLASS (cc_bolt_panel_parent_class)->finalize (object);
+}
+
+static void
+cc_bolt_panel_dispose (GObject *object)
+{
+ CcBoltPanel *panel = CC_BOLT_PANEL (object);
+
+ /* Must be destroyed in dispose, not finalize. */
+ g_clear_pointer ((GtkWidget **) &panel->device_dialog, gtk_widget_destroy);
+
+ G_OBJECT_CLASS (cc_bolt_panel_parent_class)->dispose (object);
+}
+
+static void
+cc_bolt_panel_constructed (GObject *object)
+{
+ CcBoltPanel *panel = CC_BOLT_PANEL (object);
+ GtkWindow *parent;
+ CcShell *shell;
+
+ parent = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel))));
+ gtk_window_set_transient_for (GTK_WINDOW (panel->device_dialog), parent);
+
+ G_OBJECT_CLASS (cc_bolt_panel_parent_class)->constructed (object);
+
+ shell = cc_panel_get_shell (CC_PANEL (panel));
+ cc_shell_embed_widget_in_header (shell, GTK_WIDGET (panel->headerbar_box), GTK_POS_RIGHT);
+}
+
+static void
+cc_bolt_panel_class_init (CcBoltPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = cc_bolt_panel_constructed;
+ object_class->dispose = cc_bolt_panel_dispose;
+ object_class->finalize = cc_bolt_panel_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/thunderbolt/cc-bolt-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_mode);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_spinner);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, container);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_list);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, headerbar_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, lock_button);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notb_caption);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notb_details);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notification_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notification_revealer);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, pending_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, pending_list);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_notification_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_authmode_state_set_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_device_entry_row_activated_cb);
+}
+
+static void
+cc_bolt_panel_init (CcBoltPanel *panel)
+{
+ g_resources_register (cc_thunderbolt_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (panel));
+
+ gtk_stack_set_visible_child_name (panel->container, "loading");
+
+ gtk_list_box_set_header_func (panel->devices_list,
+ cc_list_box_update_header_func,
+ NULL,
+ NULL);
+
+ gtk_list_box_set_header_func (panel->pending_list,
+ cc_list_box_update_header_func,
+ NULL,
+ NULL);
+
+ gtk_list_box_set_sort_func (panel->devices_list,
+ device_entries_sort_by_recency_cb,
+ panel,
+ NULL);
+
+ gtk_list_box_set_sort_func (panel->pending_list,
+ device_entries_sort_by_syspath_cb,
+ panel,
+ NULL);
+
+ panel->devices = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+
+ panel->device_dialog = cc_bolt_device_dialog_new ();
+ g_signal_connect_object (panel->device_dialog,
+ "delete-event",
+ G_CALLBACK (on_device_dialog_delete_event_cb),
+ panel, 0);
+
+ bolt_client_new_async (cc_panel_get_cancellable (CC_PANEL (panel)), bolt_client_ready, g_object_ref (panel));
+
+}