[gnome-disk-utility/new-ui] Add support for SAS expanders



commit 1180ddef2fddc639a1bb6d874494a82f3fad1a9a
Author: David Zeuthen <davidz redhat com>
Date:   Sun Nov 29 23:27:23 2009 -0500

    Add support for SAS expanders

 src/gdu/Makefile.am                |    8 +
 src/gdu/gdu-expander.c             |  364 ++++++++++++++++++++++++++++++++++++
 src/gdu/gdu-expander.h             |   78 ++++++++
 src/gdu/gdu-hba.c                  |   23 ++-
 src/gdu/gdu-hub.c                  |  266 ++++++++++++++++++++++++++
 src/gdu/gdu-hub.h                  |   61 ++++++
 src/gdu/gdu-pool.c                 |  338 ++++++++++++++++++++++++++++++++-
 src/gdu/gdu-pool.h                 |    7 +
 src/gdu/gdu-private.h              |    5 +
 src/gdu/gdu-types.h                |    2 +
 src/gdu/gdu.h                      |    2 +
 src/palimpsest/Makefile.am         |    1 +
 src/palimpsest/gdu-section-drive.c |   68 ++++++-
 src/palimpsest/gdu-section-hub.c   |  234 +++++++++++++++++++++++
 src/palimpsest/gdu-section-hub.h   |   58 ++++++
 src/palimpsest/gdu-shell.c         |    5 +
 16 files changed, 1498 insertions(+), 22 deletions(-)
---
diff --git a/src/gdu/Makefile.am b/src/gdu/Makefile.am
index 7594045..0b7cbc2 100644
--- a/src/gdu/Makefile.am
+++ b/src/gdu/Makefile.am
@@ -4,6 +4,7 @@ BUILT_SOURCES =                                         	\
         devkit-disks-daemon-glue.h                      	\
         devkit-disks-device-glue.h				\
         devkit-disks-adapter-glue.h				\
+        devkit-disks-expander-glue.h				\
         devkit-disks-port-glue.h				\
 	gdu-marshal.h			gdu-marshal.c
 
@@ -22,6 +23,9 @@ devkit-disks-device-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceK
 devkit-disks-adapter-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Adapter.xml Makefile.am
 	dbus-binding-tool --prefix=devkit_disks_daemon --mode=glib-client --output=devkit-disks-adapter-glue.h /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Adapter.xml
 
+devkit-disks-expander-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Expander.xml Makefile.am
+	dbus-binding-tool --prefix=devkit_disks_daemon --mode=glib-client --output=devkit-disks-expander-glue.h /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Expander.xml
+
 devkit-disks-port-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Port.xml Makefile.am
 	dbus-binding-tool --prefix=devkit_disks_daemon --mode=glib-client --output=devkit-disks-port-glue.h /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Port.xml
 
@@ -35,6 +39,7 @@ libgduinclude_HEADERS =              			\
 	gdu-callbacks.h					\
 	gdu-device.h					\
 	gdu-adapter.h					\
+	gdu-expander.h					\
 	gdu-port.h					\
 	gdu-drive.h					\
 	gdu-linux-md-drive.h				\
@@ -47,6 +52,7 @@ libgduinclude_HEADERS =              			\
 	gdu-volume.h					\
 	gdu-volume-hole.h				\
 	gdu-hba.h					\
+	gdu-hub.h					\
 	$(NULL)
 
 libgdu_la_SOURCES =                                					\
@@ -57,6 +63,7 @@ libgdu_la_SOURCES =                                					\
 	gdu-pool.c				gdu-pool.h				\
 	gdu-device.c				gdu-device.h				\
 	gdu-adapter.c				gdu-adapter.h				\
+	gdu-expander.c				gdu-expander.h				\
 	gdu-port.c				gdu-port.h				\
 	gdu-drive.c				gdu-drive.h				\
 	gdu-linux-md-drive.c			gdu-linux-md-drive.h			\
@@ -67,6 +74,7 @@ libgdu_la_SOURCES =                                					\
 	gdu-error.c				gdu-error.h				\
 	gdu-process.c				gdu-process.h				\
 	gdu-hba.c				gdu-hba.h				\
+	gdu-hub.c				gdu-hub.h				\
 						gdu-private.h				\
 	$(BUILT_SOURCES)								\
 	$(NULL)
