[Spice-commits] 7 commits - configure.ac gtk/Makefile.am gtk/channel-usbredir-priv.h gtk/channel-usbredir.c gtk/channel-usbredir.h gtk/gusb gtk/map-file gtk/spice-channel.c gtk/spice-client-gtk.defs gtk/spice-client.h gtk/spice-marshal.txt gtk/spice-option.c gtk/spice-session.c gtk/spice-session.h gtk/spice-widget-priv.h gtk/spice-widget.c gtk/spicy.c gtk/usb-device-manager.c gtk/usb-device-manager.h spice.proto

Hans de Goede jwrdegoede at kemper.freedesktop.org
Tue Aug 30 07:14:04 PDT 2011


 configure.ac                    |   17 +
 gtk/Makefile.am                 |   32 ++
 gtk/channel-usbredir-priv.h     |   43 ++
 gtk/channel-usbredir.c          |  333 +++++++++++++++++++++++
 gtk/channel-usbredir.h          |   60 ++++
 gtk/gusb/README.txt             |    5 
 gtk/gusb/gusb-context-private.h |   35 ++
 gtk/gusb/gusb-context.c         |  292 ++++++++++++++++++++
 gtk/gusb/gusb-context.h         |   63 ++++
 gtk/gusb/gusb-device-list.c     |  358 ++++++++++++++++++++++++
 gtk/gusb/gusb-device-list.h     |   72 +++++
 gtk/gusb/gusb-device-private.h  |   34 ++
 gtk/gusb/gusb-device.c          |  232 ++++++++++++++++
 gtk/gusb/gusb-device.h          |   71 ++++
 gtk/gusb/gusb-marshal.h         |    7 
 gtk/gusb/gusb-source.c          |  309 +++++++++++++++++++++
 gtk/gusb/gusb-source.h          |   58 ++++
 gtk/map-file                    |   11 
 gtk/spice-channel.c             |   17 +
 gtk/spice-client-gtk.defs       |  107 +++++++
 gtk/spice-client.h              |    2 
 gtk/spice-marshal.txt           |    1 
 gtk/spice-option.c              |    5 
 gtk/spice-session.c             |   57 +++
 gtk/spice-session.h             |    1 
 gtk/spice-widget-priv.h         |    1 
 gtk/spice-widget.c              |   61 ++++
 gtk/spicy.c                     |   41 ++
 gtk/usb-device-manager.c        |  576 ++++++++++++++++++++++++++++++++++++++++
 gtk/usb-device-manager.h        |   92 ++++++
 spice.proto                     |    8 
 31 files changed, 2999 insertions(+), 2 deletions(-)

New commits:
commit 532450c76dfe100690b027b96e02d986895c1e21
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Fri Aug 12 13:45:06 2011 +0200

    Add auto_usbredir property to spice-widget

diff --git a/gtk/spice-widget-priv.h b/gtk/spice-widget-priv.h
index a567f1b..bd6dedb 100644
--- a/gtk/spice-widget-priv.h
+++ b/gtk/spice-widget-priv.h
@@ -51,6 +51,7 @@ struct _SpiceDisplayPrivate {
     bool                    mouse_grab_enable;
     bool                    resize_guest_enable;
     bool                    auto_clipboard_enable;
+    bool                    auto_usbredir_enable;
 
     /* state */
     enum SpiceSurfaceFmt    format;
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index 9ba9be0..2beea73 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -87,6 +87,7 @@ enum {
     PROP_MOUSE_GRAB,
     PROP_RESIZE_GUEST,
     PROP_AUTO_CLIPBOARD,
+    PROP_AUTO_USBREDIR,
     PROP_SCALING,
 };
 
@@ -117,6 +118,7 @@ static void disconnect_display(SpiceDisplay *display);
 static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data);
 static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data);
 static void sync_keyboard_lock_modifiers(SpiceDisplay *display);
+static void update_auto_usbredir(SpiceDisplay *display);
 
 /* ---------------------------------------------------------------- */
 
@@ -141,6 +143,9 @@ static void spice_display_get_property(GObject    *object,
     case PROP_AUTO_CLIPBOARD:
         g_value_set_boolean(value, d->auto_clipboard_enable);
         break;
+    case PROP_AUTO_USBREDIR:
+        g_value_set_boolean(value, d->auto_usbredir_enable);
+        break;
     case PROP_SCALING:
         g_value_set_boolean(value, d->allow_scaling);
 	break;
@@ -195,6 +200,10 @@ static void spice_display_set_property(GObject      *object,
     case PROP_AUTO_CLIPBOARD:
         d->auto_clipboard_enable = g_value_get_boolean(value);
         break;
+    case PROP_AUTO_USBREDIR:
+        d->auto_usbredir_enable = g_value_get_boolean(value);
+        update_auto_usbredir(display);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
@@ -572,6 +581,22 @@ static void recalc_geometry(GtkWidget *widget, gboolean set_display)
     }
 }
 
+static void update_auto_usbredir(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    SpiceUsbDeviceManager *manager;
+    gboolean auto_connect = FALSE;
+
+    if (d->auto_usbredir_enable && d->keyboard_have_focus)
+        auto_connect = TRUE;
+
+    /* FIXME: allow specifying a different GMainContext then the default */
+    manager = spice_usb_device_manager_get(NULL /* FIXME */, NULL);
+    if (manager) {
+        g_object_set(manager, "auto-connect", auto_connect, NULL);
+    }
+}
+
 /* ---------------------------------------------------------------- */
 
 #define CONVERT_0565_TO_0888(s)                                         \
@@ -857,6 +882,7 @@ static gboolean focus_in_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UN
     sync_keyboard_lock_modifiers(display);
     d->keyboard_have_focus = true;
     try_keyboard_grab(display);
+    update_auto_usbredir(display);
 #ifdef WIN32
     focus_window = GDK_WINDOW_HWND(gtk_widget_get_window(widget));
     g_return_val_if_fail(focus_window != NULL, true);
@@ -880,6 +906,7 @@ static gboolean focus_out_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_U
 
     release_keys(display);
     d->keyboard_have_focus = false;
+    update_auto_usbredir(display);
     return true;
 }
 
@@ -1299,6 +1326,17 @@ static void spice_display_class_init(SpiceDisplayClass *klass)
                               G_PARAM_STATIC_STRINGS));
 
     g_object_class_install_property
+        (gobject_class, PROP_AUTO_USBREDIR,
+         g_param_spec_boolean("auto-usbredir",
+                              "Auto USB Redirection",
+                              "Automatically redirect newly plugged in USB"
+                              "Devices to the guest.",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
         (gobject_class, PROP_SCALING,
          g_param_spec_boolean("scaling", "Scaling",
                               "Whether we should use scaling",
diff --git a/gtk/spicy.c b/gtk/spicy.c
index a954085..761e056 100644
--- a/gtk/spicy.c
+++ b/gtk/spicy.c
@@ -686,7 +686,10 @@ static const char *spice_properties[] = {
     "grab-mouse",
     "resize-guest",
     "scaling",
-    "auto-clipboard"
+    "auto-clipboard",
+#ifdef USE_USBREDIR
+    "auto-usbredir",
+#endif
 };
 
 static const GtkToggleActionEntry tentries[] = {
@@ -711,6 +714,12 @@ static const GtkToggleActionEntry tentries[] = {
         .label       = N_("Automagic clipboard sharing between host and guest"),
         .callback    = G_CALLBACK(menu_cb_bool_prop),
     },{
+#ifdef USE_USBREDIR
+        .name        = "auto-usbredir",
+        .label       = N_("Auto redirect newly plugged in USB devices"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+#endif
         .name        = "Statusbar",
         .label       = N_("Statusbar"),
         .callback    = G_CALLBACK(menu_cb_statusbar),
@@ -752,6 +761,9 @@ static char ui_xml[] =
 "      <menuitem action='resize-guest'/>\n"
 "      <menuitem action='scaling'/>\n"
 "      <menuitem action='auto-clipboard'/>\n"
+#ifdef USE_USBREDIR
+"      <menuitem action='auto-usbredir'/>\n"
+#endif
 "    </menu>\n"
 "    <menu action='HelpMenu'>\n"
 "      <menuitem action='About'/>\n"
@@ -1304,6 +1316,24 @@ static void display_mark(SpiceChannel *channel, gint mark, spice_window *win)
     }
 }
 
+static void update_auto_usbredir_sensitive(spice_connection *conn)
+{
+#ifdef USE_USBREDIR
+    int i;
+    GtkAction *ac;
+    gboolean sensitive;
+
+    sensitive = spice_session_has_channel_type(conn->session,
+                                               SPICE_CHANNEL_USBREDIR);
+    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
+        if (conn->wins[i] == NULL)
+            continue;
+        ac = gtk_action_group_get_action(conn->wins[i]->ag, "auto-usbredir");
+        gtk_action_set_sensitive(ac, sensitive);
+    }
+#endif
+}
+
 static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
 {
     spice_connection *conn = data;
@@ -1334,6 +1364,7 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
         conn->wins[id] = create_spice_window(conn, id, channel);
         g_signal_connect(channel, "display-mark",
                          G_CALLBACK(display_mark), conn->wins[id]);
+        update_auto_usbredir_sensitive(conn);
     }
 
     if (SPICE_IS_INPUTS_CHANNEL(channel)) {
@@ -1348,6 +1379,10 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
         SPICE_DEBUG("new audio channel");
         conn->audio = spice_audio_new(s, NULL, NULL);
     }
+
+    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
+        update_auto_usbredir_sensitive(conn);
+    }
 }
 
 static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
@@ -1378,6 +1413,10 @@ static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer dat
         }
     }
 
+    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
+        update_auto_usbredir_sensitive(conn);
+    }
+
     conn->channels--;
     if (conn->channels > 0) {
         return;
commit db2ba7b1ae55146e30be7da48263880503a77c4b
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Tue Aug 30 14:31:36 2011 +0200

    spice-session: at a spice_session_has_channel_type method
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/gtk/map-file b/gtk/map-file
index c192f6c..d9765c0 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -62,6 +62,7 @@ spice_session_connect;
 spice_session_disconnect;
 spice_session_get_channels;
 spice_session_get_type;
+spice_session_has_channel_type;
 spice_session_migration_get_type;
 spice_session_new;
 spice_session_open_fd;
diff --git a/gtk/spice-client-gtk.defs b/gtk/spice-client-gtk.defs
index f5bae00..e585393 100644
--- a/gtk/spice-client-gtk.defs
+++ b/gtk/spice-client-gtk.defs
@@ -351,6 +351,15 @@
   (return-type "GList*")
 )
 
+(define-method has_channel_type
+  (of-object "SpiceSession")
+  (c-name "spice_session_has_channel_type")
+  (return-type "gboolean")
+  (parameters
+    '("gint" "type")
+  )
+)
+
 
 
 ;; From spice-channel.h
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
index b0bbb14..3ce80d5 100644
--- a/gtk/spice-session.c
+++ b/gtk/spice-session.c
@@ -1176,6 +1176,34 @@ GList *spice_session_get_channels(SpiceSession *session)
     return list;
 }
 
