summaryrefslogtreecommitdiffstats
path: root/panels/thunderbolt
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--panels/thunderbolt/bolt-client.c1054
-rw-r--r--panels/thunderbolt/bolt-client.h143
-rw-r--r--panels/thunderbolt/bolt-device.c604
-rw-r--r--panels/thunderbolt/bolt-device.h87
-rw-r--r--panels/thunderbolt/bolt-enums.c397
-rw-r--r--panels/thunderbolt/bolt-enums.h249
-rw-r--r--panels/thunderbolt/bolt-error.c99
-rw-r--r--panels/thunderbolt/bolt-error.h55
-rw-r--r--panels/thunderbolt/bolt-names.c48
-rw-r--r--panels/thunderbolt/bolt-names.h73
-rw-r--r--panels/thunderbolt/bolt-proxy.c514
-rw-r--r--panels/thunderbolt/bolt-proxy.h97
-rw-r--r--panels/thunderbolt/bolt-str.c117
-rw-r--r--panels/thunderbolt/bolt-str.h43
-rw-r--r--panels/thunderbolt/bolt-time.c44
-rw-r--r--panels/thunderbolt/bolt-time.h32
-rw-r--r--panels/thunderbolt/cc-bolt-device-dialog.c515
-rw-r--r--panels/thunderbolt/cc-bolt-device-dialog.h47
-rw-r--r--panels/thunderbolt/cc-bolt-device-dialog.ui325
-rw-r--r--panels/thunderbolt/cc-bolt-device-entry.c223
-rw-r--r--panels/thunderbolt/cc-bolt-device-entry.h36
-rw-r--r--panels/thunderbolt/cc-bolt-device-entry.ui13
-rw-r--r--panels/thunderbolt/cc-bolt-panel.c960
-rw-r--r--panels/thunderbolt/cc-bolt-panel.h30
-rw-r--r--panels/thunderbolt/cc-bolt-panel.ui361
-rw-r--r--panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in18
-rw-r--r--panels/thunderbolt/icons/meson.build4
-rw-r--r--panels/thunderbolt/icons/scalable/org.gnome.Settings-thunderbolt-symbolic.svg4
-rw-r--r--panels/thunderbolt/meson.build70
-rw-r--r--panels/thunderbolt/thunderbolt.gresource.xml9
-rwxr-xr-xpanels/thunderbolt/update-from-bolt.sh50
31 files changed, 6321 insertions, 0 deletions
diff --git a/panels/thunderbolt/bolt-client.c b/panels/thunderbolt/bolt-client.c
new file mode 100644
index 0000000..1612b65
--- /dev/null
+++ b/panels/thunderbolt/bolt-client.c
@@ -0,0 +1,1054 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "bolt-client.h"
+
+#include "bolt-device.h"
+#include "bolt-error.h"
+#include "bolt-names.h"
+
+#include <gio/gio.h>
+
+static void handle_dbus_device_added (GObject *self,
+ GDBusProxy *bus_proxy,
+ GVariant *params);
+static void handle_dbus_device_removed (GObject *self,
+ GDBusProxy *bus_proxy,
+ GVariant *params);
+
+struct _BoltClient
+{
+ BoltProxy parent;
+};
+
+enum {
+ PROP_0,
+
+ /* D-Bus Props */
+ PROP_VERSION,
+ PROP_PROBING,
+ PROP_SECURITY,
+ PROP_AUTHMODE,
+
+ PROP_LAST
+};
+
+static GParamSpec *props[PROP_LAST] = {NULL, };
+
+enum {
+ SIGNAL_DEVICE_ADDED,
+ SIGNAL_DEVICE_REMOVED,
+ SIGNAL_LAST
+};
+
+static guint signals[SIGNAL_LAST] = {0};
+
+
+G_DEFINE_TYPE (BoltClient,
+ bolt_client,
+ BOLT_TYPE_PROXY);
+
+
+static void
+bolt_client_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ if (bolt_proxy_get_dbus_property (object, pspec, value))
+ return;
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static const BoltProxySignal *
+bolt_client_get_dbus_signals (guint *n)
+{
+ static BoltProxySignal dbus_signals[] = {
+ {"DeviceAdded", handle_dbus_device_added},
+ {"DeviceRemoved", handle_dbus_device_removed},
+ };
+
+ *n = G_N_ELEMENTS (dbus_signals);
+
+ return dbus_signals;
+}
+
+
+static void
+bolt_client_class_init (BoltClientClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ BoltProxyClass *proxy_class = BOLT_PROXY_CLASS (klass);
+
+ gobject_class->get_property = bolt_client_get_property;
+
+ proxy_class->get_dbus_signals = bolt_client_get_dbus_signals;
+
+ props[PROP_VERSION]
+ = g_param_spec_uint ("version",
+ "Version", NULL,
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME);
+
+ props[PROP_PROBING]
+ = g_param_spec_boolean ("probing",
+ "Probing", NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME);
+
+ props[PROP_SECURITY]
+ = g_param_spec_enum ("security-level",
+ "SecurityLevel", NULL,
+ BOLT_TYPE_SECURITY,
+ BOLT_SECURITY_UNKNOWN,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME);
+
+ props[PROP_AUTHMODE] =
+ g_param_spec_flags ("auth-mode", "AuthMode", NULL,
+ BOLT_TYPE_AUTH_MODE,
+ BOLT_AUTH_ENABLED,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class,
+ PROP_LAST,
+ props);
+
+ /* signals */
+ signals[SIGNAL_DEVICE_ADDED] =
+ g_signal_new ("device-added",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+
+ signals[SIGNAL_DEVICE_REMOVED] =
+ g_signal_new ("device-removed",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+}
+
+
+static void
+bolt_client_init (BoltClient *cli)
+{
+}
+
+/* dbus signals */
+
+static void
+handle_dbus_device_added (GObject *self, GDBusProxy *bus_proxy, GVariant *params)
+{
+ BoltClient *cli = BOLT_CLIENT (self);
+ const char *opath = NULL;
+
+ g_variant_get_child (params, 0, "&o", &opath);
+ g_signal_emit (cli, signals[SIGNAL_DEVICE_ADDED], 0, opath);
+}
+
+static void
+handle_dbus_device_removed (GObject *self, GDBusProxy *bus_proxy, GVariant *params)
+{
+ BoltClient *cli = BOLT_CLIENT (self);
+ const char *opath = NULL;
+
+ g_variant_get_child (params, 0, "&o", &opath);
+ g_signal_emit (cli, signals[SIGNAL_DEVICE_REMOVED], 0, opath);
+}
+
+/* public methods */
+
+BoltClient *
+bolt_client_new (GError **error)
+{
+ BoltClient *cli;
+ GDBusConnection *bus;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
+ if (bus == NULL)
+ {
+ g_prefix_error (error, "Error connecting to D-Bus: ");
+ return FALSE;
+ }
+
+ cli = g_initable_new (BOLT_TYPE_CLIENT,
+ NULL, error,
+ "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+ "g-connection", bus,
+ "g-name", BOLT_DBUS_NAME,
+ "g-object-path", BOLT_DBUS_PATH,
+ "g-interface-name", BOLT_DBUS_INTERFACE,
+ NULL);
+
+ g_object_unref (bus);
+
+ return cli;
+}
+
+static void
+got_the_client (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GTask *task = user_data;
+ GObject *obj;
+
+ obj = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, &error);
+
+ if (obj == NULL)
+ {
+ /* error ownership gets transferred to the task */
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_pointer (task, obj, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+got_the_bus (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GTask *task = user_data;
+ GCancellable *cancellable;
+ GDBusConnection *bus;
+
+ bus = g_bus_get_finish (res, &error);
+ if (bus == NULL)
+ {
+ g_prefix_error (&error, "could not connect to D-Bus: ");
+ /* error ownership gets transferred to the task */
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ cancellable = g_task_get_cancellable (task);
+ g_async_initable_new_async (BOLT_TYPE_CLIENT,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ got_the_client, task,
+ "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+ "g-connection", bus,
+ "g-name", BOLT_DBUS_NAME,
+ "g-object-path", BOLT_DBUS_PATH,
+ "g-interface-name", BOLT_DBUS_INTERFACE,
+ NULL);
+ g_object_unref (bus);
+}
+
+void
+bolt_client_new_async (GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (NULL, cancellable, callback, user_data);
+ g_bus_get (G_BUS_TYPE_SYSTEM, cancellable, got_the_bus, task);
+}
+
+BoltClient *
+bolt_client_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_TASK (res), NULL);
+
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+GPtrArray *
+bolt_client_list_devices (BoltClient *client,
+ GCancellable *cancel,
+ GError **error)
+{
+ g_autoptr(GVariant) val = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+ g_autoptr(GVariantIter) iter = NULL;
+ GDBusConnection *bus = NULL;
+ const char *d;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL);
+
+ val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client),
+ "ListDevices",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancel,
+ error);
+ if (val == NULL)
+ return NULL;
+
+ bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client));
+
+ devices = g_ptr_array_new_with_free_func (g_object_unref);
+
+ g_variant_get (val, "(ao)", &iter);
+ while (g_variant_iter_loop (iter, "&o", &d, NULL))
+ {
+ BoltDevice *dev;
+
+ dev = bolt_device_new_for_object_path (bus, d, cancel, error);
+ if (dev == NULL)
+ return NULL;
+
+ g_ptr_array_add (devices, dev);
+ }
+
+ return g_steal_pointer (&devices);
+}
+
+BoltDevice *
+bolt_client_get_device (BoltClient *client,
+ const char *uid,
+ GCancellable *cancel,
+ GError **error)
+{
+ g_autoptr(GVariant) val = NULL;
+ g_autoptr(GError) err = NULL;
+ BoltDevice *dev = NULL;
+ GDBusConnection *bus = NULL;
+ const char *opath = NULL;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL);
+
+ val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client),
+ "DeviceByUid",
+ g_variant_new ("(s)", uid),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancel,
+ &err);
+
+ if (val == NULL)
+ {
+ bolt_error_propagate_stripped (error, &err);
+ return NULL;
+ }
+
+ bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client));
+ g_variant_get (val, "(&o)", &opath);
+
+ if (opath == NULL)
+ return NULL;
+
+ dev = bolt_device_new_for_object_path (bus, opath, cancel, error);
+ return dev;
+}
+
+BoltDevice *
+bolt_client_enroll_device (BoltClient *client,
+ const char *uid,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GError **error)
+{
+ g_autoptr(GVariant) val = NULL;
+ g_autoptr(GError) err = NULL;
+ g_autofree char *fstr = NULL;
+ BoltDevice *dev = NULL;
+ GDBusConnection *bus = NULL;
+ GVariant *params = NULL;
+ const char *opath = NULL;
+ const char *pstr;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL);
+
+ pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, error);
+ if (pstr == NULL)
+ return NULL;
+
+ fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, error);
+ if (fstr == NULL)
+ return NULL;
+
+ params = g_variant_new ("(sss)", uid, pstr, fstr);
+ val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client),
+ "EnrollDevice",
+ params,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+
+ if (val == NULL)
+ {
+ bolt_error_propagate_stripped (error, &err);
+ return NULL;
+ }
+
+ bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client));
+ g_variant_get (val, "(&o)", &opath);
+
+ if (opath == NULL)
+ return NULL;
+
+ dev = bolt_device_new_for_object_path (bus, opath, NULL, error);
+ return dev;
+}
+
+void
+bolt_client_enroll_device_async (BoltClient *client,
+ const char *uid,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autofree char *fstr = NULL;
+ GError *err = NULL;
+ GVariant *params;
+ const char *pstr;
+
+ g_return_if_fail (BOLT_IS_CLIENT (client));
+ g_return_if_fail (uid != NULL);
+
+ pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err);
+ if (pstr == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err);
+ if (fstr == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ params = g_variant_new ("(sss)", uid, pstr, fstr);
+ g_dbus_proxy_call (G_DBUS_PROXY (client),
+ "EnrollDevice",
+ params,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ callback,
+ user_data);
+}
+
+gboolean
+bolt_client_enroll_device_finish (BoltClient *client,
+ GAsyncResult *res,
+ char **path,
+ GError **error)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GVariant) val = NULL;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE);
+
+ val = g_dbus_proxy_call_finish (G_DBUS_PROXY (client), res, &err);
+ if (val == NULL)
+ {
+ bolt_error_propagate_stripped (error, &err);
+ return FALSE;
+ }
+
+ if (path != NULL)
+ g_variant_get (val, "(o)", path);
+
+ return TRUE;
+}
+
+typedef struct OpData
+{
+ const char *iface; /* Manager or Device */
+ const char *method; /* Enroll or Authorize */
+ char *path; /* object path */
+ GVariant *params; /* parameters */
+
+} OpData;
+
+static OpData *
+op_data_new_enroll (const char *uid,
+ const char *policy,
+ const char *flags)
+{
+ GVariant *params;
+ OpData *op;
+
+ params = g_variant_new ("(sss)", uid, policy, flags);
+
+ op = g_slice_new (OpData);
+ op->iface = BOLT_DBUS_INTERFACE;
+ op->method = "EnrollDevice";
+ op->params = g_variant_ref_sink (params);
+ op->path = g_strdup (BOLT_DBUS_PATH);
+
+ return op;
+}
+
+
+static OpData *
+op_data_new_authorize (const char *uid,
+ const char *flags)
+{
+ OpData *op = NULL;
+ GVariant *params;
+ char *path;
+
+ path = bolt_gen_object_path (BOLT_DBUS_PATH_DEVICES, uid);
+ params = g_variant_new ("(s)", flags);
+
+ op = g_slice_new (OpData);
+
+ op->iface = BOLT_DBUS_DEVICE_INTERFACE;
+ op->method = "Authorize";
+ op->params = g_variant_ref_sink (params);
+ op->path = path; /* takes ownership */
+
+ return op;
+}
+
+static void
+op_data_free (OpData *op)
+{
+ g_clear_pointer (&op->params, g_variant_unref);
+ g_clear_pointer (&op->path, g_free);
+ g_slice_free (OpData, op);
+}
+
+static void
+op_queue_free (GQueue *queue)
+{
+ g_queue_free_full (queue, (GDestroyNotify) op_data_free);
+}
+
+static void allop_one_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static gboolean
+allop_continue (BoltClient *client, GTask *task, GQueue *ops)
+{
+ GDBusConnection *bus;
+ GCancellable *cancel;
+ OpData *op;
+
+ cancel = g_task_get_cancellable (task);
+ bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client));
+
+ op = g_queue_pop_head (ops);
+
+ if (op == NULL)
+ return TRUE;
+
+ g_dbus_connection_call (bus,
+ BOLT_DBUS_NAME,
+ op->path,
+ op->iface,
+ op->method,
+ op->params,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancel,
+ allop_one_done,
+ task);
+
+ op_data_free (op);
+
+ return FALSE;
+}
+
+static void
+allop_one_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) val = NULL;
+ BoltClient *client;
+ GDBusConnection *bus;
+ gboolean done;
+ GError *err = NULL;
+ GQueue *ops;
+ GTask *task;
+
+ task = G_TASK (user_data);
+
+ ops = g_task_get_task_data (task);
+ client = g_task_get_source_object (task);
+ bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client));
+
+ val = g_dbus_connection_call_finish (bus, res, &err);
+
+ if (val == NULL)
+ {
+ g_task_return_error (task, err); /* takes ownership */
+ g_object_unref (task);
+ /* we are done (albeit with an error) */
+ return;
+ }
+
+ done = allop_continue (client, task, ops);
+
+ if (done)
+ {
+ /* we are done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ }
+}
+
+void
+bolt_client_enroll_all_async (BoltClient *client,
+ GPtrArray *uuids,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autofree char *fstr = NULL;
+ GError *err = NULL;
+ const char *pstr;
+ GQueue *ops;
+ GTask *task;
+
+ g_return_if_fail (BOLT_IS_CLIENT (client));
+ g_return_if_fail (uuids != NULL && uuids->len > 0);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (callback != NULL);
+
+ pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err);
+ if (pstr == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err);
+ if (fstr == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ task = g_task_new (client, cancellable, callback, user_data);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ ops = g_queue_new ();
+ g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free);
+
+ for (guint i = 0; i < uuids->len; i++)
+ {
+ const char *uid = g_ptr_array_index (uuids, i);
+ OpData *op;
+
+ op = op_data_new_enroll (uid, pstr, fstr);
+
+ g_queue_push_tail (ops, op);
+ }
+
+ allop_continue (client, task, ops);
+}
+
+gboolean
+bolt_client_enroll_all_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_autoptr(GError) err = NULL;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE);
+ g_return_val_if_fail (g_task_is_valid (res, client), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ok = g_task_propagate_boolean (G_TASK (res), &err);
+
+ if (!ok)
+ bolt_error_propagate_stripped (error, &err);
+
+ return ok;
+}
+
+void
+bolt_client_authorize_all_async (BoltClient *client,
+ GPtrArray *uuids,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autofree char *fstr = NULL;
+ GError *err = NULL;
+ GQueue *ops;
+ GTask *task;
+
+ g_return_if_fail (BOLT_IS_CLIENT (client));
+ g_return_if_fail (uuids != NULL && uuids->len > 0);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (callback != NULL);
+
+ fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err);
+ if (fstr == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ task = g_task_new (client, cancellable, callback, user_data);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ ops = g_queue_new ();
+ g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free);
+
+ for (guint i = 0; i < uuids->len; i++)
+ {
+ const char *uid = g_ptr_array_index (uuids, i);
+ OpData *op;
+
+ op = op_data_new_authorize (uid, fstr);
+
+ g_queue_push_tail (ops, op);
+ }
+
+ allop_continue (client, task, ops);
+}
+
+gboolean
+bolt_client_authorize_all_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_autoptr(GError) err = NULL;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE);
+ g_return_val_if_fail (g_task_is_valid (res, client), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ok = g_task_propagate_boolean (G_TASK (res), &err);
+
+ if (!ok)
+ bolt_error_propagate_stripped (error, &err);
+
+ return ok;
+}
+
+void
+bolt_client_connect_all_async (BoltClient *client,
+ GPtrArray *devices,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autofree char *fstr = NULL;
+ GError *err = NULL;
+ const char *pstr;
+ GQueue *ops;
+ GTask *task;
+
+ g_return_if_fail (BOLT_IS_CLIENT (client));
+ g_return_if_fail (devices != NULL && devices->len > 0);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (callback != NULL);
+
+ pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err);
+ if (pstr == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err);
+ if (fstr == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ task = g_task_new (client, cancellable, callback, user_data);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ ops = g_queue_new ();
+ g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free);
+
+ for (guint i = 0; i < devices->len; i++)
+ {
+ BoltDevice *dev = g_ptr_array_index (devices, i);
+ const char *uid = bolt_device_get_uid (dev);
+ OpData *op;
+
+ if (bolt_device_is_stored (dev))
+ op = op_data_new_authorize (uid, fstr);
+ else
+ op = op_data_new_enroll (uid, pstr, fstr);
+
+ g_queue_push_tail (ops, op);
+ }
+
+ allop_continue (client, task, ops);
+}
+
+gboolean
+bolt_client_connect_all_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_autoptr(GError) err = NULL;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE);
+ g_return_val_if_fail (g_task_is_valid (res, client), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ok = g_task_propagate_boolean (G_TASK (res), &err);
+
+ if (!ok)
+ bolt_error_propagate_stripped (error, &err);
+
+ return ok;
+}
+
+gboolean
+bolt_client_forget_device (BoltClient *client,
+ const char *uid,
+ GError **error)
+{
+ g_autoptr(GVariant) val = NULL;
+ g_autoptr(GError) err = NULL;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE);
+
+ val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client),
+ "ForgetDevice",
+ g_variant_new ("(s)", uid),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+
+ if (val == NULL)
+ {
+ bolt_error_propagate_stripped (error, &err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+bolt_client_forget_device_async (BoltClient *client,
+ const char *uid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (BOLT_IS_CLIENT (client));
+
+ g_dbus_proxy_call (G_DBUS_PROXY (client),
+ "ForgetDevice",
+ g_variant_new ("(s)", uid),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ callback,
+ user_data);
+}
+
+gboolean
+bolt_client_forget_device_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_autoptr(GVariant) val = NULL;
+ g_autoptr(GError) err = NULL;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE);
+
+ val = g_dbus_proxy_call_finish (G_DBUS_PROXY (client), res, &err);
+ if (val == NULL)
+ {
+ bolt_error_propagate_stripped (error, &err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* getter */
+guint
+bolt_client_get_version (BoltClient *client)
+{
+ const char *key;
+ guint val = 0;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), val);
+
+ key = g_param_spec_get_name (props[PROP_VERSION]);
+ ok = bolt_proxy_get_property_uint32 (BOLT_PROXY (client), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get property '%s'", key);
+
+ return val;
+}
+
+gboolean
+bolt_client_is_probing (BoltClient *client)
+{
+ const char *key;
+ gboolean val = FALSE;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), val);
+
+ key = g_param_spec_get_name (props[PROP_PROBING]);
+ ok = bolt_proxy_get_property_bool (BOLT_PROXY (client), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+BoltSecurity
+bolt_client_get_security (BoltClient *client)
+{
+ const char *key;
+ gboolean ok;
+ gint val = BOLT_SECURITY_UNKNOWN;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), val);
+
+ key = g_param_spec_get_name (props[PROP_SECURITY]);
+ ok = bolt_proxy_get_property_enum (BOLT_PROXY (client), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+BoltAuthMode
+bolt_client_get_authmode (BoltClient *client)
+{
+ const char *key;
+ gboolean ok;
+ guint val = BOLT_AUTH_DISABLED;
+
+ g_return_val_if_fail (BOLT_IS_CLIENT (client), val);
+
+ key = g_param_spec_get_name (props[PROP_AUTHMODE]);
+ ok = bolt_proxy_get_property_flags (BOLT_PROXY (client), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+void
+bolt_client_set_authmode_async (BoltClient *client,
+ BoltAuthMode mode,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autofree char *str = NULL;
+ GError *err = NULL;
+ GParamSpec *pspec;
+ GParamSpecFlags *flags_pspec;
+ GFlagsClass *flags_class;
+
+ pspec = props[PROP_AUTHMODE];
+ flags_pspec = G_PARAM_SPEC_FLAGS (pspec);
+ flags_class = flags_pspec->flags_class;
+ str = bolt_flags_class_to_string (flags_class, mode, &err);
+
+ if (str == NULL)
+ {
+ g_task_report_error (client, callback, user_data, NULL, err);
+ return;
+ }
+
+ bolt_proxy_set_property_async (BOLT_PROXY (client),
+ g_param_spec_get_nick (pspec),
+ g_variant_new ("s", str),
+ cancellable,
+ callback,
+ user_data);
+}
+
+gboolean
+bolt_client_set_authmode_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error)
+{
+ return bolt_proxy_set_property_finish (res, error);
+}
+
+/* utility functions */
+static gint
+device_sort_by_syspath (gconstpointer ap,
+ gconstpointer bp,
+ gpointer data)
+{
+ BoltDevice *a = BOLT_DEVICE (*((BoltDevice **) ap));
+ BoltDevice *b = BOLT_DEVICE (*((BoltDevice **) bp));
+ gint sort_order = GPOINTER_TO_INT (data);
+ const char *pa;
+ const char *pb;
+
+ pa = bolt_device_get_syspath (a);
+ pb = bolt_device_get_syspath (b);
+
+ return sort_order * g_strcmp0 (pa, pb);
+}
+
+void
+bolt_devices_sort_by_syspath (GPtrArray *devices,
+ gboolean reverse)
+{
+ gpointer sort_order = GINT_TO_POINTER (reverse ? -1 : 1);
+
+ if (devices == NULL)
+ return;
+
+ g_ptr_array_sort_with_data (devices,
+ device_sort_by_syspath,
+ sort_order);
+}
diff --git a/panels/thunderbolt/bolt-client.h b/panels/thunderbolt/bolt-client.h
new file mode 100644
index 0000000..571d5bd
--- /dev/null
+++ b/panels/thunderbolt/bolt-client.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include "bolt-enums.h"
+#include "bolt-device.h"
+#include "bolt-proxy.h"
+
+G_BEGIN_DECLS
+
+#define BOLT_TYPE_CLIENT bolt_client_get_type ()
+G_DECLARE_FINAL_TYPE (BoltClient, bolt_client, BOLT, CLIENT, BoltProxy);
+
+BoltClient * bolt_client_new (GError **error);
+
+void bolt_client_new_async (GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+BoltClient * bolt_client_new_finish (GAsyncResult *res,
+ GError **error);
+
+GPtrArray * bolt_client_list_devices (BoltClient *client,
+ GCancellable *cancellable,
+ GError **error);
+
+BoltDevice * bolt_client_get_device (BoltClient *client,
+ const char *uid,
+ GCancellable *cancellable,
+ GError **error);
+
+BoltDevice * bolt_client_enroll_device (BoltClient *client,
+ const char *uid,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GError **error);
+
+void bolt_client_enroll_device_async (BoltClient *client,
+ const char *uid,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_client_enroll_device_finish (BoltClient *client,
+ GAsyncResult *res,
+ char **path,
+ GError **error);
+
+void bolt_client_enroll_all_async (BoltClient *client,
+ GPtrArray *uuids,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_client_enroll_all_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error);
+
+void bolt_client_authorize_all_async (BoltClient *client,
+ GPtrArray *uuids,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_client_authorize_all_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error);
+
+void bolt_client_connect_all_async (BoltClient *client,
+ GPtrArray *devices,
+ BoltPolicy policy,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_client_connect_all_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error);
+
+
+gboolean bolt_client_forget_device (BoltClient *client,
+ const char *uid,
+ GError **error);
+
+void bolt_client_forget_device_async (BoltClient *client,
+ const char *uid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_client_forget_device_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error);
+
+/* getter */
+guint bolt_client_get_version (BoltClient *client);
+
+gboolean bolt_client_is_probing (BoltClient *client);
+
+BoltSecurity bolt_client_get_security (BoltClient *client);
+
+BoltAuthMode bolt_client_get_authmode (BoltClient *client);
+
+/* setter */
+
+void bolt_client_set_authmode_async (BoltClient *client,
+ BoltAuthMode mode,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_client_set_authmode_finish (BoltClient *client,
+ GAsyncResult *res,
+ GError **error);
+
+/* utility functions */
+void bolt_devices_sort_by_syspath (GPtrArray *devices,
+ gboolean reverse);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/bolt-device.c b/panels/thunderbolt/bolt-device.c
new file mode 100644
index 0000000..b316950
--- /dev/null
+++ b/panels/thunderbolt/bolt-device.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "config.h"
+
+#include "bolt-device.h"
+
+#include "bolt-enums.h"
+#include "bolt-error.h"
+#include "bolt-names.h"
+
+#include <gio/gio.h>
+
+struct _BoltDevice
+{
+ BoltProxy parent;
+};
+
+enum {
+ PROP_0,
+
+ /* D-Bus Props */
+ PROP_UID,
+ PROP_NAME,
+ PROP_VENDOR,
+ PROP_TYPE,
+ PROP_STATUS,
+ PROP_AUTHFLAGS,
+ PROP_PARENT,
+ PROP_SYSPATH,
+ PROP_CONNTIME,
+ PROP_AUTHTIME,
+
+ PROP_STORED,
+ PROP_POLICY,
+ PROP_KEY,
+ PROP_STORETIME,
+ PROP_LABEL,
+
+ PROP_LAST
+};
+
+static GParamSpec *props[PROP_LAST] = {NULL, };
+
+G_DEFINE_TYPE (BoltDevice,
+ bolt_device,
+ BOLT_TYPE_PROXY);
+
+static void
+bolt_device_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ if (bolt_proxy_get_dbus_property (object, pspec, value))
+ return;
+}
+
+
+
+static void
+bolt_device_class_init (BoltDeviceClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = bolt_device_get_property;
+
+ props[PROP_UID] =
+ g_param_spec_string ("uid",
+ "Uid", NULL,
+ "unknown",
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name", NULL,
+ "unknown",
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_VENDOR] =
+ g_param_spec_string ("vendor",
+ "Vendor", NULL,
+ "unknown",
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_TYPE] =
+ g_param_spec_enum ("type",
+ "Type", NULL,
+ BOLT_TYPE_DEVICE_TYPE,
+ BOLT_DEVICE_PERIPHERAL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_STATUS] =
+ g_param_spec_enum ("status",
+ "Status", NULL,
+ BOLT_TYPE_STATUS,
+ BOLT_STATUS_DISCONNECTED,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_AUTHFLAGS] =
+ g_param_spec_flags ("authflags",
+ "AuthFlags", NULL,
+ BOLT_TYPE_AUTH_FLAGS,
+ BOLT_AUTH_NONE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ props[PROP_PARENT] =
+ g_param_spec_string ("parent",
+ "Parent", NULL,
+ "unknown",
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_SYSPATH] =
+ g_param_spec_string ("syspath",
+ "SysfsPath", NULL,
+ "unknown",
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_CONNTIME] =
+ g_param_spec_uint64 ("conntime",
+ "ConnectTime", NULL,
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ props[PROP_AUTHTIME] =
+ g_param_spec_uint64 ("authtime",
+ "AuthorizeTime", NULL,
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ props[PROP_STORED] =
+ g_param_spec_boolean ("stored",
+ "Stored", NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_POLICY] =
+ g_param_spec_enum ("policy",
+ "Policy", NULL,
+ BOLT_TYPE_POLICY,
+ BOLT_POLICY_DEFAULT,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_KEY] =
+ g_param_spec_enum ("key",
+ "Key", NULL,
+ BOLT_TYPE_KEY_STATE,
+ BOLT_KEY_MISSING,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK);
+
+ props[PROP_STORETIME] =
+ g_param_spec_uint64 ("storetime",
+ "StoreTime", NULL,
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ props[PROP_LABEL] =
+ g_param_spec_string ("label",
+ "Label", NULL,
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class,
+ PROP_LAST,
+ props);
+
+}
+
+static void
+bolt_device_init (BoltDevice *mgr)
+{
+}
+
+/* public methods */
+
+BoltDevice *
+bolt_device_new_for_object_path (GDBusConnection *bus,
+ const char *path,
+ GCancellable *cancel,
+ GError **error)
+{
+ BoltDevice *dev;
+
+ dev = g_initable_new (BOLT_TYPE_DEVICE,
+ cancel, error,
+ "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+ "g-connection", bus,
+ "g-name", BOLT_DBUS_NAME,
+ "g-object-path", path,
+ "g-interface-name", BOLT_DBUS_DEVICE_INTERFACE,
+ NULL);
+
+ return dev;
+}
+
+gboolean
+bolt_device_authorize (BoltDevice *dev,
+ BoltAuthCtrl flags,
+ GCancellable *cancel,
+ GError **error)
+{
+ g_autoptr(GError) err = NULL;
+ g_autofree char *fstr = NULL;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE);
+
+ fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, error);
+ if (fstr == NULL)
+ return FALSE;
+
+ g_dbus_proxy_call_sync (G_DBUS_PROXY (dev),
+ "Authorize",
+ g_variant_new ("(s)", fstr),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancel,
+ &err);
+
+ if (err != NULL)
+ return bolt_error_propagate_stripped (error, &err);
+
+ return TRUE;
+}
+
+void
+bolt_device_authorize_async (BoltDevice *dev,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GError *err = NULL;
+ g_autofree char *fstr = NULL;
+
+ g_return_if_fail (BOLT_IS_DEVICE (dev));
+
+ fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err);
+ if (fstr == NULL)
+ {
+ g_task_report_error (dev, callback, user_data, NULL, err);
+ return;
+ }
+
+ g_dbus_proxy_call (G_DBUS_PROXY (dev),
+ "Authorize",
+ g_variant_new ("(s)", fstr),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ callback,
+ user_data);
+}
+
+gboolean
+bolt_device_authorize_finish (BoltDevice *dev,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GVariant) val = NULL;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE);
+
+ val = g_dbus_proxy_call_finish (G_DBUS_PROXY (dev), res, &err);
+ if (val == NULL)
+ {
+ bolt_error_propagate_stripped (error, &err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+const char *
+bolt_device_get_uid (BoltDevice *dev)
+{
+ const char *key;
+ const char *str;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL);
+
+ key = g_param_spec_get_name (props[PROP_UID]);
+ str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key);
+
+ return str;
+}
+
+const char *
+bolt_device_get_name (BoltDevice *dev)
+{
+ const char *key;
+ const char *str;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL);
+
+ key = g_param_spec_get_name (props[PROP_NAME]);
+ str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key);
+
+ return str;
+}
+
+const char *
+bolt_device_get_vendor (BoltDevice *dev)
+{
+ const char *key;
+ const char *str;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL);
+
+ key = g_param_spec_get_name (props[PROP_VENDOR]);
+ str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key);
+
+ return str;
+}
+
+BoltDeviceType
+bolt_device_get_device_type (BoltDevice *dev)
+{
+ const char *key;
+ gboolean ok;
+ gint val = BOLT_DEVICE_PERIPHERAL;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_TYPE]);
+ ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+BoltStatus
+bolt_device_get_status (BoltDevice *dev)
+{
+ const char *key;
+ gboolean ok;
+ gint val = BOLT_STATUS_UNKNOWN;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_STATUS]);
+ ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+BoltAuthFlags
+bolt_device_get_authflags (BoltDevice *dev)
+{
+ const char *key;
+ gboolean ok;
+ guint val = BOLT_AUTH_NONE;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_AUTHFLAGS]);
+ ok = bolt_proxy_get_property_flags (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+const char *
+bolt_device_get_parent (BoltDevice *dev)
+{
+ const char *key;
+ const char *str;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL);
+
+ key = g_param_spec_get_name (props[PROP_PARENT]);
+ str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key);
+
+ return str;
+}
+
+const char *
+bolt_device_get_syspath (BoltDevice *dev)
+{
+ const char *key;
+ const char *str;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL);
+
+ key = g_param_spec_get_name (props[PROP_SYSPATH]);
+ str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key);
+
+ return str;
+}
+
+guint64
+bolt_device_get_conntime (BoltDevice *dev)
+{
+ const char *key;
+ guint64 val = 0;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_CONNTIME]);
+ ok = bolt_proxy_get_property_uint64 (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+guint64
+bolt_device_get_authtime (BoltDevice *dev)
+{
+ const char *key;
+ guint64 val = 0;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_AUTHTIME]);
+ ok = bolt_proxy_get_property_uint64 (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+gboolean
+bolt_device_is_stored (BoltDevice *dev)
+{
+ const char *key;
+ gboolean val = FALSE;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_STORED]);
+ ok = bolt_proxy_get_property_bool (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+BoltPolicy
+bolt_device_get_policy (BoltDevice *dev)
+{
+ const char *key;
+ gboolean ok;
+ gint val = BOLT_POLICY_DEFAULT;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_POLICY]);
+ ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+BoltKeyState
+bolt_device_get_keystate (BoltDevice *dev)
+{
+ const char *key;
+ gboolean ok;
+ gint val = BOLT_KEY_MISSING;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_KEY]);
+ ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+guint64
+bolt_device_get_storetime (BoltDevice *dev)
+{
+ const char *key;
+ guint64 val = 0;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), val);
+
+ key = g_param_spec_get_name (props[PROP_STORETIME]);
+ ok = bolt_proxy_get_property_uint64 (BOLT_PROXY (dev), key, &val);
+
+ if (!ok)
+ g_warning ("failed to get enum property '%s'", key);
+
+ return val;
+}
+
+const char *
+bolt_device_get_label (BoltDevice *dev)
+{
+ const char *key;
+ const char *str;
+
+ g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL);
+
+ key = g_param_spec_get_name (props[PROP_LABEL]);
+ str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key);
+
+ return str;
+}
+
+char *
+bolt_device_get_display_name (BoltDevice *dev)
+{
+ const char *label;
+ const char *name;
+ const char *vendor;
+
+ label = bolt_device_get_label (dev);
+ if (label != NULL)
+ return g_strdup (label);
+
+ name = bolt_device_get_name (dev);
+ vendor = bolt_device_get_vendor (dev);
+
+ return g_strdup_printf ("%s %s", vendor, name);
+}
+
+guint64
+bolt_device_get_timestamp (BoltDevice *dev)
+{
+ BoltStatus status;
+ guint64 timestamp = 0;
+
+ status = bolt_device_get_status (dev);
+
+ switch (status)
+ {
+ case BOLT_STATUS_AUTHORIZING:
+ case BOLT_STATUS_AUTH_ERROR:
+ case BOLT_STATUS_CONNECTING:
+ case BOLT_STATUS_CONNECTED:
+ timestamp = bolt_device_get_conntime (dev);
+ break;
+
+ case BOLT_STATUS_DISCONNECTED:
+ /* implicit: device is stored */
+ timestamp = bolt_device_get_storetime (dev);
+ break;
+
+ case BOLT_STATUS_AUTHORIZED:
+ case BOLT_STATUS_AUTHORIZED_DPONLY:
+ case BOLT_STATUS_AUTHORIZED_NEWKEY:
+ case BOLT_STATUS_AUTHORIZED_SECURE:
+ timestamp = bolt_device_get_authtime (dev);
+ break;
+
+ case BOLT_STATUS_UNKNOWN:
+ timestamp = 0;
+ break;
+ }
+
+ return timestamp;
+}
diff --git a/panels/thunderbolt/bolt-device.h b/panels/thunderbolt/bolt-device.h
new file mode 100644
index 0000000..ffd09f9
--- /dev/null
+++ b/panels/thunderbolt/bolt-device.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include "bolt-enums.h"
+#include "bolt-proxy.h"
+
+G_BEGIN_DECLS
+
+#define BOLT_TYPE_DEVICE bolt_device_get_type ()
+G_DECLARE_FINAL_TYPE (BoltDevice, bolt_device, BOLT, DEVICE, BoltProxy);
+
+BoltDevice * bolt_device_new_for_object_path (GDBusConnection *bus,
+ const char *path,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean bolt_device_authorize (BoltDevice *dev,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GError **error);
+
+void bolt_device_authorize_async (BoltDevice *dev,
+ BoltAuthCtrl flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_device_authorize_finish (BoltDevice *dev,
+ GAsyncResult *res,
+ GError **error);
+
+/* getter */
+const char * bolt_device_get_uid (BoltDevice *dev);
+
+const char * bolt_device_get_name (BoltDevice *dev);
+
+const char * bolt_device_get_vendor (BoltDevice *dev);
+
+BoltDeviceType bolt_device_get_device_type (BoltDevice *dev);
+
+BoltStatus bolt_device_get_status (BoltDevice *dev);
+
+BoltAuthFlags bolt_device_get_authflags (BoltDevice *dev);
+
+const char * bolt_device_get_parent (BoltDevice *dev);
+
+const char * bolt_device_get_syspath (BoltDevice *dev);
+
+guint64 bolt_device_get_conntime (BoltDevice *dev);
+
+guint64 bolt_device_get_authtime (BoltDevice *dev);
+
+gboolean bolt_device_is_stored (BoltDevice *dev);
+
+BoltPolicy bolt_device_get_policy (BoltDevice *dev);
+
+BoltKeyState bolt_device_get_keystate (BoltDevice *dev);
+
+guint64 bolt_device_get_storetime (BoltDevice *dev);
+
+const char * bolt_device_get_label (BoltDevice *dev);
+
+/* derived getter */
+char * bolt_device_get_display_name (BoltDevice *dev);
+
+guint64 bolt_device_get_timestamp (BoltDevice *dev);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/bolt-enums.c b/panels/thunderbolt/bolt-enums.c
new file mode 100644
index 0000000..bb34ba7
--- /dev/null
+++ b/panels/thunderbolt/bolt-enums.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "config.h"
+
+#include "bolt-enums.h"
+#include "bolt-error.h"
+
+#include <gio/gio.h>
+
+#if !GLIB_CHECK_VERSION(2, 57, 0)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GFlagsClass, g_type_class_unref);
+#endif
+
+gboolean
+bolt_enum_class_validate (GEnumClass *enum_class,
+ gint value,
+ GError **error)
+{
+ const char *name;
+ gboolean oob;
+
+ if (enum_class == NULL)
+ {
+ name = g_type_name_from_class ((GTypeClass *) enum_class);
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "could not determine enum class for '%s'",
+ name);
+
+ return FALSE;
+ }
+
+ oob = value < enum_class->minimum || value > enum_class->maximum;
+
+ if (oob)
+ {
+ name = g_type_name_from_class ((GTypeClass *) enum_class);
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "enum value '%d' is out of bounds for '%s'",
+ value, name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+bolt_enum_validate (GType enum_type,
+ gint value,
+ GError **error)
+{
+ g_autoptr(GEnumClass) klass = g_type_class_ref (enum_type);
+ return bolt_enum_class_validate (klass, value, error);
+}
+
+const char *
+bolt_enum_to_string (GType enum_type,
+ gint value,
+ GError **error)
+{
+ g_autoptr(GEnumClass) klass = NULL;
+ GEnumValue *ev;
+
+ klass = g_type_class_ref (enum_type);
+
+ if (!bolt_enum_class_validate (klass, value, error))
+ return NULL;
+
+ ev = g_enum_get_value (klass, value);
+ return ev->value_nick;
+}
+
+gint
+bolt_enum_from_string (GType enum_type,
+ const char *string,
+ GError **error)
+{
+ g_autoptr(GEnumClass) klass = NULL;
+ const char *name;
+ GEnumValue *ev;
+
+ klass = g_type_class_ref (enum_type);
+
+ if (klass == NULL)
+ {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "could not determine enum class");
+ return -1;
+ }
+
+ if (string == NULL)
+ {
+ name = g_type_name_from_class ((GTypeClass *) klass);
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "empty string passed for enum class for '%s'",
+ name);
+ return -1;
+ }
+
+ ev = g_enum_get_value_by_nick (klass, string);
+
+ if (ev == NULL)
+ {
+ name = g_type_name (enum_type);
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "invalid string '%s' for enum '%s'", string, name);
+ return -1;
+ }
+
+ return ev->value;
+}
+
+char *
+bolt_flags_class_to_string (GFlagsClass *flags_class,
+ guint value,
+ GError **error)
+{
+ g_autoptr(GString) str = NULL;
+ const char *name;
+ GFlagsValue *fv;
+
+ if (flags_class == NULL)
+ {
+ name = g_type_name_from_class ((GTypeClass *) flags_class);
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "could not determine flags class for '%s'",
+ name);
+
+ return FALSE;
+ }
+
+ fv = g_flags_get_first_value (flags_class, value);
+ if (fv == NULL)
+ {
+ if (value == 0)
+ return g_strdup ("");
+
+ name = g_type_name_from_class ((GTypeClass *) flags_class);
+
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "invalid value '%u' for flags '%s'", value, name);
+ return NULL;
+ }
+
+ value &= ~fv->value;
+ str = g_string_new (fv->value_nick);
+
+ while (value != 0 &&
+ (fv = g_flags_get_first_value (flags_class, value)) != NULL)
+ {
+ g_string_append (str, " | ");
+ g_string_append (str, fv->value_nick);
+
+ value &= ~fv->value;
+ }
+
+ if (value != 0)
+ {
+ name = g_type_name_from_class ((GTypeClass *) flags_class);
+
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "unhandled value '%u' for flags '%s'", value, name);
+ return NULL;
+ }
+
+ return g_string_free (g_steal_pointer (&str), FALSE);
+}
+
+gboolean
+bolt_flags_class_from_string (GFlagsClass *flags_class,
+ const char *string,
+ guint *flags_out,
+ GError **error)
+{
+ g_auto(GStrv) vals = NULL;
+ const char *name;
+ guint flags = 0;
+
+ if (flags_class == NULL)
+ {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "could not determine flags class");
+
+ return FALSE;
+ }
+
+ if (string == NULL)
+ {
+ name = g_type_name_from_class ((GTypeClass *) flags_class);
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "empty string passed for flags class for '%s'",
+ name);
+ return FALSE;
+ }
+
+ vals = g_strsplit (string, "|", -1);
+
+ for (guint i = 0; vals[i]; i++)
+ {
+ GFlagsValue *fv;
+ char *nick;
+
+ nick = g_strstrip (vals[i]);
+ fv = g_flags_get_value_by_nick (flags_class, nick);
+
+ if (fv == NULL)
+ {
+ name = g_type_name_from_class ((GTypeClass *) flags_class);
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "invalid flag '%s' for flags '%s'", string, name);
+
+ return FALSE;
+ }
+
+ flags |= fv->value;
+ }
+
+ if (flags_out != NULL)
+ *flags_out = flags;
+
+ return TRUE;
+}
+
+char *
+bolt_flags_to_string (GType flags_type,
+ guint value,
+ GError **error)
+{
+ g_autoptr(GFlagsClass) klass = NULL;
+
+ klass = g_type_class_ref (flags_type);
+ return bolt_flags_class_to_string (klass, value, error);
+}
+
+gboolean
+bolt_flags_from_string (GType flags_type,
+ const char *string,
+ guint *flags_out,
+ GError **error)
+{
+ g_autoptr(GFlagsClass) klass = NULL;
+
+ klass = g_type_class_ref (flags_type);
+ return bolt_flags_class_from_string (klass, string, flags_out, error);
+}
+
+gboolean
+bolt_flags_update (guint from,
+ guint *to,
+ guint mask)
+{
+ guint val;
+ gboolean chg;
+
+ g_return_val_if_fail (to != NULL, FALSE);
+
+ val = *to & ~mask; /* clear all bits in mask */
+ val = val | (from & mask); /* set all bits in from and mask */
+ chg = *to != val;
+ *to = val;
+
+ return chg;
+}
+
+const char *
+bolt_status_to_string (BoltStatus status)
+{
+ return bolt_enum_to_string (BOLT_TYPE_STATUS, status, NULL);
+}
+
+gboolean
+bolt_status_is_authorized (BoltStatus status)
+{
+ return status == BOLT_STATUS_AUTHORIZED ||
+ status == BOLT_STATUS_AUTHORIZED_SECURE ||
+ status == BOLT_STATUS_AUTHORIZED_NEWKEY;
+}
+
+gboolean
+bolt_status_is_pending (BoltStatus status)
+{
+ return status == BOLT_STATUS_AUTH_ERROR ||
+ status == BOLT_STATUS_CONNECTED;
+}
+
+gboolean
+bolt_status_validate (BoltStatus status)
+{
+ return bolt_enum_validate (BOLT_TYPE_STATUS, status, NULL);
+}
+
+gboolean
+bolt_status_is_connected (BoltStatus status)
+{
+ return status > BOLT_STATUS_DISCONNECTED;
+}
+
+BoltSecurity
+bolt_security_from_string (const char *str)
+{
+ return bolt_enum_from_string (BOLT_TYPE_SECURITY, str, NULL);
+}
+
+const char *
+bolt_security_to_string (BoltSecurity security)
+{
+ return bolt_enum_to_string (BOLT_TYPE_SECURITY, security, NULL);
+}
+
+gboolean
+bolt_security_validate (BoltSecurity security)
+{
+ return bolt_enum_validate (BOLT_TYPE_SECURITY, security, NULL);
+}
+
+gboolean
+bolt_security_allows_pcie (BoltSecurity security)
+{
+ gboolean pcie = FALSE;
+
+ switch (security)
+ {
+ case BOLT_SECURITY_NONE:
+ case BOLT_SECURITY_USER:
+ case BOLT_SECURITY_SECURE:
+ pcie = TRUE;
+ break;
+
+ case BOLT_SECURITY_DPONLY:
+ case BOLT_SECURITY_USBONLY:
+ case BOLT_SECURITY_UNKNOWN:
+ pcie = FALSE;
+ break;
+ }
+
+ return pcie;
+}
+
+BoltPolicy
+bolt_policy_from_string (const char *str)
+{
+ return bolt_enum_from_string (BOLT_TYPE_POLICY, str, NULL);
+}
+
+const char *
+bolt_policy_to_string (BoltPolicy policy)
+{
+ return bolt_enum_to_string (BOLT_TYPE_POLICY, policy, NULL);
+}
+
+gboolean
+bolt_policy_validate (BoltPolicy policy)
+{
+ return bolt_enum_validate (BOLT_TYPE_POLICY, policy, NULL);
+}
+
+BoltDeviceType
+bolt_device_type_from_string (const char *str)
+{
+ return bolt_enum_from_string (BOLT_TYPE_DEVICE_TYPE, str, NULL);
+}
+
+const char *
+bolt_device_type_to_string (BoltDeviceType type)
+{
+ return bolt_enum_to_string (BOLT_TYPE_DEVICE_TYPE, type, NULL);
+}
+
+gboolean
+bolt_device_type_validate (BoltDeviceType type)
+{
+ return bolt_enum_validate (BOLT_TYPE_DEVICE_TYPE, type, NULL);
+}
+
+gboolean
+bolt_device_type_is_host (BoltDeviceType type)
+{
+ return type == BOLT_DEVICE_HOST;
+}
diff --git a/panels/thunderbolt/bolt-enums.h b/panels/thunderbolt/bolt-enums.h
new file mode 100644
index 0000000..6e2953f
--- /dev/null
+++ b/panels/thunderbolt/bolt-enums.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include "bolt-names.h"
+#include "bolt-enum-types.h"
+
+
+gboolean bolt_enum_validate (GType enum_type,
+ gint value,
+ GError **error);
+
+gboolean bolt_enum_class_validate (GEnumClass *enum_class,
+ gint value,
+ GError **error);
+
+const char * bolt_enum_to_string (GType enum_type,
+ gint value,
+ GError **error);
+
+gint bolt_enum_from_string (GType enum_type,
+ const char *string,
+ GError **error);
+
+
+char * bolt_flags_class_to_string (GFlagsClass *flags_class,
+ guint value,
+ GError **error);
+
+gboolean bolt_flags_class_from_string (GFlagsClass *flags_class,
+ const char *string,
+ guint *flags_out,
+ GError **error);
+
+char * bolt_flags_to_string (GType flags_type,
+ guint value,
+ GError **error);
+
+gboolean bolt_flags_from_string (GType flags_type,
+ const char *string,
+ guint *flags_out,
+ GError **error);
+
+gboolean bolt_flags_update (guint from,
+ guint *to,
+ guint mask);
+
+#define bolt_flag_isset(flags_, flag_) (!!(flags_ & flag_))
+#define bolt_flag_isclear(flags_, flag_) (!(flags_ & flag_))
+
+/**
+ * BoltStatus:
+ * @BOLT_STATUS_UNKNOWN: Device is in an unknown state (should normally not happen).
+ * @BOLT_STATUS_DISCONNECTED: Device is not connected.
+ * @BOLT_STATUS_CONNECTING: Device is currently being connected.
+ * @BOLT_STATUS_CONNECTED: Device is connected, but not authorized.
+ * @BOLT_STATUS_AUTHORIZING: Device is currently authorizing.
+ * @BOLT_STATUS_AUTH_ERROR: Failed to authorize a device via a key.
+ * @BOLT_STATUS_AUTHORIZED: Device connected and authorized.
+ * @BOLT_STATUS_AUTHORIZED_SECURE: Device connected and securely authorized via a key (deprecated).
+ * @BOLT_STATUS_AUTHORIZED_NEWKEY: Device connected and authorized via a new key (deprecated).
+ * @BOLT_STATUS_AUTHORIZED_DPONLY: Device authorized but with thunderbolt disabled (deprecated).
+ *
+ * The current status of the device.
+ */
+typedef enum {
+
+ BOLT_STATUS_UNKNOWN = -1,
+ BOLT_STATUS_DISCONNECTED = 0,
+ BOLT_STATUS_CONNECTING,
+ BOLT_STATUS_CONNECTED,
+ BOLT_STATUS_AUTHORIZING,
+ BOLT_STATUS_AUTH_ERROR,
+ BOLT_STATUS_AUTHORIZED,
+
+ /* deprecated, do not use */
+ BOLT_STATUS_AUTHORIZED_SECURE,
+ BOLT_STATUS_AUTHORIZED_NEWKEY,
+ BOLT_STATUS_AUTHORIZED_DPONLY
+
+} BoltStatus;
+
+const char * bolt_status_to_string (BoltStatus status);
+gboolean bolt_status_is_authorized (BoltStatus status);
+gboolean bolt_status_is_connected (BoltStatus status);
+gboolean bolt_status_is_pending (BoltStatus status);
+gboolean bolt_status_validate (BoltStatus status);
+
+/**
+ * BoltAuthFlags:
+ * @BOLT_AUTH_NONE: No specific authorization.
+ * @BOLT_AUTH_NOPCIE: PCIe tunnels are *not* authorized.
+ * @BOLT_AUTH_SECURE: Device is securely authorized.
+ * @BOLT_AUTH_NOKEY: Device does *not* support key verification.
+ * @BOLT_AUTH_BOOT: Device was already authorized during pre-boot.
+ *
+ * More specific information about device authorization.
+ */
+typedef enum { /*< flags >*/
+
+ BOLT_AUTH_NONE = 0,
+ BOLT_AUTH_NOPCIE = 1 << 0,
+ BOLT_AUTH_SECURE = 1 << 1,
+ BOLT_AUTH_NOKEY = 1 << 2,
+ BOLT_AUTH_BOOT = 1 << 3,
+
+} BoltAuthFlags;
+
+/**
+ * BoltKeyState:
+ * @BOLT_KEY_UNKNOWN: unknown key state
+ * @BOLT_KEY_MISSING: no key
+ * @BOLT_KEY_HAVE: key exists
+ * @BOLT_KEY_NEW: key is new
+ *
+ * The state of the key.
+ */
+
+typedef enum {
+
+ BOLT_KEY_UNKNOWN = -1,
+ BOLT_KEY_MISSING = 0,
+ BOLT_KEY_HAVE = 1,
+ BOLT_KEY_NEW = 2
+
+} BoltKeyState;
+
+/**
+ * BoltSecurity:
+ * @BOLT_SECURITY_UNKNOWN : Unknown security.
+ * @BOLT_SECURITY_NONE : No security, all devices are automatically connected.
+ * @BOLT_SECURITY_DPONLY : Display Port only devices only.
+ * @BOLT_SECURITY_USER : User needs to authorize devices.
+ * @BOLT_SECURITY_SECURE : User needs to authorize devices. Authorization can
+ * be done via key exchange to verify the device identity.
+ * @BOLT_SECURITY_USBONLY : Only create a PCIe tunnel to the USB controller in a
+ * connected thunderbolt dock, allowing no downstream PCIe tunnels.
+ *
+ * The security level of the thunderbolt domain.
+ */
+typedef enum {
+
+ BOLT_SECURITY_UNKNOWN = -1,
+ BOLT_SECURITY_NONE = 0,
+ BOLT_SECURITY_DPONLY = 1,
+ BOLT_SECURITY_USER = '1',
+ BOLT_SECURITY_SECURE = '2',
+ BOLT_SECURITY_USBONLY = 4,
+
+} BoltSecurity;
+
+
+BoltSecurity bolt_security_from_string (const char *str);
+const char * bolt_security_to_string (BoltSecurity security);
+gboolean bolt_security_validate (BoltSecurity security);
+gboolean bolt_security_allows_pcie (BoltSecurity security);
+
+/**
+ * BoltPolicy:
+ * @BOLT_POLICY_UNKNOWN: Unknown policy.
+ * @BOLT_POLICY_DEFAULT: Default policy.
+ * @BOLT_POLICY_MANUAL: Manual authorization of the device.
+ * @BOLT_POLICY_AUTO: Connect the device automatically,
+ * with the best possible security level supported
+ * by the domain controller.
+ *
+ * What do to for connected devices.
+ */
+typedef enum {
+
+ BOLT_POLICY_UNKNOWN = -1,
+ BOLT_POLICY_DEFAULT = 0,
+ BOLT_POLICY_MANUAL = 1,
+ BOLT_POLICY_AUTO = 2,
+
+} BoltPolicy;
+
+
+BoltPolicy bolt_policy_from_string (const char *str);
+const char * bolt_policy_to_string (BoltPolicy policy);
+gboolean bolt_policy_validate (BoltPolicy policy);
+
+/**
+ * BoltAuthCtrl:
+ * @BOLT_AUTHCTRL_NONE: No authorization flags.
+ *
+ * Control authorization.
+ */
+typedef enum { /*< flags >*/
+
+ BOLT_AUTHCTRL_NONE = 0
+
+} BoltAuthCtrl;
+
+/**
+ * BoltDeviceType:
+ * @BOLT_DEVICE_UNKNOWN_TYPE: Unknown device type
+ * @BOLT_DEVICE_HOST: The device representing the host
+ * @BOLT_DEVICE_PERIPHERAL: A generic thunderbolt peripheral
+ *
+ * The type of the device.
+ */
+typedef enum {
+
+ BOLT_DEVICE_UNKNOWN_TYPE = -1,
+ BOLT_DEVICE_HOST = 0,
+ BOLT_DEVICE_PERIPHERAL
+
+} BoltDeviceType;
+
+BoltDeviceType bolt_device_type_from_string (const char *str);
+const char * bolt_device_type_to_string (BoltDeviceType type);
+gboolean bolt_device_type_validate (BoltDeviceType type);
+gboolean bolt_device_type_is_host (BoltDeviceType type);
+
+/**
+ * BoltAuthMode:
+ * @BOLT_AUTH_DISABLED: Authorization is disabled
+ * @BOLT_AUTH_ENABLED: Authorization is enabled.
+ *
+ * Control authorization.
+ */
+typedef enum { /*< flags >*/
+
+ BOLT_AUTH_DISABLED = 0,
+ BOLT_AUTH_ENABLED = 1
+
+} BoltAuthMode;
+
+#define bolt_auth_mode_is_enabled(auth) ((auth & BOLT_AUTH_ENABLED) != 0)
+#define bolt_auth_mode_is_disabled(auth) (!bolt_auth_mode_is_enabled (auth))
diff --git a/panels/thunderbolt/bolt-error.c b/panels/thunderbolt/bolt-error.c
new file mode 100644
index 0000000..37d844e
--- /dev/null
+++ b/panels/thunderbolt/bolt-error.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "config.h"
+
+#include "bolt-error.h"
+
+#include "bolt-names.h"
+
+#include <gio/gio.h>
+
+/**
+ * SECTION:bolt-error
+ * @Title: Error codes
+ *
+ */
+
+static const GDBusErrorEntry bolt_error_entries[] = {
+ {BOLT_ERROR_FAILED, BOLT_DBUS_NAME ".Error.Failed"},
+ {BOLT_ERROR_UDEV, BOLT_DBUS_NAME ".Error.UDev"},
+};
+
+
+GQuark
+bolt_error_quark (void)
+{
+ static volatile gsize quark_volatile = 0;
+
+ g_dbus_error_register_error_domain ("bolt-error-quark",
+ &quark_volatile,
+ bolt_error_entries,
+ G_N_ELEMENTS (bolt_error_entries));
+ return (GQuark) quark_volatile;
+}
+
+gboolean
+bolt_err_notfound (const GError *error)
+{
+ return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) ||
+ g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) ||
+ g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND) ||
+ g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND);
+}
+
+gboolean
+bolt_err_exists (const GError *error)
+{
+ return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS) ||
+ g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_EXIST);
+}
+
+gboolean
+bolt_err_inval (const GError *error)
+{
+ return g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE);
+}
+
+gboolean
+bolt_err_cancelled (const GError *error)
+{
+ return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+}
+
+gboolean
+bolt_error_propagate_stripped (GError **dest,
+ GError **source)
+{
+ GError *src;
+
+ g_return_val_if_fail (source != NULL, FALSE);
+
+ src = *source;
+
+ if (src == NULL)
+ return TRUE;
+
+ if (g_dbus_error_is_remote_error (src))
+ g_dbus_error_strip_remote_error (src);
+
+ g_propagate_error (dest, g_steal_pointer (source));
+ return FALSE;
+}
diff --git a/panels/thunderbolt/bolt-error.h b/panels/thunderbolt/bolt-error.h
new file mode 100644
index 0000000..7d3823d
--- /dev/null
+++ b/panels/thunderbolt/bolt-error.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * BoltError:
+ * @BOLT_ERROR_FAILED: Generic error code
+ * @BOLT_ERROR_UDEV: UDev error
+ *
+ * Error codes used inside Bolt.
+ */
+typedef enum {
+ BOLT_ERROR_FAILED = 0,
+ BOLT_ERROR_UDEV,
+ BOLT_ERROR_NOKEY,
+ BOLT_ERROR_BADKEY,
+ BOLT_ERROR_CFG,
+} BoltError;
+
+
+GQuark bolt_error_quark (void);
+#define BOLT_ERROR (bolt_error_quark ())
+
+/* helper function to check for certain error types */
+gboolean bolt_err_notfound (const GError *error);
+gboolean bolt_err_exists (const GError *error);
+gboolean bolt_err_inval (const GError *error);
+gboolean bolt_err_cancelled (const GError *error);
+
+gboolean bolt_error_propagate_stripped (GError **dest,
+ GError **source);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/bolt-names.c b/panels/thunderbolt/bolt-names.c
new file mode 100644
index 0000000..7426b2f
--- /dev/null
+++ b/panels/thunderbolt/bolt-names.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2019 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "config.h"
+
+#include "bolt-names.h"
+
+/* Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_" */
+#define DBUS_OPATH_VALID_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"
+
+char *
+bolt_gen_object_path (const char *base,
+ const char *oid)
+{
+ g_autofree char *id = NULL;
+
+ if (oid)
+ {
+ id = g_strdup (oid);
+ g_strcanon (id, DBUS_OPATH_VALID_CHARS, '_');
+ }
+
+ if (base && id)
+ return g_build_path ("/", "/", base, id, NULL);
+ else if (base)
+ return g_build_path ("/", "/", base, NULL);
+ else if (id)
+ return g_build_path ("/", "/", id, NULL);
+
+ return g_strdup ("/");
+}
diff --git a/panels/thunderbolt/bolt-names.h b/panels/thunderbolt/bolt-names.h
new file mode 100644
index 0000000..460eed5
--- /dev/null
+++ b/panels/thunderbolt/bolt-names.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2018 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* D-Bus API revision (here for the lack of a better place) */
+#define BOLT_DBUS_API_VERSION 1U
+
+/* logging */
+
+#define BOLT_LOG_DOMAIN_UID "BOLT_DOMAIN_UID"
+#define BOLT_LOG_DOMAIN_NAME "BOLT_DOMAIN_NAME"
+
+#define BOLT_LOG_DEVICE_UID "BOLT_DEVICE_UID"
+#define BOLT_LOG_DEVICE_NAME "BOLT_DEVICE_NAME"
+#define BOLT_LOG_DEVICE_STATE "BOLT_DEVICE_STATE"
+
+#define BOLT_LOG_ERROR_DOMAIN "ERROR_DOMAIN"
+#define BOLT_LOG_ERROR_CODE "ERROR_CODE"
+#define BOLT_LOG_ERROR_MESSAGE "ERROR_MESSAGE"
+
+#define BOLT_LOG_TOPIC "BOLT_TOPIC"
+#define BOLT_LOG_VERSION "BOLT_VERSION"
+#define BOLT_LOG_CONTEXT "BOLT_LOG_CONTEXT"
+#define BOLT_LOG_BUG_MARK "BOLT_LOG_BUG"
+
+/* logging - message ids */
+#define BOLT_LOG_MSG_IDLEN 33
+#define BOLT_LOG_MSG_ID_STARTUP "dd11929c788e48bdbb6276fb5f26b08a"
+
+
+/* dbus */
+
+#define BOLT_DBUS_NAME "org.freedesktop.bolt"
+#define BOLT_DBUS_PATH "/org/freedesktop/bolt"
+#define BOLT_DBUS_PATH_DOMAINS BOLT_DBUS_PATH "/domains"
+#define BOLT_DBUS_PATH_DEVICES BOLT_DBUS_PATH "/devices"
+#define BOLT_DBUS_INTERFACE "org.freedesktop.bolt1.Manager"
+
+#define BOLT_DBUS_DEVICE_INTERFACE "org.freedesktop.bolt1.Device"
+#define BOLT_DBUS_DOMAIN_INTERFACE "org.freedesktop.bolt1.Domain"
+#define BOLT_DBUS_POWER_INTERFACE "org.freedesktop.bolt1.Power"
+
+/* other well known names */
+#define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341"
+
+/* helper functions */
+
+char * bolt_gen_object_path (const char *path_base,
+ const char *object_id);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/bolt-proxy.c b/panels/thunderbolt/bolt-proxy.c
new file mode 100644
index 0000000..e044c87
--- /dev/null
+++ b/panels/thunderbolt/bolt-proxy.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "bolt-proxy.h"
+
+#include "bolt-enums.h"
+#include "bolt-error.h"
+#include "bolt-names.h"
+#include "bolt-str.h"
+
+static void bolt_proxy_handle_props_changed (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ gpointer user_data);
+
+static void bolt_proxy_handle_dbus_signal (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *params,
+ gpointer user_data);
+
+G_DEFINE_TYPE (BoltProxy, bolt_proxy, G_TYPE_DBUS_PROXY);
+
+
+static void
+bolt_proxy_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (bolt_proxy_parent_class)->constructed (object);
+
+ g_signal_connect (object, "g-properties-changed",
+ G_CALLBACK (bolt_proxy_handle_props_changed), object);
+
+ g_signal_connect (object, "g-signal",
+ G_CALLBACK (bolt_proxy_handle_dbus_signal), object);
+}
+
+static const BoltProxySignal *
+bolt_proxy_get_dbus_signals (guint *n)
+{
+ *n = 0;
+ return NULL;
+}
+
+static void
+bolt_proxy_class_init (BoltProxyClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->constructed = bolt_proxy_constructed;
+
+ klass->get_dbus_signals = bolt_proxy_get_dbus_signals;
+
+}
+
+static void
+bolt_proxy_init (BoltProxy *object)
+{
+}
+
+static void
+bolt_proxy_handle_props_changed (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ gpointer user_data)
+{
+ g_autoptr(GVariantIter) iter = NULL;
+ gboolean handled;
+ GParamSpec **pp;
+ const char *key;
+ guint n;
+
+ pp = g_object_class_list_properties (G_OBJECT_GET_CLASS (proxy), &n);
+
+ g_variant_get (changed_properties, "a{sv}", &iter);
+ while (g_variant_iter_next (iter, "{&sv}", &key, NULL))
+ {
+ handled = FALSE;
+ for (guint i = 0; !handled && i < n; i++)
+ {
+ GParamSpec *pspec = pp[i];
+ const char *nick;
+ const char *name;
+
+ nick = g_param_spec_get_nick (pspec);
+ name = g_param_spec_get_name (pspec);
+
+ handled = bolt_streq (nick, key);
+
+ if (handled)
+ g_object_notify (G_OBJECT (user_data), name);
+ }
+ }
+
+ g_free (pp);
+}
+
+static void
+bolt_proxy_handle_dbus_signal (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *params,
+ gpointer user_data)
+{
+ const BoltProxySignal *ps;
+ guint n;
+
+ if (signal_name == NULL)
+ return;
+
+ ps = BOLT_PROXY_GET_CLASS (proxy)->get_dbus_signals (&n);
+
+ for (guint i = 0; i < n; i++)
+ {
+ const BoltProxySignal *sig = &ps[i];
+
+ if (g_str_equal (sig->theirs, signal_name))
+ {
+ sig->handle (G_OBJECT (proxy), proxy, params);
+ break;
+ }
+ }
+
+}
+
+/* public methods */
+
+gboolean
+bolt_proxy_get_dbus_property (GObject *proxy,
+ GParamSpec *spec,
+ GValue *value)
+{
+ g_autoptr(GVariant) val = NULL;
+ const GVariantType *vt;
+ gboolean handled = FALSE;
+ const char *nick;
+
+ nick = g_param_spec_get_nick (spec);
+ val = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), nick);
+
+ if (val == NULL)
+ return FALSE;
+
+ vt = g_variant_get_type (val);
+
+ if (g_variant_type_equal (vt, G_VARIANT_TYPE_STRING) &&
+ G_IS_PARAM_SPEC_ENUM (spec))
+ {
+ GParamSpecEnum *enum_spec = G_PARAM_SPEC_ENUM (spec);
+ GEnumValue *ev;
+ const char *str;
+
+ str = g_variant_get_string (val, NULL);
+ ev = g_enum_get_value_by_nick (enum_spec->enum_class, str);
+
+ handled = ev != NULL;
+
+ if (handled)
+ g_value_set_enum (value, ev->value);
+ else
+ g_value_set_enum (value, enum_spec->default_value);
+ }
+ else if (g_variant_type_equal (vt, G_VARIANT_TYPE_STRING) &&
+ G_IS_PARAM_SPEC_FLAGS (spec))
+ {
+ GParamSpecFlags *flags_spec = G_PARAM_SPEC_FLAGS (spec);
+ GFlagsClass *flags_class = flags_spec->flags_class;
+ const char *str;
+ guint v;
+
+ str = g_variant_get_string (val, NULL);
+ handled = bolt_flags_class_from_string (flags_class, str, &v, NULL);
+
+ if (handled)
+ g_value_set_flags (value, v);
+ else
+ g_value_set_flags (value, flags_spec->default_value);
+ }
+ else
+ {
+ g_dbus_gvariant_to_gvalue (val, value);
+ }
+
+ return handled;
+}
+
+gboolean
+bolt_proxy_has_name_owner (BoltProxy *proxy)
+{
+ const char *name_owner;
+
+ g_return_val_if_fail (proxy != NULL, FALSE);
+ g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE);
+
+ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy));
+
+ return name_owner != NULL;
+}
+
+static GParamSpec *
+find_property (BoltProxy *proxy,
+ const char *name,
+ GError **error)
+{
+ GParamSpec *res = NULL;
+ GParamSpec **pp;
+ guint n;
+
+ pp = g_object_class_list_properties (G_OBJECT_GET_CLASS (proxy), &n);
+
+ for (guint i = 0; i < n; i++)
+ {
+ GParamSpec *pspec = pp[i];
+
+ if (bolt_streq (pspec->name, name))
+ {
+ res = pspec;
+ break;
+ }
+ }
+
+ if (pp == NULL)
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY,
+ "could not find property '%s'", name);
+
+ g_free (pp);
+ return res;
+}
+
+static GVariant *
+bolt_proxy_get_cached_property (BoltProxy *proxy,
+ const char *name)
+{
+ const char *bus_name = NULL;
+ GParamSpec *pspec;
+ GVariant *var;
+
+ g_return_val_if_fail (BOLT_IS_PROXY (proxy), NULL);
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (proxy), name);
+
+ if (pspec == NULL)
+ return NULL;
+
+ bus_name = g_param_spec_get_nick (pspec);
+ var = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), bus_name);
+
+ return var;
+}
+
+gboolean
+bolt_proxy_get_property_bool (BoltProxy *proxy,
+ const char *name,
+ gboolean *value)
+{
+ g_autoptr(GVariant) var = NULL;
+
+ var = bolt_proxy_get_cached_property (proxy, name);
+
+ if (var == NULL)
+ return FALSE;
+ else if (value)
+ *value = g_variant_get_boolean (var);
+
+ return TRUE;
+}
+
+gboolean
+bolt_proxy_get_property_enum (BoltProxy *proxy,
+ const char *name,
+ gint *value)
+{
+ g_autoptr(GVariant) var = NULL;
+ const char *str = NULL;
+ const char *bus_name = NULL;
+ GParamSpec *pspec;
+ GEnumValue *ev;
+
+ g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE);
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (proxy), name);
+
+ if (pspec == NULL)
+ return FALSE;
+
+ bus_name = g_param_spec_get_nick (pspec);
+ var = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), bus_name);
+ if (var == NULL)
+ return FALSE;
+
+ str = g_variant_get_string (var, NULL);
+
+ if (str == NULL)
+ return FALSE;
+
+ ev = g_enum_get_value_by_nick (G_PARAM_SPEC_ENUM (pspec)->enum_class, str);
+
+ if (ev == NULL)
+ return FALSE;
+
+ if (value)
+ *value = ev->value;
+
+ return TRUE;
+}
+
+gboolean
+bolt_proxy_get_property_flags (BoltProxy *proxy,
+ const char *name,
+ guint *value)
+{
+ g_autoptr(GVariant) var = NULL;
+ const char *str = NULL;
+ const char *bus_name = NULL;
+ GFlagsClass *flags_class;
+ GParamSpec *pspec;
+ guint v;
+ gboolean ok;
+
+ g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE);
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (proxy), name);
+
+ if (pspec == NULL || !G_IS_PARAM_SPEC_FLAGS (pspec))
+ return FALSE;
+
+ bus_name = g_param_spec_get_nick (pspec);
+ var = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), bus_name);
+ if (var == NULL)
+ return FALSE;
+
+ str = g_variant_get_string (var, NULL);
+
+ if (str == NULL)
+ return FALSE;
+
+ flags_class = G_PARAM_SPEC_FLAGS (pspec)->flags_class;
+ ok = bolt_flags_class_from_string (flags_class, str, &v, NULL);
+
+ if (ok && value)
+ *value = v;
+
+ return ok;
+}
+
+gboolean
+bolt_proxy_get_property_uint32 (BoltProxy *proxy,
+ const char *name,
+ guint *value)
+{
+ g_autoptr(GVariant) var = NULL;
+
+ var = bolt_proxy_get_cached_property (proxy, name);
+
+ if (var == NULL)
+ return FALSE;
+ else if (value)
+ *value = g_variant_get_uint32 (var);
+
+ return TRUE;
+}
+
+gboolean
+bolt_proxy_get_property_int64 (BoltProxy *proxy,
+ const char *name,
+ gint64 *value)
+{
+ g_autoptr(GVariant) var = NULL;
+
+ var = bolt_proxy_get_cached_property (proxy, name);
+
+ if (var == NULL)
+ return FALSE;
+ else if (value)
+ *value = g_variant_get_int64 (var);
+
+ return TRUE;
+}
+
+gboolean
+bolt_proxy_get_property_uint64 (BoltProxy *proxy,
+ const char *name,
+ guint64 *value)
+{
+ g_autoptr(GVariant) var = NULL;
+
+ var = bolt_proxy_get_cached_property (proxy, name);
+
+ if (var == NULL)
+ return FALSE;
+ else if (value)
+ *value = g_variant_get_uint64 (var);
+
+ return TRUE;
+}
+
+const char *
+bolt_proxy_get_property_string (BoltProxy *proxy,
+ const char *name)
+{
+ g_autoptr(GVariant) var = NULL;
+ const char *val = NULL;
+
+ var = bolt_proxy_get_cached_property (proxy, name);
+
+ if (var != NULL)
+ val = g_variant_get_string (var, NULL);
+
+ if (val && *val == '\0')
+ val = NULL;
+
+ return val;
+}
+
+gboolean
+bolt_proxy_set_property (BoltProxy *proxy,
+ const char *name,
+ GVariant *value,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GParamSpec *pp;
+ const char *iface;
+ gboolean ok = FALSE;
+ GVariant *res;
+
+ pp = find_property (proxy, name, NULL);
+ if (pp != NULL)
+ name = g_param_spec_get_nick (pp);
+
+ iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy));
+
+ res = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy),
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new ("(ssv)",
+ iface,
+ name,
+ value),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ error);
+
+ if (res)
+ {
+ g_variant_unref (res);
+ ok = TRUE;
+ }
+
+ return ok;
+}
+
+void
+bolt_proxy_set_property_async (BoltProxy *proxy,
+ const char *name,
+ GVariant *value,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GParamSpec *pp;
+ const char *iface;
+
+ pp = find_property (proxy, name, NULL);
+
+ if (pp != NULL)
+ name = g_param_spec_get_nick (pp);
+
+ iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy));
+
+ g_dbus_proxy_call (G_DBUS_PROXY (proxy),
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new ("(ssv)",
+ iface,
+ name,
+ value),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ callback,
+ user_data);
+}
+
+gboolean
+bolt_proxy_set_property_finish (GAsyncResult *res,
+ GError **error)
+{
+ BoltProxy *proxy;
+ GVariant *val = NULL;
+
+ proxy = (BoltProxy *) g_async_result_get_source_object (res);
+ val = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error);
+
+ if (val == NULL)
+ return FALSE;
+
+ g_variant_unref (val);
+ return TRUE;
+}
diff --git a/panels/thunderbolt/bolt-proxy.h b/panels/thunderbolt/bolt-proxy.h
new file mode 100644
index 0000000..c05eb8c
--- /dev/null
+++ b/panels/thunderbolt/bolt-proxy.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct BoltProxySignal
+{
+
+ const char *theirs;
+ void (*handle)(GObject *self,
+ GDBusProxy *bus_proxy,
+ GVariant *params);
+
+} BoltProxySignal;
+
+#define BOLT_TYPE_PROXY (bolt_proxy_get_type ())
+G_DECLARE_DERIVABLE_TYPE (BoltProxy, bolt_proxy, BOLT, PROXY, GDBusProxy)
+
+struct _BoltProxyClass
+{
+ GDBusProxyClass parent;
+
+ /* virtuals */
+ const BoltProxySignal * (*get_dbus_signals) (guint *n);
+};
+
+gboolean bolt_proxy_get_dbus_property (GObject *proxy,
+ GParamSpec *spec,
+ GValue *value);
+
+gboolean bolt_proxy_has_name_owner (BoltProxy *proxy);
+
+gboolean bolt_proxy_get_property_bool (BoltProxy *proxy,
+ const char *name,
+ gboolean *value);
+
+gboolean bolt_proxy_get_property_enum (BoltProxy *proxy,
+ const char *name,
+ gint *value);
+
+gboolean bolt_proxy_get_property_flags (BoltProxy *proxy,
+ const char *name,
+ guint *value);
+
+gboolean bolt_proxy_get_property_uint32 (BoltProxy *proxy,
+ const char *name,
+ guint *value);
+
+gboolean bolt_proxy_get_property_int64 (BoltProxy *proxy,
+ const char *name,
+ gint64 *value);
+
+gboolean bolt_proxy_get_property_uint64 (BoltProxy *proxy,
+ const char *name,
+ guint64 *value);
+
+const char * bolt_proxy_get_property_string (BoltProxy *proxy,
+ const char *name);
+
+gboolean bolt_proxy_set_property (BoltProxy *proxy,
+ const char *name,
+ GVariant *value,
+ GCancellable *cancellable,
+ GError **error);
+
+void bolt_proxy_set_property_async (BoltProxy *proxy,
+ const char *name,
+ GVariant *value,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean bolt_proxy_set_property_finish (GAsyncResult *res,
+ GError **error);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/bolt-str.c b/panels/thunderbolt/bolt-str.c
new file mode 100644
index 0000000..fe0580d
--- /dev/null
+++ b/panels/thunderbolt/bolt-str.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "config.h"
+
+#include "bolt-str.h"
+
+#include <string.h>
+
+typedef void (* zero_fn_t) (void *s,
+ size_t n);
+void
+bolt_erase_n (void *data, gsize n)
+{
+#if !HAVE_FN_EXPLICIT_BZERO
+ #warning no explicit bzero, using fallback
+ static volatile zero_fn_t explicit_bzero = bzero;
+#endif
+
+ explicit_bzero (data, n);
+}
+
+void
+bolt_str_erase (char *str)
+{
+ if (str == NULL)
+ return;
+
+ bolt_erase_n (str, strlen (str));
+}
+
+void
+bolt_str_erase_clear (char **str)
+{
+ g_return_if_fail (str != NULL);
+ if (*str == NULL)
+ return;
+
+ bolt_str_erase (*str);
+ g_free (*str);
+ *str = NULL;
+}
+
+GStrv
+bolt_strv_from_ptr_array (GPtrArray **array)
+{
+ GPtrArray *a;
+
+ if (array == NULL || *array == NULL)
+ return NULL;
+
+ a = *array;
+
+ if (a->len == 0 || a->pdata[a->len - 1] != NULL)
+ g_ptr_array_add (a, NULL);
+
+ *array = NULL;
+ return (GStrv) g_ptr_array_free (a, FALSE);
+}
+
+char *
+bolt_strdup_validate (const char *string)
+{
+ g_autofree char *str = NULL;
+ gboolean ok;
+ gsize l;
+
+ if (string == NULL)
+ return NULL;
+
+ str = g_strdup (string);
+ str = g_strstrip (str);
+
+ l = strlen (str);
+ if (l == 0)
+ return NULL;
+
+ ok = g_utf8_validate (str, l, NULL);
+
+ if (!ok)
+ return NULL;
+
+ return g_steal_pointer (&str);
+}
+
+char *
+bolt_strstrip (char *string)
+{
+ char *str;
+
+ if (string == NULL)
+ return NULL;
+
+ str = g_strstrip (string);
+
+ if (strlen (str) == 0)
+ g_clear_pointer (&str, g_free);
+
+ return str;
+}
diff --git a/panels/thunderbolt/bolt-str.h b/panels/thunderbolt/bolt-str.h
new file mode 100644
index 0000000..ecf95a7
--- /dev/null
+++ b/panels/thunderbolt/bolt-str.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <string.h>
+
+G_BEGIN_DECLS
+
+void bolt_erase_n (void *data,
+ gsize n);
+void bolt_str_erase (char *str);
+void bolt_str_erase_clear (char **str);
+
+#define bolt_streq(s1, s2) (g_strcmp0 (s1, s2) == 0)
+
+GStrv bolt_strv_from_ptr_array (GPtrArray **array);
+
+#define bolt_yesno(val) val ? "yes" : "no"
+
+char *bolt_strdup_validate (const char *string);
+
+char *bolt_strstrip (char *string);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/bolt-time.c b/panels/thunderbolt/bolt-time.c
new file mode 100644
index 0000000..606aed6
--- /dev/null
+++ b/panels/thunderbolt/bolt-time.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2018 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#include "config.h"
+
+#include "bolt-time.h"
+
+char *
+bolt_epoch_format (guint64 seconds, const char *format)
+{
+ g_autoptr(GDateTime) dt = NULL;
+
+ dt = g_date_time_new_from_unix_utc ((gint64) seconds);
+
+ if (dt == NULL)
+ return NULL;
+
+ return g_date_time_format (dt, format);
+}
+
+guint64
+bolt_now_in_seconds (void)
+{
+ gint64 now = g_get_real_time ();
+
+ return (guint64) now / G_USEC_PER_SEC;
+}
diff --git a/panels/thunderbolt/bolt-time.h b/panels/thunderbolt/bolt-time.h
new file mode 100644
index 0000000..fc3ed97
--- /dev/null
+++ b/panels/thunderbolt/bolt-time.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2018 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian J. Kellner <christian@kellner.me>
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+char * bolt_epoch_format (guint64 seconds,
+ const char *format);
+
+guint64 bolt_now_in_seconds (void);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/cc-bolt-device-dialog.c b/panels/thunderbolt/cc-bolt-device-dialog.c
new file mode 100644
index 0000000..bfe52b7
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-dialog.c
@@ -0,0 +1,515 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Christian J. Kellner <ckellner@redhat.com>
+ *
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#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_window_set_title (GTK_WINDOW (dialog), 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;
+ BoltDevice *device = dialog->device;
+ GtkWidget *child;
+
+ g_return_if_fail (device != NULL);
+
+ dialog_operation_start (dialog);
+
+ devices = g_ptr_array_new ();
+
+ /* Iter from the last child to the first one */
+ for (child = gtk_widget_get_last_child (GTK_WIDGET (dialog->parents_devices));
+ child;
+ child = gtk_widget_get_prev_sibling (child))
+ {
+ CcBoltDeviceEntry *entry;
+ BoltDevice *dev;
+ BoltStatus status;
+
+ entry = CC_BOLT_DEVICE_ENTRY (child);
+ 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));
+}
+
+/* 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)
+ {
+ GtkWidget *child;
+
+ 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);
+
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (dialog->parents_devices))) != NULL)
+ gtk_list_box_remove (dialog->parents_devices, child);
+
+ 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_list_box_append (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;
+}
diff --git a/panels/thunderbolt/cc-bolt-device-dialog.h b/panels/thunderbolt/cc-bolt-device-dialog.h
new file mode 100644
index 0000000..abdb46f
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-dialog.h
@@ -0,0 +1,47 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Christian J. Kellner <ckellner@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "bolt-client.h"
+#include "bolt-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BOLT_DEVICE_DIALOG cc_bolt_device_dialog_get_type ()
+
+G_DECLARE_FINAL_TYPE (CcBoltDeviceDialog, cc_bolt_device_dialog, CC, BOLT_DEVICE_DIALOG, GtkDialog);
+
+CcBoltDeviceDialog * cc_bolt_device_dialog_new (void);
+
+void cc_bolt_device_dialog_set_client (CcBoltDeviceDialog *dialog,
+ BoltClient *client);
+
+void cc_bolt_device_dialog_set_device (CcBoltDeviceDialog *dialog,
+ BoltDevice *device,
+ GPtrArray *parents);
+
+BoltDevice * cc_bolt_device_dialog_peek_device (CcBoltDeviceDialog *dialog);
+
+gboolean cc_bolt_device_dialog_device_equal (CcBoltDeviceDialog *dialog,
+ BoltDevice *device);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/cc-bolt-device-dialog.ui b/panels/thunderbolt/cc-bolt-device-dialog.ui
new file mode 100644
index 0000000..55dff88
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-dialog.ui
@@ -0,0 +1,325 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="CcBoltDeviceDialog" parent="GtkDialog">
+ <property name="title">Device Identifier</property>
+ <property name="use_header_bar">1</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="hide-on-close">True</property>
+
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="show_title_buttons">True</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="margin-bottom">24</property>
+ <child>
+ <object class="GtkOverlay">
+ <child type="overlay">
+ <object class="GtkRevealer" id="notify_revealer">
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="transition_type">slide-down</property>
+ <child>
+ <object class="GtkFrame">
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="notify_label">
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <accessibility>
+ <property name="label" translatable="yes">Close notification</property>
+ </accessibility>
+ <style>
+ <class name="flat" />
+ </style>
+ <signal name="clicked"
+ handler="on_notify_button_clicked_cb"
+ object="CcBoltDeviceDialog"
+ swapped="no" />
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">window-close-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="app-notification" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkGrid">
+ <property name="margin-start">72</property>
+ <property name="margin-end">72</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">0</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">24</property>
+ <child>
+ <object class="GtkLabel" id="name_title_label">
+ <property name="halign">end</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes">Name:</property>
+ <property name="justify">right</property>
+ <property name="xalign">1</property>
+ <property name="mnemonic_widget">name_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="hexpand">True</property>
+ <property name="label">Device identifier</property>
+ <property name="use_markup">True</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_title_label">
+ <property name="halign">end</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes">Status:</property>
+ <property name="justify">right</property>
+ <property name="xalign">1</property>
+ <property name="mnemonic_widget">status_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_label">
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="label">Status</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="uuid_title_label">
+ <property name="halign">end</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes">UUID:</property>
+ <property name="justify">right</property>
+ <property name="xalign">1</property>
+ <property name="mnemonic_widget">uuid_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="uuid_label">
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="label">Status</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="time_title">
+ <property name="halign">end</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label">Timestamp:</property>
+ <property name="justify">right</property>
+ <property name="xalign">1</property>
+ <property name="mnemonic_widget">time_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="time_label">
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="label">Status</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ <!-- end of grid -->
+ <child>
+ <object class="GtkExpander" id="parents_expander">
+ <property name="visible">False</property>
+ <property name="halign">fill</property>
+ <property name="margin-start">72</property>
+ <property name="margin-end">72</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">0</property>
+ <child type="label">
+ <object class="GtkLabel" id="parents_label">
+ <property name="halign">end</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label">Depends on other devices:</property>
+ <property name="justify">center</property>
+ <property name="xalign">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="parents_devices">
+ <property name="valign">start</property>
+ <property name="vexpand">False</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <!-- end of -->
+ <child>
+ <object class="GtkBox" id="button_box">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="margin-start">72</property>
+ <property name="margin-end">72</property>
+ <property name="margin-top">36</property>
+ <property name="margin-bottom">0</property>
+ <property name="halign">fill</property>
+ <child>
+ <object class="GtkSpinner" id="spinner">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="spinning">False</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="connect_button">
+ <property name="label" translatable="yes">Authorize and Connect</property>
+ <property name="receives_default">True</property>
+ <property name="halign">fill</property>
+
+ <signal name="clicked"
+ handler="on_connect_button_clicked_cb"
+ object="CcBoltDeviceDialog"
+ swapped="yes" />
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="forget_button">
+ <property name="label" translatable="yes">Forget Device</property>
+ <property name="receives_default">False</property>
+ <property name="halign">fill</property>
+ <signal name="clicked"
+ handler="on_forget_button_clicked_cb"
+ object="CcBoltDeviceDialog"
+ swapped="yes" />
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="spinner_box">
+ </object>
+ </child>
+ </object>
+
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+ <object class="GtkSizeGroup" id="device_titles_sizegroup">
+ <widgets>
+ <widget name="name_title_label"/>
+ <widget name="status_title_label"/>
+ <widget name="uuid_title_label"/>
+ <widget name="time_title"/>
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="device_labels_sizegroup">
+ <widgets>
+ <widget name="name_label"/>
+ <widget name="status_label"/>
+ <widget name="uuid_label"/>
+ <widget name="time_label"/>
+ </widgets>
+ </object>
+
+ <object class="GtkSizeGroup" id="actions_sizegroup">
+ <widgets>
+ <widget name="forget_button"/>
+ <widget name="connect_button"/>
+ </widgets>
+ </object>
+
+ <object class="GtkSizeGroup" id="spinner_sizegroup">
+ <widgets>
+ <widget name="spinner"/>
+ <widget name="spinner_box"/>
+ </widgets>
+ </object>
+
+</interface>
diff --git a/panels/thunderbolt/cc-bolt-device-entry.c b/panels/thunderbolt/cc-bolt-device-entry.c
new file mode 100644
index 0000000..2bde1ac
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-entry.c
@@ -0,0 +1,223 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Christian J. Kellner <ckellner@redhat.com>
+ *
+ */
+
+#include <config.h>
+
+#include "bolt-str.h"
+
+#include "cc-bolt-device-entry.h"
+
+#include "cc-thunderbolt-resources.h"
+
+#include <glib/gi18n.h>
+
+#define RESOURCE_UI "/org/gnome/control-center/thunderbolt/cc-bolt-device-entry.ui"
+
+struct _CcBoltDeviceEntry
+{
+ AdwActionRow parent;
+
+ BoltDevice *device;
+
+ /* main ui */
+ GtkLabel *status_warning;
+ gboolean show_warnings;
+};
+
+static const char * device_status_to_brief_for_ui (BoltDevice *dev);
+
+G_DEFINE_TYPE (CcBoltDeviceEntry, cc_bolt_device_entry, ADW_TYPE_ACTION_ROW);
+
+enum
+{
+ SIGNAL_STATUS_CHANGED,
+ SIGNAL_LAST
+};
+
+static guint signals[SIGNAL_LAST] = { 0, };
+
+static void
+entry_set_name (CcBoltDeviceEntry *entry)
+{
+ g_autofree char *name = NULL;
+ BoltDevice *dev = entry->device;
+
+ g_return_if_fail (dev != NULL);
+
+ name = bolt_device_get_display_name (dev);
+
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (entry), name);
+}
+
+static void
+entry_update_status (CcBoltDeviceEntry *entry)
+{
+ const char *brief;
+ BoltStatus status;
+ gboolean warn;
+
+ status = bolt_device_get_status (entry->device);
+ brief = device_status_to_brief_for_ui (entry->device);
+
+ adw_action_row_set_subtitle (ADW_ACTION_ROW (entry), brief);
+
+ g_signal_emit (entry,
+ signals[SIGNAL_STATUS_CHANGED],
+ 0,
+ status);
+
+ warn = entry->show_warnings && bolt_status_is_pending (status);
+ gtk_widget_set_visible (GTK_WIDGET (entry->status_warning), warn);
+}
+
+static void
+on_device_notify_cb (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ CcBoltDeviceEntry *entry = CC_BOLT_DEVICE_ENTRY (user_data);
+ const char *what;
+
+ what = g_param_spec_get_name (pspec);
+
+ if (bolt_streq (what, "status"))
+ entry_update_status (entry);
+ else if (bolt_streq (what, "label") ||
+ bolt_streq (what, "name") ||
+ bolt_streq (what, "vendor"))
+ entry_set_name (entry);
+}
+
+/* device helpers */
+
+static const char *
+device_status_to_brief_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:
+ case BOLT_STATUS_AUTHORIZED_DPONLY:
+ return C_("Thunderbolt Device Status", "Connected");
+
+ case BOLT_STATUS_AUTH_ERROR:
+ return C_("Thunderbolt Device Status", "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:
+ if (nopcie)
+ return C_("Thunderbolt Device Status", "Connected");
+ else
+ return C_("Thunderbolt Device Status", "Authorized");
+
+ case BOLT_STATUS_UNKNOWN:
+ break; /* use function default */
+ }
+
+ return C_("Thunderbolt Device Status", "Unknown");
+}
+
+static void
+cc_bolt_device_entry_finalize (GObject *object)
+{
+ CcBoltDeviceEntry *entry = CC_BOLT_DEVICE_ENTRY (object);
+
+ g_clear_object (&entry->device);
+
+ G_OBJECT_CLASS (cc_bolt_device_entry_parent_class)->finalize (object);
+}
+
+static void
+cc_bolt_device_entry_class_init (CcBoltDeviceEntryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_bolt_device_entry_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, RESOURCE_UI);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceEntry, status_warning);
+
+ signals[SIGNAL_STATUS_CHANGED] =
+ g_signal_new ("status-changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1, BOLT_TYPE_STATUS);
+}
+
+static void
+cc_bolt_device_entry_init (CcBoltDeviceEntry *entry)
+{
+ g_resources_register (cc_thunderbolt_get_resource ());
+ gtk_widget_init_template (GTK_WIDGET (entry));
+}
+
+/* public function */
+
+CcBoltDeviceEntry *
+cc_bolt_device_entry_new (BoltDevice *device,
+ gboolean show_warnings)
+{
+ CcBoltDeviceEntry *entry;
+
+ entry = g_object_new (CC_TYPE_BOLT_DEVICE_ENTRY, NULL);
+ entry->device = g_object_ref (device);
+ entry->show_warnings = show_warnings;
+
+ entry_set_name (entry);
+ entry_update_status (entry);
+
+ g_signal_connect_object (entry->device,
+ "notify",
+ G_CALLBACK (on_device_notify_cb),
+ entry,
+ 0);
+
+ return entry;
+}
+
+BoltDevice *
+cc_bolt_device_entry_get_device (CcBoltDeviceEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+ g_return_val_if_fail (CC_IS_BOLT_DEVICE_ENTRY (entry), NULL);
+
+ return entry->device;
+}
diff --git a/panels/thunderbolt/cc-bolt-device-entry.h b/panels/thunderbolt/cc-bolt-device-entry.h
new file mode 100644
index 0000000..e95e3d8
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-entry.h
@@ -0,0 +1,36 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Christian J. Kellner <ckellner@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <adwaita.h>
+#include <gtk/gtk.h>
+#include "bolt-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BOLT_DEVICE_ENTRY cc_bolt_device_entry_get_type ()
+G_DECLARE_FINAL_TYPE (CcBoltDeviceEntry, cc_bolt_device_entry, CC, BOLT_DEVICE_ENTRY, AdwActionRow);
+
+
+CcBoltDeviceEntry * cc_bolt_device_entry_new (BoltDevice *device,
+ gboolean show_warnings);
+BoltDevice * cc_bolt_device_entry_get_device (CcBoltDeviceEntry *entry);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/cc-bolt-device-entry.ui b/panels/thunderbolt/cc-bolt-device-entry.ui
new file mode 100644
index 0000000..2790f92
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-entry.ui
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcBoltDeviceEntry" parent="AdwActionRow">
+ <property name="activatable">True</property>
+ <child type="suffix">
+ <object class="GtkImage" id="status_warning">
+ <property name="visible">False</property>
+ <property name="icon_name">dialog-warning-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/thunderbolt/cc-bolt-panel.c b/panels/thunderbolt/cc-bolt-panel.c
new file mode 100644
index 0000000..3fe4186
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-panel.c
@@ -0,0 +1,960 @@
+/* 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 <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 */
+ AdwStatusPage *notb_page;
+
+ /* notifications */
+ GtkLabel *notification_label;
+ GtkRevealer *notification_revealer;
+
+ /* authmode */
+ GtkSwitch *authmode_switch;
+ GtkSpinner *authmode_spinner;
+ GtkStack *direct_access_row;
+
+ /* 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 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.");
+
+ adw_status_page_set_description (panel->notb_page, 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 *listbox)
+{
+ GtkWidget *child;
+ gboolean show;
+
+ child = gtk_widget_get_first_child (GTK_WIDGET (listbox));
+ show = child != NULL;
+
+ gtk_widget_set_visible (GTK_WIDGET (listbox), 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 */
+ status = bolt_device_get_status (dev);
+
+ if (bolt_status_is_pending (status))
+ {
+ gtk_list_box_append (panel->pending_list, GTK_WIDGET (entry));
+ gtk_widget_show (GTK_WIDGET (panel->pending_list));
+ gtk_widget_show (GTK_WIDGET (panel->pending_box));
+ }
+ else
+ {
+ gtk_list_box_append (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_list_box_remove (GTK_LIST_BOX (p), 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;
+
+ 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);
+
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (panel->direct_access_row),
+ enabled ?
+ _("Allow direct access to devices such as docks and external GPUs.") :
+ _("Only USB and Display Port devices can attach."));
+}
+
+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_list_box_remove (from, target);
+ gtk_list_box_append (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.");
+ }
+
+ adw_status_page_set_description (panel->notb_page, 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;
+ 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);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ panel = CC_BOLT_PANEL (user_data);
+ 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);
+ }
+
+ panel = CC_BOLT_PANEL (user_data);
+ 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_set_default_size (GTK_WINDOW (panel->device_dialog), 1, 1);
+ gtk_widget_show (GTK_WIDGET (panel->device_dialog));
+}
+
+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. */
+ cc_bolt_device_dialog_set_device (panel->device_dialog, NULL, NULL);
+ g_clear_pointer ((GtkWindow **) &panel->device_dialog, gtk_window_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;
+
+ G_OBJECT_CLASS (cc_bolt_panel_parent_class)->constructed (object);
+
+ shell = cc_panel_get_shell (CC_PANEL (panel));
+ parent = GTK_WINDOW (cc_shell_get_toplevel (shell));
+ gtk_window_set_transient_for (GTK_WINDOW (panel->device_dialog), parent);
+}
+
+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_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, direct_access_row);
+ 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_page);
+ 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_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 ();
+
+ bolt_client_new_async (cc_panel_get_cancellable (CC_PANEL (panel)), bolt_client_ready, g_object_ref (panel));
+}
diff --git a/panels/thunderbolt/cc-bolt-panel.h b/panels/thunderbolt/cc-bolt-panel.h
new file mode 100644
index 0000000..5901044
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-panel.h
@@ -0,0 +1,30 @@
+/* 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>
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BOLT_PANEL cc_bolt_panel_get_type ()
+
+G_DECLARE_FINAL_TYPE (CcBoltPanel, cc_bolt_panel, CC, BOLT_PANEL, CcPanel);
+
+G_END_DECLS
diff --git a/panels/thunderbolt/cc-bolt-panel.ui b/panels/thunderbolt/cc-bolt-panel.ui
new file mode 100644
index 0000000..bb8d96e
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-panel.ui
@@ -0,0 +1,361 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcBoltPanel" parent="CcPanel">
+
+ <!-- Headerbar entries -->
+ <child type="titlebar-end">
+ <object class="GtkBox" id="headerbar_box">
+ <property name="visible">False</property>
+ <property name="spacing">6</property>
+ <property name="halign">end</property>
+ <child>
+ <object class="GtkLockButton" id="lock_button">
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child type="content">
+ <object class="GtkOverlay">
+ <child type="overlay">
+ <object class="GtkRevealer" id="notification_revealer">
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="transition_type">slide-down</property>
+ <child>
+ <object class="GtkFrame">
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="notification_label">
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="can_focus">True</property>
+ <property name="icon-name">window-close-symbolic</property>
+ <accessibility>
+ <property name="label" translatable="yes">Close notification</property>
+ </accessibility>
+ <style>
+ <class name="flat" />
+ </style>
+ <signal name="clicked"
+ handler="on_notification_button_clicked_cb"
+ object="CcBoltPanel"
+ swapped="no" />
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="app-notification" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkStack" id="container">
+ <property name="hhomogeneous">False</property>
+ <property name="vhomogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+
+ <!-- Spinner for when we are creating -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">loading</property>
+ <property name="child">
+ <object class="GtkCenterBox">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <child type="center">
+ <object class="GtkSpinner" id="loading-spinner">
+ <property name="spinning">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- No tunderbolt -->
+
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">no-thunderbolt</property>
+ <property name="child">
+ <object class="AdwStatusPage" id="notb_page">
+ <property name="title" translatable="yes">No Thunderbolt Support</property>
+ <property name="description" translatable="yes">Could not connect to the thunderbolt subsystem.</property>
+ <property name="icon-name">thunderbolt-symbolic</property>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- Normal operation mode (show list of devices) -->
+
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">devices-listing</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+
+ <child>
+ <object class="GtkViewport">
+
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="valign">start</property>
+
+ <!-- Stub box -->
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+
+ <!-- center/content box -->
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="spacing">32</property>
+ <property name="margin_top">32</property>
+ <property name="margin_bottom">32</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Auth Mode -->
+ <child>
+ <object class="GtkBox" id="authmode_box">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Direct Access</property>
+
+ <child>
+ <object class="AdwActionRow" id="direct_access_row">
+ <property name="title" translatable="yes" >Allow direct access to devices such as docks and external GPUs.</property>
+ <child type="suffix">
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">6</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+
+ <child>
+ <object class="GtkSpinner" id="authmode_spinner">
+ <property name="spinning">False</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkSwitch" id="authmode_switch">
+ <property name="halign">end</property>
+ <property name="active">True</property>
+ <signal name="state-set"
+ handler="on_authmode_state_set_cb"
+ object="CcBoltPanel"
+ swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Stack: devices/no-devices -->
+ <child>
+ <object class="GtkStack" id="devices_stack">
+ <property name="transition-type">crossfade</property>
+
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">have-devices</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">32</property>
+
+ <!-- Pending Device List -->
+ <child>
+ <object class="GtkBox" id="pending_box">
+ <property name="visible">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+
+ <!-- Pending Device List: Header -->
+ <child>
+ <object class="GtkBox" id="pending_header">
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage">
+ <property name="icon_name">dialog-warning-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Pending Devices</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="pending_spinner">
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Pending List: Devices -->
+ <child>
+ <object class="GtkFrame">
+ <property name="valign">start</property>
+ <property name="vexpand">False</property>
+ <style>
+ <class name="view" />
+ </style>
+ <child>
+ <object class="GtkListBox" id="pending_list">
+ <property name="selection-mode">none</property>
+ <property name="can_focus">True</property>
+ <signal name="row-activated"
+ handler="on_device_entry_row_activated_cb"
+ object="CcBoltPanel"
+ swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Device List -->
+ <child>
+ <object class="AdwPreferencesGroup" id="devices_box">
+ <property name="title" translatable="yes">Devices</property>
+ <property name="visible">False</property>
+
+
+ <!-- Device List: Devices -->
+ <child>
+ <object class="GtkListBox" id="devices_list">
+ <property name="selection-mode">none</property>
+ <signal name="row-activated"
+ handler="on_device_entry_row_activated_cb"
+ object="CcBoltPanel"
+ swapped="yes" />
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkSpinner" id="probing_spinner">
+ <property name="halign">start</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- No Devices -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">no-devices</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Devices</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">No devices attached</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child> <!-- End of: No Devices -->
+
+ </object>
+ </child> <!-- End of Stack: devices/no-devices -->
+
+ </object>
+ </child> <!-- End of enter/content box -->
+
+
+ <!-- Stub box -->
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+
+ <!-- End of content -->
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+
+ </property>
+ </object>
+ </child>
+
+ <!-- End of 'container' -->
+ </object>
+ </child>
+
+ <!-- End of overlay -->
+ </object>
+ </child>
+ </template>
+
+</interface>
diff --git a/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in b/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in
new file mode 100644
index 0000000..05d33a2
--- /dev/null
+++ b/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Name=Thunderbolt
+Comment=Manage Thunderbolt devices
+Exec=gnome-control-center thunderbolt
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=org.gnome.Settings-thunderbolt-symbolic
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;HardwareSettings;X-GNOME-PrivacySettings;
+OnlyShowIn=GNOME;Unity;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=thunderbolt
+X-GNOME-Bugzilla-Version=@VERSION@
+# Translators: those are keywords for the thunderbolt control-center panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Thunderbolt;privacy;
diff --git a/panels/thunderbolt/icons/meson.build b/panels/thunderbolt/icons/meson.build
new file mode 100644
index 0000000..2633534
--- /dev/null
+++ b/panels/thunderbolt/icons/meson.build
@@ -0,0 +1,4 @@
+install_data(
+ 'scalable/org.gnome.Settings-thunderbolt-symbolic.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps')
+)
diff --git a/panels/thunderbolt/icons/scalable/org.gnome.Settings-thunderbolt-symbolic.svg b/panels/thunderbolt/icons/scalable/org.gnome.Settings-thunderbolt-symbolic.svg
new file mode 100644
index 0000000..53fd173
--- /dev/null
+++ b/panels/thunderbolt/icons/scalable/org.gnome.Settings-thunderbolt-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 7.804688 6 h 3.695312 l -4.339844 6.242188 l 1.671875 0.171874 l -4.183593 3.578126 l 1.964843 -1.035157 c 0.316407 0.039063 1.007813 0.0625 1.335938 0.0625 c 3.867187 0 7.003906 -3.136719 7.003906 -7.003906 c -0.015625 -2.46875 -1.28125 -5.007813 -4.003906 -6.328125 l 0.160156 -1.6796875 z m -6.851563 2.015625 c 0 2.625 1.441406 4.917969 3.582031 6.113281 l 0.316406 -3.71875 l 1.011719 1.203125 l 1.804688 -3.613281 h -3.714844 l 3.160156 -6.933594 c -3.46875 0.414063 -6.160156 3.367188 -6.160156 6.949219" fill="#2e3434"/>
+</svg>
diff --git a/panels/thunderbolt/meson.build b/panels/thunderbolt/meson.build
new file mode 100644
index 0000000..241114f
--- /dev/null
+++ b/panels/thunderbolt/meson.build
@@ -0,0 +1,70 @@
+panels_list += cappletname
+
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+desktop_in = configure_file(
+ input: desktop + '.in.in',
+ output: desktop + '.in',
+ configuration: desktop_conf
+)
+
+i18n.merge_file(
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+)
+
+sources = files(
+ 'bolt-client.c',
+ 'bolt-device.c',
+ 'bolt-enums.c',
+ 'bolt-error.c',
+ 'bolt-names.c',
+ 'bolt-proxy.c',
+ 'bolt-str.c',
+ 'bolt-time.c',
+ 'cc-bolt-panel.c',
+ 'cc-bolt-device-dialog.c',
+ 'cc-bolt-device-entry.c',
+)
+
+enum_headers = [
+ 'bolt-enums.h',
+ 'bolt-error.h'
+]
+
+sources += gnome.mkenums_simple(
+ 'bolt-enum-types',
+ sources: enum_headers)
+
+resource_data = files(
+ 'cc-bolt-device-dialog.ui',
+ 'cc-bolt-device-entry.ui',
+ 'cc-bolt-panel.ui'
+)
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ source_dir: '.',
+ c_name: 'cc_' + cappletname,
+ dependencies: resource_data,
+ export: true
+)
+
+deps = common_deps + [
+ polkit_gobject_dep,
+ m_dep,
+]
+
+panels_libs += static_library(
+ cappletname,
+ sources: sources,
+ include_directories: [top_inc, common_inc],
+ dependencies: deps,
+ c_args: cflags
+)
+
+subdir('icons')
diff --git a/panels/thunderbolt/thunderbolt.gresource.xml b/panels/thunderbolt/thunderbolt.gresource.xml
new file mode 100644
index 0000000..8953d62
--- /dev/null
+++ b/panels/thunderbolt/thunderbolt.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/thunderbolt">
+ <file preprocess="xml-stripblanks">cc-bolt-device-dialog.ui</file>
+ <file preprocess="xml-stripblanks">cc-bolt-device-entry.ui</file>
+ <file preprocess="xml-stripblanks">cc-bolt-panel.ui</file>
+ </gresource>
+</gresources>
+
diff --git a/panels/thunderbolt/update-from-bolt.sh b/panels/thunderbolt/update-from-bolt.sh
new file mode 100755
index 0000000..8b22f08
--- /dev/null
+++ b/panels/thunderbolt/update-from-bolt.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+if [ $# -ne 1 ]; then
+ echo "$0: usage: <BOLT-SOURCE>"
+ exit 1
+fi
+
+boltsrc="$1"
+
+function die() {
+ echo $*
+ exit 1
+}
+
+function copyone() {
+ dst=$1
+ src="$boltsrc/$dst"
+
+ search=(common cli)
+ for base in ${search[*]}
+ do
+ path="$boltsrc/$base/$dst"
+ if [ -f $path ]; then
+ src=$path
+ break;
+ fi
+ done
+
+ if [ ! -f $src ]; then
+ echo -e "$dst \t[ skipped ] $src (ENOENT)"
+ elif cmp -s $src $dst; then
+ echo -e "$dst \t[ unchanged ]"
+ else
+ cp $src $dst || die "$dst [failed] source: $src"
+ echo -e "$dst \t[ updated ] $src"
+ git add $dst
+ fi
+}
+
+names=(client device enums error names proxy str time)
+
+for fn in ${names[*]}
+do
+ header="bolt-$fn.h"
+ source="bolt-$fn.c"
+
+ copyone $header
+ copyone $source
+done
+