[Spice-devel] [PATCH spice-gtk 2/3] Add a USB device selection widget.

Hans de Goede hdegoede at redhat.com
Mon Jan 30 05:33:41 PST 2012


This patch adds a SpiceUsbDeviceWidget which apps can use to easily
add an UI to select USB devices to redirect (or unredirect).

See spicy for an example usage.

Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
 doc/reference/spice-gtk-docs.xml     |    1 +
 doc/reference/spice-gtk-sections.txt |   18 ++
 doc/reference/spice-gtk.types        |    2 +
 gtk/Makefile.am                      |    3 +
 gtk/map-file                         |    2 +
 gtk/spice-client-gtk.override        |    2 +
 gtk/spicy.c                          |   60 +++++-
 gtk/usb-device-widget.c              |  393 ++++++++++++++++++++++++++++++++++
 gtk/usb-device-widget.h              |   83 +++++++
 po/POTFILES.in                       |    1 +
 10 files changed, 555 insertions(+), 10 deletions(-)
 create mode 100644 gtk/usb-device-widget.c
 create mode 100644 gtk/usb-device-widget.h

diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml
index 2b4336d..82cdce8 100644
--- a/doc/reference/spice-gtk-docs.xml
+++ b/doc/reference/spice-gtk-docs.xml
@@ -42,6 +42,7 @@
       <title>GTK Widget, from spice-client-gtk</title>
       <xi:include href="xml/spice-gtk-session.xml"/>
       <xi:include href="xml/spice-widget.xml"/>
+      <xi:include href="xml/usb-device-widget.xml"/>
     </chapter>
 
     <chapter id="application-support">
diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index 870352d..5e3af99 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -342,6 +342,24 @@ SpiceDisplayPrivate
 </SECTION>
 
 <SECTION>
+<FILE>usb-device-widget</FILE>
+<TITLE>SpiceUsbDeviceWidget</TITLE>
+SpiceUsbDeviceWidget
+SpiceUsbDeviceWidgetClass
+<SUBSECTION>
+spice_usb_device_widget_new
+<SUBSECTION Standard>
+SPICE_USB_DEVICE_WIDGET
+SPICE_IS_USB_DEVICE_WIDGET
+spice_usb_device_widget_get_type
+SPICE_USB_DEVICE_WIDGET_CLASS
+SPICE_IS_USB_DEVICE_WIDGET_CLASS
+SPICE_USB_DEVICE_WIDGET_GET_CLASS
+<SUBSECTION Private>
+SpiceUsbDeviceWidgetPrivate
+</SECTION>
+
+<SECTION>
 <FILE>spice-util</FILE>
 spice_util_set_debug
 spice_util_get_version_string
diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types
index a88ece1..ff80277 100644
--- a/doc/reference/spice-gtk.types
+++ b/doc/reference/spice-gtk.types
@@ -18,6 +18,7 @@
 #include "spice-grabsequence.h"
 #include "smartcard-manager.h"
 #include "usb-device-manager.h"
+#include "usb-device-widget.h"
 
 spice_audio_get_type
 spice_channel_event_get_type
@@ -40,3 +41,4 @@ spice_session_verify_get_type
 spice_usbredir_channel_get_type
 spice_usb_device_get_type
 spice_usb_device_manager_get_type
+spice_usb_device_widget_get_type
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 2cc0163..a5b6e29 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -109,10 +109,12 @@ SPICE_GTK_SOURCES_COMMON =		\
 	vncdisplaykeymap.h		\
 	spice-grabsequence.c		\
 	spice-grabsequence.h		\
+	usb-device-widget.c		\
 	$(NULL)
 
 nodist_SPICE_GTK_SOURCES_COMMON =	\
 	spice-widget-enums.c		\
+	spice-marshal.c			\
 	$(NULL)
 
 if WITH_X11
@@ -143,6 +145,7 @@ libspice_client_gtkinclude_HEADERS =	\
 	spice-gtk-session.h		\
 	spice-widget.h			\
 	spice-grabsequence.h		\
+	usb-device-widget.h		\
 	$(NULL)
 
 nodist_libspice_client_gtkinclude_HEADERS =	\
diff --git a/gtk/map-file b/gtk/map-file
index 4b470bb..38f7c26 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -91,6 +91,8 @@ spice_usb_device_manager_get;
 spice_usb_device_manager_get_devices;
 spice_usb_device_manager_get_type;
 spice_usb_device_manager_is_device_connected;
+spice_usb_device_widget_get_type;
+spice_usb_device_widget_new;
 spice_usbredir_channel_get_type;
 spice_util_get_debug;
 spice_util_get_version_string;