diff --git a/src/gdu/gdu-expander.c b/src/gdu/gdu-expander.c
new file mode 100644
index 0000000..376df7a
--- /dev/null
+++ b/src/gdu/gdu-expander.c
@@ -0,0 +1,364 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-expander.c
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <dbus/dbus-glib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "gdu-private.h"
+#include "gdu-pool.h"
+#include "gdu-expander.h"
+#include "devkit-disks-expander-glue.h"
+
+/* --- SUCKY CODE BEGIN --- */
+
+/* This totally sucks; dbus-bindings-tool and dbus-glib should be able
+ * to do this for us.
+ *
+ * TODO: keep in sync with code in tools/devkit-disks in DeviceKit-disks.
+ */
+
+typedef struct
+{
+        gchar *native_path;
+
+        gchar *vendor;
+        gchar *model;
+        gchar *revision;
+        guint num_ports;
+        gchar **upstream_ports;
+        gchar *adapter;
+} ExpanderProperties;
+
+static void
+collect_props (const char *key, const GValue *value, ExpanderProperties *props)
+{
+        gboolean handled = TRUE;
+
+        if (strcmp (key, "NativePath") == 0)
+                props->native_path = g_strdup (g_value_get_string (value));
+
+        else if (strcmp (key, "Vendor") == 0)
+                props->vendor = g_value_dup_string (value);
+        else if (strcmp (key, "Model") == 0)
+                props->model = g_value_dup_string (value);
+        else if (strcmp (key, "Revision") == 0)
+                props->revision = g_value_dup_string (value);
+        else if (strcmp (key, "NumPorts") == 0)
+                props->num_ports = g_value_get_uint (value);
+        else if (strcmp (key, "UpstreamPorts") == 0) {
+                guint n;
+                GPtrArray *object_paths;
+
+                object_paths = g_value_get_boxed (value);
+
+                props->upstream_ports = g_new0 (char *, object_paths->len + 1);
+                for (n = 0; n < object_paths->len; n++)
+                        props->upstream_ports[n] = g_strdup (object_paths->pdata[n]);
+                props->upstream_ports[n] = NULL;
+        }
+        else if (strcmp (key, "Adapter") == 0)
+                props->adapter = g_value_dup_boxed (value);
+
+        else
+                handled = FALSE;
+
+        if (!handled)
+                g_warning ("unhandled property '%s'", key);
+}
+
+static void
+expander_properties_free (ExpanderProperties *props)
+{
+        g_free (props->native_path);
+        g_free (props->vendor);
+        g_free (props->model);
+        g_free (props->revision);
+        g_free (props->adapter);
+        g_strfreev (props->upstream_ports);
+        g_free (props);
+}
+
+static ExpanderProperties *
+expander_properties_get (DBusGConnection *bus,
+                           const char *object_path)
+{
+        ExpanderProperties *props;
+        GError *error;
+        GHashTable *hash_table;
+        DBusGProxy *prop_proxy;
+        const char *ifname = "org.freedesktop.DeviceKit.Disks.Expander";
+
+        props = g_new0 (ExpanderProperties, 1);
+
+	prop_proxy = dbus_g_proxy_new_for_name (bus,
+                                                "org.freedesktop.DeviceKit.Disks",
+                                                object_path,
+                                                "org.freedesktop.DBus.Properties");
+        error = NULL;
+        if (!dbus_g_proxy_call (prop_proxy,
+                                "GetAll",
+                                &error,
+                                G_TYPE_STRING,
+                                ifname,
+                                G_TYPE_INVALID,
+                                dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
+                                &hash_table,
+                                G_TYPE_INVALID)) {
+                g_warning ("Couldn't call GetAll() to get properties for %s: %s", object_path, error->message);
+                g_error_free (error);
+
+                expander_properties_free (props);
+                props = NULL;
+                goto out;
+        }
+
+        g_hash_table_foreach (hash_table, (GHFunc) collect_props, props);
+
+        g_hash_table_unref (hash_table);
+
+#if 0
+        g_print ("----------------------------------------------------------------------\n");
+        g_print ("native_path: %s\n", props->native_path);
+        g_print ("vendor:      %s\n", props->vendor);
+        g_print ("model:       %s\n", props->model);
+        g_print ("revision:    %s\n", props->revision);
+        g_print ("adapter:     %s\n", props->adapter);
+        g_print ("num_ports:   %d\n", props->num_ports);
+        g_print ("upstream_ports:\n");
+        {
+                guint n;
+                for (n = 0; props->upstream_ports != NULL && props->upstream_ports[n] != NULL; n++) {
+                        g_print ("  %s\n", props->upstream_ports[n]);
+                }
+        }
+#endif
+
+out:
+        g_object_unref (prop_proxy);
+        return props;
+}
+
+/* --- SUCKY CODE END --- */
+
+struct _GduExpanderPrivate
+{
+        DBusGConnection *bus;
+        DBusGProxy *proxy;
+        GduPool *pool;
+
+        char *object_path;
+
+        ExpanderProperties *props;
+};
+
+enum {
+        CHANGED,
+        REMOVED,
+        LAST_SIGNAL,
+};
+
+static GObjectClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GduExpander, gdu_expander, G_TYPE_OBJECT);
+
+GduPool *
+gdu_expander_get_pool (GduExpander *expander)
+{
+        return g_object_ref (expander->priv->pool);
+}
+
+static void
+gdu_expander_finalize (GduExpander *expander)
+{
+        g_debug ("##### finalized expander %s",
+                 expander->priv->props != NULL ? expander->priv->props->native_path : expander->priv->object_path);
+
+        dbus_g_connection_unref (expander->priv->bus);
+        g_free (expander->priv->object_path);
+        if (expander->priv->proxy != NULL)
+                g_object_unref (expander->priv->proxy);
+        if (expander->priv->pool != NULL)
+                g_object_unref (expander->priv->pool);
+        if (expander->priv->props != NULL)
+                expander_properties_free (expander->priv->props);
+
+        if (G_OBJECT_CLASS (parent_class)->finalize)
+                (* G_OBJECT_CLASS (parent_class)->finalize) (G_OBJECT (expander));
+}
+
+static void
+gdu_expander_class_init (GduExpanderClass *klass)
+{
+        GObjectClass *obj_class = (GObjectClass *) klass;
+
+        parent_class = g_type_class_peek_parent (klass);
+
+        obj_class->finalize = (GObjectFinalizeFunc) gdu_expander_finalize;
+
+        g_type_class_add_private (klass, sizeof (GduExpanderPrivate));
+
+        signals[CHANGED] =
+                g_signal_new ("changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GduExpanderClass, changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals[REMOVED] =
+                g_signal_new ("removed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GduExpanderClass, removed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+}
+
+static void
+gdu_expander_init (GduExpander *expander)
+{
+        expander->priv = G_TYPE_INSTANCE_GET_PRIVATE (expander, GDU_TYPE_EXPANDER, GduExpanderPrivate);
+}
+
+static gboolean
+update_info (GduExpander *expander)
+{
+        ExpanderProperties *new_properties;
+
+        new_properties = expander_properties_get (expander->priv->bus, expander->priv->object_path);
+        if (new_properties != NULL) {
+                if (expander->priv->props != NULL)
+                        expander_properties_free (expander->priv->props);
+                expander->priv->props = new_properties;
+                return TRUE;
+        } else {
+                return FALSE;
+        }
+}
+
+
+GduExpander *
+_gdu_expander_new_from_object_path (GduPool *pool, const char *object_path)
+{
+        GError *error;
+        GduExpander *expander;
+
+        expander = GDU_EXPANDER (g_object_new (GDU_TYPE_EXPANDER, NULL));
+        expander->priv->object_path = g_strdup (object_path);
+        expander->priv->pool = g_object_ref (pool);
+
+        error = NULL;
+        expander->priv->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+        if (expander->priv->bus == NULL) {
+                g_warning ("Couldn't connect to system bus: %s", error->message);
+                g_error_free (error);
+                goto error;
+        }
+
+	expander->priv->proxy = dbus_g_proxy_new_for_name (expander->priv->bus,
+                                                         "org.freedesktop.DeviceKit.Disks",
+                                                         expander->priv->object_path,
+                                                         "org.freedesktop.DeviceKit.Disks.Expander");
+        dbus_g_proxy_set_default_timeout (expander->priv->proxy, INT_MAX);
+        dbus_g_proxy_add_signal (expander->priv->proxy, "Changed", G_TYPE_INVALID);
+
+        /* TODO: connect signals */
+
+        if (!update_info (expander))
+                goto error;
+
+        g_debug ("_gdu_expander_new_from_object_path: %s", expander->priv->props->native_path);
+
+        return expander;
+error:
+        g_object_unref (expander);
+        return NULL;
+}
+
+gboolean
+_gdu_expander_changed (GduExpander *expander)
+{
+        g_debug ("_gdu_expander_changed: %s", expander->priv->props->native_path);
+        if (update_info (expander)) {
+                g_signal_emit (expander, signals[CHANGED], 0);
+                return TRUE;
+        } else {
+                return FALSE;
+        }
+}
+
+const gchar *
+gdu_expander_get_object_path (GduExpander *expander)
+{
+        return expander->priv->object_path;
+}
+
+
+const gchar *
+gdu_expander_get_native_path (GduExpander *expander)
+{
+        return expander->priv->props->native_path;
+}
+
+const gchar *
+gdu_expander_get_vendor (GduExpander *expander)
+{
+        return expander->priv->props->vendor;
+}
+
+const gchar *
+gdu_expander_get_model (GduExpander *expander)
+{
+        return expander->priv->props->model;
+}
+
+const gchar *
+gdu_expander_get_revision (GduExpander *expander)
+{
+        return expander->priv->props->revision;
+}
+
+guint
+gdu_expander_get_num_ports (GduExpander *expander)
+{
+        return expander->priv->props->num_ports;
+}
+
+gchar **
+gdu_expander_get_upstream_ports (GduExpander *expander)
+{
+        return expander->priv->props->upstream_ports;
+}
+
+const gchar *
+gdu_expander_get_adapter (GduExpander *expander)
+{
+        return expander->priv->props->adapter;
+}
+
diff --git a/src/gdu/gdu-expander.h b/src/gdu/gdu-expander.h
new file mode 100644
index 0000000..0749002
--- /dev/null
+++ b/src/gdu/gdu-expander.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-expander.h
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_INSIDE_GDU_H) && !defined (GDU_COMPILATION)
+#error "Only <gdu/gdu.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __GDU_EXPANDER_H
+#define __GDU_EXPANDER_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <gdu/gdu-types.h>
+#include <gdu/gdu-callbacks.h>
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_EXPANDER           (gdu_expander_get_type ())
+#define GDU_EXPANDER(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_EXPANDER, GduExpander))
+#define GDU_EXPANDER_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST ((k), GDU_EXPANDER,  GduExpanderClass))
+#define GDU_IS_EXPANDER(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_EXPANDER))
+#define GDU_IS_EXPANDER_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_EXPANDER))
+#define GDU_EXPANDER_GET_CLASS(k)   (G_TYPE_INSTANCE_GET_CLASS ((k), GDU_TYPE_EXPANDER, GduExpanderClass))
+
+typedef struct _GduExpanderClass    GduExpanderClass;
+typedef struct _GduExpanderPrivate  GduExpanderPrivate;
+
+struct _GduExpander
+{
+        GObject parent;
+
+        /* private */
+        GduExpanderPrivate *priv;
+};
+
+struct _GduExpanderClass
+{
+        GObjectClass parent_class;
+
+        /* signals */
+        void (*changed)     (GduExpander *expander);
+        void (*removed)     (GduExpander *expander);
+};
+
+GType        gdu_expander_get_type              (void);
+const char  *gdu_expander_get_object_path       (GduExpander   *expander);
+GduPool     *gdu_expander_get_pool              (GduExpander   *expander);
+
+const gchar *gdu_expander_get_native_path       (GduExpander   *expander);
+const gchar *gdu_expander_get_vendor            (GduExpander   *expander);
+const gchar *gdu_expander_get_model             (GduExpander   *expander);
+const gchar *gdu_expander_get_revision          (GduExpander   *expander);
+guint        gdu_expander_get_num_ports         (GduExpander   *expander);
+gchar      **gdu_expander_get_upstream_ports    (GduExpander   *expander);
+const gchar *gdu_expander_get_adapter           (GduExpander   *expander);
+
+G_END_DECLS
+
+#endif /* __GDU_EXPANDER_H */
diff --git a/src/gdu/gdu-hba.c b/src/gdu/gdu-hba.c
index 74d27e7..40b7675 100644
--- a/src/gdu/gdu-hba.c
+++ b/src/gdu/gdu-hba.c
@@ -143,8 +143,27 @@ gdu_hba_get_enclosing_presentable (GduPresentable *presentable)
 static char *
 gdu_hba_get_name (GduPresentable *presentable)
 {
-        /* TODO: include type e.g. SATA-I, SATA-II, SAS etc */
-        return g_strdup (_("Host Adapter"));
+        GduHba *hba = GDU_HBA (presentable);
+        const gchar *fabric;
+        gchar *fabric_str;
+
+        fabric = gdu_adapter_get_fabric (hba->priv->adapter);
+
+        if (g_str_has_prefix (fabric, "ata_pata")) {
+                fabric_str = g_strdup ("PATA Host Adapter");
+        } else if (g_str_has_prefix (fabric, "ata_sata")) {
+                fabric_str = g_strdup ("SATA Host Adapter");
+        } else if (g_str_has_prefix (fabric, "ata")) {
+                fabric_str = g_strdup ("ATA Host Adapter");
+        } else if (g_str_has_prefix (fabric, "scsi_sas")) {
+                fabric_str = g_strdup ("SAS Host Adapter");
+        } else if (g_str_has_prefix (fabric, "scsi")) {
+                fabric_str = g_strdup ("SCSI Host Adapter");
+        } else {
+                fabric_str = g_strdup ("Host Adapter");
+        }
+
+        return fabric_str;
 }
 
 static gchar *
diff --git a/src/gdu/gdu-hub.c b/src/gdu/gdu-hub.c
new file mode 100644
index 0000000..0986d74
--- /dev/null
+++ b/src/gdu/gdu-hub.c
@@ -0,0 +1,266 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-hub.c
+ *
+ * Copyright (C) 2007 David Zeuthen
+ *
+ * This library 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <dbus/dbus-glib.h>
+#include <stdlib.h>
+
+#include "gdu-private.h"
+#include "gdu-util.h"
+#include "gdu-pool.h"
+#include "gdu-expander.h"
+#include "gdu-hub.h"
+#include "gdu-presentable.h"
+#include "gdu-linux-md-drive.h"
+
+/**
+ * SECTION:gdu-hub
+ * @title: GduHub
+ * @short_description: HUBs
+ *
+ * #GduHub objects are used to represent host board expanders (also
+ * called disk expanders).
+ *
+ * See the documentation for #GduPresentable for the big picture.
+ */
+
+struct _GduHubPrivate
+{
+        GduExpander *expander;
+        GduPool *pool;
+        GduPresentable *enclosing_presentable;
+        gchar *id;
+};
+
+static GObjectClass *parent_class = NULL;
+
+static void gdu_hub_presentable_iface_init (GduPresentableIface *iface);
+G_DEFINE_TYPE_WITH_CODE (GduHub, gdu_hub, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDU_TYPE_PRESENTABLE,
+                                                gdu_hub_presentable_iface_init))
+
+static void expander_changed (GduExpander *expander, gpointer user_data);
+
+
+static void
+gdu_hub_finalize (GObject *object)
+{
+        GduHub *hub = GDU_HUB (object);
+
+        //g_debug ("##### finalized hub '%s' %p", hub->priv->id, hub);
+
+        if (hub->priv->expander != NULL) {
+                g_signal_handlers_disconnect_by_func (hub->priv->expander, expander_changed, hub);
+                g_object_unref (hub->priv->expander);
+        }
+
+        if (hub->priv->pool != NULL)
+                g_object_unref (hub->priv->pool);
+
+        if (hub->priv->enclosing_presentable != NULL)
+                g_object_unref (hub->priv->enclosing_presentable);
+
+        g_free (hub->priv->id);
+
+        if (G_OBJECT_CLASS (parent_class)->finalize != NULL)
+                (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+gdu_hub_class_init (GduHubClass *klass)
+{
+        GObjectClass *obj_class = (GObjectClass *) klass;
+
+        parent_class = g_type_class_peek_parent (klass);
+
+        obj_class->finalize = gdu_hub_finalize;
+
+        g_type_class_add_private (klass, sizeof (GduHubPrivate));
+}
+
+static void
+gdu_hub_init (GduHub *hub)
+{
+        hub->priv = G_TYPE_INSTANCE_GET_PRIVATE (hub, GDU_TYPE_HUB, GduHubPrivate);
+}
+
+static void
+expander_changed (GduExpander *expander, gpointer user_data)
+{
+        GduHub *hub = GDU_HUB (user_data);
+        g_signal_emit_by_name (hub, "changed");
+        g_signal_emit_by_name (hub->priv->pool, "presentable-changed", hub);
+}
+
+GduHub *
+_gdu_hub_new_from_expander (GduPool *pool, GduExpander *expander, GduPresentable *enclosing_presentable)
+{
+        GduHub *hub;
+
+        hub = GDU_HUB (g_object_new (GDU_TYPE_HUB, NULL));
+        hub->priv->expander = g_object_ref (expander);
+        hub->priv->pool = g_object_ref (pool);
+        hub->priv->enclosing_presentable =
+                enclosing_presentable != NULL ? g_object_ref (enclosing_presentable) : NULL;
+        hub->priv->id = g_strdup (gdu_expander_get_native_path (hub->priv->expander));
+        g_signal_connect (expander, "changed", (GCallback) expander_changed, hub);
+        return hub;
+}
+
+static const gchar *
+gdu_hub_get_id (GduPresentable *presentable)
+{
+        GduHub *hub = GDU_HUB (presentable);
+        return hub->priv->id;
+}
+
+static GduDevice *
+gdu_hub_get_device (GduPresentable *presentable)
+{
+        return NULL;
+}
+
+static GduPresentable *
+gdu_hub_get_enclosing_presentable (GduPresentable *presentable)
+{
+        GduHub *hub = GDU_HUB (presentable);
+        if (hub->priv->enclosing_presentable != NULL)
+                return g_object_ref (hub->priv->enclosing_presentable);
+        return NULL;
+}
+
+static char *
+gdu_hub_get_name (GduPresentable *presentable)
+{
+        /* TODO: include type e.g. SATA Port Multiplier, SAS Expander etc */
+        return g_strdup (_("SAS Expander"));
+}
+
+static gchar *
+gdu_hub_get_vpd_name (GduPresentable *presentable)
+{
+        GduHub *hub = GDU_HUB (presentable);
+        gchar *s;
+        const gchar *vendor;
+        const gchar *model;
+
+        vendor = gdu_expander_get_vendor (hub->priv->expander);
+        model = gdu_expander_get_model (hub->priv->expander);
+        s = g_strdup_printf ("%s %s", vendor, model);
+        return s;
+}
+
+static gchar *
+gdu_hub_get_description (GduPresentable *presentable)
+{
+        /* TODO: include number of ports, speed, receptable type etc. */
+        return gdu_hub_get_vpd_name (presentable);
+}
+
+static GIcon *
+gdu_hub_get_icon (GduPresentable *presentable)
+{
+        GIcon *icon;
+        icon = g_themed_icon_new_with_default_fallbacks ("gdu-hba"); /* TODO */
+        return icon;
+}
+
+static guint64
+gdu_hub_get_offset (GduPresentable *presentable)
+{
+        return 0;
+}
+
+static guint64
+gdu_hub_get_size (GduPresentable *presentable)
+{
+        return 0;
+}
+
+static GduPool *
+gdu_hub_get_pool (GduPresentable *presentable)
+{
+        GduHub *hub = GDU_HUB (presentable);
+        return gdu_expander_get_pool (hub->priv->expander);
+}
+
+static gboolean
+gdu_hub_is_allocated (GduPresentable *presentable)
+{
+        return FALSE;
+}
+
+static gboolean
+gdu_hub_is_recognized (GduPresentable *presentable)
+{
+        return FALSE;
+}
+
+GduExpander *
+gdu_hub_get_expander (GduHub *hub)
+{
+        return g_object_ref (hub->priv->expander);
+}
+
+static void
+gdu_hub_presentable_iface_init (GduPresentableIface *iface)
+{
+        iface->get_id                    = gdu_hub_get_id;
+        iface->get_device                = gdu_hub_get_device;
+        iface->get_enclosing_presentable = gdu_hub_get_enclosing_presentable;
+        iface->get_name                  = gdu_hub_get_name;
+        iface->get_description           = gdu_hub_get_description;
+        iface->get_vpd_name              = gdu_hub_get_vpd_name;
+        iface->get_icon                  = gdu_hub_get_icon;
+        iface->get_offset                = gdu_hub_get_offset;
+        iface->get_size                  = gdu_hub_get_size;
+        iface->get_pool                  = gdu_hub_get_pool;
+        iface->is_allocated              = gdu_hub_is_allocated;
+        iface->is_recognized             = gdu_hub_is_recognized;
+}
+
+void
+_gdu_hub_rewrite_enclosing_presentable (GduHub *hub)
+{
+        if (hub->priv->enclosing_presentable != NULL) {
+                const gchar *enclosing_presentable_id;
+                GduPresentable *new_enclosing_presentable;
+
+                enclosing_presentable_id = gdu_presentable_get_id (hub->priv->enclosing_presentable);
+
+                new_enclosing_presentable = gdu_pool_get_presentable_by_id (hub->priv->pool,
+                                                                            enclosing_presentable_id);
+                if (new_enclosing_presentable == NULL) {
+                        g_warning ("Error rewriting enclosing_presentable for %s, no such id %s",
+                                   hub->priv->id,
+                                   enclosing_presentable_id);
+                        goto out;
+                }
+
+                g_object_unref (hub->priv->enclosing_presentable);
+                hub->priv->enclosing_presentable = new_enclosing_presentable;
+        }
+
+ out:
+        ;
+}
diff --git a/src/gdu/gdu-hub.h b/src/gdu/gdu-hub.h
new file mode 100644
index 0000000..8d65735
--- /dev/null
+++ b/src/gdu/gdu-hub.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-hub.h
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_INSIDE_GDU_H) && !defined (GDU_COMPILATION)
+#error "Only <gdu/gdu.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __GDU_HUB_H
+#define __GDU_HUB_H
+
+#include <gdu/gdu-types.h>
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_HUB           (gdu_hub_get_type ())
+#define GDU_HUB(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_HUB, GduHub))
+#define GDU_HUB_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST ((k), GDU_HUB,  GduHubClass))
+#define GDU_IS_HUB(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_HUB))
+#define GDU_IS_HUB_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_HUB))
+#define GDU_HUB_GET_CLASS(k)   (G_TYPE_INSTANCE_GET_CLASS ((k), GDU_TYPE_HUB, GduHubClass))
+
+typedef struct _GduHubClass       GduHubClass;
+typedef struct _GduHubPrivate     GduHubPrivate;
+
+struct _GduHub
+{
+        GObject parent;
+
+        /* private */
+        GduHubPrivate *priv;
+};
+
+struct _GduHubClass
+{
+        GObjectClass parent_class;
+};
+
+GType        gdu_hub_get_type     (void);
+GduExpander *gdu_hub_get_expander (GduHub *hub);
+
+G_END_DECLS
+
+#endif /* __GDU_HUB_H */
diff --git a/src/gdu/gdu-pool.c b/src/gdu/gdu-pool.c
index 69c1c5f..5903374 100644
--- a/src/gdu/gdu-pool.c
+++ b/src/gdu/gdu-pool.c
@@ -28,11 +28,14 @@
 #include "gdu-presentable.h"
 #include "gdu-device.h"
 #include "gdu-adapter.h"
+#include "gdu-expander.h"
 #include "gdu-port.h"
 #include "gdu-drive.h"
 #include "gdu-linux-md-drive.h"
 #include "gdu-volume.h"
 #include "gdu-volume-hole.h"
+#include "gdu-hba.h"
+#include "gdu-hub.h"
 #include "gdu-known-filesystem.h"
 #include "gdu-private.h"
 
@@ -55,6 +58,9 @@ enum {
         ADAPTER_ADDED,
         ADAPTER_REMOVED,
         ADAPTER_CHANGED,
+        EXPANDER_ADDED,
+        EXPANDER_REMOVED,
+        EXPANDER_CHANGED,
         PORT_ADDED,
         PORT_REMOVED,
         PORT_CHANGED,
@@ -86,6 +92,9 @@ struct _GduPoolPrivate
         /* the current set of adapters we know about */
         GHashTable *object_path_to_adapter;
 
+        /* the current set of expanders we know about */
+        GHashTable *object_path_to_expander;
+
         /* the current set of ports we know about */
         GHashTable *object_path_to_port;
 };
@@ -107,6 +116,8 @@ gdu_pool_finalize (GduPool *pool)
 
         g_hash_table_unref (pool->priv->object_path_to_adapter);
 
+        g_hash_table_unref (pool->priv->object_path_to_expander);
+
         g_hash_table_unref (pool->priv->object_path_to_port);
 
         g_list_foreach (pool->priv->presentables, (GFunc) g_object_unref, NULL);
@@ -249,6 +260,58 @@ gdu_pool_class_init (GduPoolClass *klass)
                               GDU_TYPE_ADAPTER);
 
         /**
+         * GduPool::expander-added
+         * @pool: The #GduPool emitting the signal.
+         * @expander: The #GduExpander that was added.
+         *
+         * Emitted when @expander is added to @pool.
+         **/
+        signals[EXPANDER_ADDED] =
+                g_signal_new ("expander-added",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GduPoolClass, expander_added),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1,
+                              GDU_TYPE_EXPANDER);
+
+        /**
+         * GduPool::expander-removed
+         * @pool: The #GduPool emitting the signal.
+         * @expander: The #GduExpander that was removed.
+         *
+         * Emitted when @expander is removed from @pool. Recipients
+         * should release references to @expander.
+         **/
+        signals[EXPANDER_REMOVED] =
+                g_signal_new ("expander-removed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GduPoolClass, expander_removed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1,
+                              GDU_TYPE_EXPANDER);
+
+        /**
+         * GduPool::expander-changed
+         * @pool: The #GduPool emitting the signal.
+         * @expander: A #GduExpander.
+         *
+         * Emitted when @expander is changed.
+         **/
+        signals[EXPANDER_CHANGED] =
+                g_signal_new ("expander-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GduPoolClass, expander_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1,
+                              GDU_TYPE_EXPANDER);
+
+        /**
          * GduPool::port-added
          * @pool: The #GduPool emitting the signal.
          * @port: The #GduPort that was added.
@@ -414,9 +477,14 @@ gdu_pool_init (GduPool *pool)
                                                                    g_object_unref);
 
         pool->priv->object_path_to_adapter = g_hash_table_new_full (g_str_hash,
-                                                                       g_str_equal,
-                                                                       NULL,
-                                                                       g_object_unref);
+                                                                    g_str_equal,
+                                                                    NULL,
+                                                                    g_object_unref);
+
+        pool->priv->object_path_to_expander = g_hash_table_new_full (g_str_hash,
+                                                                     g_str_equal,
+                                                                     NULL,
+                                                                     g_object_unref);
 
         pool->priv->object_path_to_port = g_hash_table_new_full (g_str_hash,
                                                                  g_str_equal,
@@ -738,6 +806,7 @@ recompute_presentables (GduPool *pool)
         GList *l;
         GList *devices;
         GList *adapters;
+        GList *expanders;
         GList *new_partitioned_drives;
         GList *new_presentables;
         GList *added_presentables;
@@ -745,6 +814,7 @@ recompute_presentables (GduPool *pool)
         GHashTable *hash_map_from_drive_to_extended_partition;
         GHashTable *hash_map_from_linux_md_uuid_to_drive;
         GHashTable *hash_map_from_adapter_objpath_to_hba;
+        GHashTable *hash_map_from_expander_objpath_to_hub;
 
         /* The general strategy for (re-)computing presentables is rather brute force; we
          * compute the complete set of presentables every time and diff it against the
@@ -774,6 +844,11 @@ recompute_presentables (GduPool *pool)
                                                                          NULL,
                                                                          NULL);
 
+        hash_map_from_expander_objpath_to_hub = g_hash_table_new_full (g_str_hash,
+                                                                       g_str_equal,
+                                                                       NULL,
+                                                                       NULL);
+
         /* First add all HBAs */
         adapters = gdu_pool_get_adapters (pool);
         for (l = adapters; l != NULL; l = l->next) {
@@ -789,6 +864,47 @@ recompute_presentables (GduPool *pool)
                 new_presentables = g_list_prepend (new_presentables, hba);
         } /* for all adapters */
 
