summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2012-01-26 15:12:01 +0100
committerHans de Goede <hdegoede@redhat.com>2012-01-31 14:31:38 +0100
commitbf0350423caba9fb9f04103d18032a9a4ce912dd (patch)
tree164aec6bd02126ddf72e67fb10992a865d573440
parent9e26b538db86f255e0cb2a20a064da8aa4e3315f (diff)
Add a USB device selection widget
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@redhat.com>
-rw-r--r--doc/reference/spice-gtk-docs.xml1
-rw-r--r--doc/reference/spice-gtk-sections.txt18
-rw-r--r--doc/reference/spice-gtk.types2
-rw-r--r--gtk/Makefile.am5
-rw-r--r--gtk/map-file2
-rw-r--r--gtk/spice-client-gtk.override2
-rw-r--r--gtk/spicy.c59
-rw-r--r--gtk/usb-device-widget.c394
-rw-r--r--gtk/usb-device-widget.h83
-rw-r--r--po/POTFILES.in1
10 files changed, 558 insertions, 9 deletions
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..f294c76 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -101,6 +101,8 @@ SPICE_GTK_LIBADD_COMMON = \
$(NULL)
SPICE_GTK_SOURCES_COMMON = \
+ glib-compat.c \
+ glib-compat.h \
spice-gtk-session.c \
spice-gtk-session-priv.h \
spice-widget.c \
@@ -109,10 +111,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 +147,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..cbc702e 100644
--- a/gtk/spicy.c
+++ b/gtk/spicy.c
@@ -38,6 +38,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 +99,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 +418,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 +738,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 +835,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 +1563,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 +1652,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..028723f
--- /dev/null
+++ b/gtk/usb-device-widget.c
@@ -0,0 +1,394 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2012 Red Hat, Inc.
+
+ Red Hat Authors:
+ Hans de Goede <hdegoede@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 "glib-compat.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@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