diff --git a/gtk/spice-client-gtk.override b/gtk/spice-client-gtk.override
index e393037..31e4f9e 100644
--- a/gtk/spice-client-gtk.override
+++ b/gtk/spice-client-gtk.override
@@ -6,12 +6,14 @@ headers
 #include "spice-widget.h"
 #include "spice-gtk-session.h"
 #include "spice-audio.h"
+#include "usb-device-widget.h"
 %%
 modulename spice_client_gtk
 %%
 import gobject.GObject as PyGObject_Type
 import gtk.DrawingArea as PyGtkDrawingArea_Type
 import gtk.Widget as PyGtkWidget_Type
+import gtk.VBox as PyGtkVBox_Type
 %%
 ignore-glob
   *_get_type
diff --git a/gtk/spicy.c b/gtk/spicy.c
index e37ce82..c002a5e 100644
--- a/gtk/spicy.c
+++ b/gtk/spicy.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
    Copyright (C) 2010-2011 Red Hat, Inc.
 
@@ -38,6 +37,7 @@
 #include "spice-common.h"
 #include "spice-cmdline.h"
 #include "spice-option.h"
+#include "usb-device-widget.h"
 
 /* config */
 static gboolean fullscreen = false;
@@ -98,10 +98,10 @@ static void connection_disconnect(spice_connection *conn);
 static void connection_destroy(spice_connection *conn);
 static void resolution_fullscreen(struct spice_window *win);
 static void resolution_restore(struct spice_window *win);
-static void auto_connect_failed(SpiceUsbDeviceManager *manager,
-                                SpiceUsbDevice        *device,
-                                GError                *error,
-                                gpointer               data);
+static void usb_connect_failed(GObject               *object,
+                               SpiceUsbDevice        *device,
+                               GError                *error,
+                               gpointer               data);
 static gboolean is_gtk_session_property(const gchar *property);
 
 /* ------------------------------------------------------------------ */
@@ -417,6 +417,35 @@ static void menu_cb_remove_smartcard(GtkAction *action, void *data)
 }
 #endif
 
+#ifdef USE_USBREDIR
+static void menu_cb_select_usb_devices(GtkAction *action, void *data)
+{
+    GtkWidget *dialog, *area, *usb_device_widget;
+    struct spice_window *win = data;
+
+    /* Create the widgets */
+    dialog = gtk_dialog_new_with_buttons(
+                    _("Select USB devices for redirection"),
+                    GTK_WINDOW(win->toplevel),
+                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                    GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                    NULL);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+    area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+    usb_device_widget = spice_usb_device_widget_new(win->conn->session,
+                                                    "%s %s");
+    g_signal_connect(usb_device_widget, "connect-failed",
+                     G_CALLBACK(usb_connect_failed), NULL);
+    gtk_box_pack_start(GTK_BOX(area), usb_device_widget, TRUE, TRUE, 5);
+
+    /* show and run */
+    gtk_widget_show_all(dialog);
+    gtk_dialog_run(GTK_DIALOG(dialog));
+    gtk_widget_destroy(dialog);
+}
+#endif
+
 static void menu_cb_bool_prop(GtkToggleAction *action, gpointer data)
 {
     struct spice_window *win = data;
@@ -708,6 +737,14 @@ static const GtkActionEntry entries[] = {
     },{
 #endif
 
+#ifdef USE_USBREDIR
+        .name        = "SelectUsbDevices",
+        .label       = N_("_Select USB Devices for redirection"),
+        .callback    = G_CALLBACK(menu_cb_select_usb_devices),
+        .accelerator = "<shift>F10",
+    },{
+#endif
+
         /* Help menu */
         .name        = "About",
         .stock_id    = GTK_STOCK_ABOUT,
@@ -797,6 +834,9 @@ static char ui_xml[] =
 "      <menuitem action='InsertSmartcard'/>\n"
 "      <menuitem action='RemoveSmartcard'/>\n"
 #endif
+#ifdef USE_USBREDIR
+"      <menuitem action='SelectUsbDevices'/>\n"
+#endif
 "    </menu>\n"
 "    <menu action='OptionMenu'>\n"
 "      <menuitem action='grab-keyboard'/>\n"
@@ -1522,7 +1562,7 @@ static spice_connection *connection_new(void)
     manager = spice_usb_device_manager_get(conn->session, NULL);
     if (manager) {
         g_signal_connect(manager, "auto-connect-failed",
-                         G_CALLBACK(auto_connect_failed), NULL);
+                         G_CALLBACK(usb_connect_failed), NULL);
     }
 
     connections++;
@@ -1611,10 +1651,10 @@ signal_handler(int signum)
     g_main_loop_quit(mainloop);
 }
 