+        /* Then all expanders */
+        expanders = gdu_pool_get_expanders (pool);
+        for (l = expanders; l != NULL; l = l->next) {
+                GduExpander *expander = GDU_EXPANDER (l->data);
+                GduHub *hub;
+                gchar **port_object_paths;
+                GduPresentable *expander_parent;
+
+                /* we are guaranteed that upstream ports all stem from the same expander or
+                 * host adapter - so just pick the first one */
+                expander_parent = NULL;
+                port_object_paths = gdu_expander_get_upstream_ports (expander);
+                if (port_object_paths != NULL && port_object_paths[0] != NULL) {
+                        GduPort *port;
+
+                        port = gdu_pool_get_port_by_object_path (pool, port_object_paths[0]);
+
+                        /* For now, always choose the adapter as the parent - this is *probably*
+                         * the right thing (e.g. what people expect) to do _anyway_ because of
+                         * the way expanders are daisy-chained
+                         */
+                        if (port != NULL) {
+                                const gchar *adapter_object_path;
+                                adapter_object_path = gdu_port_get_adapter (port);
+                                expander_parent = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
+                                                                       adapter_object_path);
+                                g_object_unref (port);
+                        }
+                }
+
+                g_warn_if_fail (expander_parent != NULL);
+
+                hub = _gdu_hub_new_from_expander (pool, expander, expander_parent);
+
+                g_hash_table_insert (hash_map_from_expander_objpath_to_hub,
+                                     (gpointer) gdu_expander_get_object_path (expander),
+                                     hub);
+
+                new_presentables = g_list_prepend (new_presentables, hub);
+        } /* for all expanders */
+
         /* TODO: Ensure that pool->priv->devices is in topological sort order, then just loop
          *       through it and handle devices sequentially.
          *
@@ -838,17 +954,46 @@ recompute_presentables (GduPool *pool)
 
 
                         } else {
+                                GduPresentable *drive_parent;
+
+                                drive_parent = NULL;
+#if 1
+                                gchar **port_object_paths;
+
+                                /* we are guaranteed that upstream ports all stem from the same expander or
+                                 * host adapter - so just pick the first one */
+                                port_object_paths = gdu_device_drive_get_ports (device);
+                                if (port_object_paths != NULL && port_object_paths[0] != NULL) {
+                                        GduPort *port;
+
+                                        port = gdu_pool_get_port_by_object_path (pool, port_object_paths[0]);
+                                        /* choose the expander, if available, otherwise the adapter */
+                                        if (port != NULL) {
+                                                const gchar *parent_object_path;
+                                                const gchar *adapter_object_path;
+
+                                                parent_object_path = gdu_port_get_parent (port);
+                                                adapter_object_path = gdu_port_get_adapter (port);
+                                                if (g_strcmp0 (parent_object_path, adapter_object_path) != 0) {
+                                                        drive_parent = g_hash_table_lookup (hash_map_from_expander_objpath_to_hub,
+                                                                                            parent_object_path);
+                                                } else {
+                                                        drive_parent = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
+                                                                                            adapter_object_path);
+                                                }
+                                                g_object_unref (port);
+                                        }
+                                }
+#else
                                 const gchar *adapter_objpath;