+/**
+ * spice_session_has_channel_type:
+ * @session: a #SpiceSession
+ *
+ * See if there is a @type channel in the channels associated with this
+ * @session.
+ *
+ * Returns: TRUE if a @type channel is available otherwise FALSE.
+ **/
+gboolean spice_session_has_channel_type(SpiceSession *session, gint type)
+{
+    SpiceSessionPrivate *s = SPICE_SESSION_GET_PRIVATE(session);
+    struct channel *item;
+    RingItem *ring;
+
+    g_return_val_if_fail(s != NULL, FALSE);
+
+    for (ring = ring_get_head(&s->channels);
+         ring != NULL;
+         ring = ring_next(&s->channels, ring)) {
+        item = SPICE_CONTAINEROF(ring, struct channel, link);
+        if (spice_channel_get_channel_type(item->channel) == type) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
 /* ------------------------------------------------------------------ */
 /* private functions                                                  */
 
diff --git a/gtk/spice-session.h b/gtk/spice-session.h
index a07d297..72effce 100644
--- a/gtk/spice-session.h
+++ b/gtk/spice-session.h
@@ -92,6 +92,7 @@ gboolean spice_session_connect(SpiceSession *session);
 gboolean spice_session_open_fd(SpiceSession *session, int fd);
 void spice_session_disconnect(SpiceSession *session);
 GList *spice_session_get_channels(SpiceSession *session);
+gboolean spice_session_has_channel_type(SpiceSession *session, gint type);
 
 G_END_DECLS
 
commit fd392f5353b79661628ebb7017d65bb800d27903
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Fri Aug 12 12:19:39 2011 +0200

    Add an USB device manager
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index f11576c..c69d124 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -215,6 +215,7 @@ libspice_client_glib_2_0_la_SOURCES =	\
 	smartcard-manager.c		\
 	smartcard-manager.h		\
 	smartcard-manager-priv.h	\
+	usb-device-manager.c		\
 	$(GUSB_SRCS)			\
 	\
 	decode.h			\
@@ -264,6 +265,7 @@ libspice_client_glibinclude_HEADERS =	\
 	channel-record.h		\
 	channel-smartcard.h		\
 	channel-usbredir.h		\
+	usb-device-manager.h		\
 	$(NULL)
 
 # file for API compatibility, but we don't want warning during our compilation
@@ -524,6 +526,7 @@ glib_introspection_files =			\
 	channel-record.c			\
 	channel-smartcard.c			\
 	channel-usbredir.c			\
+	usb-device-manager.c            	\
 	$(NULL)
 
 gtk_introspection_files =			\
diff --git a/gtk/map-file b/gtk/map-file
index 669b5dd..c192f6c 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -75,6 +75,15 @@ spice_smartcard_manager_remove_card;
 spice_smartcard_reader_get_type;
 spice_smartcard_reader_is_software;
 spice_usbredir_channel_get_type;
+spice_usb_device_get_type;
+spice_usb_device_manager_get_type;
+spice_usb_device_manager_get;
+spice_usb_device_manager_register_channel;
+spice_usb_device_manager_unregister_channel;
+spice_usb_device_manager_get_devices;
+spice_usb_device_manager_is_device_connected;
+spice_usb_device_manager_connect_device;
+spice_usb_device_manager_disconnect_device;
 spice_util_get_debug;
 spice_util_get_version_string;
 spice_util_set_debug;
diff --git a/gtk/spice-client-gtk.defs b/gtk/spice-client-gtk.defs
index 513815e..f5bae00 100644
--- a/gtk/spice-client-gtk.defs
+++ b/gtk/spice-client-gtk.defs
@@ -77,6 +77,13 @@
   (gtype-id "SPICE_TYPE_SMARTCARD_CHANNEL")
 )
 
+(define-object UsbDeviceManager
+  (in-module "Spice")
+  (parent "GObject")
+  (c-name "SpiceUsbDeviceManager")
+  (gtype-id "SPICE_TYPE_USB_DEVICE_MANAGER")
+)
+
 (define-object UsbredirChannel
   (in-module "Spice")
   (parent "SpiceChannel")
@@ -665,3 +672,78 @@
 )
 
 
+
+;; From usb-device-manager.h
+
+(define-function spice_usb_device_get_type
+  (c-name "spice_usb_device_get_type")
+  (return-type "GType")
+)
+
+(define-function spice_usb_device_manager_get_type
+  (c-name "spice_usb_device_manager_get_type")
+  (return-type "GType")
+)
+
+(define-function spice_usb_device_manager_get
+  (c-name "spice_usb_device_manager_get")
+  (return-type "SpiceUsbDeviceManager*")
+  (parameters
+    '("GMainContext*" "main_context")
+    '("GError**" "err")
+  )
+)
+
+(define-method register_channel
+  (of-object "SpiceUsbDeviceManager")
+  (c-name "spice_usb_device_manager_register_channel")
+  (return-type "none")
+  (parameters
+    '("SpiceUsbredirChannel*" "channel")
+  )
+)
+
+(define-method unregister_channel
+  (of-object "SpiceUsbDeviceManager")
+  (c-name "spice_usb_device_manager_unregister_channel")
+  (return-type "none")
+  (parameters
+    '("SpiceUsbredirChannel*" "channel")
+  )
+)
+
+(define-method get_devices
+  (of-object "SpiceUsbDeviceManager")
+  (c-name "spice_usb_device_manager_get_devices")
+  (return-type "GPtrArray*")
+)
+
+(define-method is_device_connected
+  (of-object "SpiceUsbDeviceManager")
+  (c-name "spice_usb_device_manager_is_device_connected")
+  (return-type "gboolean")
+  (parameters
+    '("SpiceUsbDevice*" "device")
+  )
+)
+
+(define-method connect_device
+  (of-object "SpiceUsbDeviceManager")
+  (c-name "spice_usb_device_manager_connect_device")
+  (return-type "gboolean")
+  (parameters
+    '("SpiceUsbDevice*" "device")
+    '("GError**" "err")
+  )
+)
+
+(define-method disconnect_device
+  (of-object "SpiceUsbDeviceManager")
+  (c-name "spice_usb_device_manager_disconnect_device")
+  (return-type "none")
+  (parameters
+    '("SpiceUsbDevice*" "device")
+  )
+)
+
+
diff --git a/gtk/spice-client.h b/gtk/spice-client.h
index 54284ce..48ea96d 100644
--- a/gtk/spice-client.h
+++ b/gtk/spice-client.h
@@ -40,6 +40,7 @@
 #include "channel-record.h"
 #include "channel-smartcard.h"
 #include "channel-usbredir.h"
+#include "usb-device-manager.h"
 
 #define SPICE_CLIENT_ERROR spice_client_error_quark()
 
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index c4f7226..9ba9be0 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -1884,6 +1884,18 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
     }
 #endif
 
+    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
+        SpiceUsbDeviceManager *manager;
+
+        /* FIXME: allow specifying a different GMainContext then the default */
+        manager = spice_usb_device_manager_get(NULL /* FIXME */, NULL);
+        if (manager) {
+            spice_usb_device_manager_register_channel(manager,
+                                              SPICE_USBREDIR_CHANNEL(channel));
+        }
+        return;
+    }
+
     return;
 }
 
@@ -1927,6 +1939,17 @@ static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer dat
     }
 #endif
 
+    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
+        SpiceUsbDeviceManager *manager;
+
+        manager = spice_usb_device_manager_get(NULL /* FIXME */, NULL);
+        if (manager) {
+            spice_usb_device_manager_unregister_channel(manager,
+                                              SPICE_USBREDIR_CHANNEL(channel));
+        }
+        return;
+    }
+
     return;
 }
 
