gnome-control-center/SOURCES/0003-thunderbolt-new-panel-for-device-management.patch
2021-10-08 11:25:49 +00:00

6389 lines
192 KiB
Diff

From 3d9ad5e5657059e054f011d65e3f81b3723b41a5 Mon Sep 17 00:00:00 2001
From: Christian Kellner <christian@kellner.me>
Date: Mon, 26 Mar 2018 16:18:30 +0200
Subject: [PATCH 3/4] thunderbolt: new panel for device management
Thunderbolt devices need to be approved before they can be used.
This is done via the boltd system daemon and gnome-shell. The new
panel enables the user to manage thunderbolt devices, i.e.:
- forget devices that have previously been authorized
- authorize currently unauthorize devices
Additionally authorization of devices an be temporarily disabled
to ensure no evil device will gain access to the computers
resources.
File starting with "bolt-" are copied from bolt's source tree
and currently correspond to the bolt upstream commit with the id
f22b1cd6104bdc2b33a95d9896b50f29a141b8d8
They can be updated from bolt via the update-from-bolt.sh script.
---
meson.build | 3 +
panels/meson.build | 1 +
panels/thunderbolt/bolt-client.c | 697 +++++++++++++
panels/thunderbolt/bolt-client.h | 107 ++
panels/thunderbolt/bolt-device.c | 604 +++++++++++
panels/thunderbolt/bolt-device.h | 87 ++
panels/thunderbolt/bolt-enums.c | 395 ++++++++
panels/thunderbolt/bolt-enums.h | 249 +++++
panels/thunderbolt/bolt-error.c | 99 ++
panels/thunderbolt/bolt-error.h | 55 +
panels/thunderbolt/bolt-names.h | 50 +
panels/thunderbolt/bolt-proxy.c | 514 ++++++++++
panels/thunderbolt/bolt-proxy.h | 97 ++
panels/thunderbolt/bolt-str.c | 117 +++
panels/thunderbolt/bolt-str.h | 43 +
panels/thunderbolt/bolt-time.c | 44 +
panels/thunderbolt/bolt-time.h | 32 +
panels/thunderbolt/cc-bolt-device-dialog.c | 476 +++++++++
panels/thunderbolt/cc-bolt-device-dialog.h | 45 +
panels/thunderbolt/cc-bolt-device-dialog.ui | 359 +++++++
panels/thunderbolt/cc-bolt-device-entry.c | 218 ++++
panels/thunderbolt/cc-bolt-device-entry.h | 34 +
panels/thunderbolt/cc-bolt-device-entry.ui | 49 +
panels/thunderbolt/cc-bolt-panel.c | 958 ++++++++++++++++++
panels/thunderbolt/cc-bolt-panel.ui | 594 +++++++++++
.../gnome-thunderbolt-panel.desktop.in.in | 17 +
panels/thunderbolt/meson.build | 74 ++
panels/thunderbolt/thunderbolt.gresource.xml | 9 +
panels/thunderbolt/update-from-bolt.sh | 50 +
shell/cc-panel-list.c | 1 +
shell/cc-panel-loader.c | 6 +
31 files changed, 6084 insertions(+)
create mode 100644 panels/thunderbolt/bolt-client.c
create mode 100644 panels/thunderbolt/bolt-client.h
create mode 100644 panels/thunderbolt/bolt-device.c
create mode 100644 panels/thunderbolt/bolt-device.h
create mode 100644 panels/thunderbolt/bolt-enums.c
create mode 100644 panels/thunderbolt/bolt-enums.h
create mode 100644 panels/thunderbolt/bolt-error.c
create mode 100644 panels/thunderbolt/bolt-error.h
create mode 100644 panels/thunderbolt/bolt-names.h
create mode 100644 panels/thunderbolt/bolt-proxy.c
create mode 100644 panels/thunderbolt/bolt-proxy.h
create mode 100644 panels/thunderbolt/bolt-str.c
create mode 100644 panels/thunderbolt/bolt-str.h
create mode 100644 panels/thunderbolt/bolt-time.c
create mode 100644 panels/thunderbolt/bolt-time.h
create mode 100644 panels/thunderbolt/cc-bolt-device-dialog.c
create mode 100644 panels/thunderbolt/cc-bolt-device-dialog.h
create mode 100644 panels/thunderbolt/cc-bolt-device-dialog.ui
create mode 100644 panels/thunderbolt/cc-bolt-device-entry.c
create mode 100644 panels/thunderbolt/cc-bolt-device-entry.h
create mode 100644 panels/thunderbolt/cc-bolt-device-entry.ui
create mode 100644 panels/thunderbolt/cc-bolt-panel.c
create mode 100644 panels/thunderbolt/cc-bolt-panel.ui
create mode 100644 panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in
create mode 100644 panels/thunderbolt/meson.build
create mode 100644 panels/thunderbolt/thunderbolt.gresource.xml
create mode 100755 panels/thunderbolt/update-from-bolt.sh
diff --git a/meson.build b/meson.build
index 90ee21cb0f39..ab0e91af627a 100644
--- a/meson.build
+++ b/meson.build
@@ -203,6 +203,7 @@ if host_is_linux_not_s390
description: 'Define to 1 if libwacom provides definition for 3D styli')
else
message('Bluetooth and Wacom panels will not be built (no USB support on this platform)')
+ message('Thunderbolt panel will not be built (not supported on this platform)')
endif
config_h.set('BUILD_BLUETOOTH', host_is_linux_not_s390,
description: 'Define to 1 to build the Bluetooth panel')
@@ -212,6 +213,8 @@ config_h.set('BUILD_WACOM', host_is_linux_not_s390,
description: 'Define to 1 to build the Wacom panel')
config_h.set('HAVE_WACOM', host_is_linux_not_s390,
description: 'Define to 1 if Wacom is supportted')
+config_h.set('BUILD_THUNDERBOLT', host_is_linux_not_s390,
+ description: 'Define to 1 to build the Thunderbolt panel')
# Check for info panel
gnome_session_libexecdir = get_option('gnome_session_libexecdir')
diff --git a/panels/meson.build b/panels/meson.build
index d671c4775736..37a343642218 100644
--- a/panels/meson.build
+++ b/panels/meson.build
@@ -28,6 +28,7 @@ endif
if host_is_linux_not_s390
panels += [
'bluetooth',
+ 'thunderbolt',
'wacom'
]
endif
diff --git a/panels/thunderbolt/bolt-client.c b/panels/thunderbolt/bolt-client.c
new file mode 100644
index 000000000000..0ebc360b18ff
--- /dev/null
+++ b/panels/thunderbolt/bolt-client.c
@@ -0,0 +1,697 @@
+/*
+ * 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)
+{
+ g_autoptr(GError) error = NULL;
+ GTask *task = user_data;
+ GObject *obj;
+
+ obj = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, &error);
+
+ if (obj == NULL)
+ {
+ g_task_return_error (task, error);
+ 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)
+{
+ g_autoptr(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: ");
+ g_task_return_error (task, error);
+ 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)
+{
+ 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;
+ }
+
+ if (path != NULL)
+ g_variant_get (val, "(o)", path);
+
+ return TRUE;
+}
+
+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 000000000000..85382301182b
--- /dev/null
+++ b/panels/thunderbolt/bolt-client.h
@@ -0,0 +1,107 @@
+/*
+ * 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);
+
+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 000000000000..b316950d3b81
--- /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 000000000000..ffd09f9a8ad7
--- /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 000000000000..de77737f8088
--- /dev/null
+++ b/panels/thunderbolt/bolt-enums.c
@@ -0,0 +1,395 @@
+/*
+ * 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>
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GFlagsClass, g_type_class_unref);
+
+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 000000000000..6e2953fa2fd2
--- /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 000000000000..37d844e4a14d
--- /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 000000000000..39b3eee98917
--- /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.
+ */
+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.h b/panels/thunderbolt/bolt-names.h
new file mode 100644
index 000000000000..2c0a97b24b49
--- /dev/null
+++ b/panels/thunderbolt/bolt-names.h
@@ -0,0 +1,50 @@
+/*
+ * 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
+
+/* D-Bus API revision (here for the lack of a better place) */
+#define BOLT_DBUS_API_VERSION 1U
+
+/* logging */
+
+#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"
+
+/* logging - message ids */
+#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_INTERFACE "org.freedesktop.bolt1.Manager"
+
+#define BOLT_DBUS_DEVICE_INTERFACE "org.freedesktop.bolt1.Device"
diff --git a/panels/thunderbolt/bolt-proxy.c b/panels/thunderbolt/bolt-proxy.c
new file mode 100644
index 000000000000..e044c871f747
--- /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 000000000000..c05eb8c8850f
--- /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 000000000000..fe0580d4863a
--- /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 000000000000..ecf95a7ed885
--- /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 000000000000..606aed69a444
--- /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 000000000000..fc3ed9741940
--- /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 000000000000..11469d46cb0b
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-dialog.c
@@ -0,0 +1,476 @@
+/* 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"
+
+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;
+
+ /* actions */
+ GtkWidget *button_box;
+ GtkSpinner *spinner;
+ GtkButton *connect_button;
+ GtkButton *forget_button;
+};
+
+static void on_notify_button_clicked_cb (GtkButton *button,
+ CcBoltDeviceDialog *panel);
+
+static void on_forget_button_clicked_cb (CcBoltDeviceDialog *dialog);
+static void on_connect_button_clicked_cb (CcBoltDeviceDialog *dialog);
+
+G_DEFINE_TYPE (CcBoltDeviceDialog, cc_bolt_device_dialog, GTK_TYPE_DIALOG);
+
+#define RESOURCE_UI "/org/gnome/control-center/thunderbolt/cc-bolt-device-dialog.ui"
+
+static const char *
+status_to_string_for_ui (BoltDevice *dev)
+{
+ BoltStatus status;
+ BoltAuthFlags aflags;
+ gboolean nopcie;
+
+ status = bolt_device_get_status (dev);
+ aflags = bolt_device_get_authflags(dev);
+ nopcie = bolt_flag_isset (aflags, BOLT_AUTH_NOPCIE);
+
+ switch (status)
+ {
+ case BOLT_STATUS_DISCONNECTED:
+ return C_("Thunderbolt Device Status", "Disconnected");
+
+ case BOLT_STATUS_CONNECTING:
+ return C_("Thunderbolt Device Status", "Connecting");
+
+ case BOLT_STATUS_CONNECTED:
+ return C_("Thunderbolt Device Status", "Connected");
+
+ case BOLT_STATUS_AUTH_ERROR:
+ return C_("Thunderbolt Device Status", "Authorization Error");
+
+ case BOLT_STATUS_AUTHORIZING:
+ return C_("Thunderbolt Device Status", "Authorizing");
+
+ case BOLT_STATUS_AUTHORIZED:
+ case BOLT_STATUS_AUTHORIZED_NEWKEY:
+ case BOLT_STATUS_AUTHORIZED_SECURE:
+ case BOLT_STATUS_AUTHORIZED_DPONLY:
+ if (nopcie)
+ return C_("Thunderbolt Device Status", "Reduced Functionality");
+ else
+ return C_("Thunderbolt Device Status", "Connected & Authorized");
+
+ case BOLT_STATUS_UNKNOWN:
+ break; /* use default return value, i.e. Unknown */
+ }
+
+ return C_("Thunderbolt Device Status", "Unknown");
+}
+
+static void
+dialog_update_from_device (CcBoltDeviceDialog *dialog)
+{
+ g_autofree char *generated = NULL;
+ g_autofree char *timestr = NULL;
+ const char *label;
+ const char *uuid;
+ const char *status_brief;
+ BoltStatus status;
+ gboolean stored;
+ BoltDevice *dev;
+ guint timestamp;
+
+ if (gtk_widget_in_destruction (GTK_WIDGET (dialog)))
+ return;
+
+ dev = dialog->device;
+
+ uuid = bolt_device_get_uid (dev);
+ label = bolt_device_get_label (dev);
+
+ stored = bolt_device_is_stored (dev);
+ status = bolt_device_get_status (dev);
+
+ if (label == NULL)
+ {
+ const char *name = bolt_device_get_name (dev);
+ const char *vendor = bolt_device_get_vendor (dev);
+
+ generated = g_strdup_printf ("%s %s", name, vendor);
+ label = generated;
+ }
+
+ gtk_label_set_label (dialog->name_label, label);
+ gtk_header_bar_set_title (dialog->header_bar, label);
+
+ status_brief = status_to_string_for_ui (dev);
+ gtk_label_set_label (dialog->status_label, status_brief);
+ gtk_widget_set_visible (GTK_WIDGET (dialog->forget_button), stored);
+
+ /* while we are having an ongoing operation we are setting the buttons
+ * to be in-sensitive. In that case, if the button was visible
+ * before it will be hidden when the operation is finished by the
+ * dialog_operation_done() function */
+ if (gtk_widget_is_sensitive (GTK_WIDGET (dialog->connect_button)))
+ gtk_widget_set_visible (GTK_WIDGET (dialog->connect_button),
+ status == BOLT_STATUS_CONNECTED);
+
+ gtk_label_set_label (dialog->uuid_label, uuid);
+
+ if (bolt_status_is_authorized (status))
+ {
+ /* Translators: The time point the device was authorized. */
+ gtk_label_set_label (dialog->time_title, _("Authorized at:"));
+ timestamp = bolt_device_get_authtime (dev);
+ }
+ else if (bolt_status_is_connected (status))
+ {
+ /* Translators: The time point the device was connected. */
+ gtk_label_set_label (dialog->time_title, _("Connected at:"));
+ timestamp = bolt_device_get_conntime (dev);
+ }
+ else
+ {
+ /* Translators: The time point the device was enrolled,
+ * i.e. authorized and stored in the device database. */
+ gtk_label_set_label (dialog->time_title, _("Enrolled at:"));
+ timestamp = bolt_device_get_storetime (dev);
+ }
+
+ timestr = bolt_epoch_format (timestamp, "%c");
+ gtk_label_set_label (dialog->time_label, timestr);
+
+}
+
+static void
+on_device_notify_cb (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data);
+
+ dialog_update_from_device (dialog);
+}
+
+static void
+dialog_operation_start (CcBoltDeviceDialog *dialog)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (dialog->connect_button), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (dialog->forget_button), FALSE);
+ gtk_spinner_start (dialog->spinner);
+}
+
+static void
+dialog_operation_done (CcBoltDeviceDialog *dialog,
+ GtkWidget *sender,
+ GError *error)
+{
+ GtkWidget *cb = GTK_WIDGET (dialog->connect_button);
+ GtkWidget *fb = GTK_WIDGET (dialog->forget_button);
+
+ /* don' do anything if we are being destroyed */
+ if (gtk_widget_in_destruction (GTK_WIDGET (dialog)))
+ return;
+
+ /* also don't do anything if the op was canceled */
+ if (error != NULL && bolt_err_cancelled (error))
+ return;
+
+ gtk_spinner_stop (dialog->spinner);
+
+ if (error != NULL)
+ {
+ gtk_label_set_label (dialog->notify_label, error->message);
+ gtk_revealer_set_reveal_child (dialog->notify_revealer, TRUE);
+
+ /* set the *other* button to sensitive */
+ gtk_widget_set_sensitive (cb, cb != sender);
+ gtk_widget_set_sensitive (fb, fb != sender);
+ }
+ else
+ {
+ gtk_widget_set_visible (sender, FALSE);
+ gtk_widget_set_sensitive (cb, TRUE);
+ gtk_widget_set_sensitive (fb, TRUE);
+ }
+}
+
+static void
+dialog_authorize_done (CcBoltDeviceDialog *dialog,
+ gboolean ok,
+ GError *error)
+{
+ if (!ok)
+ g_prefix_error (&error, _("Failed to authorize device: "));
+
+ dialog_operation_done (dialog, GTK_WIDGET (dialog->connect_button), error);
+}
+
+static void
+on_device_authorized (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) err = NULL;
+ CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data);
+ gboolean ok;
+
+ ok = bolt_device_authorize_finish (BOLT_DEVICE (source), res, &err);
+ dialog_authorize_done (dialog, ok, err);
+}
+
+static void
+on_device_enrolled (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_enroll_device_finish (dialog->client, res, NULL, &err);
+ dialog_authorize_done (dialog, ok, err);
+}
+
+static void
+on_connect_button_clicked_cb (CcBoltDeviceDialog *dialog)
+{
+ BoltDevice *device = dialog->device;
+ gboolean stored;
+
+ g_return_if_fail (device != NULL);
+
+ dialog_operation_start (dialog);
+
+ stored = bolt_device_is_stored (device);
+ if (stored)
+ {
+ bolt_device_authorize_async (device,
+ BOLT_AUTHCTRL_NONE,
+ dialog->cancel,
+ on_device_authorized,
+ dialog);
+ }
+ else
+ {
+ const char *uid = bolt_device_get_uid (device);
+
+ bolt_client_enroll_device_async (dialog->client,
+ uid,
+ BOLT_POLICY_DEFAULT,
+ BOLT_AUTHCTRL_NONE,
+ dialog->cancel,
+ on_device_enrolled,
+ 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, 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)
+{
+ if (device == dialog->device)
+ return;
+
+ if (dialog->device)
+ {
+ g_cancellable_cancel (dialog->cancel);
+ g_clear_object (&dialog->cancel);
+ dialog->cancel = g_cancellable_new ();
+
+ g_signal_handlers_disconnect_by_func (dialog->device,
+ G_CALLBACK (on_device_notify_cb),
+ dialog);
+ g_clear_object (&dialog->device);
+ }
+
+ 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);
+}
+
+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 000000000000..d2c44c8589a8
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-dialog.h
@@ -0,0 +1,45 @@
+/* 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);
+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 000000000000..cd19796db20d
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-dialog.ui
@@ -0,0 +1,359 @@
+<?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="can_focus">False</property>
+ <property name="type_hint">dialog</property>
+ <property name="use_header_bar">1</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_close_button">True</property>
+ <property name="title">Device Identifier</property>
+ <property name="subtitle"></property>
+ <property name="has-subtitle">False</property>
+ </object>
+ </child>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-bottom">24</property>
+ <property name="border-width">0</property>
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child type="overlay">
+ <object class="GtkRevealer" id="notify_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="transition_type">slide-down</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="notify_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">none</property>
+ <signal name="clicked"
+ handler="on_notify_button_clicked_cb"
+ object="CcBoltDeviceDialog"
+ swapped="no" />
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="expand">True</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin-left">72</property>
+ <property name="margin-right">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="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_title_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="uuid_title_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="uuid_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="time_title">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="time_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">1</property>
+ <property name="position">1</property>
+ </packing>
+ <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>
+ </child>
+ <!-- end of grid -->
+ <child>
+ <object class="GtkBox" id="button_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="margin-left">72</property>
+ <property name="margin-right">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="visible">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="active">False</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="connect_button">
+ <property name="label" translatable="yes">Authorize and Connect</property>
+ <property name="visible">True</property>
+ <property name="no_show_all">True</property>
+ <property name="can_focus">True</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>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="forget_button">
+ <property name="label" translatable="yes">Forget Device</property>
+ <property name="visible">True</property>
+ <property name="no_show_all">True</property>
+ <property name="can_focus">True</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>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="spinner_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+
+ <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>
+
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/thunderbolt/cc-bolt-device-entry.c b/panels/thunderbolt/cc-bolt-device-entry.c
new file mode 100644
index 000000000000..1e6d6e75a228
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-entry.c
@@ -0,0 +1,218 @@
+/* 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>
+
+struct _CcBoltDeviceEntry
+{
+ GtkListBoxRow parent;
+
+ BoltDevice *device;
+
+ /* main ui */
+ GtkLabel *name_label;
+ GtkLabel *status_label;
+};
+
+static const char * device_status_to_brief_for_ui (BoltDevice *dev);
+
+enum
+{
+ SIGNAL_STATUS_CHANGED,
+ SIGNAL_LAST
+};
+
+static guint signals[SIGNAL_LAST] = {0};
+
+G_DEFINE_TYPE (CcBoltDeviceEntry, cc_bolt_device_entry, GTK_TYPE_LIST_BOX_ROW);
+
+#define RESOURCE_UI "/org/gnome/control-center/thunderbolt/cc-bolt-device-entry.ui"
+
+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);
+
+ gtk_label_set_label (entry->name_label, name);
+}
+
+static void
+entry_update_status (CcBoltDeviceEntry *entry)
+{
+ const char *brief;
+ BoltStatus status;
+
+ status = bolt_device_get_status (entry->device);
+ brief = device_status_to_brief_for_ui (entry->device);
+
+ gtk_label_set_label (entry->status_label, brief);
+
+ g_signal_emit (entry,
+ signals[SIGNAL_STATUS_CHANGED],
+ 0,
+ status);
+}
+
+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, name_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceEntry, status_label);
+
+ 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)
+{
+ CcBoltDeviceEntry *entry;
+
+ entry = g_object_new (CC_TYPE_BOLT_DEVICE_ENTRY, NULL);
+ entry->device = g_object_ref (device);
+
+ 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 000000000000..f93cee5f825b
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-entry.h
@@ -0,0 +1,34 @@
+/* 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-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, GtkListBoxRow);
+
+
+CcBoltDeviceEntry * cc_bolt_device_entry_new (BoltDevice *device);
+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 000000000000..37f865333d71
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-device-entry.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+
+ <template class="CcBoltDeviceEntry" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="activatable">True</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="border-width">12</property>
+ <property name="margin_left">6</property>
+ <property name="margin_right">6</property>
+ <property name="column-spacing">12</property>
+ <property name="row-spacing">2</property>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="ellipsize">end</property>
+ <property name="use-markup">True</property>
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="label">Device Name</property>
+ <property name="xalign">0.0</property>
+ <property name="valign">center</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_label">
+ <property name="visible">True</property>
+ <property name="hexpand">False</property>
+ <property name="label">Status</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="xalign">1.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ </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 000000000000..e67e3625eb2c
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-panel.c
@@ -0,0 +1,958 @@
+/* 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 <shell/cc-panel.h>
+#include <shell/list-box-helper.h>
+
+#include <glib/gi18n.h>
+#include <polkit/polkit.h>
+
+#include "cc-bolt-device-dialog.h"
+#include "cc-bolt-device-entry.h"
+
+#include "bolt-client.h"
+#include "bolt-str.h"
+
+#include "cc-thunderbolt-resources.h"
+
+#define CC_TYPE_BOLT_PANEL cc_bolt_panel_get_type ()
+G_DECLARE_FINAL_TYPE (CcBoltPanel, cc_bolt_panel, CC, BOLT_PANEL, CcPanel);
+
+struct _CcBoltPanel
+{
+ CcPanel parent;
+
+ BoltClient *client;
+ GCancellable *cancel;
+
+ /* headerbar menu */
+ GtkBox *headerbar_box;
+ GtkLockButton *lock_button;
+
+ /* main ui */
+ GtkStack *container;
+
+ /* empty state */
+ GtkLabel *notb_caption;
+ GtkLabel *notb_details;
+
+ /* notifications */
+ GtkLabel *notification_label;
+ GtkRevealer *notification_revealer;
+
+ /* authmode */
+ GtkSwitch *authmode_switch;
+ GtkSpinner *authmode_spinner;
+ GtkStack *authmode_mode;
+
+ /* device list */
+ GHashTable *devices;
+
+ GtkStack *devices_stack;
+ GtkBox *devices_box;
+ GtkBox *pending_box;
+
+ GtkListBox *devices_list;
+ GtkListBox *pending_list;
+
+ /* device details dialog */
+ CcBoltDeviceDialog *device_dialog;
+
+ /* polkit integration */
+ GPermission *permission;
+};
+
+/* initialization */
+static void bolt_client_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data);
+
+/* panel functions */
+static void cc_bolt_panel_set_no_thunderbolt (CcBoltPanel *panel,
+ const char *custom_msg);
+
+static void cc_bolt_panel_name_owner_changed (CcBoltPanel *panel);
+
+static CcBoltDeviceEntry * cc_bolt_panel_add_device (CcBoltPanel *panel,
+ BoltDevice *dev);
+
+static void cc_bolt_panel_del_device_entry (CcBoltPanel *panel,
+ CcBoltDeviceEntry *entry);
+
+static void cc_bolt_panel_authmode_sync (CcBoltPanel *panel);
+
+static void cc_panel_list_box_migrate (CcBoltPanel *panel,
+ GtkListBox *from,
+ GtkListBox *to,
+ CcBoltDeviceEntry *entry);
+
+/* bolt client signals */
+static void on_bolt_name_owner_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+static void on_bolt_device_added_cb (BoltClient *cli,
+ const char *path,
+ CcBoltPanel *panel);
+
+static void on_bolt_device_removed_cb (BoltClient *cli,
+ const char *opath,
+ CcBoltPanel *panel);
+
+static void on_bolt_notify_authmode_cb (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+/* panel signals */
+static gboolean on_authmode_state_set_cb (CcBoltPanel *panel,
+ gboolean state,
+ GtkSwitch *toggle);
+
+static void on_device_entry_row_activated_cb (CcBoltPanel *panel,
+ GtkListBoxRow *row);
+
+static gboolean on_device_dialog_delete_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ CcBoltPanel *panel);
+
+static void on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
+ BoltStatus new_status,
+ CcBoltPanel *panel);
+
+static void on_notification_button_clicked_cb (GtkButton *button,
+ CcBoltPanel *panel);
+
+
+/* polkit */
+static void on_permission_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void on_permission_notify_cb (GPermission *permission,
+ GParamSpec *pspec,
+ CcBoltPanel *panel);
+
+/* device related helpers helpers */
+static gint device_entries_sort_by_recency (GtkListBoxRow *a_row,
+ GtkListBoxRow *b_row,
+ gpointer user_data);
+
+static gint device_entries_sort_by_syspath (GtkListBoxRow *a_row,
+ GtkListBoxRow *b_row,
+ gpointer user_data);
+
+#define RESOURCE_PANEL_UI "/org/gnome/control-center/thunderbolt/cc-bolt-panel.ui"
+
+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 setup properly.");
+
+ gtk_label_set_label (panel->notb_details, text);
+ gtk_stack_set_visible_child_name (panel->container, "no-thunderbolt");
+
+ return;
+ }
+
+ g_signal_connect_object (client, "notify::g-name-owner",
+ G_CALLBACK (on_bolt_name_owner_changed_cb),
+ panel, 0);
+
+ g_signal_connect_object (client, "device-added",
+ G_CALLBACK (on_bolt_device_added_cb),
+ panel, 0);
+
+ g_signal_connect_object (client, "device-removed",
+ G_CALLBACK (on_bolt_device_removed_cb),
+ panel, 0);
+
+ g_signal_connect_object (client, "notify::auth-mode",
+ G_CALLBACK (on_bolt_notify_authmode_cb),
+ panel, 0);
+
+ 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_SYNC_CREATE);
+
+ g_object_bind_property (panel->authmode_switch, "active",
+ panel->pending_box, "sensitive",
+ 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(GError) err = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+ g_autoptr(GHashTable) old = NULL;
+
+ devices = bolt_client_list_devices (panel->client, panel->cancel, &err);
+
+ if (devices == NULL)
+ {
+ 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 (guint i = 0; i < devices->len; i++)
+ {
+ BoltDevice *dev = g_ptr_array_index (devices, i);
+ const char *path;
+ gboolean found;
+
+ path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
+ found = devices_table_transfer_entry (old, panel->devices, path);
+
+ if (found)
+ continue;
+
+ cc_bolt_panel_add_device (panel, dev);
+ }
+
+ devices_table_clear_entries (old, panel);
+ gtk_stack_set_visible_child_name (panel->container, "devices-listing");
+}
+
+static gboolean
+list_box_sync_visible (GtkListBox *lstbox)
+{
+ g_autoptr(GList) children = NULL;
+ gboolean show;
+
+ children = gtk_container_get_children (GTK_CONTAINER (lstbox));
+ show = g_list_length (children) > 0;
+
+ gtk_widget_set_visible (GTK_WIDGET (lstbox), show);
+
+ return show;
+}
+
+static GtkWidget *
+cc_bolt_panel_box_for_listbox (CcBoltPanel *panel,
+ GtkListBox *lstbox)
+{
+ if ((gpointer) lstbox == panel->devices_list)
+ return GTK_WIDGET (panel->devices_box);
+ else if ((gpointer) lstbox == panel->pending_list)
+ return GTK_WIDGET (panel->pending_box);
+
+ g_return_val_if_reached (NULL);
+}
+
+static CcBoltDeviceEntry *
+cc_bolt_panel_add_device (CcBoltPanel *panel,
+ BoltDevice *dev)
+{
+ CcBoltDeviceEntry *entry;
+ BoltDeviceType type;
+ BoltStatus status;
+ const char *path;
+
+ type = bolt_device_get_device_type (dev);
+
+ if (type != BOLT_DEVICE_PERIPHERAL)
+ return FALSE;
+
+ entry = cc_bolt_device_entry_new (dev);
+ path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
+
+ /* add to the list box */
+ gtk_widget_show_all (GTK_WIDGET (entry));
+
+ status = bolt_device_get_status (dev);
+
+ if (bolt_status_is_pending (status))
+ {
+ gtk_container_add (GTK_CONTAINER (panel->pending_list), GTK_WIDGET (entry));
+ gtk_widget_show_all (GTK_WIDGET (panel->pending_list));
+ gtk_widget_show_all (GTK_WIDGET (panel->pending_box));
+ }
+ else
+ {
+ gtk_container_add (GTK_CONTAINER (panel->devices_list), GTK_WIDGET (entry));
+ gtk_widget_show_all (GTK_WIDGET (panel->devices_list));
+ gtk_widget_show_all (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);
+ }
+
+ p = gtk_widget_get_parent (GTK_WIDGET (entry));
+ gtk_widget_destroy (GTK_WIDGET (entry));
+
+ box = cc_bolt_panel_box_for_listbox (panel, GTK_LIST_BOX (p));
+ show = list_box_sync_visible (GTK_LIST_BOX (p));
+ gtk_widget_set_visible (box, show);
+
+ if (!gtk_widget_is_visible (GTK_WIDGET (panel->pending_list)) &&
+ !gtk_widget_is_visible (GTK_WIDGET (panel->devices_list)))
+ gtk_stack_set_visible_child_name (panel->devices_stack, "no-devices");
+}
+
+static void
+cc_bolt_panel_authmode_sync (CcBoltPanel *panel)
+{
+ BoltClient *client = panel->client;
+ BoltAuthMode mode;
+ gboolean enabled;
+ const char *name;
+
+ mode = bolt_client_get_authmode (client);
+
+ enabled = (mode & BOLT_AUTH_ENABLED) != 0;
+
+ g_signal_handlers_block_by_func (panel->authmode_switch,
+ on_authmode_state_set_cb,
+ panel);
+
+ gtk_switch_set_state (panel->authmode_switch, enabled);
+
+ g_signal_handlers_unblock_by_func (panel->authmode_switch,
+ on_authmode_state_set_cb,
+ panel);
+
+ name = enabled ? "enabled" : "disabled";
+ gtk_stack_set_visible_child_name (panel->authmode_mode, name);
+}
+
+static void
+cc_panel_list_box_migrate (CcBoltPanel *panel,
+ GtkListBox *from,
+ GtkListBox *to,
+ CcBoltDeviceEntry *entry)
+{
+ GtkWidget *from_box;
+ GtkWidget *to_box;
+ gboolean show;
+ GtkWidget *target;
+
+ target = GTK_WIDGET (entry);
+
+ gtk_container_remove (GTK_CONTAINER (from), target);
+ gtk_container_add (GTK_CONTAINER (to), target);
+ gtk_widget_show_all (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 == NULL)
+ msg = _("Thunderbolt could not be detected.\n"
+ "Either the system lacks Thunderbolt support, "
+ "it has been disabled in the BIOS or is set to "
+ "an unsupported security level in the BIOS.");
+
+ gtk_label_set_label (panel->notb_details, msg);
+ gtk_stack_set_visible_child_name (panel->container, "no-thunderbolt");
+}
+
+static void
+cc_bolt_panel_name_owner_changed (CcBoltPanel *panel)
+{
+ BoltClient *client = panel->client;
+ BoltSecurity sl;
+ gboolean notb = TRUE;
+ const char *text = NULL;
+ const char *name_owner;
+
+ 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 = NULL;
+ 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,
+ panel->cancel,
+ 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)
+{
+ CcBoltPanel *panel = CC_BOLT_PANEL (user_data);
+
+ cc_bolt_panel_name_owner_changed (panel);
+}
+
+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, panel->cancel, &err);
+
+ if (dev == NULL)
+ {
+ 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 == NULL)
+ 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)
+{
+ CcBoltPanel *panel = CC_BOLT_PANEL (user_data);
+
+ cc_bolt_panel_authmode_sync (panel);
+}
+
+/* panel signals */
+
+static void
+on_authmode_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ CcBoltPanel *panel = CC_BOLT_PANEL (user_data);
+ gboolean ok;
+
+ ok = bolt_client_set_authmode_finish (BOLT_CLIENT (source_object), res, &error);
+ if (!ok)
+ {
+ g_autofree char *text;
+
+ g_warning ("Could not set authmode: %s", error->message);
+
+ text = g_strdup_printf (_("Error switching direct mode: %s"), error->message);
+ gtk_label_set_markup (panel->notification_label, text);
+ gtk_revealer_set_reveal_child (panel->notification_revealer, TRUE);
+
+ /* make sure we are reflecting the correct state */
+ cc_bolt_panel_authmode_sync (panel);
+ }
+
+ gtk_spinner_stop (panel->authmode_spinner);
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), TRUE);
+}
+
+static gboolean
+on_authmode_state_set_cb (CcBoltPanel *panel,
+ gboolean enable,
+ GtkSwitch *toggle)
+{
+ BoltClient *client = panel->client;
+ BoltAuthMode mode;
+
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), FALSE);
+ gtk_spinner_start (panel->authmode_spinner);
+
+ mode = bolt_client_get_authmode (client);
+
+ if (enable)
+ mode = mode | BOLT_AUTH_ENABLED;
+ else
+ mode = mode & ~BOLT_AUTH_ENABLED;
+
+ bolt_client_set_authmode_async (client, mode, NULL, on_authmode_ready, panel);
+
+ return TRUE;
+}
+
+static void
+on_device_entry_row_activated_cb (CcBoltPanel *panel,
+ GtkListBoxRow *row)
+{
+ CcBoltDeviceEntry *entry;
+ BoltDevice *device;
+
+ if (!CC_IS_BOLT_DEVICE_ENTRY (row))
+ return;
+
+ entry = CC_BOLT_DEVICE_ENTRY (row);
+ device = cc_bolt_device_entry_get_device (entry);
+
+ cc_bolt_device_dialog_set_device (panel->device_dialog, device);
+ gtk_window_resize (GTK_WINDOW (panel->device_dialog), 1, 1);
+ gtk_widget_show (GTK_WIDGET (panel->device_dialog));
+}
+
+static gboolean
+on_device_dialog_delete_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ CcBoltPanel *panel)
+{
+ CcBoltDeviceDialog *dialog;
+
+ dialog = CC_BOLT_DEVICE_DIALOG (widget);
+
+ cc_bolt_device_dialog_set_device (dialog, NULL);
+ gtk_widget_hide (widget);
+
+ return TRUE;
+}
+
+static void
+on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
+ BoltStatus new_status,
+ CcBoltPanel *panel)
+{
+ GtkListBox *from = NULL;
+ GtkListBox *to = NULL;
+ GtkWidget *p;
+ gboolean is_pending;
+ gboolean parent_pending;
+
+ /* if we are doing some active work, then lets not change
+ * the list the entry is in; otherwise we might just hop
+ * from one box to the other and back again.
+ */
+ if (new_status == BOLT_STATUS_CONNECTING ||
+ new_status == BOLT_STATUS_AUTHORIZING)
+ return;
+
+ is_pending = bolt_status_is_pending (new_status);
+
+ p = gtk_widget_get_parent (GTK_WIDGET (entry));
+ parent_pending = (gpointer) p == panel->pending_list;
+
+ /* */
+ if (is_pending && !parent_pending)
+ {
+ from = panel->devices_list;
+ to = panel->pending_list;
+ }
+ else if (!is_pending && parent_pending)
+ {
+ from = panel->pending_list;
+ to = panel->devices_list;
+ }
+
+ if (from && to)
+ cc_panel_list_box_migrate (panel, from, to, entry);
+}
+
+
+static void
+on_notification_button_clicked_cb (GtkButton *button,
+ CcBoltPanel *panel)
+{
+ gtk_revealer_set_reveal_child (panel->notification_revealer, FALSE);
+}
+
+/* polkit */
+
+static void
+on_permission_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(CcBoltPanel) panel = user_data;
+ GPermission *permission;
+ gboolean is_allowed;
+ const char *name;
+
+ permission = polkit_permission_new_finish (res, &err);
+ panel->permission = permission;
+
+ if (panel->permission == NULL)
+ {
+ 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 (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 (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);
+}
+
+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);
+
+ /* cancel any ongoing operation */
+ g_cancellable_cancel (panel->cancel);
+
+ /* Must be destroyed in dispose, not finalize. */
+ g_clear_pointer (&panel->device_dialog, gtk_widget_destroy);
+
+ G_OBJECT_CLASS (cc_bolt_panel_parent_class)->dispose (object);
+}
+
+static void
+cc_bolt_panel_constructed (GObject *object)
+{
+ CcBoltPanel *panel = CC_BOLT_PANEL (object);
+ GtkWindow *parent;
+ CcShell *shell;
+
+ parent = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel))));
+ gtk_window_set_transient_for (GTK_WINDOW (panel->device_dialog), parent);
+
+ G_OBJECT_CLASS (cc_bolt_panel_parent_class)->constructed (object);
+
+ shell = cc_panel_get_shell (CC_PANEL (panel));
+ cc_shell_embed_widget_in_header (shell, GTK_WIDGET (panel->headerbar_box));
+}
+
+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, RESOURCE_PANEL_UI);
+ 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, container);
+
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notb_caption);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notb_details);
+
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notification_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notification_revealer);
+
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_mode);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_spinner);
+
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, pending_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_list);
+ gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, pending_list);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_notification_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_authmode_state_set_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_device_entry_row_activated_cb);
+}
+
+static void
+cc_bolt_panel_init (CcBoltPanel *panel)
+{
+ g_resources_register (cc_thunderbolt_get_resource ());
+ gtk_widget_init_template (GTK_WIDGET (panel));
+
+ gtk_stack_set_visible_child_name (panel->container, "loading");
+
+ gtk_list_box_set_header_func (panel->devices_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (panel->pending_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_sort_func (panel->devices_list,
+ device_entries_sort_by_recency,
+ panel,
+ NULL);
+
+ gtk_list_box_set_sort_func (panel->pending_list,
+ device_entries_sort_by_syspath,
+ panel,
+ NULL);
+
+ panel->devices = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+
+ panel->device_dialog = cc_bolt_device_dialog_new ();
+ g_signal_connect_object (panel->device_dialog, "delete-event",
+ G_CALLBACK (on_device_dialog_delete_event_cb),
+ panel, 0);
+
+ panel->cancel = g_cancellable_new ();
+ bolt_client_new_async (panel->cancel,
+ bolt_client_ready,
+ g_object_ref (panel));
+
+}
diff --git a/panels/thunderbolt/cc-bolt-panel.ui b/panels/thunderbolt/cc-bolt-panel.ui
new file mode 100644
index 000000000000..5ec6748600b9
--- /dev/null
+++ b/panels/thunderbolt/cc-bolt-panel.ui
@@ -0,0 +1,594 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+
+ <template class="CcBoltPanel" parent="CcPanel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child type="overlay">
+ <object class="GtkRevealer" id="notification_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="transition_type">slide-down</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="notification_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">none</property>
+ <signal name="clicked"
+ handler="on_notification_button_clicked_cb"
+ object="CcBoltPanel"
+ swapped="no" />
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="GtkStack" id="container">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="homogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+
+ <!-- Spinner for when we are creating -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="expand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <property name="margin">18</property>
+ <child type="center">
+ <object class="GtkSpinner" id="loading-spinner">
+ <property name="visible">True</property>
+ <property name="active">True</property>
+ <property name="expand">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">loading</property>
+ </packing>
+ </child>
+
+ <!-- No tunderbolt -->
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="expand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <property name="margin">18</property>
+ <child type="center" >
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">24</property>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">thunderbolt-symbolic</property>
+ <property name="pixel_size">96</property>
+ <property name="yalign">0</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="notb_caption">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">No Thunderbolt support</property>
+ <attributes>
+ <attribute name="scale" value="1.2" />
+ </attributes>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="notb_details">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">40</property>
+ <property name="use_markup">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="wrap">True</property>
+ <property name="label" translatable="no">Could not connect to the thunderbolt subsystem.</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="name">no-thunderbolt</property>
+ </packing>
+ </child>
+
+ <!-- Normal operation mode (show list of devices) -->
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hscrollbar-policy">never</property>
+
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow-type">none</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="valign">start</property>
+
+ <!-- Stub box -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+
+ <!-- center/content box -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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_left">18</property>
+ <property name="margin_right">18</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Auth Mode -->
+ <child>
+ <object class="GtkBox" id="authmode_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">False</property>
+ <property name="halign">start</property>
+ <property name="xalign">0.0</property>
+ <property name="label" translatable="yes">Direct Access</property>
+ <property name="mnemonic_widget">authmode_switch</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ </attributes>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkStack" id="authmode_mode">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="transition-type">crossfade</property>
+ <property name="homogeneous">True</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_left">0</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes" >Allow direct access to devices such as docks and external GPUs.</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ <property name="xalign">0.0</property>
+ <property name="yalign">0.0</property>
+ <property name="max-width-chars">45</property>
+ </object>
+ <packing>
+ <property name="name">enabled</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_left">0</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes" >Only USB and Display Port devices can attach.</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ <property name="xalign">0.0</property>
+ <property name="yalign">0.0</property>
+ <property name="max-width-chars">45</property>
+ </object>
+ <packing>
+ <property name="name">disabled</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">6</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+
+ <child>
+ <object class="GtkSpinner" id="authmode_spinner">
+ <property name="visible">True</property>
+ <property name="active">False</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkSwitch" id="authmode_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="active">True</property>
+ <signal name="state-set"
+ handler="on_authmode_state_set_cb"
+ object="CcBoltPanel"
+ swapped="yes" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ <!-- Stack: devices/no-devices -->
+ <child>
+ <object class="GtkStack" id="devices_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="transition-type">crossfade</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="can_focus">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="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">dialog-warning-symbolic</property>
+ <property name="icon_size">1</property>
+ <property name="margin_left">0</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Pending Devices</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="pending_spinner">
+ <property name="hexpand">True</property>
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ <!-- Pending List: Devices -->
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="valign">start</property>
+ <property name="vexpand">False</property>
+ <style>
+ <class name="view" />
+ </style>
+ <child>
+ <object class="GtkListBox" id="pending_list">
+ <property name="visible">True</property>
+ <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="GtkBox" id="devices_box">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+
+ <!-- Device List: Header -->
+ <child>
+ <object class="GtkBox" id="devices_header">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <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="GtkSpinner" id="probing_spinner">
+ <property name="hexpand">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Device List: Devices -->
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="valign">start</property>
+ <property name="vexpand">False</property>
+ <style>
+ <class name="view" />
+ </style>
+ <child>
+ <object class="GtkListBox" id="devices_list">
+ <property name="visible">True</property>
+ <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>
+
+ </object>
+ <packing>
+ <property name="name">have-devices</property>
+ </packing>
+ </child>
+
+ <!-- No Devices -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <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="visible">True</property>
+ <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="visible">True</property>
+ <property name="label" translatable="yes">No devices attached</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">no-devices</property>
+ </packing>
+ </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="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+
+ <!-- End of content -->
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">devices-listing</property>
+ </packing>
+ </child>
+
+ <!-- End of 'container' -->
+ </object>
+ </child>
+
+ <!-- End of overlay -->
+ </object>
+ </child>
+ </template>
+
+ <!-- Headerbar entries -->
+ <object class="GtkBox" id="headerbar_box">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="halign">end</property>
+ <child>
+ <object class="GtkLockButton" id="lock_button">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+
+</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 000000000000..db2477e45a74
--- /dev/null
+++ b/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in
@@ -0,0 +1,17 @@
+[Desktop Entry]
+Name=Thunderbolt
+Comment=Manage Thunderbolt devices
+Exec=gnome-control-center thunderbolt
+Icon=thunderbolt
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;HardwareSettings;X-GNOME-DevicesSettings;X-GNOME-ConnectivitySettings;
+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;
diff --git a/panels/thunderbolt/meson.build b/panels/thunderbolt/meson.build
new file mode 100644
index 000000000000..e855661574fc
--- /dev/null
+++ b/panels/thunderbolt/meson.build
@@ -0,0 +1,74 @@
+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(
+ desktop,
+ 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-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 + [
+ gnome_desktop_dep,
+ polkit_gobject_dep,
+ m_dep,
+]
+
+cflags += [
+ '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir),
+ '-DBINDIR="@0@"'.format(control_center_bindir)
+]
+
+panels_libs += static_library(
+ cappletname,
+ sources: sources,
+ include_directories: top_inc,
+ dependencies: deps,
+ c_args: cflags
+)
diff --git a/panels/thunderbolt/thunderbolt.gresource.xml b/panels/thunderbolt/thunderbolt.gresource.xml
new file mode 100644
index 000000000000..8953d6243275
--- /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 000000000000..8b22f0831781
--- /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
+
diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c
index 0fd093cf9758..99d8a91144ad 100644
--- a/shell/cc-panel-list.c
+++ b/shell/cc-panel-list.c
@@ -276,6 +276,7 @@ static const gchar * const panel_order[] = {
"wifi",
"mobile-broadband",
"bluetooth",
+ "thunderbolt",
"background",
"notifications",
"search",
diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c
index 675833c129d7..9b8aca5c6f9b 100644
--- a/shell/cc-panel-loader.c
+++ b/shell/cc-panel-loader.c
@@ -54,6 +54,9 @@ extern GType cc_region_panel_get_type (void);
extern GType cc_search_panel_get_type (void);
extern GType cc_sharing_panel_get_type (void);
extern GType cc_sound_panel_get_type (void);
+#ifdef BUILD_THUNDERBOLT
+extern GType cc_bolt_panel_get_type (void);
+#endif /* BUILD_THUNDERBOLT */
extern GType cc_ua_panel_get_type (void);
extern GType cc_user_panel_get_type (void);
#ifdef BUILD_WACOM
@@ -99,6 +102,9 @@ static struct {
PANEL_TYPE("search", cc_search_panel_get_type ),
PANEL_TYPE("sharing", cc_sharing_panel_get_type ),
PANEL_TYPE("sound", cc_sound_panel_get_type ),
+#ifdef BUILD_THUNDERBOLT
+ PANEL_TYPE("thunderbolt", cc_bolt_panel_get_type ),
+#endif
PANEL_TYPE("universal-access", cc_ua_panel_get_type ),
PANEL_TYPE("user-accounts", cc_user_panel_get_type ),
#ifdef BUILD_WACOM
--
2.17.0