Blob Blame History Raw
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