diff --git a/gtk/usb-device-manager.c b/gtk/usb-device-manager.c
new file mode 100644
index 0000000..0e2a9ed
--- /dev/null
+++ b/gtk/usb-device-manager.c
@@ -0,0 +1,576 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 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-object.h>
+#include <gio/gio.h> /* For GInitable */
+
+#ifdef USE_USBREDIR
+#include <gusb/gusb-source.h>
+#include <gusb/gusb-device-list.h>
+#include "channel-usbredir-priv.h"
+#endif
+
+#include "spice-client.h"
+#include "spice-marshal.h"
+
+#define SPICE_USB_DEVICE_MANAGER_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerPrivate))
+
+enum {
+    PROP_0,
+    PROP_MAIN_CONTEXT,
+    PROP_AUTO_CONNECT,
+};
+
+enum
+{
+    DEVICE_ADDED,
+    DEVICE_REMOVED,
+    LAST_SIGNAL,
+};
+
+struct _SpiceUsbDeviceManagerPrivate {
+    GMainContext *main_context;
+    gboolean auto_connect;
+#ifdef USE_USBREDIR
+    GUsbContext *context;
+    GUsbDeviceList *devlist;
+    GUsbSource *source;
+#endif
+    GPtrArray *devices;
+    GPtrArray *channels;
+};
+
+#ifdef USE_USBREDIR
+static void spice_usb_device_manager_dev_added(GUsbDeviceList *devlist,
+                                               GUsbDevice     *device,
+                                               GUdevDevice    *udev,
+                                               gpointer        user_data);
+static void spice_usb_device_manager_dev_removed(GUsbDeviceList *devlist,
+                                                 GUsbDevice     *device,
+                                                 GUdevDevice    *udev,
+                                                 gpointer        user_data);
+#endif
+static void spice_usb_device_manager_initable_iface_init(GInitableIface *iface);
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_CODE(SpiceUsbDeviceManager, spice_usb_device_manager, G_TYPE_OBJECT,
+     G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, spice_usb_device_manager_initable_iface_init));
+
+G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device, g_object_ref, g_object_unref)
+
+static void spice_usb_device_manager_init(SpiceUsbDeviceManager *self)
+{
+    SpiceUsbDeviceManagerPrivate *priv;
+
+    priv = SPICE_USB_DEVICE_MANAGER_GET_PRIVATE(self);
+    self->priv = priv;
+
+    priv->main_context = NULL;
+    priv->channels = g_ptr_array_new();
+    priv->devices  = g_ptr_array_new_with_free_func((GDestroyNotify)
+                                                    g_object_unref);
+#ifdef USE_USBREDIR
+    priv->context = NULL;
+    priv->source  = NULL;
+    priv->devlist = NULL;
+#endif
+}
+
+static gboolean spice_usb_device_manager_initable_init(GInitable  *initable,
+                                                    GCancellable  *cancellable,
+                                                    GError        **err)
+{
+#ifdef USE_USBREDIR
+    GError *my_err = NULL;
+    SpiceUsbDeviceManager *self;
+    SpiceUsbDeviceManagerPrivate *priv;
+
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(initable), FALSE);
+    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+    if (cancellable != NULL) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Cancellable initialization not supported");
+    }
+
+    self = SPICE_USB_DEVICE_MANAGER(initable);
+    priv = self->priv;
+
+    priv->context = g_usb_context_new(&my_err);
+    if (priv->context == NULL) {
+        g_warning("Could not get a GUsbContext, disabling USB support: %s",
+                  my_err->message);
+        if (err) {
+            *err = my_err;
+        } else {
+            g_error_free(my_err);
+        }
+        return FALSE;
+    }
+
+    priv->devlist = g_usb_device_list_new(priv->context);
+    g_signal_connect(G_OBJECT(priv->devlist), "device_added",
+                     G_CALLBACK(spice_usb_device_manager_dev_added),
+                     self);
+    g_signal_connect(G_OBJECT(priv->devlist), "device_removed",
+                     G_CALLBACK(spice_usb_device_manager_dev_removed),
+                     self);
+    g_usb_device_list_coldplug(priv->devlist);
+    return TRUE;
+#else
+    g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                        "USB redirection support not compiled in");
+    return FALSE;
+#endif
+}
+
+static void spice_usb_device_manager_finalize(GObject *gobject)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+#ifdef USE_USBREDIR
+    if (priv->source)
+        g_usb_source_destroy(priv->source);
+    if (priv->devlist) {
+        g_object_unref(priv->devlist);
+        g_object_unref(priv->context);
+    }
+#endif
+
+    g_ptr_array_unref(priv->channels);
+    g_ptr_array_unref(priv->devices);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->finalize(gobject);
+}
+
+static void spice_usb_device_manager_initable_iface_init(GInitableIface *iface)
+{
+    iface->init = spice_usb_device_manager_initable_init;
+}
+
+static void spice_usb_device_manager_get_property(GObject     *gobject,
+                                                  guint        prop_id,
+                                                  GValue      *value,
+                                                  GParamSpec  *pspec)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_MAIN_CONTEXT:
+        g_value_set_pointer(value, priv->main_context);
+        break;
+    case PROP_AUTO_CONNECT:
+        g_value_set_boolean(value, priv->auto_connect);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_manager_set_property(GObject       *gobject,
+                                                  guint          prop_id,
+                                                  const GValue  *value,
+                                                  GParamSpec    *pspec)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_MAIN_CONTEXT:
+        priv->main_context = g_value_get_pointer(value);
+        break;
+    case PROP_AUTO_CONNECT:
+        priv->auto_connect = g_value_get_boolean(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_manager_class_init(SpiceUsbDeviceManagerClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GParamSpec *pspec;
+
+    gobject_class->finalize     = spice_usb_device_manager_finalize;
+    gobject_class->get_property = spice_usb_device_manager_get_property;
+    gobject_class->set_property = spice_usb_device_manager_set_property;
+
+    /**
+     * SpiceUsbDeviceManager:main-context:
+     */
+    pspec = g_param_spec_pointer("main-context", "Main Context",
+                                 "GMainContext to use for the event source",
+                                 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                 G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_MAIN_CONTEXT, pspec);
+
+    /**
+     * SpiceUsbDeviceManager:auto-connect:
+     */
+    pspec = g_param_spec_boolean("auto-connect", "Auto Connect",
+                                 "Auto connect plugged in USB devices",
+                                 FALSE,
+                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_AUTO_CONNECT, pspec);
+
+    /**
+     * SpiceUsbDeviceManager::device-added:
+     * @manager: the #SpiceUsbDeviceManager that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the added device
+     *
+     * The #SpiceUsbDeviceManager::device-added signal is emitted whenever
+     * a new USB device has been plugged in.
+     **/
+    signals[DEVICE_ADDED] =
+        g_signal_new("device-added",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_added),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_USB_DEVICE);
+
+    /**
+     * SpiceUsbDeviceManager::device-removed:
+     * @manager: the #SpiceUsbDeviceManager that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the removed device
+     *
+     * The #SpiceUsbDeviceManager::device-removed signal is emitted whenever
+     * an USB device has been removed.
+     **/
+    signals[DEVICE_REMOVED] =
+        g_signal_new("device-removed",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_removed),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_USB_DEVICE);
+
+    g_type_class_add_private(klass, sizeof(SpiceUsbDeviceManagerPrivate));
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+
+#ifdef USE_USBREDIR
+static gboolean spice_usb_device_manager_source_callback(gpointer user_data)
+{
+    SpiceUsbDeviceManager *self = user_data;
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    guint i;
+
+    /*
+     * Flush any writes which may have been caused by async usb packets
+     * completing.
+     */
+    for (i = 0; i < priv->channels->len; i++) {
+        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
+
+        spice_usbredir_channel_do_write(channel);
+    }
+
+    return TRUE;
+}
+
+static void spice_usb_device_manager_dev_added(GUsbDeviceList *devlist,
+                                               GUsbDevice     *device,
+                                               GUdevDevice    *udev,
+                                               gpointer        user_data)
+{
+    SpiceUsbDeviceManager *manager = user_data;
+    SpiceUsbDeviceManagerPrivate *priv = manager->priv;
+
+    g_ptr_array_add(priv->devices, g_object_ref(device));
+
+    if (priv->auto_connect) {
+        GError *err = NULL;
+        spice_usb_device_manager_connect_device(manager,
+                                                (SpiceUsbDevice *)device,
+                                                &err);
+        if (err) {
+            g_warning("Could not auto-redirect USB device: %s", err->message);
+            g_error_free(err);
+        }
+    }
+
+    SPICE_DEBUG("device added %p", device);
+    g_signal_emit(manager, signals[DEVICE_ADDED], 0, device);
+}
+
+static void spice_usb_device_manager_dev_removed(GUsbDeviceList *devlist,
+                                                 GUsbDevice     *device,
+                                                 GUdevDevice    *udev,
+                                                 gpointer        user_data)
+{
+    SpiceUsbDeviceManager *manager = user_data;
+    SpiceUsbDeviceManagerPrivate *priv = manager->priv;
+
+    spice_usb_device_manager_disconnect_device(manager,
+                                               (SpiceUsbDevice *)device);
+
+    SPICE_DEBUG("device removed %p", device);
+    g_signal_emit(manager, signals[DEVICE_REMOVED], 0, device);
+    g_ptr_array_remove(priv->devices, device);
+}
+#endif
+
+struct spice_usb_device_manager_new_params {
+    GMainContext *main_context;
+    GError **err;
+};
+
+static SpiceUsbDeviceManager *spice_usb_device_manager_new(void *p)
+{
+    struct spice_usb_device_manager_new_params *params = p;
+
+    return g_initable_new(SPICE_TYPE_USB_DEVICE_MANAGER, NULL, params->err,
+                          "main-context", params->main_context, NULL);
+}
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+static SpiceUsbredirChannel *spice_usb_device_manager_get_channel_for_dev(
+    SpiceUsbDeviceManager *manager, SpiceUsbDevice *_device)
+{
+#ifdef USE_USBREDIR
+    SpiceUsbDeviceManagerPrivate *priv = manager->priv;
+    GUsbDevice *device = (GUsbDevice *)_device;
+    guint i;
+
+    for (i = 0; i < priv->channels->len; i++) {
+        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
+        if (spice_usbredir_channel_get_device(channel) == device)
+            return channel;
+    }
+#endif
+    return NULL;
+}
+
+/* ------------------------------------------------------------------ */
+/* public api                                                         */
+
+/**
+ * spice_usb_device_manager_get:
+ * @main_context: #GMainContext to use. If %NULL, the default context is used.
+ *
+ * #SpiceUsbDeviceManager is a singleton, use this function to get a pointer
+ * to it. A new #SpiceUsbDeviceManager instance will be created the first
+ * time this function is called
+ *
+ * Returns: a weak reference to the #SpiceUsbDeviceManager singleton
+ */
+SpiceUsbDeviceManager *spice_usb_device_manager_get(GMainContext *main_context,
+                                                    GError **err)
+{
+    static GOnce manager_singleton_once = G_ONCE_INIT;
+    struct spice_usb_device_manager_new_params params;
+
+    g_return_val_if_fail(err == NULL || *err == NULL, NULL);
+
+    params.main_context = main_context;
+    params.err = err;
+
+    return g_once(&manager_singleton_once,
+                  (GThreadFunc)spice_usb_device_manager_new,
+                  &params);
+}
+
+/**
+ * spice_usb_device_manager_register_channel:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @channel: a #SpiceUsbredirChannel to register
+ *
+ * Register @channel to be managed by the USB device @manager.  When a
+ * new device is added/plugged, the @manager will use an available
+ * channel to establish the redirection with the Spice server.
+ *
+ * Note that this function takes a weak reference to the channel, it is the
+ * callers responsibility to call spice_usb_device_manager_unregister_channel()
+ * before it unrefs its own reference.
+ **/
+void spice_usb_device_manager_register_channel(SpiceUsbDeviceManager *self,
+                                               SpiceUsbredirChannel *channel)
+{
+    SpiceUsbDeviceManagerPrivate *priv;
+    guint i;
+
+    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
+    g_return_if_fail(SPICE_IS_USBREDIR_CHANNEL(channel));
+
+    priv = self->priv;
+
+    for (i = 0; i < priv->channels->len; i++) {
+        if (g_ptr_array_index(priv->channels, i) == channel) {
+            g_return_if_reached();
+        }
+    }
+    g_ptr_array_add(self->priv->channels, channel);
+}
+
+/**
+ * spice_usb_device_manager_unregister_channel:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @channel: a #SpiceUsbredirChannel to unregister
+ *
+ * Remove @channel from the list of USB channels to be managed by @manager.
+ */
+void spice_usb_device_manager_unregister_channel(SpiceUsbDeviceManager *self,
+                                                 SpiceUsbredirChannel *channel)
+{
+    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
+    g_return_if_fail(SPICE_IS_USBREDIR_CHANNEL(channel));
+
+    g_warn_if_fail(g_ptr_array_remove(self->priv->channels, channel));
+}
+
+/**
+ * spice_usb_device_manager_get_devices:
+ * @manager: the #SpiceUsbDeviceManager manager
+ *
+ * Returns: a %GPtrArray array of %SpiceUsbDevice
+ */
+GPtrArray* spice_usb_device_manager_get_devices(SpiceUsbDeviceManager *self)
+{
+    SpiceUsbDeviceManagerPrivate *priv;
+    GPtrArray *devices_copy;
+    guint i;
+
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), NULL);
+
+    priv = self->priv;
+    devices_copy = g_ptr_array_new_with_free_func((GDestroyNotify)
+                                                  g_object_unref);
+    for (i = 0; i < priv->devices->len; i++) {
+        SpiceUsbDevice *device = g_ptr_array_index(priv->devices, i);
+        g_ptr_array_add(devices_copy, g_object_ref(device));
+    }
+
+    return devices_copy;
+}
+
+/**
+ * spice_usb_device_manager_is_device_connected:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @device: a #SpiceUsbDevice
+ *
+ * Returns: %TRUE if @device has an associated USB redirection channel
+ */
+gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *self,
+                                                      SpiceUsbDevice *device)
+{
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), FALSE);
+    g_return_val_if_fail(device != NULL, FALSE);
+
+    return !!spice_usb_device_manager_get_channel_for_dev(self, device);
+}
+
+/**
+ * spice_usb_device_manager_connect_device:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @device: a #SpiceUsbDevice to redirect
+ *
+ * Returns: %TRUE if @device has been successfully connected and
+ * associated with a redirection chanel
+ */
+gboolean spice_usb_device_manager_connect_device(SpiceUsbDeviceManager *self,
+                                                 SpiceUsbDevice *device, GError **err)
+{
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), FALSE);
+    g_return_val_if_fail(device != NULL, FALSE);
+    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+    SPICE_DEBUG("connecting device %p", device);
+
+#ifdef USE_USBREDIR
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    guint i;
+
+    if (spice_usb_device_manager_is_device_connected(self, device)) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Cannot connect an already connected usb device");
+        return FALSE;
+    }
+
+    if (!priv->source) {
+        priv->source = g_usb_source_new(priv->main_context, priv->context, err);
+        if (*err)
+            return FALSE;
+
+        g_usb_source_set_callback(priv->source,
+                                  spice_usb_device_manager_source_callback,
+                                  self, NULL);
+    }
+
+    for (i = 0; i < priv->channels->len; i++) {
+        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
+
+        if (spice_usbredir_channel_get_device(channel))
+            continue; /* Skip already used channels */
+
+        return spice_usbredir_channel_connect(channel, priv->context,
+                                              (GUsbDevice *)device, err);
+    }
+#endif
+
+    g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                        "No free USB channel");
+    return FALSE;
+}
+
+/**
+ * spice_usb_device_manager_disconnect_device:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @device: a #SpiceUsbDevice to disconnect
+ *
+ * Returns: %TRUE if @device has an associated USB redirection channel
+ */
+void spice_usb_device_manager_disconnect_device(SpiceUsbDeviceManager *self,
+                                                SpiceUsbDevice *device)
+{
+    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
+    g_return_if_fail(device != NULL);
+
+    SPICE_DEBUG("disconnecting device %p", device);
+
+#ifdef USE_USBREDIR
+    SpiceUsbredirChannel *channel;
+
+    channel = spice_usb_device_manager_get_channel_for_dev(self, device);
+    if (channel)
+        spice_usbredir_channel_disconnect(channel);
+#endif
+}
diff --git a/gtk/usb-device-manager.h b/gtk/usb-device-manager.h
new file mode 100644
index 0000000..855accb
--- /dev/null
+++ b/gtk/usb-device-manager.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 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_MANAGER_H__
+#define __SPICE_USB_DEVICE_MANAGER_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USB_DEVICE_MANAGER            (spice_usb_device_manager_get_type ())
+#define SPICE_USB_DEVICE_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManager))
+#define SPICE_USB_DEVICE_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerClass))
+#define SPICE_IS_USB_DEVICE_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_DEVICE_MANAGER))
+#define SPICE_IS_USB_DEVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_DEVICE_MANAGER))
+#define SPICE_USB_DEVICE_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerClass))
+
+#define SPICE_TYPE_USB_DEVICE                    (spice_usb_device_get_type())
+
+typedef struct _SpiceUsbDeviceManager SpiceUsbDeviceManager;
+typedef struct _SpiceUsbDeviceManagerClass SpiceUsbDeviceManagerClass;
+typedef struct _SpiceUsbDeviceManagerPrivate SpiceUsbDeviceManagerPrivate;
+
+typedef struct _SpiceUsbDevice SpiceUsbDevice;
+
+struct _SpiceUsbDeviceManager
+{
+    GObject parent;
+
+    /*< private >*/
+    SpiceUsbDeviceManagerPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceUsbDeviceManagerClass
+{
+    GObjectClass parent_class;
+    /*< public >*/
+
+    /*< private >*/
+    void (*device_added) (SpiceUsbDeviceManager *manager,
+                          SpiceUsbDevice *device);
+    void (*device_removed) (SpiceUsbDeviceManager *manager,
+                            SpiceUsbDevice *device);
+    /*
+     * 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_get_type(void);
+GType spice_usb_device_manager_get_type(void);
+
+SpiceUsbDeviceManager *spice_usb_device_manager_get(GMainContext *main_context,
+                                                    GError **err);
+
+void spice_usb_device_manager_register_channel(SpiceUsbDeviceManager *manager,
+                                               SpiceUsbredirChannel *channel);
+void spice_usb_device_manager_unregister_channel(SpiceUsbDeviceManager *manager,
+                                                 SpiceUsbredirChannel *channel);
+
+GPtrArray *spice_usb_device_manager_get_devices(SpiceUsbDeviceManager *manager);
+
+gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *manager,
+                                                      SpiceUsbDevice *device);
+gboolean spice_usb_device_manager_connect_device(SpiceUsbDeviceManager *manager,
+                                                 SpiceUsbDevice *device,
+                                                 GError **err);
+void spice_usb_device_manager_disconnect_device(SpiceUsbDeviceManager *manager,
+                                                SpiceUsbDevice *device);
+
+G_END_DECLS
+
+#endif /* __SPICE_USB_DEVICE_MANAGER_H__ */
commit 6af3e8abe071c6d3d0d29fbd9a514ab17dccaafd
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Thu Aug 11 18:53:24 2011 +0200

    Add an usbredir channel
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/configure.ac b/configure.ac
index 9fa2b20..98b91de 100644
--- a/configure.ac
+++ b/configure.ac
@@ -321,6 +321,7 @@ if test "x$enable_usbredir" = "xno"; then
 else
   PKG_CHECK_MODULES(GUDEV, gudev-1.0)
   PKG_CHECK_MODULES(LIBUSB, libusb-1.0 >= 1.0.9)
+  PKG_CHECK_MODULES(LIBUSBREDIRHOST, libusbredirhost >= 0.3.1)
   AC_DEFINE(USE_USBREDIR, [1], [Define if supporting usbredir proxying])
   AM_CONDITIONAL(WITH_USBREDIR, true)
 fi
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 1b841e2..f11576c 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -74,6 +74,7 @@ SPICE_COMMON_CPPFLAGS = \
 	$(SMARTCARD_CFLAGS)		\
 	$(GUDEV_CFLAGS)			\
 	$(LIBUSB_CFLAGS)		\
+	$(LIBUSBREDIRHOST_CFLAGS)	\
 	$(NULL)
 
 AM_CPPFLAGS = \
@@ -154,6 +155,7 @@ libspice_client_glib_2_0_la_LIBADD =	\
 	$(SMARTCARD_LIBS)		\
 	$(GUDEV_LIBS)			\
 	$(LIBUSB_LIBS)			\
+	$(LIBUSBREDIRHOST_LIBS)		\
 	$(NULL)
 
 if WITH_USBREDIR
@@ -208,6 +210,8 @@ libspice_client_glib_2_0_la_SOURCES =	\
 	channel-playback.c		\
 	channel-record.c		\
 	channel-smartcard.c		\
+	channel-usbredir.c		\
+	channel-usbredir-priv.h		\
 	smartcard-manager.c		\
 	smartcard-manager.h		\
 	smartcard-manager-priv.h	\
@@ -259,6 +263,7 @@ libspice_client_glibinclude_HEADERS =	\
 	channel-playback.h		\
 	channel-record.h		\
 	channel-smartcard.h		\
+	channel-usbredir.h		\
 	$(NULL)
 
 # file for API compatibility, but we don't want warning during our compilation
@@ -518,6 +523,7 @@ glib_introspection_files =			\
 	channel-playback.c			\
 	channel-record.c			\
 	channel-smartcard.c			\
+	channel-usbredir.c			\
 	$(NULL)
 
 gtk_introspection_files =			\
diff --git a/gtk/channel-usbredir-priv.h b/gtk/channel-usbredir-priv.h
new file mode 100644
index 0000000..05988e1
--- /dev/null
+++ b/gtk/channel-usbredir-priv.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 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_CLIENT_USBREDIR_CHANNEL_PRIV_H__
+#define __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__
+
+#include <gusb/gusb-context.h>
+#include <gusb/gusb-device.h>
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+gboolean spice_usbredir_channel_connect(SpiceUsbredirChannel *channel,
+                                        GUsbContext *context,
+                                        GUsbDevice *device,
+                                        GError **err);
+void spice_usbredir_channel_disconnect(SpiceUsbredirChannel *channel);
+
+GUsbDevice *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel);
+
+void spice_usbredir_channel_do_write(SpiceUsbredirChannel *channel);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__ */
diff --git a/gtk/channel-usbredir.c b/gtk/channel-usbredir.c
new file mode 100644
index 0000000..b567fcc
--- /dev/null
+++ b/gtk/channel-usbredir.c
@@ -0,0 +1,333 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright 2010-2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+   Richard Hughes <rhughes 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"
+
+#ifdef USE_USBREDIR
+#include <usbredirhost.h>
+#include <gusb/gusb-context-private.h>
+#include <gusb/gusb-device-private.h>
+#include "channel-usbredir-priv.h"
+#endif
+
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-channel-priv.h"
+
+/* libusb_strerror is awaiting merging upstream */
+#define libusb_strerror(error) "unknown"
+
+/**
+ * SECTION:channel-usbredir
+ * @short_description: usb redirection
+ * @title: USB Redirection Channel
+ * @section_id:
+ * @stability: API Stable (channel in development)
+ * @include: channel-usbredir.h
+ *
+ * The Spice protocol defines a set of messages to redirect USB devices
+ * from the Spice client to the VM. This channel handles these messages.
+ */
+
+#define SPICE_USBREDIR_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelPrivate))
+
+struct _SpiceUsbredirChannelPrivate {
+#ifdef USE_USBREDIR
+    GUsbContext *context;
+    GUsbDevice *device;
+    struct usbredirhost *host;
+    /* To catch usbredirhost error messages and report them as a GError */
+    GError **catch_error;
+    /* Data passed from channel handle msg to the usbredirhost read cb */
+    const uint8_t *read_buf;
+    int read_buf_size;
+    SpiceMsgOut *msg_out;
+    gboolean up;
+#endif
+};
+
+G_DEFINE_TYPE(SpiceUsbredirChannel, spice_usbredir_channel, SPICE_TYPE_CHANNEL)
+
+static void spice_usbredir_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
+static void spice_usbredir_channel_up(SpiceChannel *channel);
+static void usbredir_handle_msg(SpiceChannel *channel, SpiceMsgIn *in);
+
+#ifdef USE_USBREDIR
+static void usbredir_log(void *user_data, int level, const char *msg);
+static int usbredir_read_callback(void *user_data, uint8_t *data, int count);
+static int usbredir_write_callback(void *user_data, uint8_t *data, int count);
+#endif
+
+/* ------------------------------------------------------------------ */
+
+static void spice_usbredir_channel_init(SpiceUsbredirChannel *channel)
+{
+    channel->priv = SPICE_USBREDIR_CHANNEL_GET_PRIVATE(channel);
+
+    memset(channel->priv, 0, sizeof(SpiceUsbredirChannelPrivate));
+}
+
+static void spice_usbredir_channel_finalize(GObject *obj)
+{
+#ifdef USE_USBREDIR
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(obj);
+
+    spice_usbredir_channel_disconnect(channel);
+#endif
+
+    if (G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->finalize(obj);
+}
+
+static void spice_usbredir_channel_class_init(SpiceUsbredirChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_usbredir_channel_finalize;
+    channel_class->handle_msg   = spice_usbredir_handle_msg;
+    channel_class->channel_up   = spice_usbredir_channel_up;
+
+    g_type_class_add_private(klass, sizeof(SpiceUsbredirChannelPrivate));
+}
+
+static const spice_msg_handler usbredir_handlers[] = {
+    [ SPICE_MSG_SPICEVMC_DATA ] = usbredir_handle_msg,
+};
+
+#ifdef USE_USBREDIR
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+G_GNUC_INTERNAL
+gboolean spice_usbredir_channel_connect(SpiceUsbredirChannel *channel,
+                                        GUsbContext *context,
+                                        GUsbDevice *device,
+                                        GError **err)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+    libusb_device_handle *handle = NULL;
+    int rc;
+
+    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+    SPICE_DEBUG("connecting usb channel %p", channel);
+
+    spice_usbredir_channel_disconnect(channel);
+
+    rc = libusb_open(_g_usb_device_get_device(device), &handle);
+    if (rc != 0) {
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    "Could not open usb device: %s [%i]",
+                    libusb_strerror(rc), rc);
+        return FALSE;
+    }
+
+    priv->catch_error = err;
+    priv->host = usbredirhost_open(_g_usb_context_get_context(context),
+                                   handle, usbredir_log,
+                                   usbredir_read_callback,
+                                   usbredir_write_callback,
+                                   channel, PACKAGE_STRING,
+                                   spice_util_get_debug() ? usbredirparser_debug : usbredirparser_warning,
+                                   usbredirhost_fl_write_cb_owns_buffer);
+    priv->catch_error = NULL;
+    if (!priv->host) {
+        return FALSE;
+    }
+
+    priv->context = g_object_ref(context);
+    priv->device  = g_object_ref(device);
+
+    spice_channel_connect(SPICE_CHANNEL(channel));
+
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+void spice_usbredir_channel_disconnect(SpiceUsbredirChannel *channel)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    SPICE_DEBUG("disconnecting usb channel %p", channel);
+
+    spice_channel_disconnect(SPICE_CHANNEL(channel), SPICE_CHANNEL_NONE);
+    priv->up = FALSE;
+
+    if (priv->host) {
+        /* This also closes the libusb handle we passed to its _open */
+        usbredirhost_close(priv->host);
+        priv->host = NULL;
+        g_clear_object(&priv->device);
+        g_clear_object(&priv->context);
+    }
+}
+
+G_GNUC_INTERNAL
+GUsbDevice *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel)
+{
+    return channel->priv->device;
+}
+
+G_GNUC_INTERNAL
+void spice_usbredir_channel_do_write(SpiceUsbredirChannel *channel)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    /* No recursion allowed! */
+    g_return_if_fail(priv->msg_out == NULL);
+
+    if (!priv->up || !usbredirhost_has_data_to_write(priv->host))
+        return;
+
+    priv->msg_out = spice_msg_out_new(SPICE_CHANNEL(channel),
+                                      SPICE_MSGC_SPICEVMC_DATA);
+
+    /* Collect all pending writes in priv->msg_out->marshaller */
+    usbredirhost_write_guest_data(priv->host);
+
+    spice_msg_out_send(priv->msg_out);
+    spice_msg_out_unref(priv->msg_out);
+    priv->msg_out = NULL;
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks (any context)                                            */
+
+static void usbredir_log(void *user_data, int level, const char *msg)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    if (priv->catch_error && level == usbredirparser_error) {
+        SPICE_DEBUG("%s", msg);
+        g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
+                            SPICE_CLIENT_ERROR_FAILED, msg);
+        return;
+    }
+
+    switch (level) {
+        case usbredirparser_error:
+            g_critical("%s", msg); break;
+        case usbredirparser_warning:
+            g_warning("%s", msg); break;
+        default:
+            SPICE_DEBUG("%s", msg); break;
+    }
+}
+
+static int usbredir_read_callback(void *user_data, uint8_t *data, int count)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    if (priv->read_buf_size < count) {
+        count = priv->read_buf_size;
+    }
+
+    memcpy(data, priv->read_buf, count);
+
+    priv->read_buf_size -= count;
+    if (priv->read_buf_size) {
+        priv->read_buf += count;
+    } else {
+        priv->read_buf = NULL;
+    }
+
+    return count;
+}
+
+static void usbredir_free_write_cb_data(uint8_t *data, void *user_data)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    usbredirhost_free_write_buffer(priv->host, data);
+}
+
+static int usbredir_write_callback(void *user_data, uint8_t *data, int count)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    spice_marshaller_add_ref_full(priv->msg_out->marshaller, data, count,
+                                  usbredir_free_write_cb_data, channel);
+    return count;
+}
+
+#endif /* USE_USBREDIR */
+
+/* --------------------------------------------------------------------- */
+/* coroutine context                                                     */
+static void spice_usbredir_handle_msg(SpiceChannel *c, SpiceMsgIn *msg)
+{
+    int type = spice_msg_in_type(msg);
+    SpiceChannelClass *parent_class;
+
+    g_return_if_fail(type < SPICE_N_ELEMENTS(usbredir_handlers));
+
+    parent_class = SPICE_CHANNEL_CLASS(spice_usbredir_channel_parent_class);
+
+    if (usbredir_handlers[type] != NULL)
+        usbredir_handlers[type](c, msg);
+    else if (parent_class->handle_msg)
+        parent_class->handle_msg(c, msg);
+    else
+        g_return_if_reached();
+}
+
+static void spice_usbredir_channel_up(SpiceChannel *c)
+{
+#ifdef USE_USBREDIR
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    priv->up = TRUE;
+    /* Flush any pending writes */
+    spice_usbredir_channel_do_write(channel);
+#endif
+}
+
+static void usbredir_handle_msg(SpiceChannel *c, SpiceMsgIn *in)
+{
+#ifdef USE_USBREDIR
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+    int size;
+    uint8_t *buf;
+
+    g_return_if_fail(priv->host != NULL);
+
+    /* No recursion allowed! */
+    g_return_if_fail(priv->read_buf == NULL);
+
+    buf = spice_msg_in_raw(in, &size);
+    priv->read_buf = buf;
+    priv->read_buf_size = size;
+
+    usbredirhost_read_guest_data(priv->host);
+    /* Send any acks, etc. which may be queued now */
+    spice_usbredir_channel_do_write(channel);
+#endif
+}
diff --git a/gtk/channel-usbredir.h b/gtk/channel-usbredir.h
new file mode 100644
index 0000000..ca0b535
--- /dev/null
+++ b/gtk/channel-usbredir.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 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_CLIENT_USBREDIR_CHANNEL_H__
+#define __SPICE_CLIENT_USBREDIR_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USBREDIR_CHANNEL            (spice_usbredir_channel_get_type())
+#define SPICE_USBREDIR_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannel))
+#define SPICE_USBREDIR_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelClass))
+#define SPICE_IS_USBREDIR_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_USBREDIR_CHANNEL))
+#define SPICE_IS_USBREDIR_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_USBREDIR_CHANNEL))
+#define SPICE_USBREDIR_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelClass))
+
+typedef struct _SpiceUsbredirChannel SpiceUsbredirChannel;
+typedef struct _SpiceUsbredirChannelClass SpiceUsbredirChannelClass;
+typedef struct _SpiceUsbredirChannelPrivate SpiceUsbredirChannelPrivate;
+
+struct _SpiceUsbredirChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceUsbredirChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceUsbredirChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_usbredir_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_USBREDIR_CHANNEL_H__ */
diff --git a/gtk/map-file b/gtk/map-file
index f358066..669b5dd 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -74,6 +74,7 @@ spice_smartcard_manager_insert_card;
 spice_smartcard_manager_remove_card;
 spice_smartcard_reader_get_type;
 spice_smartcard_reader_is_software;