-                                GduPresentable *hba;
-
-                                hba = NULL;
 
                                 adapter_objpath = gdu_device_drive_get_adapter (device);
                                 if (adapter_objpath != NULL)
-                                        hba = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
-                                                                   adapter_objpath);
+                                        drive_parent = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
+                                                                            adapter_objpath);
+#endif
 
-                                drive = _gdu_drive_new_from_device (pool, device, hba);
+                                drive = _gdu_drive_new_from_device (pool, device, drive_parent);
                         }
                         new_presentables = g_list_prepend (new_presentables, drive);
 
@@ -981,6 +1126,7 @@ recompute_presentables (GduPool *pool)
         g_hash_table_unref (hash_map_from_drive_to_extended_partition);
         g_hash_table_unref (hash_map_from_linux_md_uuid_to_drive);
         g_hash_table_unref (hash_map_from_adapter_objpath_to_hba);
+        g_hash_table_unref (hash_map_from_expander_objpath_to_hub);
 
         /* figure out the diff */
         new_presentables = g_list_sort (new_presentables, (GCompareFunc) gdu_presentable_compare);
@@ -1013,7 +1159,9 @@ recompute_presentables (GduPool *pool)
                 /* rewrite all enclosing_presentable references for presentables we are going to add
                  * such that they really refer to presentables _previously_ added
                  */
