From ec695fae92ef7470ef05211160e431f5c3486299 Mon Sep 17 00:00:00 2001
From: Christian Kellner <christian@kellner.me>
Date: Tue, 10 Apr 2018 09:43:22 +0200
Subject: [PATCH 1/4] shell: Don't set per-panel icon

The control center app is considered one single application with
a single icon to represent it. Therefore get rid of per-panel
icons.
---
 shell/cc-window.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/shell/cc-window.c b/shell/cc-window.c
index 557819e0c76c..33f1ddcad511 100644
--- a/shell/cc-window.c
+++ b/shell/cc-window.c
@@ -118,7 +118,6 @@ activate_panel (CcWindow    *self,
                 GIcon       *gicon)
 {
   GtkWidget *box, *title_widget;
-  const gchar *icon_name;
 
   if (!id)
     return FALSE;
@@ -144,12 +143,8 @@ activate_panel (CcWindow    *self,
   gtk_stack_set_visible_child_name (GTK_STACK (self->stack), id);
 
   /* set the title of the window */
-  icon_name = get_icon_name_from_g_icon (gicon);
-
   gtk_window_set_role (GTK_WINDOW (self), id);
   gtk_header_bar_set_title (GTK_HEADER_BAR (self->panel_headerbar), name);
-  gtk_window_set_default_icon_name (icon_name);
-  gtk_window_set_icon_name (GTK_WINDOW (self), icon_name);
 
   title_widget = cc_panel_get_title_widget (CC_PANEL (self->current_panel));
   gtk_header_bar_set_custom_title (GTK_HEADER_BAR (self->panel_headerbar), title_widget);
@@ -778,4 +773,4 @@ cc_window_set_search_item (CcWindow   *center,
   gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (center->search_bar), TRUE);
   gtk_entry_set_text (GTK_ENTRY (center->search_entry), search);
   gtk_editable_set_position (GTK_EDITABLE (center->search_entry), -1);
-}
\ No newline at end of file
+}
-- 
2.17.0

From b24a8e9aa82b64de970d8137181bf8a03b6f724a Mon Sep 17 00:00:00 2001
From: Christian Kellner <christian@kellner.me>
Date: Tue, 10 Apr 2018 09:47:48 +0200
Subject: [PATCH 2/4] shell: Icon name helper returns symbolic name

The helper function to get the icon name from a GIcon directly
returns the symbolic icon now. This makes it in turn possible
to also directly check if the theme has the icon with the symbolic
name instead of checking of for the full colored one and then
deriving the symbolic name from that. The latter (old) practice
will fail if there is a symbolic icon in the theme that has no
full color icon (like e.g. thunderbolt).
---
 shell/cc-window.c | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/shell/cc-window.c b/shell/cc-window.c
index 33f1ddcad511..3af9cf0bd9fc 100644
--- a/shell/cc-window.c
+++ b/shell/cc-window.c
@@ -88,8 +88,8 @@ enum
 };
 
 /* Auxiliary methods */
-static const gchar *
-get_icon_name_from_g_icon (GIcon *gicon)
+static gchar *
+get_symbolic_icon_name_from_g_icon (GIcon *gicon)
 {
   const gchar * const *names;
   GtkIconTheme *icon_theme;
@@ -103,8 +103,11 @@ get_icon_name_from_g_icon (GIcon *gicon)
 
   for (i = 0; names[i] != NULL; i++)
     {
-      if (gtk_icon_theme_has_icon (icon_theme, names[i]))
-        return names[i];
+      g_autofree gchar *name = NULL;
+      name = g_strdup_printf ("%s-symbolic", names[i]);
+
+      if (gtk_icon_theme_has_icon (icon_theme, name))
+        return g_steal_pointer (&name);
     }
 
   return NULL;
@@ -248,9 +251,8 @@ setup_model (CcWindow *shell)
       g_autofree gchar *name = NULL;
       g_autofree gchar *description = NULL;
       g_autofree gchar *id = NULL;
-      g_autofree gchar *symbolic_icon = NULL;
+      g_autofree gchar *icon_name = NULL;
       g_autofree GStrv keywords = NULL;
-      const gchar *icon_name;
 
       gtk_tree_model_get (model, &iter,
                           COL_CATEGORY, &category,
@@ -261,8 +263,7 @@ setup_model (CcWindow *shell)
                           COL_KEYWORDS, &keywords,
                           -1);
 
-      icon_name = get_icon_name_from_g_icon (icon);
-      symbolic_icon = g_strdup_printf ("%s-symbolic", icon_name);
+      icon_name = get_symbolic_icon_name_from_g_icon (icon);
 
       cc_panel_list_add_panel (CC_PANEL_LIST (shell->panel_list),
                                category,
@@ -270,7 +271,7 @@ setup_model (CcWindow *shell)
                                name,
                                description,
                                keywords,
-                               symbolic_icon);
+                               icon_name);
 
       valid = gtk_tree_model_iter_next (model, &iter);
     }
-- 
2.17.0

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

From 2d1da22e17f703e27ff1b3177e35a54aa0c3aecc Mon Sep 17 00:00:00 2001
From: Christian Kellner <christian@kellner.me>
Date: Fri, 13 Apr 2018 16:03:21 +0200
Subject: [PATCH 4/4] thunderbolt: move to the 'Devices' page

The 'Devices' page is a fitting place for the thunderbolt, being
an IO technology. It is expected that people that need to go to
that page will be sent there via a gnome-shell notification, so
there is no need for it to be on the main page.
Ok'ed by the design team (jimmac).
---
 panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in | 2 +-
 shell/cc-panel-list.c                                    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in b/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in
index db2477e45a74..abd317341bfd 100644
--- a/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in
+++ b/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in
@@ -7,7 +7,7 @@ Terminal=false
 Type=Application
 NoDisplay=true
 StartupNotify=true
-Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;HardwareSettings;X-GNOME-DevicesSettings;X-GNOME-ConnectivitySettings;
+Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;HardwareSettings;X-GNOME-DevicesSettings;
 OnlyShowIn=GNOME;Unity;
 X-GNOME-Bugzilla-Bugzilla=GNOME
 X-GNOME-Bugzilla-Product=gnome-control-center
diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c
index 99d8a91144ad..f5b83509d646 100644
--- a/shell/cc-panel-list.c
+++ b/shell/cc-panel-list.c
@@ -276,7 +276,6 @@ static const gchar * const panel_order[] = {
   "wifi",
   "mobile-broadband",
   "bluetooth",
-  "thunderbolt",
   "background",
   "notifications",
   "search",
@@ -295,6 +294,7 @@ static const gchar * const panel_order[] = {
   "mouse",
   "printers",
   "removable-media",
+  "thunderbolt",
   "wacom",
   "color",
 
-- 
2.17.0