+spice_usbredir_channel_get_type;
 spice_util_get_debug;
 spice_util_get_version_string;
 spice_util_set_debug;
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index a89b75c..3a17204 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -1658,7 +1658,8 @@ const gchar* spice_channel_type_to_string(gint type)
         [ SPICE_CHANNEL_PLAYBACK ] = "playback",
         [ SPICE_CHANNEL_RECORD ] = "record",
         [ SPICE_CHANNEL_TUNNEL ] = "tunnel",
-        [ SPICE_CHANNEL_SMARTCARD ] = "smartcard"
+        [ SPICE_CHANNEL_SMARTCARD ] = "smartcard",
+        [ SPICE_CHANNEL_USBREDIR ] = "usbredir",
     };
     const char *str = NULL;
 
@@ -1717,6 +1718,18 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
         break;
     }
 #endif
+#ifdef USE_USBREDIR
+    case SPICE_CHANNEL_USBREDIR: {
+        gboolean enabled;
+        g_object_get(G_OBJECT(s), "enable-usbredir", &enabled, NULL);
+        if (!enabled) {
+            g_debug("usbredir channel is disabled, not creating it");
+            return NULL;
+        }
+        gtype = SPICE_TYPE_USBREDIR_CHANNEL;
+        break;
+    }
+#endif
     default:
         g_debug("unsupported channel kind: %s: %d",
                 spice_channel_type_to_string(type), type);
diff --git a/gtk/spice-client-gtk.defs b/gtk/spice-client-gtk.defs
index f718c8c..513815e 100644
--- a/gtk/spice-client-gtk.defs
+++ b/gtk/spice-client-gtk.defs
@@ -77,6 +77,13 @@
   (gtype-id "SPICE_TYPE_SMARTCARD_CHANNEL")
 )
 