-                if (GDU_IS_DRIVE (p))
+                if (GDU_IS_HUB (p))
+                        _gdu_hub_rewrite_enclosing_presentable (GDU_HUB (p));
+                else if (GDU_IS_DRIVE (p))
                         _gdu_drive_rewrite_enclosing_presentable (GDU_DRIVE (p));
                 else if (GDU_IS_VOLUME (p))
                         _gdu_volume_rewrite_enclosing_presentable (GDU_VOLUME (p));
@@ -1038,6 +1186,8 @@ recompute_presentables (GduPool *pool)
         g_list_free (devices);
         g_list_foreach (adapters, (GFunc) g_object_unref, NULL);
         g_list_free (adapters);
+        g_list_foreach (expanders, (GFunc) g_object_unref, NULL);
+        g_list_free (expanders);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -1250,6 +1400,96 @@ adapter_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpoi
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
+expander_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data);
+
+static void
+expander_added_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data)
+{
+        GduPool *pool;
+        GduExpander *expander;
+
+        pool = GDU_POOL (user_data);
+
+        expander = gdu_pool_get_expander_by_object_path (pool, object_path);
+        if (expander != NULL) {
+                g_object_unref (expander);
+                g_warning ("Treating add for previously added expander %s as change", object_path);
+                expander_changed_signal_handler (proxy, object_path, user_data);
+                goto out;
+        }
+
+        expander = _gdu_expander_new_from_object_path (pool, object_path);
+        if (expander == NULL)
+                goto out;
+
+        g_hash_table_insert (pool->priv->object_path_to_expander,
+                             (gpointer) gdu_expander_get_object_path (expander),
+                             expander);
+        g_signal_emit (pool, signals[EXPANDER_ADDED], 0, expander);
+        //g_debug ("Added expander %s", object_path);
+
+        recompute_presentables (pool);
+
+ out:
+        ;
+}
+
+static void
+expander_removed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data)
+{
+        GduPool *pool;
+        GduExpander *expander;
+
+        pool = GDU_POOL (user_data);
+
+        expander = gdu_pool_get_expander_by_object_path (pool, object_path);
+        if (expander == NULL) {
+                g_warning ("No expander to remove for remove %s", object_path);
+                goto out;
+        }
+
+        g_hash_table_remove (pool->priv->object_path_to_expander,
+                             gdu_expander_get_object_path (expander));
+        g_signal_emit (pool, signals[EXPANDER_REMOVED], 0, expander);
+        g_signal_emit_by_name (expander, "removed");
+        g_object_unref (expander);
+        g_debug ("Removed expander %s", object_path);
+
+        recompute_presentables (pool);
+
+ out:
+        ;
+}
+
+static void
+expander_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data)
+{
+        GduPool *pool;
+        GduExpander *expander;
+
+        pool = GDU_POOL (user_data);
+
+        expander = gdu_pool_get_expander_by_object_path (pool, object_path);
+        if (expander == NULL) {
+                g_warning ("Ignoring change event on non-existant expander %s", object_path);
+                goto out;
+        }
+
+        if (_gdu_expander_changed (expander)) {
+                g_signal_emit (pool, signals[EXPANDER_CHANGED], 0, expander);
+                g_signal_emit_by_name (expander, "changed");
+        }
+        g_object_unref (expander);
+
+        recompute_presentables (pool);
+
+ out:
+        ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
 port_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data);
 
 static void
