[Spice-devel,spice-gtk,4/6] Add an usbredir channel

Submitted by Hans de Goede on Aug. 26, 2011, 3:42 p.m.

Details

Message ID 1314348179-9006-4-git-send-email-hdegoede@redhat.com
State New, archived
Headers show

Not browsing as part of any series.

Commit Message

Hans de Goede Aug. 26, 2011, 3:42 p.m.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 configure.ac                |    1 +
 gtk/Makefile.am             |    6 +
 gtk/channel-usbredir-priv.h |   43 ++++++
 gtk/channel-usbredir.c      |  353 +++++++++++++++++++++++++++++++++++++++++++
 gtk/channel-usbredir.h      |   60 ++++++++
 gtk/map-file                |    1 +
 gtk/spice-channel.c         |   15 ++-
 gtk/spice-client-gtk.defs   |   16 ++
 gtk/spice-client.h          |    1 +
 gtk/spice-option.c          |    5 +
 gtk/spice-session.c         |   29 ++++
 11 files changed, 529 insertions(+), 1 deletions(-)
 create mode 100644 gtk/channel-usbredir-priv.h
 create mode 100644 gtk/channel-usbredir.c
 create mode 100644 gtk/channel-usbredir.h

Patch hide | download patch | download mbox

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@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..2baca5f
--- /dev/null
+++ b/gtk/channel-usbredir.c
@@ -0,0 +1,353 @@ 
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright 2010-2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede@redhat.com>
+   Richard Hughes <rhughes@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 {
+    int verbose;
+#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)
+
+enum {
+
+    SPICE_USBREDIR_LAST_SIGNAL,
+};
+
+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)
+{
+    SpiceUsbredirChannelPrivate *priv;
+
+    channel->priv = SPICE_USBREDIR_CHANNEL_GET_PRIVATE(channel);
+    priv = channel->priv;
+
+    memset(priv, 0, sizeof(SpiceUsbredirChannelPrivate));
+#ifdef USE_USBREDIR
+    if (spice_util_get_debug()) {
+        priv->verbose = usbredirparser_debug;
+    } else {
+        priv->verbose = usbredirparser_warning;
+    }
+#endif
+}
+
+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,
+};
+
+GQuark spice_usbredir_channel_error_quark(void)
+{
+    static GQuark quark = 0;
+    if (!quark)
+        quark = g_quark_from_static_string ("spice-usbredir-channel-error-quark");
+    return quark;
+}
+
+#ifdef USE_USBREDIR
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+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_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, priv->verbose,
+                                   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;
+}
+
+void spice_usbredir_channel_disconnect(SpiceUsbredirChannel *channel)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    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);
+    }
+}
+
+GUsbDevice *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel)
+{
+    return channel->priv->device;
+}
+
+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) {
+        g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
+                            SPICE_CLIENT_ERROR_FAILED, msg);
+        return;
+    }
+
+    if (level > priv->verbose) {
+        return;
+    }
+
+    switch(level) {
+        case usbredirparser_error:
+            g_critical(G_STRLOC " " "%s", msg); break;
+        case usbredirparser_warning:
+            g_warning(G_STRLOC " " "%s", msg); break;
+        case usbredirparser_info:
+            g_message(G_STRLOC " " "%s", msg); break;
+        default:
+            g_debug(G_STRLOC " " "%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@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