[Spice-devel,spice-gtk,5/7] Add an USB device manager

Submitted by Hans de Goede on Aug. 30, 2011, 7:52 p.m.

Details

Message ID 1314708748-24547-5-git-send-email-hdegoede@redhat.com
State New, archived
Headers show

Not browsing as part of any series.

Commit Message

Hans de Goede Aug. 30, 2011, 7:52 p.m.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 gtk/.gitignore            |    5 -
 gtk/Makefile.am           |    3 +
 gtk/map-file              |    9 +
 gtk/spice-client-gtk.defs |   79 ++++++
 gtk/spice-client.h        |    1 +
 gtk/spice-widget.c        |   23 ++
 gtk/usb-device-manager.c  |  576 +++++++++++++++++++++++++++++++++++++++++++++
 gtk/usb-device-manager.h  |   92 +++++++
 8 files changed, 783 insertions(+), 5 deletions(-)
 create mode 100644 gtk/usb-device-manager.c
 create mode 100644 gtk/usb-device-manager.h

Patch hide | download patch | download mbox

diff --git a/gtk/.gitignore b/gtk/.gitignore
index f070332..2ae158f 100644
--- a/gtk/.gitignore
+++ b/gtk/.gitignore
@@ -20,9 +20,6 @@ 
 /SpiceClientGLib-2.0.typelib
 /SpiceClientGtk-3.0.gir
 /SpiceClientGtk-3.0.typelib
-/SpiceClientGtk-2.0.gir
-/SpiceClientGtk-2.0.typelib
-/SpiceClientGtk.la
 /TAGS
 /_libs
 /generated_demarshallers.c
@@ -30,11 +27,9 @@ 
 /generated_marshallers.c
 /generated_marshallers1.c
 /libspice-client-glib-2.0.la
-/libspice-client-gtk-2.0.la
 /libspice-client-gtk-3.0.la
 /snappy
 /so_locations
-/spice-client-gtk-module.defs.c
 /spice-glib-enums.c
 /spice-glib-enums.h
 /spice-marshal.c
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..f0a4b1d 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,75 @@ 
 )
 
 
+
+;; From usb-device-manager.h
+
+(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")
+  )
+)
+
+(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*")
+  (parameters
+    '("GError**" "err")
+  )
+)
+
+(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..498f6b4
--- /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@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@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__ */

Comments

On Tue, Aug 30, 2011 at 3:52 PM, Hans de Goede <hdegoede@redhat.com> wrote:
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> ---
>  gtk/.gitignore            |    5 -

Please do not remove unrelated fields in .gitignore.

There is some trailing whitespace (in gtk/usb-device-manager.c)

otherwise ack

>  gtk/Makefile.am           |    3 +
>  gtk/map-file              |    9 +
>  gtk/spice-client-gtk.defs |   79 ++++++
>  gtk/spice-client.h        |    1 +
>  gtk/spice-widget.c        |   23 ++
>  gtk/usb-device-manager.c  |  576 +++++++++++++++++++++++++++++++++++++++++++++
>  gtk/usb-device-manager.h  |   92 +++++++
>  8 files changed, 783 insertions(+), 5 deletions(-)
>  create mode 100644 gtk/usb-device-manager.c
>  create mode 100644 gtk/usb-device-manager.h
>
> diff --git a/gtk/.gitignore b/gtk/.gitignore
> index f070332..2ae158f 100644
> --- a/gtk/.gitignore
> +++ b/gtk/.gitignore
> @@ -20,9 +20,6 @@
>  /SpiceClientGLib-2.0.typelib
>  /SpiceClientGtk-3.0.gir
>  /SpiceClientGtk-3.0.typelib
> -/SpiceClientGtk-2.0.gir
> -/SpiceClientGtk-2.0.typelib
> -/SpiceClientGtk.la
>  /TAGS
>  /_libs
>  /generated_demarshallers.c
> @@ -30,11 +27,9 @@
>  /generated_marshallers.c
>  /generated_marshallers1.c
>  /libspice-client-glib-2.0.la
> -/libspice-client-gtk-2.0.la
>  /libspice-client-gtk-3.0.la
>  /snappy
>  /so_locations
> -/spice-client-gtk-module.defs.c
>  /spice-glib-enums.c
>  /spice-glib-enums.h
>  /spice-marshal.c
> 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..f0a4b1d 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,75 @@
>  )
>
>
> +
> +;; From usb-device-manager.h
> +
> +(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")
> +  )
> +)
> +
> +(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*")
> +  (parameters
> +    '("GError**" "err")
> +  )
> +)
> +
> +(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..498f6b4
> --- /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@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@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__ */
> --
> 1.7.6.1
>
> _______________________________________________
> Spice-devel mailing list
> Spice-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/spice-devel
>