+(define-object UsbredirChannel
+  (in-module "Spice")
+  (parent "SpiceChannel")
+  (c-name "SpiceUsbredirChannel")
+  (gtype-id "SPICE_TYPE_USBREDIR_CHANNEL")
+)
+
 ;; Enumerations and flags ...
 
 (define-enum DisplayKeyEvent
@@ -649,3 +656,12 @@
 )
 
 
+
+;; From channel-usbredir.h
+
+(define-function spice_usbredir_channel_get_type
+  (c-name "spice_usbredir_channel_get_type")
+  (return-type "GType")
+)
+
+
diff --git a/gtk/spice-client.h b/gtk/spice-client.h
index 885d81c..54284ce 100644
--- a/gtk/spice-client.h
+++ b/gtk/spice-client.h
@@ -39,6 +39,7 @@
 #include "channel-playback.h"
 #include "channel-record.h"
 #include "channel-smartcard.h"
+#include "channel-usbredir.h"
 
 #define SPICE_CLIENT_ERROR spice_client_error_quark()
 
diff --git a/gtk/spice-option.c b/gtk/spice-option.c
index 6c4e50c..4a2ba90 100644
--- a/gtk/spice-option.c
+++ b/gtk/spice-option.c
@@ -32,6 +32,7 @@ static char *host_subject = NULL;
 static char *smartcard_db = NULL;
 static char *smartcard_certificates = NULL;
 static gboolean smartcard = FALSE;
+static gboolean disable_usbredir = FALSE;
 
 static void option_version(void)
 {
@@ -69,6 +70,8 @@ GOptionGroup* spice_get_option_group(void)
           N_("Certificates to use for software smartcards (field=values separated by commas)"), N_("<certificates>") },
         { "spice-smartcard-db", '\0', 0, G_OPTION_ARG_STRING, &smartcard_db,
           N_("Path to the local certificate database to use for software smartcard certificates"), N_("<certificate-db>") },
+        { "spice-disable-usbredir", '\0', 0, G_OPTION_ARG_NONE, &disable_usbredir,
+          N_("Disable USB redirection support"), NULL },
 
         { "spice-debug", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, option_debug,
           N_("Enable Spice-GTK debugging"), NULL },
@@ -127,4 +130,6 @@ void spice_set_session_option(SpiceSession *session)
         if (smartcard_db)
             g_object_set(session, "smartcard-db", smartcard_db, NULL);
     }
