[gnome-builder/wip/chergert/dspy] dspy: start on dspy plugin



commit e6c48b1608b1a0864e965212781e3213b20cacb4
Author: Christian Hergert <chergert redhat com>
Date:   Thu Apr 11 14:41:12 2019 -0700

    dspy: start on dspy plugin

 meson_options.txt                           |   1 +
 src/plugins/dspy/dspy-connection-model.c    | 521 ++++++++++++++++++++++++++++
 src/plugins/dspy/dspy-connection-model.h    |  42 +++
 src/plugins/dspy/dspy-connection-row.c      | 116 +++++++
 src/plugins/dspy/dspy-connection-row.h      |  41 +++
 src/plugins/dspy/dspy-connection-row.ui     |  19 +
 src/plugins/dspy/dspy-name-row.c            | 125 +++++++
 src/plugins/dspy/dspy-name-row.h            |  36 ++
 src/plugins/dspy/dspy-name-row.ui           |  33 ++
 src/plugins/dspy/dspy-name-view.c           | 120 +++++++
 src/plugins/dspy/dspy-name-view.h           |  40 +++
 src/plugins/dspy/dspy-name-view.ui          | 128 +++++++
 src/plugins/dspy/dspy-name.c                | 289 +++++++++++++++
 src/plugins/dspy/dspy-name.h                |  49 +++
 src/plugins/dspy/dspy-plugin.c              |  34 ++
 src/plugins/dspy/dspy.gresource.xml         |  11 +
 src/plugins/dspy/dspy.plugin                |   9 +
 src/plugins/dspy/gbp-dspy-surface.c         | 208 +++++++++++
 src/plugins/dspy/gbp-dspy-surface.h         |  33 ++
 src/plugins/dspy/gbp-dspy-surface.ui        |  79 +++++
 src/plugins/dspy/gbp-dspy-workspace-addin.c |  92 +++++
 src/plugins/dspy/gbp-dspy-workspace-addin.h |  31 ++
 src/plugins/dspy/gtk/menus.ui               |  15 +
 src/plugins/dspy/meson.build                |  22 ++
 src/plugins/meson.build                     |   2 +
 25 files changed, 2096 insertions(+)
---
diff --git a/meson_options.txt b/meson_options.txt
index 57fe9301c..3d6fc2e40 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -31,6 +31,7 @@ option('plugin_color_picker', type: 'boolean')
 option('plugin_ctags', type: 'boolean')
 option('plugin_devhelp', type: 'boolean')
 option('plugin_deviced', type: 'boolean', value: false)
+option('plugin_dspy', type: 'boolean')
 option('plugin_editorconfig', type: 'boolean')
 option('plugin_eslint', type: 'boolean')
 option('plugin_file_search', type: 'boolean')