@@ -1420,6 +1660,7 @@ gdu_pool_new (void)
         int n;
         GPtrArray *devices;
         GPtrArray *adapters;
+        GPtrArray *expanders;
         GPtrArray *ports;
         GduPool *pool;
         GError *error;
@@ -1481,6 +1722,16 @@ gdu_pool_new (void)
         dbus_g_proxy_connect_signal (pool->priv->proxy, "AdapterChanged",
                                      G_CALLBACK (adapter_changed_signal_handler), pool, NULL);
 
+        dbus_g_proxy_add_signal (pool->priv->proxy, "ExpanderAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+        dbus_g_proxy_add_signal (pool->priv->proxy, "ExpanderRemoved", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+        dbus_g_proxy_add_signal (pool->priv->proxy, "ExpanderChanged", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+        dbus_g_proxy_connect_signal (pool->priv->proxy, "ExpanderAdded",
+                                     G_CALLBACK (expander_added_signal_handler), pool, NULL);
+        dbus_g_proxy_connect_signal (pool->priv->proxy, "ExpanderRemoved",
+                                     G_CALLBACK (expander_removed_signal_handler), pool, NULL);
+        dbus_g_proxy_connect_signal (pool->priv->proxy, "ExpanderChanged",
+                                     G_CALLBACK (expander_changed_signal_handler), pool, NULL);
+
         dbus_g_proxy_add_signal (pool->priv->proxy, "PortAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
         dbus_g_proxy_add_signal (pool->priv->proxy, "PortRemoved", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
         dbus_g_proxy_add_signal (pool->priv->proxy, "PortChanged", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
@@ -1544,6 +1795,28 @@ gdu_pool_new (void)
         g_ptr_array_foreach (adapters, (GFunc) g_free, NULL);
         g_ptr_array_free (adapters, TRUE);
 
+        /* prime the list of expanders */
+        error = NULL;
+        if (!org_freedesktop_DeviceKit_Disks_enumerate_expanders (pool->priv->proxy, &expanders, &error)) {
+                g_warning ("Couldn't enumerate expanders: %s", error->message);
+                g_error_free (error);
+                goto error;
+        }
+        for (n = 0; n < (int) expanders->len; n++) {
+                const char *object_path;
+                GduExpander *expander;
+
+                object_path = expanders->pdata[n];
+
+                expander = _gdu_expander_new_from_object_path (pool, object_path);
+
+                g_hash_table_insert (pool->priv->object_path_to_expander,
+                                     (gpointer) gdu_expander_get_object_path (expander),
+                                     expander);
+        }
+        g_ptr_array_foreach (expanders, (GFunc) g_free, NULL);
+        g_ptr_array_free (expanders, TRUE);
+
         /* prime the list of ports */
         error = NULL;
         if (!org_freedesktop_DeviceKit_Disks_enumerate_ports (pool->priv->proxy, &ports, &error)) {
@@ -1599,7 +1872,7 @@ gdu_pool_get_by_object_path (GduPool *pool, const char *object_path)
 }
 
 /**
- * gdu_pool_get_by_object_path:
+ * gdu_pool_get_adapter_by_object_path:
  * @pool: the pool
  * @object_path: the D-Bus object path
  *
@@ -1621,6 +1894,28 @@ gdu_pool_get_adapter_by_object_path (GduPool *pool, const char *object_path)
 }
 
 /**
+ * gdu_pool_get_expander_by_object_path:
+ * @pool: the pool
+ * @object_path: the D-Bus object path
+ *
+ * Looks up #GduExpander object for @object_path.
+ *
+ * Returns: A #GduExpander object for @object_path, otherwise
+ * #NULL. Caller must unref this object using g_object_unref().
+ **/
+GduExpander *
+gdu_pool_get_expander_by_object_path (GduPool *pool, const char *object_path)
+{
+        GduExpander *ret;
+
+        ret = g_hash_table_lookup (pool->priv->object_path_to_expander, object_path);
+        if (ret != NULL) {
+                g_object_ref (ret);
+        }
+        return ret;
+}
+
+/**
  * gdu_pool_get_by_object_path:
  * @pool: the pool
  * @object_path: the D-Bus object path
@@ -1846,6 +2141,27 @@ gdu_pool_get_adapters (GduPool *pool)
 }
 
 /**
+ * gdu_pool_get_expanders:
+ * @pool: A #GduPool.
+ *
+ * Get a list of all expanders.
+ *
+ * Returns: A #GList of #GduExpander objects. Caller must free this
+ * (unref all objects, then use g_list_free()).
+ **/
+GList *
+gdu_pool_get_expanders (GduPool *pool)
+{
+        GList *ret;
+
+        ret = NULL;
+
+        ret = g_hash_table_get_values (pool->priv->object_path_to_expander);
+        g_list_foreach (ret, (GFunc) g_object_ref, NULL);
+        return ret;
+}
+
+/**
  * gdu_pool_get_ports:
  * @pool: A #GduPool.
  *
diff --git a/src/gdu/gdu-pool.h b/src/gdu/gdu-pool.h
index a44a500..532a5d8 100644
--- a/src/gdu/gdu-pool.h
+++ b/src/gdu/gdu-pool.h
@@ -63,6 +63,10 @@ struct _GduPoolClass
         void (*adapter_removed) (GduPool *pool, GduAdapter *adapter);
         void (*adapter_changed) (GduPool *pool, GduAdapter *adapter);
 
+        void (*expander_added) (GduPool *pool, GduExpander *expander);
+        void (*expander_removed) (GduPool *pool, GduExpander *expander);
+        void (*expander_changed) (GduPool *pool, GduExpander *expander);
+
         void (*port_added) (GduPool *pool, GduPort *port);
         void (*port_removed) (GduPool *pool, GduPort *port);
         void (*port_changed) (GduPool *pool, GduPort *port);
@@ -98,6 +102,9 @@ GList      *gdu_pool_get_enclosed_presentables (GduPool *pool, GduPresentable *p
 GduAdapter *gdu_pool_get_adapter_by_object_path (GduPool *pool, const char *object_path);
 GList      *gdu_pool_get_adapters               (GduPool *pool);
 
+GduExpander *gdu_pool_get_expander_by_object_path (GduPool *pool, const char *object_path);
+GList      *gdu_pool_get_expanders               (GduPool *pool);
+
 GduPort    *gdu_pool_get_port_by_object_path (GduPool *pool, const char *object_path);
 GList      *gdu_pool_get_ports               (GduPool *pool);
 
diff --git a/src/gdu/gdu-private.h b/src/gdu/gdu-private.h
index 52bc2f5..4306580 100644
--- a/src/gdu/gdu-private.h
+++ b/src/gdu/gdu-private.h
@@ -108,9 +108,14 @@ GduAdapter *_gdu_adapter_new_from_object_path (GduPool *pool, const char *object
 gboolean    _gdu_adapter_changed              (GduAdapter   *adapter);
 GduHba     *_gdu_hba_new_from_adapter         (GduPool *pool, GduAdapter *adapter);
 
+GduExpander *_gdu_expander_new_from_object_path (GduPool *pool, const char *object_path);
+gboolean    _gdu_expander_changed              (GduExpander   *expander);
+GduHub      *_gdu_hub_new_from_expander         (GduPool *pool, GduExpander *expander, GduPresentable *enclosing_presentable);
+
 GduPort    *_gdu_port_new_from_object_path (GduPool *pool, const char *object_path);
 gboolean    _gdu_port_changed               (GduPort   *port);
 
+void _gdu_hub_rewrite_enclosing_presentable (GduHub *hub);
 void _gdu_drive_rewrite_enclosing_presentable (GduDrive *drive);
 void _gdu_volume_rewrite_enclosing_presentable (GduVolume *volume);
 void _gdu_volume_hole_rewrite_enclosing_presentable (GduVolumeHole *volume_hole);
diff --git a/src/gdu/gdu-types.h b/src/gdu/gdu-types.h
index 96ede57..999b33a 100644
--- a/src/gdu/gdu-types.h
+++ b/src/gdu/gdu-types.h
@@ -36,6 +36,7 @@ G_BEGIN_DECLS
 typedef struct _GduPool                   GduPool;
 typedef struct _GduDevice                 GduDevice;
 typedef struct _GduAdapter                GduAdapter;
+typedef struct _GduExpander               GduExpander;
 typedef struct _GduPort                   GduPort;
 
 typedef struct _GduPresentable            GduPresentable; /* Dummy typedef */
@@ -44,6 +45,7 @@ typedef struct _GduLinuxMdDrive           GduLinuxMdDrive;
 typedef struct _GduVolume                 GduVolume;
 typedef struct _GduVolumeHole             GduVolumeHole;
 typedef struct _GduHba                    GduHba;
+typedef struct _GduHub                    GduHub;
 
 typedef struct _GduKnownFilesystem        GduKnownFilesystem;
 typedef struct _GduProcess                GduProcess;
diff --git a/src/gdu/gdu.h b/src/gdu/gdu.h
index 7bd9dbc..cedf7e0 100644
--- a/src/gdu/gdu.h
+++ b/src/gdu/gdu.h
@@ -32,6 +32,7 @@
 #include <gdu/gdu-linux-md-drive.h>
 #include <gdu/gdu-device.h>
 #include <gdu/gdu-adapter.h>
+#include <gdu/gdu-expander.h>
 #include <gdu/gdu-port.h>
 #include <gdu/gdu-drive.h>
 #include <gdu/gdu-error.h>
@@ -43,6 +44,7 @@
 #include <gdu/gdu-volume.h>
 #include <gdu/gdu-volume-hole.h>
 #include <gdu/gdu-hba.h>
+#include <gdu/gdu-hub.h>
 #include <gdu/gdu-callbacks.h>
 
 #undef __GDU_INSIDE_GDU_H
diff --git a/src/palimpsest/Makefile.am b/src/palimpsest/Makefile.am
index 8423f49..21776ec 100644
--- a/src/palimpsest/Makefile.am
+++ b/src/palimpsest/Makefile.am
@@ -19,6 +19,7 @@ palimpsest_SOURCES = 									\
 	gdu-section-drive.h			gdu-section-drive.c			\
 	gdu-section-volumes.h			gdu-section-volumes.c			\
 	gdu-section-hba.h			gdu-section-hba.c			\
+	gdu-section-hub.h			gdu-section-hub.c			\
 	$(NULL)
 
 palimpsest_CPPFLAGS = 					\
diff --git a/src/palimpsest/gdu-section-drive.c b/src/palimpsest/gdu-section-drive.c
index 9ad7833..f98952f 100644
--- a/src/palimpsest/gdu-section-drive.c
+++ b/src/palimpsest/gdu-section-drive.c
@@ -139,21 +139,71 @@ gdu_section_drive_update (GduSection *_section)
         }
         if (port != NULL) {
                 gint port_number;
+                GduPool *pool;
+                GduAdapter *adapter;
+                GduExpander *expander;
+                const gchar *port_parent_object_path;
+                const gchar *fabric;
+                gchar *s;
+
+                pool = gdu_device_get_pool (d);
+
+                port_parent_object_path = gdu_port_get_parent (port);
+
+                expander = NULL;
+                adapter = gdu_pool_get_adapter_by_object_path (pool, port_parent_object_path);
+                if (adapter == NULL) {
+                        expander = gdu_pool_get_expander_by_object_path (pool, port_parent_object_path);
+                        adapter = gdu_pool_get_adapter_by_object_path (pool, gdu_device_drive_get_adapter (d));
+                }
+                fabric = NULL;
+                if (adapter != NULL)
+                        fabric = gdu_adapter_get_fabric (adapter);
 
+                s = NULL;
                 port_number = gdu_port_get_number (port);
                 if (port_number >= 0) {
                         /* TODO: provide a link to the HBA? Probably */
 
-                        /* Translators: This is used in the "Location" element for a disk
-                         * directly connected to the Host Adapter (aka HBA) - port numbers
-                         * start at 1
-                         */
-                        s = g_strdup_printf (_("Port %d of Host Adapter"), port_number + 1);
-                        gdu_details_element_set_text (section->priv->location_element, s);
-                        g_free (s);
-                } else {
-                        gdu_details_element_set_text (section->priv->location_element, "â??");
+                        if (expander == NULL) {
+                                if (g_strcmp0 (fabric, "scsi_sas") == 0) {
+                                        /* Translators: This is used in the "Location" element for a disk
+                                         * directly connected to the SAS Host Adapter - PHY numbers
+                                         * start at 1
+                                         */
+                                        s = g_strdup_printf (_("PHY %d of SAS Host Adapter"), port_number + 1);
+                                } else {
+                                        /* Translators: This is used in the "Location" element for a disk
+                                         * directly connected to the Host Adapter (aka HBA) - port numbers
+                                         * start at 1
+                                         */
+                                        s = g_strdup_printf (_("Port %d of Host Adapter"), port_number + 1);
+                                }
+                        } else {
+                                if (g_strcmp0 (fabric, "scsi_sas") == 0) {
+                                        /* Translators: This is used in the "Location" element for a disk
+                                         * connected to a SAS expander - PHY numbers start at 1
+                                         */
+                                        s = g_strdup_printf (_("PHY %d of Expander"), port_number + 1);
+                                } else {
+                                        /* Translators: This is used in the "Location" element for a disk
+                                         * connected to an expander / port multiplier - port numbers
+                                         * start at 1
+                                         */
+                                        s = g_strdup_printf (_("Port %d of Expander"), port_number + 1);
+                                }
+                        }
                 }
+                if (s == NULL)
+                        s = g_strdup ("â??");
+                gdu_details_element_set_text (section->priv->location_element, s);
+                g_free (s);
+
+                g_object_unref (pool);
+                if (adapter != NULL)
+                        g_object_unref (adapter);
+                if (expander != NULL)
+                        g_object_unref (expander);
         } else {
                 gdu_details_element_set_text (section->priv->location_element, "â??");
         }
diff --git a/src/palimpsest/gdu-section-hub.c b/src/palimpsest/gdu-section-hub.c
new file mode 100644
index 0000000..413ac81
--- /dev/null
+++ b/src/palimpsest/gdu-section-hub.c
@@ -0,0 +1,234 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-section-hub.c
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <dbus/dbus-glib.h>
+#include <stdlib.h>
+#include <math.h>
+#include <gio/gdesktopappinfo.h>
+
+#include <gdu-gtk/gdu-gtk.h>
+#include "gdu-section-hub.h"
+
+struct _GduSectionHubPrivate
+{
+        GduDetailsElement *vendor_element;
+        GduDetailsElement *model_element;
+        GduDetailsElement *revision_element;
+        GduDetailsElement *driver_element;
+        GduDetailsElement *fabric_element;
+        GduDetailsElement *num_ports_element;
+};
+
+G_DEFINE_TYPE (GduSectionHub, gdu_section_hub, GDU_TYPE_SECTION)
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_hub_finalize (GObject *object)
+{
+        //GduSectionHub *section = GDU_SECTION_HUB (object);
+
+        if (G_OBJECT_CLASS (gdu_section_hub_parent_class)->finalize != NULL)
+                G_OBJECT_CLASS (gdu_section_hub_parent_class)->finalize (object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_hub_update (GduSection *_section)
+{
+        GduSectionHub *section = GDU_SECTION_HUB (_section);
+        GduPresentable *p;
+        GduExpander *e;
+        GduPool *pool;
+        const gchar *adapter_object_path;
+        GduAdapter *a;
+        const gchar *vendor;
+        const gchar *model;
+        const gchar *revision;
+        const gchar *fabric;
+        guint num_ports;
+        gchar *num_ports_str;
+
+        a = NULL;
+        e = NULL;
+        num_ports_str = NULL;
+
+        p = gdu_section_get_presentable (_section);
+
+        e = gdu_hub_get_expander (GDU_HUB (p));
+        if (e == NULL)
+                goto out;
+
+        pool = gdu_expander_get_pool (e);
+        if (pool == NULL)
+                goto out;
+
+        adapter_object_path = gdu_expander_get_adapter (e);
+        if (adapter_object_path != NULL) {
+                a = gdu_pool_get_adapter_by_object_path (pool, adapter_object_path);
+        }
+
+        vendor = gdu_expander_get_vendor (e);
+        model = gdu_expander_get_model (e);
+        revision = gdu_expander_get_revision (e);
+        fabric = NULL;
+        if (a != NULL)
+                fabric = gdu_adapter_get_fabric (a);
+        num_ports = gdu_expander_get_num_ports (e);
+
+        /* TODO: maybe move these blocks of code to gdu-util.c as util functions */
+
+        if (num_ports > 0) {
+                if (g_strcmp0 (fabric, "scsi_sas") == 0) {
+                        /* Translators: Used for SAS to convey the number of PHYs in the
+                         * "Number of Ports" element. You should probably not translate PHY.
+                         */
+                        num_ports_str = g_strdup_printf (_("%d PHYs"), num_ports);
+                } else {
+                        num_ports_str = g_strdup_printf ("%d", num_ports);
+                }
+        } else {
+                num_ports_str = g_strdup ("â??");
+        }
+
+        if (revision == NULL || strlen (revision) == 0)
+                revision = "â??";
+
+        gdu_details_element_set_text (section->priv->vendor_element, vendor);
+        gdu_details_element_set_text (section->priv->model_element, model);
+        gdu_details_element_set_text (section->priv->revision_element, revision);
+        gdu_details_element_set_text (section->priv->num_ports_element, num_ports_str);
+
+ out:
+        g_free (num_ports_str);
+        if (pool != NULL)
+                g_object_unref (pool);
+        if (a != NULL)
+                g_object_unref (a);
+        if (e != NULL)
+                g_object_unref (e);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_hub_constructed (GObject *object)
+{
+        GduSectionHub *section = GDU_SECTION_HUB (object);
+        GtkWidget *align;
+        GtkWidget *label;
+        GtkWidget *table;
+        GtkWidget *vbox;
+        gchar *s;
+        GduPresentable *p;
+        GduDevice *d;
+        GPtrArray *elements;
+        GduDetailsElement *element;
+
+        p = gdu_section_get_presentable (GDU_SECTION (section));
+        d = gdu_presentable_get_device (p);
+
+        gtk_box_set_spacing (GTK_BOX (section), 12);
+
+        /*------------------------------------- */
+
+        label = gtk_label_new (NULL);
+        gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+        s = g_strconcat ("<b>", _("SAS Expander"), "</b>", NULL);
+        gtk_label_set_markup (GTK_LABEL (label), s);
+        g_free (s);
+        gtk_box_pack_start (GTK_BOX (section), label, FALSE, FALSE, 0);
+
+        align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+        gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 12, 0);
+        gtk_box_pack_start (GTK_BOX (section), align, FALSE, FALSE, 0);
+
+        vbox = gtk_vbox_new (FALSE, 6);
+        gtk_container_add (GTK_CONTAINER (align), vbox);
+
+        elements = g_ptr_array_new_with_free_func (g_object_unref);
+
+        element = gdu_details_element_new (_("Vendor:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->vendor_element = element;
+
+        element = gdu_details_element_new (_("Model:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->model_element = element;
+
+        element = gdu_details_element_new (_("Revision:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->revision_element = element;
+
+        element = gdu_details_element_new (_("Number of Ports:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->num_ports_element = element;
+
+        table = gdu_details_table_new (1, elements);
+        g_ptr_array_unref (elements);
+        gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+
+        /* -------------------------------------------------------------------------------- */
+
+        gtk_widget_show_all (GTK_WIDGET (section));
+
+        if (d != NULL)
+                g_object_unref (d);
+
+        if (G_OBJECT_CLASS (gdu_section_hub_parent_class)->constructed != NULL)
+                G_OBJECT_CLASS (gdu_section_hub_parent_class)->constructed (object);
+}
+
+static void
+gdu_section_hub_class_init (GduSectionHubClass *klass)
+{
+        GObjectClass *gobject_class;
+        GduSectionClass *section_class;
+
+        gobject_class = G_OBJECT_CLASS (klass);
+        section_class = GDU_SECTION_CLASS (klass);
+
+        gobject_class->finalize    = gdu_section_hub_finalize;
+        gobject_class->constructed = gdu_section_hub_constructed;
+        section_class->update      = gdu_section_hub_update;
+
+        g_type_class_add_private (klass, sizeof (GduSectionHubPrivate));
+}
+
+static void
+gdu_section_hub_init (GduSectionHub *section)
+{
+        section->priv = G_TYPE_INSTANCE_GET_PRIVATE (section, GDU_TYPE_SECTION_HUB, GduSectionHubPrivate);
+}
+
+GtkWidget *
+gdu_section_hub_new (GduShell       *shell,
+                     GduPresentable *presentable)
+{
+        return GTK_WIDGET (g_object_new (GDU_TYPE_SECTION_HUB,
+                                         "shell", shell,
+                                         "presentable", presentable,
+                                         NULL));
+}
diff --git a/src/palimpsest/gdu-section-hub.h b/src/palimpsest/gdu-section-hub.h
new file mode 100644
index 0000000..9cd36a3
--- /dev/null
+++ b/src/palimpsest/gdu-section-hub.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-section-hub.h
+ *
+ * Copyright (C) 2007 David Zeuthen
+ *
+ * This library 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include "gdu-section.h"
+
+#ifndef GDU_SECTION_HUB_H
+#define GDU_SECTION_HUB_H
+
+#define GDU_TYPE_SECTION_HUB           (gdu_section_hub_get_type ())
+#define GDU_SECTION_HUB(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_SECTION_HUB, GduSectionHub))
+#define GDU_SECTION_HUB_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST ((k), GDU_TYPE_SECTION_HUB,  GduSectionHubClass))
+#define GDU_IS_SECTION_HUB(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_SECTION_HUB))
+#define GDU_IS_SECTION_HUB_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_SECTION_HUB))
+#define GDU_SECTION_HUB_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), GDU_TYPE_SECTION_HUB, GduSectionHubClass))
+
+typedef struct _GduSectionHubClass       GduSectionHubClass;
+typedef struct _GduSectionHub            GduSectionHub;
+
+struct _GduSectionHubPrivate;
+typedef struct _GduSectionHubPrivate     GduSectionHubPrivate;
+
+struct _GduSectionHub
+{
+        GduSection parent;
+
+        /* private */
+        GduSectionHubPrivate *priv;
+};
+
+struct _GduSectionHubClass
+{
+        GduSectionClass parent_class;
+};
+
+GType            gdu_section_hub_get_type (void);
+GtkWidget       *gdu_section_hub_new      (GduShell       *shell,
+                                           GduPresentable *presentable);
+
+#endif /* GDU_SECTION_HUB_H */
diff --git a/src/palimpsest/gdu-shell.c b/src/palimpsest/gdu-shell.c
index 9c37727..73d84da 100644
--- a/src/palimpsest/gdu-shell.c
+++ b/src/palimpsest/gdu-shell.c
@@ -46,6 +46,7 @@
 #include "gdu-section-drive.h"
 #include "gdu-section-volumes.h"
 #include "gdu-section-hba.h"
+#include "gdu-section-hub.h"
 
 struct _GduShellPrivate
 {
@@ -209,6 +210,10 @@ compute_sections_to_show (GduShell *shell)
 
                 sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_HBA);
 
+        } else if (GDU_IS_HUB (shell->priv->presentable_now_showing)) {
+
+                sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_HUB);
+
         } else if (GDU_IS_LINUX_MD_DRIVE (shell->priv->presentable_now_showing)) {
 
                 sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_LINUX_MD_DRIVE);



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