+    if (disable_usbredir)
+        g_object_set(session, "enable-usbredir", FALSE, NULL);
 }
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
index d6100f6..b0bbb14 100644
--- a/gtk/spice-session.c
+++ b/gtk/spice-session.c
@@ -59,6 +59,10 @@ struct _SpiceSessionPrivate {
      * fallback to using a default database.
      */
     char *            smartcard_db;
+
+    /* whether to enable USB redirection */
+    gboolean          usbredir;
+
     GStrv             disable_effects;
     gint              color_depth;
 
@@ -139,6 +143,7 @@ enum {
     PROP_SMARTCARD,
     PROP_SMARTCARD_CERTIFICATES,
     PROP_SMARTCARD_DB,
+    PROP_USBREDIR,
     PROP_DISABLE_EFFECTS,
     PROP_COLOR_DEPTH,
 };
@@ -160,6 +165,7 @@ static void spice_session_init(SpiceSession *session)
     SPICE_DEBUG("New session (compiled from package " PACKAGE_STRING ")");
     s = session->priv = SPICE_SESSION_GET_PRIVATE(session);
     memset(s, 0, sizeof(*s));
+    s->usbredir = TRUE;
 
     ring_init(&s->channels);
     cache_init(&s->images, "image");
@@ -393,6 +399,9 @@ static void spice_session_get_property(GObject    *gobject,
     case PROP_SMARTCARD_DB:
         g_value_set_string(value, s->smartcard_db);
         break;
+    case PROP_USBREDIR:
+        g_value_set_boolean(value, s->usbredir);
+        break;
     case PROP_DISABLE_EFFECTS:
         g_value_set_boxed(value, s->disable_effects);
         break;
@@ -479,6 +488,9 @@ static void spice_session_set_property(GObject      *gobject,
         g_free(s->smartcard_db);
         s->smartcard_db = g_value_dup_string(value);
         break;
+    case PROP_USBREDIR:
+        s->usbredir = g_value_get_boolean(value);
+        break;
     case PROP_DISABLE_EFFECTS:
         g_strfreev(s->disable_effects);
         s->disable_effects = g_value_dup_boxed(value);
@@ -788,6 +800,23 @@ static void spice_session_class_init(SpiceSessionClass *klass)
                               G_PARAM_STATIC_STRINGS));
 
     /**
+     * SpiceSession:enable-usbredir:
+     *
+     * If set to TRUE, the usbredir channel will be enabled and USB devices
+     * can be redirected to the guest
+     *
+     * Since: 0.7
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_USBREDIR,
+         g_param_spec_boolean("enable-usbredir",
+                          "Enable USB device redirection",
+                          "Forward USB devices to the SPICE server",
+                          TRUE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
      * SpiceSession::channel-new:
      * @session: the session that emitted the signal
      * @channel: the new #SpiceChannel
commit 8184e23a9e352c102574a2245b560a29628e4e1c
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Thu Aug 11 16:58:58 2011 +0200

    Add a private copy of gusb
    
    While working on usb redirection support for spice-gtk I needed some
    code to integrate libusb into glib's mainloop amongst other things. I ended
    up borrowing code from colord for this. Richard (the colord author) and I
    quickly agreed that doing generic glib bindings for libusb is a good idea,
    akin to the gudev bindings for libudev we've called our WIP on this gusb:
    https://gitorious.org/gusb
    
    Since this very much is a WIP, the API is nowere near stable, so for now
    we bundle a copy of this code with spice-gtk. When gusb has an official
    release out the door with a stable API we should switch to that.
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/configure.ac b/configure.ac
index e60ed78..9fa2b20 100644
--- a/configure.ac
+++ b/configure.ac
@@ -310,6 +310,21 @@ else
   AM_CONDITIONAL(WITH_SMARTCARD, true)
 fi
 
+AC_ARG_ENABLE([usbredir],
+  AS_HELP_STRING([--enable-usbredir=@<:@yes/no@:>@],
+                 [Enable usbredir support @<:@default=yes@:>@]),
+  [],
+  [enable_usbredir="yes"])
+
+if test "x$enable_usbredir" = "xno"; then
+  AM_CONDITIONAL(WITH_USBREDIR, false)
+else
+  PKG_CHECK_MODULES(GUDEV, gudev-1.0)
+  PKG_CHECK_MODULES(LIBUSB, libusb-1.0 >= 1.0.9)
+  AC_DEFINE(USE_USBREDIR, [1], [Define if supporting usbredir proxying])
+  AM_CONDITIONAL(WITH_USBREDIR, true)
+fi
+
 AC_ARG_WITH([coroutine],
   AS_HELP_STRING([--with-coroutine=@<:@ucontext/gthread/winfiber/auto@:>@],
                  [use ucontext or GThread for coroutines @<:@default=auto@:>@]),
@@ -544,6 +559,7 @@ AC_MSG_NOTICE([
         Target:                   ${red_target}
         SASL support:             ${enable_sasl}
         Smartcard support:        ${enable_smartcard}
+        USB redirection support:  ${enable_usbredir}
         Gtk:                      $GTK_API_VERSION
 
         Now type 'make' to build $PACKAGE
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index cbcaa79..1b841e2 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -15,6 +15,7 @@ EXTRA_DIST =					\
 	coroutine_winfibers.c			\
 	continuation.h continuation.c		\
 	map-file				\
+	gusb/README.txt				\
 	$(NULL)
 
 bin_PROGRAMS = spicy snappy spicy-stats
@@ -71,6 +72,8 @@ SPICE_COMMON_CPPFLAGS = \
 	$(SASL_CFLAGS)			\
 	$(GST_CFLAGS)			\
 	$(SMARTCARD_CFLAGS)		\
+	$(GUDEV_CFLAGS)			\
+	$(LIBUSB_CFLAGS)		\
 	$(NULL)
 
 AM_CPPFLAGS = \
@@ -149,8 +152,27 @@ libspice_client_glib_2_0_la_LIBADD =	\
 	$(GST_LIBS)			\
 	$(SASL_LIBS)			\
 	$(SMARTCARD_LIBS)		\
+	$(GUDEV_LIBS)			\
+	$(LIBUSB_LIBS)			\
 	$(NULL)
 
+if WITH_USBREDIR
+GUSB_SRCS =				\
+	gusb/gusb-context.c		\
+	gusb/gusb-context.h		\
+	gusb/gusb-context-private.h	\
+	gusb/gusb-device.c		\
+	gusb/gusb-device.h		\
+	gusb/gusb-device-private.h	\
+	gusb/gusb-device-list.c		\
+	gusb/gusb-device-list.h		\
+	gusb/gusb-marshal.h		\
+	gusb/gusb-source.c		\
+	gusb/gusb-source.h
+else
+GUSB_SRCS =
+endif
+
 libspice_client_glib_2_0_la_SOURCES =	\
 	spice-audio.c			\
 	spice-common.h			\
@@ -189,6 +211,7 @@ libspice_client_glib_2_0_la_SOURCES =	\
 	smartcard-manager.c		\
 	smartcard-manager.h		\
 	smartcard-manager-priv.h	\
+	$(GUSB_SRCS)			\
 	\
 	decode.h			\
 	decode-glz.c			\
diff --git a/gtk/gusb/README.txt b/gtk/gusb/README.txt
new file mode 100644
index 0000000..8d76ed7
--- /dev/null
+++ b/gtk/gusb/README.txt
@@ -0,0 +1,5 @@
+HDG: this is a private copy of gusb, from:
+https://gitorious.org/gusb
+
+This is a temporary solution until gusb upstream has a stable release out
+the door.
diff --git a/gtk/gusb/gusb-context-private.h b/gtk/gusb/gusb-context-private.h
new file mode 100644
index 0000000..4069a4d
--- /dev/null
+++ b/gtk/gusb/gusb-context-private.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Richard Hughes <richard at hughsie.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef __GUSB_CONTEXT_PRIVATE_H__
+#define __GUSB_CONTEXT_PRIVATE_H__
+
+#include <libusb-1.0/libusb.h>
+
+#include <gusb/gusb-context.h>
+
+G_BEGIN_DECLS
+
+libusb_context	*_g_usb_context_get_context	(GUsbContext	*context);
+
+G_END_DECLS
+
+#endif /* __GUSB_CONTEXT_PRIVATE_H__ */
diff --git a/gtk/gusb/gusb-context.c b/gtk/gusb/gusb-context.c
new file mode 100644
index 0000000..339b821
--- /dev/null
+++ b/gtk/gusb/gusb-context.c
@@ -0,0 +1,292 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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/>.
+ */
+
+/**
+ * SECTION:gusb-context
+ * @short_description: Per-thread instance integration for libusb
+ *
+ * This object is used to get a context that is thread safe.
+ */
+
+#include "config.h"
+
+#include <libusb-1.0/libusb.h>
+
+#include "gusb-context.h"
+#include "gusb-context-private.h"
+
+/* libusb_strerror is awaiting merging upstream */
+#define libusb_strerror(error) "unknown"
+
+static void g_usb_context_finalize (GObject *object);
+
+#define G_USB_CONTEXT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), G_USB_TYPE_CONTEXT, GUsbContextPrivate))
+
+enum {
+	PROP_0,
+	PROP_LIBUSB_CONTEXT,
+	PROP_DEBUG_LEVEL,
+};
+
+/**
+ * GUsbContextPrivate:
+ *
+ * Private #GUsbContext data
+ **/
+struct _GUsbContextPrivate
+{
+	libusb_context		*context;
+	int			 debug_level;
+};
+
+G_DEFINE_TYPE (GUsbContext, g_usb_context, G_TYPE_OBJECT)
+
+/**
+ * usb_context_get_property:
+ **/
+static void
+g_usb_context_get_property (GObject		*object,
+			    guint		 prop_id,
+			    GValue		*value,
+			    GParamSpec		*pspec)
+{
+	GUsbContext *context = G_USB_CONTEXT (object);
+	GUsbContextPrivate *priv = context->priv;
+
+	switch (prop_id) {
+	case PROP_LIBUSB_CONTEXT:
+		g_value_set_pointer (value, priv->context);
+		break;
+	case PROP_DEBUG_LEVEL:
+		g_value_set_int (value, priv->debug_level);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * usb_context_set_property:
+ **/
+static void
+g_usb_context_set_property (GObject		*object,
+			   guint		 prop_id,
+			   const GValue		*value,
+			   GParamSpec		*pspec)
+{
+	GUsbContext *context = G_USB_CONTEXT (object);
+	GUsbContextPrivate *priv = context->priv;
+
+	switch (prop_id) {
+	case PROP_LIBUSB_CONTEXT:
+		priv->context = g_value_get_pointer (value);
+		break;
+	case PROP_DEBUG_LEVEL:
+		priv->debug_level = g_value_get_int (value);
+		libusb_set_debug (priv->context, priv->debug_level);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static GObject *
+g_usb_context_constructor (GType			 gtype,
+			   guint			 n_properties,
+			   GObjectConstructParam	*properties)
+{
+	GObject *obj;
+	GUsbContext *context;
+	GUsbContextPrivate *priv;
+
+	{
+		/* Always chain up to the parent constructor */
+		GObjectClass *parent_class;
+		parent_class = G_OBJECT_CLASS (g_usb_context_parent_class);
+		obj = parent_class->constructor (gtype, n_properties,
+						 properties);
+	}
+
+	context = G_USB_CONTEXT (obj);
+	priv = context->priv;
+
+	/*
+	 * Yes you're reading this right the sole reason for this constructor
+	 * is to check the context has been set (for now).
+	 */
+	if (!priv->context)
+		g_error("constructed without a context");
+
+	return obj;
+}
+
+/**
+ * usb_context_class_init:
+ **/
+static void
+g_usb_context_class_init (GUsbContextClass *klass)
+{
+	GParamSpec *pspec;
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->constructor	= g_usb_context_constructor;
+	object_class->finalize		= g_usb_context_finalize;
+	object_class->get_property	= g_usb_context_get_property;
+	object_class->set_property	= g_usb_context_set_property;
+
+	/**
+	 * GUsbContext:libusb_context:
+	 */
+	pspec = g_param_spec_pointer ("libusb_context", NULL, NULL,
+				      G_PARAM_CONSTRUCT_ONLY|
+				      G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_LIBUSB_CONTEXT,
+					 pspec);
+
+	/**
+	 * GUsbContext:debug_level:
+	 */
+	pspec = g_param_spec_int ("debug_level", NULL, NULL,
+				  0, 3, 0,
+				  G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_DEBUG_LEVEL,
+					 pspec);
+
+	g_type_class_add_private (klass, sizeof (GUsbContextPrivate));
+}
+
+/**
+ * g_usb_context_init:
+ **/
+static void
+g_usb_context_init (GUsbContext *context)
+{
+	context->priv = G_USB_CONTEXT_GET_PRIVATE (context);
+}
+
+/**
+ * g_usb_context_finalize:
+ **/
+static void
+g_usb_context_finalize (GObject *object)
+{
+	GUsbContext *context = G_USB_CONTEXT (object);
+	GUsbContextPrivate *priv = context->priv;
+
+	libusb_exit (priv->context);
+
+	G_OBJECT_CLASS (g_usb_context_parent_class)->finalize (object);
+}
+
+/**
+ * g_usb_context_error_quark:
+ *
+ * Return value: Our personal error quark.
+ *
+ * Since: 0.0.1
+ **/
+GQuark
+g_usb_context_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("g_usb_context_error");
+	return quark;
+}
+
+/**
+ * _g_usb_context_get_context:
+ * @context: a #GUsbContext
+ *
+ * Gets the internal libusb_context.
+ *
+ * Return value: (transfer none): the libusb_context
+ *
+ * Since: 0.0.1
+ **/
+libusb_context *
+_g_usb_context_get_context (GUsbContext *context)
+{
+	return context->priv->context;
+}
+
+/**
+ * g_usb_context_set_debug:
+ * @context: a #GUsbContext
+ * @flags: a GLogLevelFlags such as %G_LOG_LEVEL_ERROR | %G_LOG_LEVEL_INFO, or 0
+ *
+ * Sets the debug flags which control what is logged to the console.
+ *
+ * Using %G_LOG_LEVEL_INFO will output to standard out, and everything
+ * else logs to standard error.
+ *
+ * Since: 0.0.1
+ **/
+void
+g_usb_context_set_debug (GUsbContext *context, GLogLevelFlags flags)
+{
+	GUsbContextPrivate *priv = context->priv;
+
+	if (flags & (G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_INFO))
+		priv->debug_level = 3;
+	else if (flags & (G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING))
+		priv->debug_level = 2;
+	else if (flags & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR))
+		priv->debug_level = 1;
+	else
+		priv->debug_level = 0;
+
+	libusb_set_debug (priv->context, priv->debug_level);
+}
+
+/**
+ * g_usb_context_new:
+ * @error: a #GError, or %NULL
+ *
+ * Creates a new context for accessing USB devices.
+ *
+ * Return value: a new %GUsbContext object or %NULL on error.
+ *
+ * Since: 0.0.1
+ **/
+GUsbContext *
+g_usb_context_new (GError **error)
+{
+	gint rc;
+	GObject *obj;
+	libusb_context *context;
+
+	rc = libusb_init (&context);
+	if (rc < 0) {
+		g_set_error (error,
+			     G_USB_CONTEXT_ERROR,
+			     G_USB_CONTEXT_ERROR_INTERNAL,
+			     "failed to init libusb: %s [%i]",
+			     libusb_strerror (rc), rc);
+		return NULL;
+	}
+
+	obj = g_object_new (G_USB_TYPE_CONTEXT, "libusb_context", context,
+			    NULL);
+	return G_USB_CONTEXT (obj);
+}
diff --git a/gtk/gusb/gusb-context.h b/gtk/gusb/gusb-context.h
new file mode 100644
index 0000000..ca9d28c
--- /dev/null
+++ b/gtk/gusb/gusb-context.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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 __GUSB_CONTEXT_H__
+#define __GUSB_CONTEXT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define G_USB_TYPE_CONTEXT		(g_usb_context_get_type ())
+#define G_USB_CONTEXT(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), G_USB_TYPE_CONTEXT, GUsbContext))
+#define G_USB_IS_CONTEXT(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), G_USB_TYPE_CONTEXT))
+#define G_USB_CONTEXT_ERROR		(g_usb_context_error_quark ())
+
+typedef struct _GUsbContextPrivate	GUsbContextPrivate;
+typedef struct _GUsbContext		GUsbContext;
+typedef struct _GUsbContextClass	GUsbContextClass;
+
+struct _GUsbContext
+{
+	 GObject			 parent;
+	 GUsbContextPrivate		*priv;
+};
+
+struct _GUsbContextClass
+{
+	GObjectClass			 parent_class;
+};
+
+typedef enum {
+	G_USB_CONTEXT_ERROR_INTERNAL
+} GUsbContextError;
+
+GType		 g_usb_context_get_type		(void);
+GQuark		 g_usb_context_error_quark	(void);
+
+GUsbContext	*g_usb_context_new		(GError		**error);
+
+void		 g_usb_context_set_debug	(GUsbContext	*context,
+						 GLogLevelFlags	 flags);
+
+G_END_DECLS
+
+#endif /* __GUSB_CONTEXT_H__ */
diff --git a/gtk/gusb/gusb-device-list.c b/gtk/gusb/gusb-device-list.c
new file mode 100644
index 0000000..dbe73f8
--- /dev/null
+++ b/gtk/gusb/gusb-device-list.c
@@ -0,0 +1,358 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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 <string.h>
+#include <stdlib.h>
+#include <gudev/gudev.h>
+#include <libusb-1.0/libusb.h>
+
+#include "gusb-marshal.h"
+#include "gusb-context.h"
+#include "gusb-context-private.h"
+#include "gusb-device.h"
+#include "gusb-device-private.h"
+
+#include "gusb-device-list.h"
+
+#define G_USB_DEVICE_LIST_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), G_USB_TYPE_DEVICE_LIST, GUsbDeviceListPrivate))
+
+enum {
+	PROP_0,
+	PROP_CONTEXT,
+};
+
+enum
+{
+	DEVICE_ADDED_SIGNAL,
+	DEVICE_REMOVED_SIGNAL,
+	LAST_SIGNAL,
+};
+
+struct _GUsbDeviceListPrivate {
+	GUsbContext	 *context;
+	GUdevClient	 *udev;
+	GPtrArray	 *devices;
+	libusb_device	**coldplug_list;
+};
+
+static void g_usb_device_list_uevent_cb (GUdevClient	*client,
+					const gchar	*action,
+					GUdevDevice	*udevice,
+					gpointer	 user_data);
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (GUsbDeviceList, g_usb_device_list, G_TYPE_OBJECT);
+
+static void
+g_usb_device_list_finalize (GObject *object)
+{
+	GUsbDeviceList *list = G_USB_DEVICE_LIST (object);
+	GUsbDeviceListPrivate *priv = list->priv;
+
+	g_object_unref (priv->udev);
+	g_ptr_array_unref (priv->devices);
+}
+
+/**
+ * g_usb_device_list_get_property:
+ **/
+static void
+g_usb_device_list_get_property (GObject		*object,
+				guint		 prop_id,
+				GValue		*value,
+				GParamSpec	*pspec)
+{
+	GUsbDeviceList *list = G_USB_DEVICE_LIST (object);
+	GUsbDeviceListPrivate *priv = list->priv;
+
+	switch (prop_id) {
+	case PROP_CONTEXT:
+		g_value_set_object (value, priv->context);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * usb_device_list_set_property:
+ **/
+static void
+g_usb_device_list_set_property (GObject		*object,
+				guint		 prop_id,
+				const GValue	*value,
+				GParamSpec	*pspec)
+{
+	GUsbDeviceList *list = G_USB_DEVICE_LIST (object);
+	GUsbDeviceListPrivate *priv = list->priv;
+
+	switch (prop_id) {
+	case PROP_CONTEXT:
+		priv->context = g_value_get_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static GObject *
+g_usb_device_list_constructor (GType			 gtype,
+			       guint			 n_properties,
+			       GObjectConstructParam	*properties)
+{
+	GObject *obj;
+	GUsbDeviceList *list;
+	GUsbDeviceListPrivate *priv;
+	const gchar *const subsystems[] = {"usb", NULL};
+
+	{
+		/* Always chain up to the parent constructor */
+		GObjectClass *parent_class;
+		parent_class = G_OBJECT_CLASS (g_usb_device_list_parent_class);
+		obj = parent_class->constructor (gtype, n_properties,
+						 properties);
+	}
+
+	list = G_USB_DEVICE_LIST (obj);
+	priv = list->priv;
+
+	if (!priv->context)
+		g_error("constructed without a context");
+
+	priv->udev = g_udev_client_new (subsystems);
+	g_signal_connect (G_OBJECT (priv->udev), "uevent",
+			  G_CALLBACK (g_usb_device_list_uevent_cb), list);
+
+	priv->devices = g_ptr_array_new_with_free_func ((GDestroyNotify)
+							g_object_unref);
+
+	priv->coldplug_list = NULL;
+
+	return obj;
+}
+
+/**
+ * g_usb_device_list_class_init:
+ **/
+static void
+g_usb_device_list_class_init (GUsbDeviceListClass *klass)
+{
+	GParamSpec *pspec;
+	GObjectClass *object_class = (GObjectClass *) klass;
+
+	object_class->constructor	= g_usb_device_list_constructor;
+	object_class->finalize		= g_usb_device_list_finalize;
+	object_class->get_property	= g_usb_device_list_get_property;
+	object_class->set_property	= g_usb_device_list_set_property;
+
+	/**
+	 * GUsbDeviceList:context:
+	 */
+	pspec = g_param_spec_object ("context", NULL, NULL,
+				     G_USB_TYPE_CONTEXT,
+				     G_PARAM_CONSTRUCT_ONLY|
+				     G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_CONTEXT, pspec);
+
+	signals[DEVICE_ADDED_SIGNAL] = g_signal_new ("device_added",
+			G_TYPE_FROM_CLASS (klass),
+			G_SIGNAL_RUN_LAST,
+			G_STRUCT_OFFSET (GUsbDeviceListClass, device_added),
+			NULL,
+			NULL,
+			g_cclosure_user_marshal_VOID__OBJECT_OBJECT,
+			G_TYPE_NONE,
+			2,
+			G_TYPE_OBJECT,
+			G_TYPE_OBJECT);
+
+	signals[DEVICE_REMOVED_SIGNAL] = g_signal_new ("device_removed",
+			G_TYPE_FROM_CLASS (klass),
+			G_SIGNAL_RUN_LAST,
+			G_STRUCT_OFFSET (GUsbDeviceListClass, device_removed),
+			NULL,
+			NULL,
+			g_cclosure_user_marshal_VOID__OBJECT_OBJECT,
+			G_TYPE_NONE,
+			2,
+			G_TYPE_OBJECT,
+			G_TYPE_OBJECT);
+
+	g_type_class_add_private (klass, sizeof (GUsbDeviceListPrivate));
+}
+
+static void
+g_usb_device_list_init (GUsbDeviceList *list)
+{
+	list->priv = G_USB_DEVICE_LIST_GET_PRIVATE (list);
+}
+
+static gboolean
+g_usb_device_list_get_bus_n_address (GUdevDevice	*udev,
+				     gint		*bus,
+				     gint		*address)
+{
+	const gchar *bus_str, *address_str;
+
+	*bus = *address = 0;
+
+	bus_str = g_udev_device_get_property (udev, "BUSNUM");
+	address_str = g_udev_device_get_property (udev, "DEVNUM");
+	if (bus_str)
+		*bus = atoi(bus_str);
+	if (address_str)
+		*address = atoi(address_str);
+
+	return *bus && *address;
+}
+
+static void
+g_usb_device_list_add_dev (GUsbDeviceList *list, GUdevDevice *udev)
+{
+	GUsbDeviceListPrivate *priv = list->priv;
+	GUsbDevice *device = NULL;
+	libusb_device **dev_list = NULL;
+	const gchar *devtype, *devclass;
+	gint i, bus, address;
+	libusb_context *ctx = _g_usb_context_get_context (priv->context);
+
+	devtype = g_udev_device_get_property (udev, "DEVTYPE");
+	/* Check if this is a usb device (and not an interface) */
+	if (!devtype || strcmp(devtype, "usb_device"))
+		return;
+
+	/* Skip hubs */
+	devclass = g_udev_device_get_sysfs_attr(udev, "bDeviceClass");
+	if (!devclass || !strcmp(devclass, "09"))
+		return;
+
+	if (!g_usb_device_list_get_bus_n_address (udev, &bus, &address)) {
+		g_warning ("usb-device without bus number or device address");
+		return;
+	}
+
+	if (priv->coldplug_list)
+		dev_list = priv->coldplug_list;
+	else
+		libusb_get_device_list(ctx, &dev_list);
+
+	for (i = 0; dev_list && dev_list[i]; i++) {
+		if (libusb_get_bus_number (dev_list[i]) == bus &&
+		    libusb_get_device_address (dev_list[i]) == address) {
+			device = _g_usb_device_new (dev_list[i]);
+			break;
+		}
+	}
+
+	if (!priv->coldplug_list)
+		libusb_free_device_list (dev_list, 1);
+
+	if (!device) {
+		g_warning ("Could not find usb dev at busnum %d devaddr %d",
+			   bus, address);
+		return;
+	}
+
+	g_ptr_array_add (priv->devices, device);
+	g_signal_emit (list, signals[DEVICE_ADDED_SIGNAL], 0, device, udev);
+}
+
+static void
+g_usb_device_list_remove_dev (GUsbDeviceList *list, GUdevDevice *udev)
+{
+	GUsbDeviceListPrivate *priv = list->priv;
+	GUsbDevice *device;
+	gint bus, address;
+
+	if (!g_usb_device_list_get_bus_n_address (udev, &bus, &address))
+		return;
+
+	device = g_usb_device_list_get_dev_by_bus_n_address (list, bus,
+							     address);
+	if (!device)
+		return;
+
+	g_signal_emit (list, signals[DEVICE_REMOVED_SIGNAL], 0, device, udev);
+	g_ptr_array_remove (priv->devices, device);
+}
+
+static void
+g_usb_device_list_uevent_cb (GUdevClient		*client,
+			     const gchar		*action,
+			     GUdevDevice		*udevice,
+			     gpointer			 user_data)
+{
+	GUsbDeviceList *list = G_USB_DEVICE_LIST (user_data);
+
+	if (g_str_equal (action, "add"))
+		g_usb_device_list_add_dev (list, udevice);
+	else if (g_str_equal (action, "remove"))
+		g_usb_device_list_remove_dev (list, udevice);
+}
+
+void
+g_usb_device_list_coldplug (GUsbDeviceList *list)
+{
+	GUsbDeviceListPrivate *priv = list->priv;
+	GList *devices, *elem;
+	libusb_context *ctx = _g_usb_context_get_context (priv->context);
+
+	libusb_get_device_list(ctx, &priv->coldplug_list);
+	devices = g_udev_client_query_by_subsystem (priv->udev, "usb");
+	for (elem = g_list_first (devices); elem; elem = g_list_next (elem)) {
+		g_usb_device_list_add_dev (list, elem->data);
+		g_object_unref (elem->data);
+	}
+	g_list_free (devices);
+	libusb_free_device_list (priv->coldplug_list, 1);
+	priv->coldplug_list = NULL;
+}
+
+GUsbDevice *
+g_usb_device_list_get_dev_by_bus_n_address (GUsbDeviceList	*list,
+					    guint8		 bus,
+					    guint8		 address)
+{
+	GUsbDeviceListPrivate *priv = list->priv;
+	GUsbDevice *device = NULL;
+	guint i;
+
+	for (i = 0; i < priv->devices->len; i++) {
+		GUsbDevice *curr = g_ptr_array_index (priv->devices, i);
+		if (g_usb_device_get_bus (curr) == bus &&
+		    g_usb_device_get_address (curr) == address) {
+			device = curr;
+			break;
+                }
+	}
+
+	return device;
+}
+
+GUsbDeviceList *
+g_usb_device_list_new (GUsbContext *context)
+{
+	GObject *obj;
+	obj = g_object_new (G_USB_TYPE_DEVICE_LIST, "context", context, NULL);
+	return G_USB_DEVICE_LIST (obj);
+}
diff --git a/gtk/gusb/gusb-device-list.h b/gtk/gusb/gusb-device-list.h
new file mode 100644
index 0000000..430984f
--- /dev/null
+++ b/gtk/gusb/gusb-device-list.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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 __GUSB_DEVICE_LIST_H__
+#define __GUSB_DEVICE_LIST_H__
+
+#include <glib-object.h>
+#include <gudev/gudev.h>
+
+#include <gusb/gusb-context.h>
+#include <gusb/gusb-device.h>
+
+G_BEGIN_DECLS
+
+#define G_USB_TYPE_DEVICE_LIST		(g_usb_device_list_get_type ())
+#define G_USB_DEVICE_LIST(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), G_USB_TYPE_DEVICE_LIST, GUsbDeviceList))
+#define G_USB_IS_DEVICE_LIST(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), G_USB_TYPE_DEVICE_LIST))
+
+typedef struct _GUsbDeviceListPrivate	GUsbDeviceListPrivate;
+typedef struct _GUsbDeviceList		GUsbDeviceList;
+typedef struct _GUsbDeviceListClass	GUsbDeviceListClass;
+
+struct _GUsbDeviceList
+{
+	 GObject			 parent;
+	 GUsbDeviceListPrivate		*priv;
+};
+
+struct _GUsbDeviceListClass
+{
+	GObjectClass			 parent_class;
+	/* Signals */
+	void (*device_added)		(GUsbDeviceList		*list,
+					 GUsbDevice		*device,
+					 GUdevDevice		*udev);
+	void (*device_removed)		(GUsbDeviceList		*list,
+					 GUsbDevice		*device,
+					 GUdevDevice		*udev);
+};
+
+GType			 g_usb_device_list_get_type (void);
+
+GUsbDeviceList		*g_usb_device_list_new (GUsbContext *context);
+
+void			 g_usb_device_list_coldplug (GUsbDeviceList *list);
+
+GUsbDevice		*g_usb_device_list_get_dev_by_bus_n_address (
+					GUsbDeviceList	*list,
+					guint8		 bus,
+					guint8		 address);
+
+
+G_END_DECLS
+
+#endif /* __GUSB_DEVICE_LIST_H__ */
diff --git a/gtk/gusb/gusb-device-private.h b/gtk/gusb/gusb-device-private.h
new file mode 100644
index 0000000..c93b51a
--- /dev/null
+++ b/gtk/gusb/gusb-device-private.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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 __GUSB_DEVICE_PRIVATE_H__
+#define __GUSB_DEVICE_PRIVATE_H__
+
+#include <gusb/gusb-device.h>
+
+G_BEGIN_DECLS
+
+GUsbDevice	*_g_usb_device_new		(libusb_device	*device);
+
+libusb_device	*_g_usb_device_get_device	(GUsbDevice	*device);
+
+G_END_DECLS
+
+#endif /* __GUSB_DEVICE_PRIVATE_H__ */
diff --git a/gtk/gusb/gusb-device.c b/gtk/gusb/gusb-device.c
new file mode 100644
index 0000000..26a25f3
--- /dev/null
+++ b/gtk/gusb/gusb-device.c
@@ -0,0 +1,232 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2011 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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/>.
+ */
+
+/**
+ * SECTION:usb-device
+ * @short_description: GLib device integration for libusb
+ *
+ * This object is a thin glib wrapper around a libusb_device
+ */
+
+#include "config.h"
+
+#include <libusb-1.0/libusb.h>
+
+#include "gusb-device.h"
+#include "gusb-device-private.h"
+
+static void     g_usb_device_finalize	(GObject     *object);
+
+#define G_USB_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), G_USB_TYPE_DEVICE, GUsbDevicePrivate))
+
+/**
+ * GUsbDevicePrivate:
+ *
+ * Private #GUsbDevice data
+ **/
+struct _GUsbDevicePrivate
+{
+	libusb_device		*device;
+};
+
+enum {
+	PROP_0,
+	PROP_LIBUSB_DEVICE,
+};
+
+G_DEFINE_TYPE (GUsbDevice, g_usb_device, G_TYPE_OBJECT)
+
+
+/**
+ * g_usb_device_error_quark:
+ *
+ * Return value: Our personal error quark.
+ *
+ * Since: 0.0.1
+ **/
+GQuark
+g_usb_device_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("g_usb_device_error");
+	return quark;
+}
+
+/**
+ * usb_device_get_property:
+ **/
+static void
+g_usb_device_get_property (GObject		*object,
+			   guint		 prop_id,
+			   GValue		*value,
+			   GParamSpec		*pspec)
+{
+	GUsbDevice *device = G_USB_DEVICE (object);
+	GUsbDevicePrivate *priv = device->priv;
+
+	switch (prop_id) {
+	case PROP_LIBUSB_DEVICE:
+		g_value_set_pointer (value, priv->device);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * usb_device_set_property:
+ **/
+static void
+g_usb_device_set_property (GObject		*object,
+			   guint		 prop_id,
+			   const GValue		*value,
+			   GParamSpec		*pspec)
+{
+	GUsbDevice *device = G_USB_DEVICE (object);
+	GUsbDevicePrivate *priv = device->priv;
+
+	switch (prop_id) {
+	case PROP_LIBUSB_DEVICE:
+		priv->device = g_value_get_pointer (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static GObject *
+g_usb_device_constructor (GType			 gtype,
+			  guint			 n_properties,
+			  GObjectConstructParam	*properties)
+{
+	GObject *obj;
+	GUsbDevice *device;
+	GUsbDevicePrivate *priv;
+
+	{
+		/* Always chain up to the parent constructor */
+		GObjectClass *parent_class;
+		parent_class = G_OBJECT_CLASS (g_usb_device_parent_class);
+		obj = parent_class->constructor (gtype, n_properties,
+						 properties);
+	}
+
+	device = G_USB_DEVICE (obj);
+	priv = device->priv;
+
+	if (!priv->device)
+		g_error("constructed without a libusb_device");
+
+	libusb_ref_device(priv->device);
+
+	return obj;
+}
+
+/**
+ * usb_device_class_init:
+ **/
+static void
+g_usb_device_class_init (GUsbDeviceClass *klass)
+{
+	GParamSpec *pspec;
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->constructor	= g_usb_device_constructor;
+	object_class->finalize		= g_usb_device_finalize;
+	object_class->get_property	= g_usb_device_get_property;
+	object_class->set_property	= g_usb_device_set_property;
+
+	/**
+	 * GUsbDevice:libusb_device:
+	 */
+	pspec = g_param_spec_pointer ("libusb_device", NULL, NULL,
+				     G_PARAM_CONSTRUCT_ONLY|
+				     G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_LIBUSB_DEVICE,
+					 pspec);
+
+	g_type_class_add_private (klass, sizeof (GUsbDevicePrivate));
+}
+
+/**
+ * g_usb_device_init:
+ **/
+static void
+g_usb_device_init (GUsbDevice *device)
+{
+	device->priv = G_USB_DEVICE_GET_PRIVATE (device);
+}
+
+/**
+ * g_usb_device_finalize:
+ **/
+static void
+g_usb_device_finalize (GObject *object)
+{
+	GUsbDevice *device = G_USB_DEVICE (object);
+	GUsbDevicePrivate *priv = device->priv;
+
+	libusb_unref_device(priv->device);
+
+	G_OBJECT_CLASS (g_usb_device_parent_class)->finalize (object);
+}
+
+/**
+ * _g_usb_device_new:
+ *
+ * Return value: a new #GUsbDevice object.
+ **/
+GUsbDevice *
+_g_usb_device_new (libusb_device	*device)
+{
+	GObject *obj;
+	obj = g_object_new (G_USB_TYPE_DEVICE, "libusb_device", device, NULL);
+	return G_USB_DEVICE (obj);
+}
+
+/**
+ * _g_usb_device_get_device:
+ * @device: a #GUsbDevice instance
+ *
+ * Gets the low-level libusb_device
+ *
+ * Return value: The #libusb_device or %NULL. Do not unref this value.
+ **/
+libusb_device *
+_g_usb_device_get_device (GUsbDevice	*device)
+{
+	return device->priv->device;
+}
+
+guint8
+g_usb_device_get_bus (GUsbDevice	*device)
+{
+	return libusb_get_bus_number (device->priv->device);
+}
+
+guint8
+g_usb_device_get_address (GUsbDevice	*device)
+{
+	return libusb_get_device_address (device->priv->device);
+}
diff --git a/gtk/gusb/gusb-device.h b/gtk/gusb/gusb-device.h
new file mode 100644
index 0000000..a504182
--- /dev/null
+++ b/gtk/gusb/gusb-device.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2011 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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 __GUSB_DEVICE_H__
+#define __GUSB_DEVICE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define G_USB_TYPE_DEVICE		(g_usb_device_get_type ())
+#define G_USB_DEVICE(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), G_USB_TYPE_DEVICE, GUsbDevice))
+#define G_USB_IS_DEVICE(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), G_USB_TYPE_DEVICE))
+#define G_USB_DEVICE_ERROR		(g_usb_device_error_quark ())
+
+typedef struct _GUsbDevicePrivate	GUsbDevicePrivate;
+typedef struct _GUsbDevice		GUsbDevice;
+typedef struct _GUsbDeviceClass		GUsbDeviceClass;
+
+/**
+ * GUsbDeviceError:
+ *
+ * The error code.
+ **/
+typedef enum {
+	G_USB_DEVICE_ERROR_INTERNAL
+} GUsbDeviceError;
+
+struct _GUsbDevice
+{
+	 GObject			 parent;
+	 GUsbDevicePrivate		*priv;
+};
+
+struct _GUsbDeviceClass
+{
+	GObjectClass			 parent_class;
+};
+
+GType			 g_usb_device_get_type		(void);
+GQuark			 g_usb_device_error_quark	(void);
+
+guint8			 g_usb_device_get_bus		(GUsbDevice      *device);
+guint8			 g_usb_device_get_address	(GUsbDevice      *device);
+
+#if 0 /* TODO */
+GUsbDeviceHandle 	*g_usb_device_get_device_handle	(GUsbDevice	 *device,
+							 GError		**err);
+#endif
+
+G_END_DECLS
+
+#endif /* __GUSB_DEVICE_H__ */
diff --git a/gtk/gusb/gusb-marshal.h b/gtk/gusb/gusb-marshal.h
new file mode 100644
index 0000000..b00fd65
--- /dev/null
+++ b/gtk/gusb/gusb-marshal.h
@@ -0,0 +1,7 @@
+/*
+ * HDG: hack
+ * Important: Don't forget to remove VOID:OBJECT,OBJECT from spice-marshal.txt
+ * when we remove our bundled gusb.
+ */
+
+#include "spice-marshal.h"
diff --git a/gtk/gusb/gusb-source.c b/gtk/gusb/gusb-source.c
new file mode 100644
index 0000000..0324e68
--- /dev/null
+++ b/gtk/gusb/gusb-source.c
@@ -0,0 +1,309 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2011 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/**
+ * SECTION:gusb-source
+ * @short_description: GSource integration for libusb
+ *
+ * This object can be used to integrate libusb into the GLib main loop.
+ */
+
+#include "config.h"
+
+#include <libusb-1.0/libusb.h>
+#include <poll.h>
+#include <stdlib.h>
+
+#include "gusb-context.h"
+#include "gusb-context-private.h"
+#include "gusb-source.h"
+
+/**
+ * g_usb_source_error_quark:
+ *
+ * Return value: Our personal error quark.
+ *
+ * Since: 0.0.1
+ **/
+GQuark
+g_usb_source_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("g_usb_source_error");
+	return quark;
+}
+
+/* libusb_strerror is awaiting merging upstream */
+#define libusb_strerror(error) "unknown"
+
+struct _GUsbSource {
+	GSource		 source;
+	GSList		*pollfds;
+	GUsbContext	*usbcontext;
+	libusb_context	*ctx;
+};
+
+static void
+g_usb_source_pollfd_add (GUsbSource *source, int fd, short events)
+{
+	GPollFD *pollfd = g_slice_new(GPollFD);
+	pollfd->fd = fd;
+	pollfd->events = 0;
+	pollfd->revents = 0;
+	if (events & POLLIN)
+		pollfd->events |= G_IO_IN;
+	if (events & POLLOUT)
+		pollfd->events |= G_IO_OUT;
+
+	source->pollfds = g_slist_prepend(source->pollfds, pollfd);
+	g_source_add_poll((GSource *)source, pollfd);
+}
+
+static void
+g_usb_source_pollfd_added_cb (int fd, short events, void *user_data)
+{
+	GUsbSource *source = user_data;
+	g_usb_source_pollfd_add (source, fd, events);
+}
+
+static void
+g_usb_source_pollfd_remove (GUsbSource *source, int fd)
+{
+	GPollFD *pollfd;
+	GSList *elem = source->pollfds;
+
+	/* nothing to see here, move along */
+	if (elem == NULL) {
+		g_warning("cannot remove from list as list is empty?");
+		return;
+	}
+
+	/* find the pollfd in the list */
+	do {
+		pollfd = elem->data;
+		if (pollfd->fd != fd)
+			continue;
+
+		g_source_remove_poll((GSource *)source, pollfd);
+		g_slice_free(GPollFD, pollfd);
+		source->pollfds = g_slist_delete_link(source->pollfds, elem);
+		return;
+	} while ((elem = g_slist_next(elem)));
+	g_warning ("couldn't find fd %d in list", fd);
+}
+
+static void
+g_usb_source_pollfd_removed_cb(int fd, void *user_data)
+{
+	GUsbSource *source = user_data;
+
+	g_usb_source_pollfd_remove (source, fd);
+}
+
+static void
+g_usb_source_pollfd_remove_all (GUsbSource *source)
+{
+	GPollFD *pollfd;
+	GSList *curr, *next;
+
+	next = source->pollfds;
+	while (next) {
+		curr = next;
+		next = g_slist_next(curr);
+		pollfd = curr->data;
+		g_source_remove_poll((GSource *)source, pollfd);
+		g_slice_free (GPollFD, pollfd);
+		source->pollfds = g_slist_delete_link(source->pollfds, curr);
+	}
+}
+
+/**
+ * g_usb_source_prepare:
+ *
+ * Called before all the file descriptors are polled.
+ * As we are a file descriptor source, the prepare function returns FALSE.
+ * It sets the returned timeout to -1 to indicate that it doesn't mind
+ * how long the poll() call blocks.
+ *
+ * No, we're not going to support FreeBSD.
+ **/
+static gboolean
+g_usb_source_prepare (GSource *source, gint *timeout)
+{
+	*timeout = -1;
+	return FALSE;
+}
+
+/**
+ * g_usb_source_check:
+ *
+ * In the check function, it tests the results of the poll() call to see
+ * if the required condition has been met, and returns TRUE if so.
+ **/
+static gboolean
+g_usb_source_check (GSource *source)
+{
+	GUsbSource *usb_source = (GUsbSource *)source;
+	GPollFD *pollfd;
+	GSList *elem = usb_source->pollfds;
+
+	/* no fds */
+	if (elem == NULL)
+		return FALSE;
+
+	/* check each pollfd */
+	do {
+		pollfd = elem->data;
+		if (pollfd->revents)
+			return TRUE;
+	} while ((elem = g_slist_next(elem)));
+
+	return FALSE;
+}
+
+static gboolean
+g_usb_source_dispatch (GSource *source,
+		       GSourceFunc callback,
+		       gpointer user_data)
+{
+	GUsbSource *usb_source = (GUsbSource *)source;
+	struct timeval tv = { 0, 0 };
+	gint rc;
+
+	rc = libusb_handle_events_timeout (usb_source->ctx, &tv);
+	if (rc < 0) {
+		g_warning ("failed to handle event: %s [%i]",
+			   libusb_strerror (rc), rc);
+	}
+
+	if (callback)
+		callback(user_data);
+
+	return TRUE;
+}
+
+static void
+g_usb_source_finalize (GSource *source)
+{
+	GUsbSource *usb_source = (GUsbSource *)source;
+	g_object_unref (usb_source->usbcontext);
+	g_slist_free (usb_source->pollfds);
+}
+
+static GSourceFuncs usb_source_funcs = {
+	g_usb_source_prepare,
+	g_usb_source_check,
+	g_usb_source_dispatch,
+	g_usb_source_finalize,
+	NULL, NULL
+};
+
+/**
+ * g_usb_source_new:
+ * @main_ctx: a #GMainContext, or %NULL
+ * @gusb_ctx: a #GUsbContext
+ * @error: a #GError, or %NULL
+ *
+ * Creates a source for integration into libusb1.
+ *
+ * Return value: (transfer none): the #GUsbSource, or %NULL. Use g_usb_source_destroy() to unref.
+ *
+ * Since: 0.0.1
+ **/
+GUsbSource *
+g_usb_source_new (GMainContext *main_ctx,
+		  GUsbContext *gusb_ctx,
+		  GError **error)
+{
+	guint i;
+	const struct libusb_pollfd **pollfds;
+	GUsbSource *gusb_source;
+
+	gusb_source = (GUsbSource *)g_source_new (&usb_source_funcs,
+						  sizeof(GUsbSource));
+	gusb_source->pollfds = NULL;
+	gusb_source->usbcontext = g_object_ref (gusb_ctx);
+	gusb_source->ctx = _g_usb_context_get_context (gusb_ctx);
+
+	/* watch the fd's already created */
+	pollfds = libusb_get_pollfds (gusb_source->ctx);
+	if (pollfds == NULL) {
+		g_set_error_literal (error,
+				     G_USB_SOURCE_ERROR,
+				     G_USB_SOURCE_ERROR_INTERNAL,
+				     "failed to allocate memory");
+		g_free (gusb_source);
+		gusb_source = NULL;
+		goto out;
+	}
+	for (i=0; pollfds[i] != NULL; i++)
+		g_usb_source_pollfd_add (gusb_source,
+					 pollfds[i]->fd,
+					 pollfds[i]->events);
+	free (pollfds);
+
+	/* watch for PollFD changes */
+	g_source_attach ((GSource *)gusb_source, main_ctx);
+	libusb_set_pollfd_notifiers (gusb_source->ctx,
+				     g_usb_source_pollfd_added_cb,
+				     g_usb_source_pollfd_removed_cb,
+				     gusb_source);
+out:
+	return gusb_source;
+}
+
+/**
+ * g_usb_source_destroy:
+ * @source: a #GUsbSource
+ *
+ * Destroys a #GUsbSource
+ *
+ * Since: 0.0.1
+ **/
+void
+g_usb_source_destroy (GUsbSource *source)
+{
+	libusb_set_pollfd_notifiers (source->ctx, NULL, NULL, NULL);
+	g_usb_source_pollfd_remove_all (source);
+	g_source_destroy ((GSource *)source);
+}
+
+/**
+ * g_usb_source_set_callback:
+ * @source: a #GUsbSource
+ * @func: a function to call
+ * @data: data to pass to @func
+ * @notify: a #GDestroyNotify
+ *
+ * Set a callback to be called when the source is dispatched.
+ *
+ * Since: 0.0.1
+ **/
+void
+g_usb_source_set_callback (GUsbSource *source,
+			   GSourceFunc func,
+			   gpointer data,
+			   GDestroyNotify notify)
+{
+	g_source_set_callback ((GSource *)source, func, data, notify);
+}
diff --git a/gtk/gusb/gusb-source.h b/gtk/gusb/gusb-source.h
new file mode 100644
index 0000000..40f6880
--- /dev/null
+++ b/gtk/gusb/gusb-source.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2011 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2011 Hans de Goede <hdegoede at redhat.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef __GUSB_SOURCE_H__
+#define __GUSB_SOURCE_H__
+
+#include <glib.h>
+
+#include <gusb/gusb-context.h>
+
+G_BEGIN_DECLS
+
+#define G_USB_SOURCE_ERROR			(g_usb_source_error_quark ())
+
+typedef struct _GUsbSource GUsbSource;
+
+/**
+ * GUsbSourceError:
+ *
+ * The error code.
+ **/
+typedef enum {
+	G_USB_SOURCE_ERROR_INTERNAL
+} GUsbSourceError;
+
+GQuark		 g_usb_source_error_quark	(void);
+GUsbSource	*g_usb_source_new		(GMainContext	*main_ctx,
+						 GUsbContext	*gusb_ctx,
+						 GError		**error);
+void		 g_usb_source_destroy		(GUsbSource	*source);
+
+void		 g_usb_source_set_callback	(GUsbSource	*source,
+						 GSourceFunc	 func,
+						 gpointer	 data,
+						 GDestroyNotify	 notify);
+
+G_END_DECLS
+
+#endif /* __GUSB_SOURCE_H__ */
diff --git a/gtk/spice-marshal.txt b/gtk/spice-marshal.txt
index 8e56394..b9630eb 100644
--- a/gtk/spice-marshal.txt
+++ b/gtk/spice-marshal.txt
@@ -11,3 +11,4 @@ VOID:UINT,POINTER,UINT
 VOID:UINT,UINT,POINTER,UINT
 BOOLEAN:UINT,POINTER,UINT
 BOOLEAN:UINT,UINT
+VOID:OBJECT,OBJECT
commit ba6ecf23f401c5412561e9e02b7300ae9842edfd
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Wed Jul 27 16:48:44 2011 +0200

    spice.proto: Add usbredir channel
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/spice.proto b/spice.proto
index 4c072ed..748ae95 100644
--- a/spice.proto
+++ b/spice.proto
@@ -1173,6 +1173,13 @@ channel SmartcardChannel : BaseChannel {
     } @ctype(VSCMsgReaderAdd) reader_add = 101;
 } @ifdef(USE_SMARTCARD);
 
+channel UsbredirChannel : BaseChannel {
+server:
+    Data data = 101;
+client:
+    Data data = 101;
+};
+
 protocol Spice {
     MainChannel main = 1;
     DisplayChannel display;
@@ -1182,4 +1189,5 @@ protocol Spice {
     RecordChannel record;
     TunnelChannel tunnel;
     SmartcardChannel smartcard;
+    UsbredirChannel usbredir;
 };
commit 5159a2599ced7691a168a1c96d5214564a1450ea
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Thu Aug 11 16:30:38 2011 +0200

    spice-channel: Reset SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION on disconnect
    
    Our disconnect handler clears the common_caps array so that a new
    connection starts with a clean slate. But in our constructor we set
    the SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION in common_caps as starting
    cap. Do the same on disconnect, so the behavior of a re-using a channel
    after disconnect is the same as using a fresh channel.
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index f8c79eb..a89b75c 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -2170,6 +2170,8 @@ static void channel_disconnect(SpiceChannel *channel)
     g_array_set_size(c->remote_caps, 0);
     g_array_set_size(c->common_caps, 0);
     g_array_set_size(c->caps, 0);
+    /* Restore our default capabilities in case the channel gets re-used */
+    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
 
     if (c->state == SPICE_CHANNEL_STATE_READY)
         emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_CLOSED);


More information about the Spice-commits mailing list