diff --git a/src/plugins/dspy/dspy-connection-model.c b/src/plugins/dspy/dspy-connection-model.c
new file mode 100644
index 000000000..72a5f5412
--- /dev/null
+++ b/src/plugins/dspy/dspy-connection-model.c
@@ -0,0 +1,521 @@
+/* dspy-connection-model.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "dspy-connection-model"
+
+#include "config.h"
+
+#include "dspy-connection-model.h"
+#include "dspy-name.h"
+
+struct _DspyConnectionModel
+{
+  GObject          parent;
+
+  GDBusConnection *connection;
+  GCancellable    *cancellable;
+  GSequence       *names;
+  GDBusProxy      *bus_proxy;
+  gchar           *address;
+  GBusType         bus_type;
+
+  guint            name_owner_changed_handler;
+};
+
+enum {
+  PROP_0,
+  PROP_CONNECTION,
+  N_PROPS
+};
+
+static guint
+dspy_connection_model_get_n_items (GListModel *model)
+{
+  DspyConnectionModel *self = DSPY_CONNECTION_MODEL (model);
+  return g_sequence_get_length (self->names);
+}
+
+static GType
+dspy_connection_model_get_item_type (GListModel *model)
+{
+  /* XXX: switch to type */
+  return G_TYPE_OBJECT;
+}
+
+static gpointer
+dspy_connection_model_get_item (GListModel *model,
+                                guint       position)
+{
+  DspyConnectionModel *self = DSPY_CONNECTION_MODEL (model);
+  GSequenceIter *iter = g_sequence_get_iter_at_pos (self->names, position);
+
+  if (g_sequence_iter_is_end (iter))
+    return NULL;
+
+  return g_object_ref (g_sequence_get (iter));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = dspy_connection_model_get_item_type;
+  iface->get_n_items = dspy_connection_model_get_n_items;
+  iface->get_item = dspy_connection_model_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (DspyConnectionModel, dspy_connection_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+dspy_connection_model_finalize (GObject *object)
+{
+  DspyConnectionModel *self = (DspyConnectionModel *)object;
+
+  g_assert (self->connection == NULL);
+  g_assert (self->cancellable == NULL);
+
+  g_clear_object (&self->connection);
+  g_clear_object (&self->cancellable);
+  g_clear_object (&self->bus_proxy);
+  g_clear_pointer (&self->names, g_sequence_free);
+  g_clear_pointer (&self->address, g_free);
+
+  G_OBJECT_CLASS (dspy_connection_model_parent_class)->finalize (object);
+}
+
+static void
+dspy_connection_model_dispose (GObject *object)
+{
+  DspyConnectionModel *self = (DspyConnectionModel *)object;
+
+  dspy_connection_model_set_connection (self, NULL);
+
+  G_OBJECT_CLASS (dspy_connection_model_parent_class)->dispose (object);
+}
+
+static void
+dspy_connection_model_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  DspyConnectionModel *self = DSPY_CONNECTION_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONNECTION:
+      g_value_set_object (value, dspy_connection_model_get_connection (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+dspy_connection_model_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  DspyConnectionModel *self = DSPY_CONNECTION_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONNECTION:
+      dspy_connection_model_set_connection (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+dspy_connection_model_class_init (DspyConnectionModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = dspy_connection_model_dispose;
+  object_class->finalize = dspy_connection_model_finalize;
+  object_class->get_property = dspy_connection_model_get_property;
+  object_class->set_property = dspy_connection_model_set_property;
+
+  /**
+   * DspyConnectionModel:connection:
+   *
+   * The "connection" property contains the #GDBusConnection that will be monitored
+   * for changes to the bus.
+   */
+  properties [PROP_CONNECTION] =
+    g_param_spec_object ("connection",
+                         "Connection",
+                         "A GDBus connection for the source of bus changes",
+                         G_TYPE_DBUS_CONNECTION,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+dspy_connection_model_init (DspyConnectionModel *self)
+{
+  self->names = g_sequence_new (g_object_unref);
+}
+
+static void
+dspy_connection_model_get_name_owner_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  GDBusProxy *proxy = (GDBusProxy *)object;
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(DspyName) name = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (DSPY_IS_NAME (name));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (DSPY_IS_NAME (name));
+
+  if ((ret = g_dbus_proxy_call_finish (proxy, result, &error)))
+    {
+      const gchar *owner = NULL;
+
+      g_variant_get (ret, "(&s)", &owner);
+      dspy_name_set_owner (name, owner);
+    }
+}
+
+static void
+dspy_connection_model_get_process_id_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  GDBusProxy *proxy = (GDBusProxy *)object;
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(DspyName) name = user_data;
+  g_autoptr(GError) error = NULL;
+  GPid pid = 0;
+
+  g_assert (DSPY_IS_NAME (name));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (DSPY_IS_NAME (name));
+
+  if ((ret = g_dbus_proxy_call_finish (proxy, result, &error)))
+    g_variant_get (ret, "(u)", &pid);
+
+  dspy_name_set_pid (name, pid);
+}
+
+static void
+dspy_connection_model_add_names (DspyConnectionModel *self,
+                                 const gchar * const *names,
+                                 gboolean             activatable)
+{
+  g_assert (DSPY_IS_CONNECTION_MODEL (self));
+  g_assert (names != NULL);
+
+  for (guint i = 0; names[i]; i++)
+    {
+      g_autoptr(DspyName) name = dspy_name_new (names[i], activatable);
+      GSequenceIter *seq;
+      guint removed = 0;
+
+      /* Skip if we already know about the name, but replace
+       * the item if we found something activatable.
+       */
+      if ((seq = g_sequence_lookup (self->names,
+                                    name,
+                                    (GCompareDataFunc) dspy_name_compare,
+                                    NULL)))
+        {
+          if (!activatable)
+            continue;
+          g_sequence_remove (seq);
+          removed++;
+        }
+
+      seq = g_sequence_insert_sorted (self->names,
+                                      g_object_ref (name),
+                                      (GCompareDataFunc) dspy_name_compare,
+                                      NULL);
+
+      g_list_model_items_changed (G_LIST_MODEL (self),
+                                  g_sequence_iter_get_position (seq),
+                                  removed, 1);
+
+      g_dbus_proxy_call (self->bus_proxy,
+                         "GetConnectionUnixProcessID",
+                         g_variant_new ("(s)", names[i]),
+                         G_DBUS_CALL_FLAGS_NONE,
+                         -1,
+                         self->cancellable,
+                         dspy_connection_model_get_process_id_cb,
+                         g_object_ref (name));
+
+      if (names[i][0] != ':')
+        g_dbus_proxy_call (self->bus_proxy,
+                           "GetNameOwner",
+                           g_variant_new ("(s)", names[i]),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           self->cancellable,
+                           dspy_connection_model_get_name_owner_cb,
+                           g_object_ref (name));
+    }
+}
+
+static void
+dspy_connection_model_name_owner_changed_cb (GDBusConnection *connection,
+                                             const gchar     *sender_name,
+                                             const gchar     *object_path,
+                                             const gchar     *interface_name,
+                                             const gchar     *signal_name,
+                                             GVariant        *params,
+                                             gpointer         user_data)
+{
+  DspyConnectionModel *self = user_data;
+  g_autoptr(DspyName) name = NULL;
+  GSequenceIter *seq;
+  const gchar *vname;
+  const gchar *vold_name;
+  const gchar *vnew_name;
+
+  g_assert (G_IS_DBUS_CONNECTION (connection));
+  g_assert (DSPY_IS_CONNECTION_MODEL (self));
+
+  g_variant_get (params, "(&s&s&s)", &vname, &vold_name, &vnew_name);
+
+  name = dspy_name_new (vname, FALSE);
+  seq = g_sequence_lookup (self->names,
+                           name,
+                           (GCompareDataFunc) dspy_name_compare,
+                           NULL);
+
+  if (seq == NULL && vnew_name[0])
+    {
+      const gchar *names[] = { vname, NULL };
+      dspy_connection_model_add_names (self, names, FALSE);
+    }
+  else if (!vnew_name[0])
+    {
+      guint position = g_sequence_iter_get_position (seq);
+      g_sequence_remove (seq);
+      g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
+    }
+
+#if 0
+  g_print ("%s %s %s %s %s\n",
+           sender_name, object_path, interface_name, signal_name,
+           g_variant_print (params, TRUE));
+#endif
+}
+
+static void
+dspy_connection_model_list_activatable_names_cb (GObject      *object,
+                                                 GAsyncResult *result,
+                                                 gpointer      user_data)
+{
+  GDBusProxy *proxy = (GDBusProxy *)object;
+  g_autoptr(DspyConnectionModel) self = user_data;
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(GError) error = NULL;
+  g_auto(GStrv) strv = NULL;
+
+  g_assert (G_IS_DBUS_PROXY (proxy));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (DSPY_IS_CONNECTION_MODEL (self));
+
+  if (!(ret = g_dbus_proxy_call_finish (proxy, result, &error)))
+    {
+      g_warning ("Failed to list activatable names: %s", error->message);
+      return;
+    }
+
+  g_variant_get (ret, "(^as)", &strv);
+  dspy_connection_model_add_names (self, (const gchar * const *)strv, TRUE);
+}
+
+static void
+dspy_connection_model_list_names_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  GDBusProxy *proxy = (GDBusProxy *)object;
+  g_autoptr(DspyConnectionModel) self = user_data;
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(GError) error = NULL;
+  g_auto(GStrv) strv = NULL;
+
+  g_assert (G_IS_DBUS_PROXY (proxy));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (DSPY_IS_CONNECTION_MODEL (self));
+
+  if (!(ret = g_dbus_proxy_call_finish (proxy, result, &error)))
+    {
+      g_warning ("Failed to list names: %s", error->message);
+      return;
+    }
+
+  g_variant_get (ret, "(^as)", &strv);
+  dspy_connection_model_add_names (self, (const gchar * const *)strv, FALSE);
+}
+
+DspyConnectionModel *
+dspy_connection_model_new (void)
+{
+  return g_object_new (DSPY_TYPE_CONNECTION_MODEL, NULL);
+}
+
+/**
+ * dspy_connection_model_get_connection:
+ * @self: a #DspyConnectionModel
+ *
+ * Gets the #GDBusConnection used for the model.
+ *
+ * Returns: (transfer none) (nullable): a #GDBusConnection or %NULL
+ */
+GDBusConnection *
+dspy_connection_model_get_connection (DspyConnectionModel *self)
+{
+  g_return_val_if_fail (DSPY_IS_CONNECTION_MODEL (self), NULL);
+
+  return self->connection;
+}
+
+void
+dspy_connection_model_set_connection (DspyConnectionModel *self,
+                                      GDBusConnection     *connection)
+{
+  g_return_if_fail (DSPY_IS_CONNECTION_MODEL (self));
+
+  if (self->connection == connection)
+    return;
+
+  if (self->connection != NULL)
+    {
+      g_cancellable_cancel (self->cancellable);
+      g_dbus_connection_signal_unsubscribe (self->connection,
+                                            self->name_owner_changed_handler);
+      g_clear_object (&self->cancellable);
+      g_clear_object (&self->connection);
+      g_clear_object (&self->bus_proxy);
+      g_clear_pointer (&self->names, g_sequence_free);
+      self->names = g_sequence_new (g_object_unref);
+    }
+
+  g_assert (self->cancellable == NULL);
+  g_assert (self->connection == NULL);
+  g_assert (self->bus_proxy == NULL);
+  g_assert (g_sequence_is_empty (self->names));
+
+  if (connection != NULL)
+    {
+      g_autoptr(GError) error = NULL;
+
+      self->connection = g_object_ref (connection);
+      self->cancellable = g_cancellable_new ();
+      self->name_owner_changed_handler =
+        g_dbus_connection_signal_subscribe (self->connection,
+                                            NULL,
+                                            "org.freedesktop.DBus",
+                                            "NameOwnerChanged",
+                                            NULL,
+                                            NULL,
+                                            0,
+                                            dspy_connection_model_name_owner_changed_cb,
+                                            self,
+                                            NULL);
+      self->bus_proxy = g_dbus_proxy_new_sync (self->connection,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               "org.freedesktop.DBus",
+                                               "/org/freedesktop/DBus",
+                                               "org.freedesktop.DBus",
+                                               self->cancellable, &error);
+
+      if (self->bus_proxy == NULL)
+        {
+          g_warning ("Failed to create DBus proxy: %s", error->message);
+          goto notify;
+        }
+
+      g_dbus_proxy_call (self->bus_proxy,
+                         "ListActivatableNames",
+                         g_variant_new ("()"),
+                         G_DBUS_CALL_FLAGS_NONE,
+                         -1,
+                         self->cancellable,
+                         dspy_connection_model_list_activatable_names_cb,
+                         g_object_ref (self));
+
+      g_dbus_proxy_call (self->bus_proxy,
+                         "ListNames",
+                         g_variant_new ("()"),
+                         G_DBUS_CALL_FLAGS_NONE,
+                         -1,
+                         self->cancellable,
+                         dspy_connection_model_list_names_cb,
+                         g_object_ref (self));
+    }
+
+notify:
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONNECTION]);
+}
+
+GBusType
+dspy_connection_model_get_bus_type (DspyConnectionModel *self)
+{
+  g_return_val_if_fail (DSPY_IS_CONNECTION_MODEL (self), G_BUS_TYPE_NONE);
+
+  return self->bus_type;
+}
+
+void
+dspy_connection_model_set_bus_type (DspyConnectionModel *self,
+                                    GBusType             bus_type)
+{
+  g_return_if_fail (DSPY_IS_CONNECTION_MODEL (self));
+
+  self->bus_type = bus_type;
+}
+
+const gchar *
+dspy_connection_model_get_address (DspyConnectionModel *self)
+{
+  g_return_val_if_fail (DSPY_IS_CONNECTION_MODEL (self), NULL);
+
+  return self->address;
+}
+
+void
+dspy_connection_model_set_address (DspyConnectionModel *self,
+                                   const gchar         *address)
+{
+  g_return_if_fail (DSPY_IS_CONNECTION_MODEL (self));
+
+  if (g_strcmp0 (self->address, address) != 0)
+    {
+      g_free (self->address);
+      self->address = g_strdup (address);
+    }
+}
diff --git a/src/plugins/dspy/dspy-connection-model.h b/src/plugins/dspy/dspy-connection-model.h
new file mode 100644
index 000000000..0e442ecbc
--- /dev/null
+++ b/src/plugins/dspy/dspy-connection-model.h
@@ -0,0 +1,42 @@
+/* dspy-connection-model.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define DSPY_TYPE_CONNECTION_MODEL (dspy_connection_model_get_type())
+
+G_DECLARE_FINAL_TYPE (DspyConnectionModel, dspy_connection_model, DSPY, CONNECTION_MODEL, GObject)
+
+DspyConnectionModel *dspy_connection_model_new            (void);
+GBusType             dspy_connection_model_get_bus_type   (DspyConnectionModel *self);
+void                 dspy_connection_model_set_bus_type   (DspyConnectionModel *self,
+                                                           GBusType             bus_type);
+const gchar         *dspy_connection_model_get_address    (DspyConnectionModel *self);
+void                 dspy_connection_model_set_address    (DspyConnectionModel *self,
+                                                           const gchar         *address);
+GDBusConnection     *dspy_connection_model_get_connection (DspyConnectionModel *self);
+void                 dspy_connection_model_set_connection (DspyConnectionModel *self,
+                                                           GDBusConnection     *connection);
+
+G_END_DECLS
diff --git a/src/plugins/dspy/dspy-connection-row.c b/src/plugins/dspy/dspy-connection-row.c
new file mode 100644
index 000000000..c06dc4b7d
--- /dev/null
+++ b/src/plugins/dspy/dspy-connection-row.c
@@ -0,0 +1,116 @@
+/* dspy-connection-row.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "dspy-connection-row"
+
+#include "config.h"
+
+#include "dspy-connection-row.h"
+
+struct _DspyConnectionRow
+{
+  GtkListBoxRow  parent;
+  GtkLabel      *label;
+  gchar         *address;
+  GBusType       bus_type;
+};
+
+G_DEFINE_TYPE (DspyConnectionRow, dspy_connection_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+dspy_connection_row_finalize (GObject *object)
+{
+  DspyConnectionRow *self = (DspyConnectionRow *)object;
+
+  g_clear_pointer (&self->address, g_free);
+
+  G_OBJECT_CLASS (dspy_connection_row_parent_class)->finalize (object);
+}
+
+static void
+dspy_connection_row_class_init (DspyConnectionRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = dspy_connection_row_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/dspy/dspy-connection-row.ui");
+  gtk_widget_class_bind_template_child (widget_class, DspyConnectionRow, label);
+}
+
+static void
+dspy_connection_row_init (DspyConnectionRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+DspyConnectionRow *
+dspy_connection_row_new (void)
+{
+  return g_object_new (DSPY_TYPE_CONNECTION_ROW, NULL);
+}
+
+const gchar *
+dspy_connection_row_get_address (DspyConnectionRow *self)
+{
+  g_return_val_if_fail (DSPY_IS_CONNECTION_ROW (self), NULL);
+
+  return self->address;
+}
+
+void
+dspy_connection_row_set_address (DspyConnectionRow *self,
+                                 const gchar       *address)
+{
+  g_return_if_fail (DSPY_IS_CONNECTION_ROW (self));
+
+  if (g_strcmp0 (address, self->address) != 0)
+    {
+      g_free (self->address);
+      self->address = g_strdup (address);
+    }
+}
+
+GBusType
+dspy_connection_row_get_bus_type (DspyConnectionRow *self)
+{
+  g_return_val_if_fail (DSPY_IS_CONNECTION_ROW (self), 0);
+
+  return self->bus_type;
+}
+
+void
+dspy_connection_row_set_bus_type (DspyConnectionRow *self,
+                                  GBusType           bus_type)
+{
+  g_return_if_fail (DSPY_IS_CONNECTION_ROW (self));
+
+  self->bus_type = bus_type;
+}
+
+void
+dspy_connection_row_set_title (DspyConnectionRow *self,
+                               const gchar       *title)
+{
+  g_return_if_fail (DSPY_IS_CONNECTION_ROW (self));
+
+  gtk_label_set_label (self->label, title);
+}
diff --git a/src/plugins/dspy/dspy-connection-row.h b/src/plugins/dspy/dspy-connection-row.h
new file mode 100644
index 000000000..3b5ca7a4a
--- /dev/null
+++ b/src/plugins/dspy/dspy-connection-row.h
@@ -0,0 +1,41 @@
+/* dspy-connection-row.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define DSPY_TYPE_CONNECTION_ROW (dspy_connection_row_get_type())
+
+G_DECLARE_FINAL_TYPE (DspyConnectionRow, dspy_connection_row, DSPY, CONNECTION_ROW, GtkListBoxRow)
+
+DspyConnectionRow *dspy_connection_row_new          (void);
+const gchar       *dspy_connection_row_get_address  (DspyConnectionRow *self);
+void               dspy_connection_row_set_address  (DspyConnectionRow *self,
+                                                     const gchar       *address);
+GBusType           dspy_connection_row_get_bus_type (DspyConnectionRow *self);
+void               dspy_connection_row_set_bus_type (DspyConnectionRow *self,
+                                                     GBusType           bus_type);
+void               dspy_connection_row_set_title    (DspyConnectionRow *self,
+                                                     const gchar       *title);
+
+G_END_DECLS
diff --git a/src/plugins/dspy/dspy-connection-row.ui b/src/plugins/dspy/dspy-connection-row.ui
new file mode 100644
index 000000000..06428c2b1
--- /dev/null
+++ b/src/plugins/dspy/dspy-connection-row.ui
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="DspyConnectionRow" parent="GtkListBoxRow">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkLabel" id="label">
+            <property name="margin">6</property>
+            <property name="hexpand">true</property>
+            <property name="visible">true</property>
+            <property name="xalign">0.0</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/dspy/dspy-name-row.c b/src/plugins/dspy/dspy-name-row.c
new file mode 100644
index 000000000..054e78acb
--- /dev/null
+++ b/src/plugins/dspy/dspy-name-row.c
@@ -0,0 +1,125 @@
+/* dspy-name-row.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "dspy-name-row"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "dspy-name-row.h"
+
+struct _DspyNameRow
+{
+  GtkListBoxRow  parent;
+
+  DspyName      *name;
+
+  GtkLabel      *label;
+  GtkLabel      *subtitle;
+};
+
+G_DEFINE_TYPE (DspyNameRow, dspy_name_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+update_subtitle (DspyNameRow *self)
+{
+  g_autoptr(GString) str = g_string_new (NULL);
+  GPid pid = dspy_name_get_pid (self->name);
+
+  if (dspy_name_get_activatable (self->name))
+    g_string_append_printf (str, _("%s: %s"), _("Activatable"), _("Yes"));
+  else
+    g_string_append_printf (str, _("%s: %s"), _("Activatable"), _("No"));
+
+  if (pid != 0)
+    {
+      g_string_append (str, ", ");
+      g_string_append_printf (str, _("%s: %u"), _("Pid"), pid);
+    }
+
+  gtk_label_set_label (self->subtitle, str->str);
+}
+
+static void
+dspy_name_row_finalize (GObject *object)
+{
+  DspyNameRow *self = (DspyNameRow *)object;
+
+  g_clear_object (&self->name);
+
+  G_OBJECT_CLASS (dspy_name_row_parent_class)->finalize (object);
+}
+
+static void
+dspy_name_row_class_init (DspyNameRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = dspy_name_row_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/dspy/dspy-name-row.ui");
+  gtk_widget_class_bind_template_child (widget_class, DspyNameRow, label);
+  gtk_widget_class_bind_template_child (widget_class, DspyNameRow, subtitle);
+}
+
+static void
+dspy_name_row_init (DspyNameRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+DspyNameRow *
+dspy_name_row_new (DspyName *name)
+{
+  DspyNameRow *self;
+
+  g_return_val_if_fail (DSPY_IS_NAME (name), NULL);
+
+  self = g_object_new (DSPY_TYPE_NAME_ROW, NULL);
+  self->name = g_object_ref (name);
+
+  gtk_label_set_label (self->label, dspy_name_get_name (name));
+
+  g_signal_connect_object (name,
+                           "notify::pid",
+                           G_CALLBACK (update_subtitle),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  update_subtitle (self);
+
+  return g_steal_pointer (&self);
+}
+
+/**
+ * dspy_name_row_get_name:
+ * @self: a #DspyNameRow
+ *
+ * Returns: (transfer none): a #DspyNameRow or %NULL
+ */
+DspyName *
+dspy_name_row_get_name (DspyNameRow *self)
+{
+  g_return_val_if_fail (DSPY_IS_NAME_ROW (self), NULL);
+
+  return self->name;
+}
diff --git a/src/plugins/dspy/dspy-name-row.h b/src/plugins/dspy/dspy-name-row.h
new file mode 100644
index 000000000..39d6c4ce8
--- /dev/null
+++ b/src/plugins/dspy/dspy-name-row.h
@@ -0,0 +1,36 @@
+/* dspy-name-row.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "dspy-name.h"
+
+G_BEGIN_DECLS
+
+#define DSPY_TYPE_NAME_ROW (dspy_name_row_get_type())
+
+G_DECLARE_FINAL_TYPE (DspyNameRow, dspy_name_row, DSPY, NAME_ROW, GtkListBoxRow)
+
+DspyNameRow *dspy_name_row_new      (DspyName    *name);
+DspyName    *dspy_name_row_get_name (DspyNameRow *self);
+
+G_END_DECLS
diff --git a/src/plugins/dspy/dspy-name-row.ui b/src/plugins/dspy/dspy-name-row.ui
new file mode 100644
index 000000000..6fa701b5d
--- /dev/null
+++ b/src/plugins/dspy/dspy-name-row.ui
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="DspyNameRow" parent="GtkListBoxRow">
+    <child>
+      <object class="GtkBox">
+        <property name="margin">6</property>
+        <property name="spacing">3</property>
+        <property name="orientation">vertical</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkLabel" id="label">
+            <property name="hexpand">true</property>
+            <property name="visible">true</property>
+            <property name="xalign">0.0</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="subtitle">
+            <property name="hexpand">true</property>
+            <property name="visible">true</property>
+            <property name="xalign">0.0</property>
+            <property name="visible">true</property>
+            <property name="ellipsize">end</property>
+            <attributes>
+              <attribute name="foreground-alpha" value="39321"/>
+              <attribute name="scale" value="0.75"/>
+            </attributes>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/dspy/dspy-name-view.c b/src/plugins/dspy/dspy-name-view.c
new file mode 100644
index 000000000..f31f0f943
--- /dev/null
+++ b/src/plugins/dspy/dspy-name-view.c
@@ -0,0 +1,120 @@
+/* dspy-name-view.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "dspy-name-view"
+
+#include "config.h"
+
+#include <dazzle.h>
+
+#include "dspy-name-view.h"
+
+struct _DspyNameView
+{
+  GtkBin           parent;
+
+  GDBusConnection *connection;
+  DspyName        *name;
+
+  GtkLabel        *address_label;
+  GtkLabel        *name_label;
+  GtkLabel        *unique_label;
+} DspyNameViewPrivate;
+
+G_DEFINE_TYPE (DspyNameView, dspy_name_view, GTK_TYPE_BIN)
+
+static void
+dspy_name_view_finalize (GObject *object)
+{
+  DspyNameView *self = (DspyNameView *)object;
+
+  g_clear_object (&self->connection);
+  g_clear_object (&self->name);
+
+  G_OBJECT_CLASS (dspy_name_view_parent_class)->finalize (object);
+}
+
+static void
+dspy_name_view_class_init (DspyNameViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = dspy_name_view_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/dspy/dspy-name-view.ui");
+  gtk_widget_class_bind_template_child (widget_class, DspyNameView, address_label);
+  gtk_widget_class_bind_template_child (widget_class, DspyNameView, name_label);
+  gtk_widget_class_bind_template_child (widget_class, DspyNameView, unique_label);
+
+  g_type_ensure (DZL_TYPE_THREE_GRID);
+}
+
+static void
+dspy_name_view_init (DspyNameView *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+DspyNameView *
+dspy_name_view_new (void)
+{
+  return g_object_new (DSPY_TYPE_NAME_VIEW, NULL);
+}
+
+static void
+dspy_name_view_clear (DspyNameView *self)
+{
+  g_return_if_fail (DSPY_IS_NAME_VIEW (self));
+
+  gtk_label_set_label (self->address_label, NULL);
+  gtk_label_set_label (self->name_label, NULL);
+  gtk_label_set_label (self->unique_label, NULL);
+}
+
+void
+dspy_name_view_set_name (DspyNameView    *self,
+                         GDBusConnection *connection,
+                         GBusType         bus_type,
+                         const gchar     *address,
+                         DspyName        *name)
+{
+  g_autofree gchar *bus_address = NULL;
+
+  g_return_if_fail (DSPY_IS_NAME_VIEW (self));
+
+  if (self->connection == connection && self->name == name)
+    return;
+
+  dspy_name_view_clear (self);
+
+  if (name == NULL)
+    return;
+
+  g_set_object (&self->connection, connection);
+  g_set_object (&self->name, name);
+
+  if (bus_type != G_BUS_TYPE_NONE)
+    address = bus_address = g_dbus_address_get_for_bus_sync (bus_type, NULL, NULL);
+
+  gtk_label_set_label (self->address_label, address);
+  gtk_label_set_label (self->name_label, dspy_name_get_name (name));
+  gtk_label_set_label (self->unique_label, dspy_name_get_owner (name));
+}
diff --git a/src/plugins/dspy/dspy-name-view.h b/src/plugins/dspy/dspy-name-view.h
new file mode 100644
index 000000000..05d3eabf7
--- /dev/null
+++ b/src/plugins/dspy/dspy-name-view.h
@@ -0,0 +1,40 @@
+/* dspy-name-view.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "dspy-name.h"
+
+G_BEGIN_DECLS
+
+#define DSPY_TYPE_NAME_VIEW (dspy_name_view_get_type())
+
+G_DECLARE_FINAL_TYPE (DspyNameView, dspy_name_view, DSPY, NAME_VIEW, GtkBin)
+
+DspyNameView *dspy_name_view_new      (void);
+void          dspy_name_view_set_name (DspyNameView    *self,
+                                       GDBusConnection *connection,
+                                       GBusType         bus_type,
+                                       const gchar     *address,
+                                       DspyName        *name);
+
+G_END_DECLS
diff --git a/src/plugins/dspy/dspy-name-view.ui b/src/plugins/dspy/dspy-name-view.ui
new file mode 100644
index 000000000..37ace2705
--- /dev/null
+++ b/src/plugins/dspy/dspy-name-view.ui
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="DspyNameView" parent="GtkBin">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="DzlThreeGrid">
+            <property name="column-spacing">12</property>
+            <property name="row-spacing">0</property>
+            <property name="halign">center</property>
+            <property name="margin">18</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="xalign">1.0</property>
+                <property name="label" translatable="yes">Address</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="column">left</property>
+                <property name="row">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="xalign">1.0</property>
+                <property name="label" translatable="yes">Name</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="column">left</property>
+                <property name="row">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="xalign">1.0</property>
+                <property name="label" translatable="yes">Unique Name</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="column">left</property>
+                <property name="row">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="address_label">
+                <property name="hexpand">true</property>
+                <property name="xalign">0.0</property>
+                <property name="visible">true</property>
+                <property name="width-chars">50</property>
+                <property name="tooltip-text" translatable="yes">See 
http://dbus.freedesktop.org/doc/dbus-specification.html#addresses</property>
+              </object>
+              <packing>
+                <property name="column">center</property>
+                <property name="row">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="name_label">
+                <property name="hexpand">true</property>
+                <property name="xalign">0.0</property>
+                <property name="visible">true</property>
+                <property name="width-chars">50</property>
+              </object>
+              <packing>
+                <property name="column">center</property>
+                <property name="row">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="unique_label">
+                <property name="hexpand">true</property>
+                <property name="xalign">0.0</property>
+                <property name="visible">true</property>
+                <property name="width-chars">50</property>
+              </object>
+              <packing>
+                <property name="column">center</property>
+                <property name="row">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="refresh_button">
+                <property name="halign">start</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="image-button"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="icon-name">view-refresh-symbolic</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="column">right</property>
+                <property name="row">0</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">vertical</property>
+    <widgets>
+      <widget name="name_label"/>
+      <widget name="unique_label"/>
+      <widget name="address_label"/>
+      <widget name="refresh_button"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/src/plugins/dspy/dspy-name.c b/src/plugins/dspy/dspy-name.c
new file mode 100644
index 000000000..7473473bb
--- /dev/null
+++ b/src/plugins/dspy/dspy-name.c
@@ -0,0 +1,289 @@
+/* dspy-name.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "dspy-name"
+
+#include "dspy-name.h"
+
+typedef struct
+{
+  gchar *name;
+  gchar *owner;
+  GPid pid;
+  guint activatable : 1;
+} DspyNamePrivate;
+
+enum {
+  PROP_0,
+  PROP_ACTIVATABLE,
+  PROP_NAME,
+  PROP_OWNER,
+  PROP_PID,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (DspyName, dspy_name, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+dspy_name_finalize (GObject *object)
+{
+  DspyName *self = (DspyName *)object;
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  g_clear_pointer (&priv->name, g_free);
+  g_clear_pointer (&priv->owner, g_free);
+
+  G_OBJECT_CLASS (dspy_name_parent_class)->finalize (object);
+}
+
+static void
+dspy_name_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  DspyName *self = DSPY_NAME (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACTIVATABLE:
+      g_value_set_boolean (value, dspy_name_get_activatable (self));
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, dspy_name_get_name (self));
+      break;
+
+    case PROP_OWNER:
+      g_value_set_string (value, dspy_name_get_owner (self));
+      break;
+
+    case PROP_PID:
+      g_value_set_uint (value, dspy_name_get_pid (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+dspy_name_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  DspyName *self = DSPY_NAME (object);
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ACTIVATABLE:
+      priv->activatable = g_value_get_boolean (value);
+      break;
+
+    case PROP_NAME:
+      priv->name = g_value_dup_string (value);
+      break;
+
+    case PROP_OWNER:
+      dspy_name_set_owner (self, g_value_get_string (value));
+      break;
+
+    case PROP_PID:
+      dspy_name_set_pid (self, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+dspy_name_class_init (DspyNameClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = dspy_name_finalize;
+  object_class->get_property = dspy_name_get_property;
+  object_class->set_property = dspy_name_set_property;
+
+  properties [PROP_ACTIVATABLE] =
+    g_param_spec_boolean ("activatable",
+                          "Activatable",
+                          "Activatable",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "The peer name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_OWNER] =
+    g_param_spec_string ("owner",
+                         "Owner",
+                         "The owner of the DBus name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PID] =
+    g_param_spec_uint ("pid",
+                       "Pid",
+                       "The pid of the peer",
+                       0, G_MAXUINT, 0,
+                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+dspy_name_init (DspyName *self)
+{
+}
+
+DspyName *
+dspy_name_new (const gchar *name,
+               gboolean     activatable)
+{
+  return g_object_new (DSPY_TYPE_NAME,
+                       "activatable", activatable,
+                       "name", name,
+                       NULL);
+}
+
+gboolean
+dspy_name_get_activatable (DspyName *self)
+{
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  g_return_val_if_fail (DSPY_IS_NAME (self), FALSE);
+
+  return priv->activatable;
+}
+
+const gchar *
+dspy_name_get_name (DspyName *self)
+{
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  return priv->name;
+}
+
+void
+dspy_name_set_name (DspyName    *self,
+                    const gchar *name)
+{
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  g_return_if_fail (DSPY_IS_NAME (self));
+
+  if (g_strcmp0 (name, priv->name) != 0)
+    {
+      g_free (priv->name);
+      priv->name = g_strdup (name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+    }
+}
+
+gint
+dspy_name_compare (gconstpointer a,
+                   gconstpointer b)
+{
+  DspyName *item1 = DSPY_NAME ((gpointer)a);
+  DspyName *item2 = DSPY_NAME ((gpointer)b);
+  const gchar *name1 = dspy_name_get_name (item1);
+  const gchar *name2 = dspy_name_get_name (item2);
+
+  if (name1[0] != name2[0])
+    {
+      if (name1[0] == ':')
+        return 1;
+      if (name2[0] == ':')
+        return -1;
+    }
+
+  /* Sort numbers like :1.300 better */
+  if (g_str_has_prefix (name1, ":1.") &&
+      g_str_has_prefix (name2, ":1."))
+    {
+      gint i1 = g_ascii_strtoll (name1 + 3, NULL, 10);
+      gint i2 = g_ascii_strtoll (name2 + 3, NULL, 10);
+
+      return i1 - i2;
+    }
+
+  return g_strcmp0 (name1, name2);
+}
+
+GPid
+dspy_name_get_pid (DspyName *self)
+{
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  g_return_val_if_fail (DSPY_IS_NAME (self), 0);
+
+  return priv->pid;
+}
+
+void
+dspy_name_set_pid (DspyName *self,
+                   GPid      pid)
+{
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  g_return_if_fail (DSPY_IS_NAME (self));
+
+  if (priv->pid != pid)
+    {
+      priv->pid = pid;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PID]);
+    }
+}
+
+const gchar *
+dspy_name_get_owner (DspyName *self)
+{
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  g_return_val_if_fail (DSPY_IS_NAME (self), NULL);
+
+  return priv->owner ? priv->owner : priv->name;
+}
+
+void
+dspy_name_set_owner (DspyName    *self,
+                     const gchar *owner)
+{
+  DspyNamePrivate *priv = dspy_name_get_instance_private (self);
+
+  g_return_if_fail (DSPY_IS_NAME (self));
+
+  if (g_strcmp0 (owner, priv->owner) != 0)
+    {
+      g_free (priv->owner);
+      priv->owner = g_strdup (owner);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_OWNER]);
+    }
+}
diff --git a/src/plugins/dspy/dspy-name.h b/src/plugins/dspy/dspy-name.h
new file mode 100644
index 000000000..eca03b177
--- /dev/null
+++ b/src/plugins/dspy/dspy-name.h
@@ -0,0 +1,49 @@
+/* dspy-name.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define DSPY_TYPE_NAME (dspy_name_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (DspyName, dspy_name, DSPY, NAME, GObject)
+
+struct _DspyNameClass
+{
+  GObjectClass parent_class;
+};
+
+DspyName    *dspy_name_new             (const gchar   *name,
+                                        gboolean       activatable);
+gboolean     dspy_name_get_activatable (DspyName      *self);
+GPid         dspy_name_get_pid         (DspyName      *self);
+void         dspy_name_set_pid         (DspyName      *self,
+                                        GPid           pid);
+const gchar *dspy_name_get_name        (DspyName      *self);
+const gchar *dspy_name_get_owner       (DspyName      *self);
+void         dspy_name_set_owner       (DspyName      *self,
+                                        const gchar   *owner);
+gint         dspy_name_compare         (gconstpointer  a,
+                                        gconstpointer  b);
+
+G_END_DECLS
diff --git a/src/plugins/dspy/dspy-plugin.c b/src/plugins/dspy/dspy-plugin.c
new file mode 100644
index 000000000..85bd90efc
--- /dev/null
+++ b/src/plugins/dspy/dspy-plugin.c
@@ -0,0 +1,34 @@
+/* dspy-plugin.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#include <libide-editor.h>
+#include <libpeas/peas.h>
+
+#include "gbp-dspy-workspace-addin.h"
+
+_IDE_EXTERN void
+_gbp_dspy_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKSPACE_ADDIN,
+                                              GBP_TYPE_DSPY_WORKSPACE_ADDIN);
+}
diff --git a/src/plugins/dspy/dspy.gresource.xml b/src/plugins/dspy/dspy.gresource.xml
new file mode 100644
index 000000000..c2063d1fc
--- /dev/null
+++ b/src/plugins/dspy/dspy.gresource.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/plugins/dspy">
+    <file>dspy.plugin</file>
+    <file>gtk/menus.ui</file>
+    <file preprocess="xml-stripblanks">dspy-connection-row.ui</file>
+    <file preprocess="xml-stripblanks">dspy-name-row.ui</file>
+    <file preprocess="xml-stripblanks">dspy-name-view.ui</file>
+    <file preprocess="xml-stripblanks">gbp-dspy-surface.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/dspy/dspy.plugin b/src/plugins/dspy/dspy.plugin
new file mode 100644
index 000000000..b98a209a9
--- /dev/null
+++ b/src/plugins/dspy/dspy.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Authors=Christian Hergert <christian hergert me>
+Builtin=true
+Copyright=Copyright © 2019 Christian Hergert
+Description=Explore DBus session and system connections
+Embedded=_gbp_dspy_register_types
+Module=dspy
+Name=DBus Connection Explorer
+X-Workspace-Kind=primary;editor;
diff --git a/src/plugins/dspy/gbp-dspy-surface.c b/src/plugins/dspy/gbp-dspy-surface.c
new file mode 100644
index 000000000..46d63dbc0
--- /dev/null
+++ b/src/plugins/dspy/gbp-dspy-surface.c
@@ -0,0 +1,208 @@
+/* gbp-dspy-surface.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-dspy-surface"
+
+#include "config.h"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+
+#include "dspy-connection-model.h"
+#include "dspy-connection-row.h"
+#include "dspy-name.h"
+#include "dspy-name-row.h"
+#include "dspy-name-view.h"
+
+#include "gbp-dspy-surface.h"
+
+struct _GbpDspySurface
+{
+  IdeSurface           parent_instance;
+
+  GtkListBox          *connections_list_box;
+  GtkScrolledWindow   *connections_scroller;
+  GtkPaned            *paned;
+  GtkScrolledWindow   *names_scroller;
+  GtkListBox          *names_list_box;
+  GtkStack            *view_stack;
+  DspyNameView        *name_view;
+
+  DspyConnectionModel *model;
+};
+
+G_DEFINE_TYPE (GbpDspySurface, gbp_dspy_surface, IDE_TYPE_SURFACE)
+
+static GtkWidget *
+create_names_row (gpointer item,
+                  gpointer user_data)
+{
+  DspyNameRow *row;
+  DspyName *name = item;
+
+  g_assert (DSPY_IS_NAME (name));
+
+  row = dspy_name_row_new (name);
+  gtk_widget_show (GTK_WIDGET (row));
+
+  return GTK_WIDGET (row);
+}
+
+static void
+name_row_activated_cb (GbpDspySurface *self,
+                       DspyNameRow    *row,
+                       GtkListBox     *list_box)
+{
+  g_assert (GBP_IS_DSPY_SURFACE (self));
+  g_assert (DSPY_IS_NAME_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (list_box));
+
+  dspy_name_view_set_name (self->name_view,
+                           dspy_connection_model_get_connection (self->model),
+                           dspy_connection_model_get_bus_type (self->model),
+                           dspy_connection_model_get_address (self->model),
+                           dspy_name_row_get_name (row));
+  gtk_stack_set_visible_child_name (self->view_stack, "name");
+}
+
+static void
+connection_row_activated_cb (GbpDspySurface *self,
+                             GtkListBoxRow  *row,
+                             GtkListBox     *list_box)
+{
+  g_autoptr(GDBusConnection) bus = NULL;
+  g_autoptr(DspyConnectionModel) model = NULL;
+  g_autoptr(GError) error = NULL;
+  const gchar *addr = NULL;
+  GBusType bus_type;
+
+  g_assert (GBP_IS_DSPY_SURFACE (self));
+  g_assert (DSPY_IS_CONNECTION_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (list_box));
+
+  if ((bus_type = dspy_connection_row_get_bus_type (DSPY_CONNECTION_ROW (row))))
+    bus = g_bus_get_sync (bus_type, NULL, &error);
+  else if ((addr = dspy_connection_row_get_address (DSPY_CONNECTION_ROW (row))))
+    bus = g_dbus_connection_new_for_address_sync (addr,
+                                                  (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
+                                                   G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
+                                                  NULL, NULL, &error);
+  else
+    g_return_if_reached ();
+
+  if (error != NULL)
+    {
+      g_critical ("Failed to connect to bus: %s", error->message);
+      return;
+    }
+
+  model = dspy_connection_model_new ();
+  dspy_connection_model_set_connection (model, bus);
+  dspy_connection_model_set_bus_type (model, bus_type);
+  dspy_connection_model_set_address (model, addr);
+  gtk_list_box_bind_model (self->names_list_box, G_LIST_MODEL (model), create_names_row, NULL, NULL);
+  g_set_object (&self->model, model);
+}
+
+static void
+add_connection (GbpDspySurface *self,
+                const gchar    *name,
+                GBusType        bus_type,
+                const gchar    *addr)
+{
+  DspyConnectionRow *row;
+
+  g_assert (GBP_IS_DSPY_SURFACE (self));
+
+  row = dspy_connection_row_new ();
+  dspy_connection_row_set_title (row, name);
+
+  if (bus_type != G_BUS_TYPE_NONE)
+    dspy_connection_row_set_bus_type (row, bus_type);
+  else if (addr)
+    dspy_connection_row_set_address (row, addr);
+  else
+    g_return_if_reached ();
+
+  gtk_container_add (GTK_CONTAINER (self->connections_list_box), GTK_WIDGET (row));
+
+  gtk_widget_show (GTK_WIDGET (row));
+}
+
+static void
+gbp_dspy_surface_finalize (GObject *object)
+{
+  GbpDspySurface *self = (GbpDspySurface *)object;
+
+  g_clear_object (&self->model);
+
+  G_OBJECT_CLASS (gbp_dspy_surface_parent_class)->finalize (object);
+}
+
+static void
+gbp_dspy_surface_class_init (GbpDspySurfaceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gbp_dspy_surface_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/dspy/gbp-dspy-surface.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpDspySurface, connections_list_box);
+  gtk_widget_class_bind_template_child (widget_class, GbpDspySurface, connections_scroller);
+  gtk_widget_class_bind_template_child (widget_class, GbpDspySurface, paned);
+  gtk_widget_class_bind_template_child (widget_class, GbpDspySurface, names_list_box);
+  gtk_widget_class_bind_template_child (widget_class, GbpDspySurface, names_scroller);
+  gtk_widget_class_bind_template_child (widget_class, GbpDspySurface, name_view);
+  gtk_widget_class_bind_template_child (widget_class, GbpDspySurface, view_stack);
+
+  g_type_ensure (DSPY_TYPE_NAME_VIEW);
+}
+
+static void
+gbp_dspy_surface_init (GbpDspySurface *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_widget_set_name (GTK_WIDGET (self), "dspy");
+  ide_surface_set_icon_name (IDE_SURFACE (self), "edit-find-symbolic");
+  ide_surface_set_title (IDE_SURFACE (self), _("DBus Inspector"));
+
+  g_signal_connect_object (self->connections_list_box,
+                           "row-activated",
+                           G_CALLBACK (connection_row_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->names_list_box,
+                           "row-activated",
+                           G_CALLBACK (name_row_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  add_connection (self, _("System Bus"), G_BUS_TYPE_SYSTEM, NULL);
+  add_connection (self, _("Session Bus"), G_BUS_TYPE_SESSION, NULL);
+}
+
+GbpDspySurface *
+gbp_dspy_surface_new (void)
+{
+  return g_object_new (GBP_TYPE_DSPY_SURFACE, NULL);
+}
diff --git a/src/plugins/dspy/gbp-dspy-surface.h b/src/plugins/dspy/gbp-dspy-surface.h
new file mode 100644
index 000000000..2ec6a35f7
--- /dev/null
+++ b/src/plugins/dspy/gbp-dspy-surface.h
@@ -0,0 +1,33 @@
+/* gbp-dspy-surface.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-gui.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_DSPY_SURFACE (gbp_dspy_surface_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpDspySurface, gbp_dspy_surface, GBP, DSPY_SURFACE, IdeSurface)
+
+GbpDspySurface *gbp_dspy_surface_new (void);
+
+G_END_DECLS
diff --git a/src/plugins/dspy/gbp-dspy-surface.ui b/src/plugins/dspy/gbp-dspy-surface.ui
new file mode 100644
index 000000000..294fefef0
--- /dev/null
+++ b/src/plugins/dspy/gbp-dspy-surface.ui
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpDspySurface" parent="IdeSurface">
+    <child>
+      <object class="GtkPaned" id="paned">
+        <property name="orientation">horizontal</property>
+        <property name="position">300</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkPaned" id="left_paned">
+            <property name="orientation">vertical</property>
+            <property name="position">75</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkScrolledWindow" id="connections_scroller">
+                <property name="propagate-natural-height">true</property>
+                <property name="propagate-natural-width">true</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkListBox" id="connections_list_box">
+                    <property name="selection-mode">browse</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="shrink">false</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow" id="names_scroller">
+                <property name="propagate-natural-height">true</property>
+                <property name="propagate-natural-width">true</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkListBox" id="names_list_box">
+                    <property name="selection-mode">browse</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="shrink">false</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="shrink">false</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkStack" id="view_stack">
+            <property name="expand">true</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="DzlEmptyState" id="empty_state">
+                <property name="icon-name">edit-find-symbolic</property>
+                <property name="title" translatable="yes">Select a Connection</property>
+                <property name="subtitle" translatable="yes">Select a connection and name on the bus to view 
details</property>
+                <property name="visible">true</property>
+              </object>
+              <packing>
+                <property name="name">empty</property>
+              </packing>
+            </child>
+            <child>
+              <object class="DspyNameView" id="name_view">
+                <property name="visible">true</property>
+              </object>
+              <packing>
+                <property name="name">name</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/dspy/gbp-dspy-workspace-addin.c b/src/plugins/dspy/gbp-dspy-workspace-addin.c
new file mode 100644
index 000000000..4ddc43e18
--- /dev/null
+++ b/src/plugins/dspy/gbp-dspy-workspace-addin.c
@@ -0,0 +1,92 @@
+/* gbp-dspy-workspace-addin.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-dspy-workspace-addin"
+
+#include <libide-editor.h>
+
+#include "dspy-connection-model.h"
+
+#include "gbp-dspy-surface.h"
+#include "gbp-dspy-workspace-addin.h"
+
+struct _GbpDspyWorkspaceAddin
+{
+  GObject              parent_instance;
+  DspyConnectionModel *model;
+  GbpDspySurface      *surface;
+};
+
+static void
+gbp_dspy_workspace_addin_load (IdeWorkspaceAddin *addin,
+                               IdeWorkspace      *workspace)
+{
+  GbpDspyWorkspaceAddin *self = (GbpDspyWorkspaceAddin *)addin;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_DSPY_WORKSPACE_ADDIN (addin));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace) || IDE_IS_EDITOR_WORKSPACE (workspace));
+
+  self->surface = gbp_dspy_surface_new ();
+  g_signal_connect (self->surface,
+                    "destroy",
+                    G_CALLBACK (gtk_widget_destroyed),
+                    &self->surface);
+  ide_workspace_add_surface (workspace, IDE_SURFACE (self->surface));
+  gtk_widget_show (GTK_WIDGET (self->surface));
+
+  //self->model = dspy_connection_model_new ();
+  //dspy_connection_model_set_connection (self->model, g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL));
+}
+
+static void
+gbp_dspy_workspace_addin_unload (IdeWorkspaceAddin *addin,
+                                 IdeWorkspace      *workspace)
+{
+  GbpDspyWorkspaceAddin *self = (GbpDspyWorkspaceAddin *)addin;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_DSPY_WORKSPACE_ADDIN (addin));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace) || IDE_IS_EDITOR_WORKSPACE (workspace));
+
+  if (self->surface != NULL)
+    gtk_widget_destroy (GTK_WIDGET (self->surface));
+
+}
+
+static void
+workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
+{
+  iface->load = gbp_dspy_workspace_addin_load;
+  iface->unload = gbp_dspy_workspace_addin_unload;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpDspyWorkspaceAddin, gbp_dspy_workspace_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
+
+static void
+gbp_dspy_workspace_addin_class_init (GbpDspyWorkspaceAddinClass *klass)
+{
+}
+
+static void
+gbp_dspy_workspace_addin_init (GbpDspyWorkspaceAddin *self)
+{
+}
diff --git a/src/plugins/dspy/gbp-dspy-workspace-addin.h b/src/plugins/dspy/gbp-dspy-workspace-addin.h
new file mode 100644
index 000000000..e4cfec50e
--- /dev/null
+++ b/src/plugins/dspy/gbp-dspy-workspace-addin.h
@@ -0,0 +1,31 @@
+/* gbp-dspy-workspace-addin.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-gui.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_DSPY_WORKSPACE_ADDIN (gbp_dspy_workspace_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpDspyWorkspaceAddin, gbp_dspy_workspace_addin, GBP, DSPY_WORKSPACE_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/dspy/gtk/menus.ui b/src/plugins/dspy/gtk/menus.ui
new file mode 100644
index 000000000..03ae767e4
--- /dev/null
+++ b/src/plugins/dspy/gtk/menus.ui
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <menu id="ide-primary-workspace-surfaces-menu">
+    <section id="ide-primary-workspace-surfaces-menu-section">
+      <item>
+        <attribute name="id">surface-menu-dspy</attribute>
+        <attribute name="label" translatable="yes">DBus Inspector</attribute>
+        <attribute name="role">normal</attribute>
+        <attribute name="action">win.surface</attribute>
+        <attribute name="target">dspy</attribute>
+        <attribute name="verb-icon-name">edit-find-symbolic</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/src/plugins/dspy/meson.build b/src/plugins/dspy/meson.build
new file mode 100644
index 000000000..b3e937d9f
--- /dev/null
+++ b/src/plugins/dspy/meson.build
@@ -0,0 +1,22 @@
+if get_option('plugin_dspy')
+
+plugins_sources += files([
+  'dspy-plugin.c',
+  'dspy-connection-model.c',
+  'dspy-connection-row.c',
+  'dspy-name.c',
+  'dspy-name-row.c',
+  'dspy-name-view.c',
+  'gbp-dspy-surface.c',
+  'gbp-dspy-workspace-addin.c',
+])
+
+plugin_dspy_resources = gnome.compile_resources(
+  'dspy-resources',
+  'dspy.gresource.xml',
+  c_name: 'gbp_dspy',
+)
+
+plugins_sources += plugin_dspy_resources
+
+endif
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index 0c5fd6aa4..4feeb4a41 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -56,6 +56,7 @@ subdir('devhelp')
 subdir('deviceui')
 subdir('deviced')
 subdir('doap')
+subdir('dspy')
 subdir('editor')
 subdir('editorconfig')
 subdir('emacs')
@@ -143,6 +144,7 @@ status += [
   'CTags ................. : @0@'.format(get_option('plugin_ctags')),
   'Devhelp ............... : @0@'.format(get_option('plugin_devhelp')),
   'Deviced ............... : @0@'.format(get_option('plugin_deviced')),
+  'DBus Spy .............. : @0@'.format(get_option('plugin_dspy')),
   'Editorconfig .......... : @0@'.format(get_option('plugin_editorconfig')),
   'ESLint ................ : @0@'.format(get_option('plugin_eslint')),
   'File Search ........... : @0@'.format(get_option('plugin_file_search')),


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]