-static void auto_connect_failed(SpiceUsbDeviceManager *manager,
-                                SpiceUsbDevice        *device,
-                                GError                *error,
-                                gpointer               data)
+static void usb_connect_failed(GObject               *object,
+                               SpiceUsbDevice        *device,
+                               GError                *error,
+                               gpointer               data)
 {
     GtkWidget *dialog;
 
diff --git a/gtk/usb-device-widget.c b/gtk/usb-device-widget.c
new file mode 100644
index 0000000..7a57e27
--- /dev/null
+++ b/gtk/usb-device-widget.c
@@ -0,0 +1,393 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include "spice-client.h"
+#include "spice-marshal.h"
+#include "usb-device-widget.h"
+
+/**
+ * SECTION:usb-device-widget
+ * @short_description: USB device selection widget
+ * @title: Spice USB device selection widget
+ * @section_id:
+ * @see_also:
+ * @stability: Stable
+ * @include: usb-device-widget.h
+ *
+ * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
+ * add an UI to select USB devices to redirect (or unredirect).
+ */
+
+/* ------------------------------------------------------------------ */
+/* Prototypes for callbacks  */
+static void device_added_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data);
+static void device_removed_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data);
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
+                                 SpiceUsbDeviceWidgetPrivate))
+
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_DEVICE_FORMAT_STRING,
+};
+
+enum {
+    CONNECT_FAILED,
+    LAST_SIGNAL,
+};
+
+struct _SpiceUsbDeviceWidgetPrivate {
+    SpiceSession *session;
+    gchar *device_format_string;
+    SpiceUsbDeviceManager *manager;
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_VBOX);
+
+static void spice_usb_device_widget_get_property(GObject     *gobject,
+                                                 guint        prop_id,
+                                                 GValue      *value,
+                                                 GParamSpec  *pspec)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, priv->session);
+        break;
+    case PROP_DEVICE_FORMAT_STRING:
+        g_value_set_string(value, priv->device_format_string);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_widget_set_property(GObject       *gobject,
+                                                 guint          prop_id,
+                                                 const GValue  *value,
+                                                 GParamSpec    *pspec)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        priv->session = g_value_dup_object(value);
+        break;
+    case PROP_DEVICE_FORMAT_STRING:
+        priv->device_format_string = g_value_dup_string(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static GObject *spice_usb_device_widget_constructor(
+    GType gtype, guint n_properties, GObjectConstructParam *properties)
+{
+    GObject *obj;
+    SpiceUsbDeviceWidget *self;
+    SpiceUsbDeviceWidgetPrivate *priv;
+    const gchar *err_msg = NULL;
+    GPtrArray *devices = NULL;
+    GError *err = NULL;
+    GtkWidget *label;
+    gboolean enabled;
+    int i;
+
+    {
+        /* Always chain up to the parent constructor */
+        GObjectClass *parent_class;
+        parent_class = G_OBJECT_CLASS(spice_usb_device_widget_parent_class);
+        obj = parent_class->constructor(gtype, n_properties, properties);
+    }
+
+    self = SPICE_USB_DEVICE_WIDGET(obj);
+    priv = self->priv;
+    if (!priv->session)
+        g_error("SpiceUsbDeviceWidget constructed without a session");
+
+    g_object_get(G_OBJECT(priv->session), "enable-usbredir", &enabled, NULL);
+    if (!enabled)
+        err_msg = _("USB redirection is disabled");
+
+    if (!err_msg && !spice_session_has_channel_type(priv->session,
+                                                    SPICE_CHANNEL_USBREDIR))
+        err_msg = _("The connected VM is not configured for USB redirection");
+
+    if (!err_msg) {
+        priv->manager = spice_usb_device_manager_get(priv->session, &err);
+        if (!err) {
+            g_signal_connect(priv->manager, "device-added",
+                             G_CALLBACK(device_added_cb), self);
+            g_signal_connect(priv->manager, "device-removed",
+                             G_CALLBACK(device_removed_cb), self);
+            devices = spice_usb_device_manager_get_devices(priv->manager);
+        } else
+            err_msg = err->message;
+    }
+
+    if (err_msg) {
+        label = gtk_label_new(err_msg);
+        gtk_box_pack_start(GTK_BOX(self), label, TRUE, TRUE, 5);
+        g_clear_error(&err);
+        return obj;
+    }
+
+    label = gtk_label_new(_("Select USB devices to redirect"));
+    gtk_box_pack_start(GTK_BOX(self), label, TRUE, TRUE, 5);
+
+    for (i = 0; i < devices->len; i++)
+        device_added_cb(NULL, g_ptr_array_index(devices, i), self);
+
+    g_ptr_array_unref(devices);
+
+    return obj;
+}
+
+static void spice_usb_device_widget_finalize(GObject *object)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    if (priv->manager) {
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_added_cb, self);
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_removed_cb, self);
+    }
+    g_object_unref(priv->session);
+    g_free(priv->device_format_string);
+}
+
+static void spice_usb_device_widget_class_init(
+    SpiceUsbDeviceWidgetClass *klass)
+{
+    GObjectClass *gobject_class = (GObjectClass *)klass;
+    GParamSpec *pspec;
+
+    g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate));
+
+    gobject_class->constructor  = spice_usb_device_widget_constructor;
+    gobject_class->finalize     = spice_usb_device_widget_finalize;
+    gobject_class->get_property = spice_usb_device_widget_get_property;
+    gobject_class->set_property = spice_usb_device_widget_set_property;
+
+    /**
+     * SpiceUsbDeviceWidget:session:
+     *
+     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
+     *
+     **/
+    pspec = g_param_spec_object("session",
+                                "Session",
+                                "SpiceSession",
+                                SPICE_TYPE_SESSION,
+                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
+
+    /**
+     * SpiceUsbDeviceWidget:device-format-string:
+     *
+     * Format string to pass to spice_usb_device_get_description() for getting
+     * the device USB descriptions.
+     */
+    pspec = g_param_spec_string("device-format-string",
+                                "Device format string",
+                                "Format string for device description",
+                                NULL,
+                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_DEVICE_FORMAT_STRING,
+                                    pspec);
+
+    /**
+     * SpiceUsbDeviceWidget::connect-failed:
+     * @widget: The #SpiceUsbDeviceWidget that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the added device
+     * @error:  #GError describing the reason why the connect failed
+     *
+     * The #SpiceUsbDeviceWidget::connect-failed signal is emitted whenever
+     * the user has requested for a device to be redirected and this has
+     * failed.
+     **/
+    signals[CONNECT_FAILED] =
+        g_signal_new("connect-failed",
+                    G_OBJECT_CLASS_TYPE(gobject_class),
+                    G_SIGNAL_RUN_FIRST,
+                    G_STRUCT_OFFSET(SpiceUsbDeviceWidgetClass, connect_failed),
+                    NULL, NULL,
+                    g_cclosure_user_marshal_VOID__BOXED_BOXED,
+                    G_TYPE_NONE,
+                    2,
+                    SPICE_TYPE_USB_DEVICE,
+                    G_TYPE_ERROR);
+}
+
+static void spice_usb_device_widget_init(SpiceUsbDeviceWidget *self)
+{
+    self->priv = SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(self);
+}
+
+/* ------------------------------------------------------------------ */
+/* public api                                                         */
+
+/**
+ * spice_usb_device_widget_new:
+ * @session: #SpiceSession for which to widget will control USB redirection
+ * @device_format_string: String passed to spice_usb_device_get_description()
+ *
+ * Returns: a new #SpiceUsbDeviceWidget instance
+ */
+GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
+                                       const gchar     *device_format_string)
+{
+    return g_object_new(SPICE_TYPE_USB_DEVICE_WIDGET,
+                        "session", session,
+                        "device-format-string", device_format_string,
+                        NULL);
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+typedef struct _connect_cb_data {
+    GtkWidget *check;
+    SpiceUsbDeviceWidget *self;
+} connect_cb_data;
+
+static void connect_cb(GObject *gobject, GAsyncResult *res, gpointer user_data)
+{
+    SpiceUsbDeviceManager *manager = SPICE_USB_DEVICE_MANAGER(gobject);
+    connect_cb_data *data = user_data;
+    SpiceUsbDeviceWidget *self = data->self;
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDevice *device;
+    GError *err = NULL;
+    gchar *desc;
+
+    spice_usb_device_manager_connect_device_finish(manager, res, &err);
+    if (err) {
+        device = g_object_get_data(G_OBJECT(data->check), "usb-device");
+        desc = spice_usb_device_get_description(device,
+                                                priv->device_format_string);
+        g_prefix_error(&err, "Could not redirect %s: ", desc);
+        g_free(desc);
+
+        SPICE_DEBUG("%s", err->message);
+        g_signal_emit(self, signals[CONNECT_FAILED], 0, device, err);
+        g_error_free(err);
+
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->check), FALSE);
+    }
+
+    g_object_unref(data->check);
+    g_object_unref(data->self);
+    g_free(data);
+}
+
+static void checkbox_clicked_cb(GtkWidget *check, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDevice *device;
+
+    device = g_object_get_data(G_OBJECT(check), "usb-device");
+
+    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check))) {
+        connect_cb_data *data = g_new(connect_cb_data, 1);
+        data->check = g_object_ref(check);
+        data->self  = g_object_ref(self);
+        spice_usb_device_manager_connect_device_async(priv->manager,
+                                                      device,
+                                                      NULL,
+                                                      connect_cb,
+                                                      data);
+    } else {
+        spice_usb_device_manager_disconnect_device(priv->manager,
+                                                   device);
+    }
+}
+
+static void checkbox_usb_device_destroy_notify(gpointer data)
+{
+    g_boxed_free(spice_usb_device_get_type(), data);
+}
+
+static void device_added_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkWidget *check;
+    gchar *desc;
+
+    desc = spice_usb_device_get_description(device,
+                                            priv->device_format_string);
+
+    check = gtk_check_button_new_with_label(desc);
+
+    if (spice_usb_device_manager_is_device_connected(priv->manager,
+                                                     device))
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
+
+    g_object_set_data_full(
+            G_OBJECT(check), "usb-device",
+            g_boxed_copy(spice_usb_device_get_type(), device),
+            checkbox_usb_device_destroy_notify);
+    g_signal_connect(G_OBJECT(check), "clicked",
+                     G_CALLBACK(checkbox_clicked_cb), self);
+
+    gtk_box_pack_start(GTK_BOX(self), check, TRUE, TRUE, 5);
+    gtk_widget_show(check);
+
+    g_free(desc);
+}
+
+static void destroy_widget_by_usb_device(GtkWidget *widget, gpointer user_data)
+{
+    if (g_object_get_data(G_OBJECT(widget), "usb-device") == user_data)
+        gtk_widget_destroy(widget);
+}
+
+static void device_removed_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+
+    gtk_container_foreach(GTK_CONTAINER(self),
+                          destroy_widget_by_usb_device, device);
+}
diff --git a/gtk/usb-device-widget.h b/gtk/usb-device-widget.h
new file mode 100644
index 0000000..27ec795
--- /dev/null
+++ b/gtk/usb-device-widget.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef __SPICE_USB_DEVICE_WIDGET_H__
+#define __SPICE_USB_DEVICE_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USB_DEVICE_WIDGET            (spice_usb_device_widget_get_type ())
+#define SPICE_USB_DEVICE_WIDGET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidget))
+#define SPICE_USB_DEVICE_WIDGET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidgetClass))
+#define SPICE_IS_USB_DEVICE_WIDGET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_DEVICE_WIDGET))
+#define SPICE_IS_USB_DEVICE_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_DEVICE_WIDGET))
+#define SPICE_USB_DEVICE_WIDGET_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidgetClass))
+
+typedef struct _SpiceUsbDeviceWidget SpiceUsbDeviceWidget;
+typedef struct _SpiceUsbDeviceWidgetClass SpiceUsbDeviceWidgetClass;
+typedef struct _SpiceUsbDeviceWidgetPrivate SpiceUsbDeviceWidgetPrivate;
+
+/**
+ * SpiceUsbDeviceWidget:
+ * @parent: Parent instance.
+ *
+ * The #SpiceUsbDeviceWidget struct is opaque and should not be accessed directly.
+ */
+struct _SpiceUsbDeviceWidget
+{
+    GtkVBox parent;
+
+    /*< private >*/
+    SpiceUsbDeviceWidgetPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceUsbDeviceWidgetClass:
+ * @parent_class: Parent class.
+ * @connect_failed: Signal class handler for the #SpiceUsbDeviceWidget::connect-failed signal.
+ *
+ * Class structure for #SpiceUsbDeviceWidget.
+ */
+struct _SpiceUsbDeviceWidgetClass
+{
+    GtkVBoxClass parent_class;
+
+    /* signals */
+    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
+                            SpiceUsbDevice *device, GError *error);
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+GType spice_usb_device_widget_get_type(void);
+GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
+                                       const gchar     *device_format_string);
+
+G_END_DECLS
+
+#endif /* __SPICE_USB_DEVICE_WIDGET_H__ */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b008d10..3c87ed1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -11,3 +11,4 @@ gtk/spice-option.c
 gtk/spicy-stats.c
 gtk/spicy.c
 gtk/usb-device-manager.c
+gtk/usb-device-widget.c
-- 
1.7.7.6



More information about the Spice-devel mailing list