From: Arnon Gilboa <agilboa@xxxxxxxxxx> - Added win-usb-dev.[ch] - Added GUdevDevice and GUdevClient like classes - Added uevent signal based on WM_DEVICECHANGE --- gtk/Makefile.am | 13 ++ gtk/usb-device-manager.c | 8 + gtk/win-usb-dev.c | 510 ++++++++++++++++++++++++++++++++++++++++++++++ gtk/win-usb-dev.h | 110 ++++++++++ 4 files changed, 641 insertions(+), 0 deletions(-) create mode 100644 gtk/win-usb-dev.c create mode 100644 gtk/win-usb-dev.h diff --git a/gtk/Makefile.am b/gtk/Makefile.am index ccc9285..c3e820b 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -308,6 +308,19 @@ libspice_client_glib_2_0_la_SOURCES += coroutine_gthread.c libspice_client_glib_2_0_la_LIBADD += $(GTHREAD_LIBS) endif + +WIN_USB_FILES= \ + win-usb-dev.h \ + win-usb-dev.c \ + $(NULL) + +if OS_WIN32 +if WITH_USBREDIR +libspice_client_glib_2_0_la_SOURCES += \ + $(WIN_USB_FILES) +endif +endif + displaysrc = \ glib-compat.h \ display/edid.h \ diff --git a/gtk/usb-device-manager.c b/gtk/usb-device-manager.c index 93bcc9b..0f6ef34 100644 --- a/gtk/usb-device-manager.c +++ b/gtk/usb-device-manager.c @@ -28,7 +28,15 @@ #ifdef USE_USBREDIR #include <errno.h> #include <libusb.h> + +#if defined(USE_GUDEV) #include <gudev/gudev.h> +#elif defined(G_OS_WIN32) +#include "win-usb-dev.h" +#else +#warning "Expecting one of G_OS_WIN32 and USE_GUDEV to be defined" +#endif + #include "channel-usbredir-priv.h" #include "usbredirhost.h" #include "usbutil.h" diff --git a/gtk/win-usb-dev.c b/gtk/win-usb-dev.c new file mode 100644 index 0000000..bc21e08 --- /dev/null +++ b/gtk/win-usb-dev.c @@ -0,0 +1,510 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2012 Red Hat, Inc. + + Red Hat Authors: + Arnon Gilboa <agilboa@xxxxxxxxxx> + Uri Lublin <uril@xxxxxxxxxx> + + 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/>. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <windows.h> +#include <libusb.h> +#include "win-usb-dev.h" +#include "spice-marshal.h" +#include "spice-util.h" +#include "usbutil.h" + +#define G_UDEV_CLIENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), G_UDEV_TYPE_CLIENT, GUdevClientPrivate)) + +struct _GUdevClientPrivate { + libusb_context *ctx; + gssize udev_list_size; + GList *udev_list; + HWND hwnd; +}; + +#define G_UDEV_CLIENT_WINCLASS_NAME TEXT("G_UDEV_CLIENT") + +static void g_udev_client_initable_iface_init(GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE(GUdevClient, g_udev_client, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, g_udev_client_initable_iface_init)); + + +typedef struct _GUdevDeviceInfo GUdevDeviceInfo; + +struct _GUdevDeviceInfo { + guint16 bus; + guint16 addr; + guint16 vid; + guint16 pid; + guint16 class; + gchar sclass[4]; + gchar sbus[4]; + gchar saddr[4]; +}; + +struct _GUdevDevicePrivate +{ + /* FixMe: move above fields to this structure and access them directly */ + GUdevDeviceInfo *udevinfo; +}; + +G_DEFINE_TYPE(GUdevDevice, g_udev_device, G_TYPE_OBJECT) + + +enum +{ + UEVENT_SIGNAL, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = { 0, }; +static GUdevClient *singleton = NULL; + +static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo); +static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); +static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo); + +//uncomment to debug gudev device lists. +//#define DEBUG_GUDEV_DEVICE_LISTS + +#ifdef DEBUG_GUDEV_DEVICE_LISTS +static void g_udev_device_print_list(GList *l, const gchar *msg); +#else +static void g_udev_device_print_list(GList *l, const gchar *msg) {} +#endif +static void g_udev_device_print(GUdevDevice *udev, const gchar *msg); + +GQuark g_udev_client_error_quark(void) +{ + return g_quark_from_static_string("win-gudev-client-error-quark"); +} + +GUdevClient *g_udev_client_new(const gchar* const *subsystems) +{ + if (!singleton) { + singleton = g_initable_new(G_UDEV_TYPE_CLIENT, NULL, NULL, NULL); + return singleton; + } else { + return g_object_ref(singleton); + } +} + + +/* + * devs [in,out] an empty devs list in, full devs list out + * Returns: number-of-devices, or a negative value on error. + */ +static ssize_t +g_udev_client_list_devices(GUdevClient *self, GList **devs, + GError **err, const gchar *name) +{ + gssize rc; + libusb_device **lusb_list, **dev; + GUdevClientPrivate *priv; + GUdevDeviceInfo *udevinfo; + GUdevDevice *udevice; + ssize_t n; + + g_return_val_if_fail(G_UDEV_IS_CLIENT(self), -1); + g_return_val_if_fail(devs != NULL, -2); + + priv = self->priv; + + g_return_val_if_fail(self->priv->ctx != NULL, -3); + + rc = libusb_get_device_list(priv->ctx, &lusb_list); + if (rc < 0) { + const char *errstr = spice_usbutil_libusb_strerror(rc); + g_warning("%s: libusb_get_device_list failed", name); + g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED, + "%s: Error getting device list from libusb: %s [%i]", + name, errstr, rc); + return -4; + } + + n = rc; + + for (dev = lusb_list; *dev; dev++) { + udevinfo = g_new0(GUdevDeviceInfo, 1); + get_usb_dev_info(*dev, udevinfo); + udevice = g_udev_device_new(udevinfo); + *devs = g_list_prepend(*devs, udevice); + } + libusb_free_device_list(lusb_list, 1); + + return n; +} + +static void g_udev_client_free_device_list(GList **devs) +{ + g_return_if_fail(devs != NULL); + if (*devs) { + g_list_free_full(*devs, g_object_unref); + *devs = NULL; + } +} + + +static gboolean +g_udev_client_initable_init(GInitable *initable, GCancellable *cancellable, + GError **err) +{ + GUdevClient *self; + GUdevClientPrivate *priv; + WNDCLASS wcls; + int rc; + + g_return_val_if_fail(G_UDEV_IS_CLIENT(initable), FALSE); + g_return_val_if_fail(cancellable == NULL, FALSE); + + self = G_UDEV_CLIENT(initable); + priv = self->priv; + + rc = libusb_init(&priv->ctx); + if (rc < 0) { + const char *errstr = spice_usbutil_libusb_strerror(rc); + g_warning("Error initializing USB support: %s [%i]", errstr, rc); + g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED, + "Error initializing USB support: %s [%i]", errstr, rc); + return FALSE; + } + + /* get initial device list */ + priv->udev_list_size = g_udev_client_list_devices(self, &priv->udev_list, + err, __FUNCTION__); + if (priv->udev_list_size < 0) { + goto g_udev_client_init_failed; + } + + g_udev_device_print_list(priv->udev_list, "init: first list is: "); + + /* create a hidden window */ + memset(&wcls, 0, sizeof(wcls)); + wcls.lpfnWndProc = wnd_proc; + wcls.lpszClassName = G_UDEV_CLIENT_WINCLASS_NAME; + if (!RegisterClass(&wcls)) { + DWORD e = GetLastError(); + g_warning("RegisterClass failed , %ld", (long)e); + g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_WINAPI_FAILED, + "RegisterClass failed: %ld", (long)e); + goto g_udev_client_init_failed; + } + priv->hwnd = CreateWindow(G_UDEV_CLIENT_WINCLASS_NAME, + NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + if (!priv->hwnd) { + DWORD e = GetLastError(); + g_warning("CreateWindow failed: %ld", (long)e); + g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED, + "CreateWindow failed: %ld", (long)e); + goto g_udev_client_init_failed_unreg; + } + + return TRUE; + + g_udev_client_init_failed_unreg: + UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL); + g_udev_client_init_failed: + libusb_exit(priv->ctx); + priv->ctx = NULL; + + return FALSE; +} + +static void g_udev_client_initable_iface_init(GInitableIface *iface) +{ + iface->init = g_udev_client_initable_init; +} + +GList *g_udev_client_query_by_subsystem(GUdevClient *self, const gchar *subsystem) +{ + GList *l = g_list_copy(self->priv->udev_list); + g_list_foreach(l, (GFunc)g_object_ref, NULL); + return l; +} + +static void g_udev_client_init(GUdevClient *self) +{ + self->priv = G_UDEV_CLIENT_GET_PRIVATE(self); +} + +static void g_udev_client_finalize(GObject *gobject) +{ + GUdevClient *self = G_UDEV_CLIENT(gobject); + GUdevClientPrivate *priv = self->priv; + + singleton = NULL; + DestroyWindow(priv->hwnd); + UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL); + g_udev_client_free_device_list(&priv->udev_list); + + /* free libusb context initializing by libusb_init() */ + g_warn_if_fail(priv->ctx != NULL); + libusb_exit(priv->ctx); + + /* Chain up to the parent class */ + if (G_OBJECT_CLASS(g_udev_client_parent_class)->finalize) + G_OBJECT_CLASS(g_udev_client_parent_class)->finalize(gobject); +} + +static void g_udev_client_class_init(GUdevClientClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->finalize = g_udev_client_finalize; + + signals[UEVENT_SIGNAL] = + g_signal_new("uevent", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(GUdevClientClass, uevent), + NULL, NULL, + g_cclosure_user_marshal_VOID__BOXED_BOXED, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_UDEV_TYPE_DEVICE); + + g_type_class_add_private(klass, sizeof(GUdevClientPrivate)); +} + +static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo) +{ + struct libusb_device_descriptor desc; + + g_return_val_if_fail(dev, FALSE); + g_return_val_if_fail(udevinfo, FALSE); + + if (libusb_get_device_descriptor(dev, &desc) < 0) { + g_warning("cannot get device descriptor %p", dev); + return FALSE; + } + + udevinfo->bus = libusb_get_bus_number(dev); + udevinfo->addr = libusb_get_device_address(dev); + udevinfo->class = desc.bDeviceClass; + udevinfo->vid = desc.idVendor; + udevinfo->pid = desc.idProduct; + snprintf(udevinfo->sclass, sizeof(udevinfo->sclass), "%d", udevinfo->class); + snprintf(udevinfo->sbus, sizeof(udevinfo->sbus), "%d", udevinfo->bus); + snprintf(udevinfo->saddr, sizeof(udevinfo->saddr), "%d", udevinfo->addr); + return TRUE; +} + +/* Only bus,addr are compared */ +static gboolean gudev_devices_are_equal(GUdevDevice *a, GUdevDevice *b) +{ + GUdevDeviceInfo *ai, *bi; + gboolean same_bus; + gboolean same_addr; + + ai = a->priv->udevinfo; + bi = b->priv->udevinfo; + + same_bus = (ai->bus == bi->bus); + same_addr = (ai->addr == bi->addr); + + return (same_bus && same_addr); +} + + +/* Assumes each event stands for a single device change (at most) */ +static void handle_dev_change(GUdevClient *self) +{ + GUdevClientPrivate *priv = self->priv; + GUdevDevice *changed_dev = NULL; + ssize_t dev_count; + int is_dev_change; + GError *err; + GList *now_devs = NULL; + GList *llist, *slist; /* long-list and short-list*/ + GList *lit, *sit; /* iterators for long-list and short-list */ + GUdevDevice *ldev, *sdev; /* devices on long-list and short-list */ + + dev_count = g_udev_client_list_devices(self, &now_devs, &err, + __FUNCTION__); + g_return_if_fail(dev_count >= 0); + + SPICE_DEBUG("number of current devices %d, I know about %d devices", + dev_count, priv->udev_list_size); + + is_dev_change = dev_count - priv->udev_list_size; + if (is_dev_change == 0) { + g_udev_client_free_device_list(&now_devs); + return; + } + + if (is_dev_change > 0) { + llist = now_devs; + slist = priv->udev_list; + } else { + llist = priv->udev_list; + slist = now_devs; + } + + g_udev_device_print_list(llist, "handle_dev_change: long list:"); + g_udev_device_print_list(slist, "handle_dev_change: short list:"); + + /* Go over the longer list */ + for (lit = g_list_first(llist); lit != NULL; lit=g_list_next(lit)) { + ldev = lit->data; + /* Look for dev in the shorther list */ + for (sit = g_list_first(slist); sit != NULL; sit=g_list_next(sit)) { + sdev = sit->data; + if (gudev_devices_are_equal(ldev, sdev)) + break; + } + if (sit == NULL) { + /* Found a device which appears only in the longer list */ + changed_dev = ldev; + break; + } + } + + if (!changed_dev) { + g_warning("couldn't find any device change"); + goto leave; + } + + if (is_dev_change > 0) { + g_udev_device_print(changed_dev, ">>> USB device inserted"); + g_signal_emit(self, signals[UEVENT_SIGNAL], 0, "add", changed_dev); + } else { + g_udev_device_print(changed_dev, "<<< USB device removed"); + g_signal_emit(self, signals[UEVENT_SIGNAL], 0, "remove", changed_dev); + } + +leave: + /* keep most recent info: free previous list, and keep current list */ + g_udev_client_free_device_list(&priv->udev_list); + priv->udev_list = now_devs; + priv->udev_list_size = dev_count; +} + +static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +{ + /* Only DBT_DEVNODES_CHANGED recieved */ + if (message == WM_DEVICECHANGE) { + handle_dev_change(singleton); + } + return DefWindowProc(hwnd, message, wparam, lparam); +} + +/*** GUdevDevice ***/ + +static void g_udev_device_finalize(GObject *object) +{ + GUdevDevice *device = G_UDEV_DEVICE(object); + + g_free(device->priv->udevinfo); + if (G_OBJECT_CLASS(g_udev_device_parent_class)->finalize != NULL) + (* G_OBJECT_CLASS(g_udev_device_parent_class)->finalize)(object); +} + +static void g_udev_device_class_init(GUdevDeviceClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = g_udev_device_finalize; + g_type_class_add_private (klass, sizeof(GUdevDevicePrivate)); +} + +static void g_udev_device_init(GUdevDevice *device) +{ + device->priv = G_TYPE_INSTANCE_GET_PRIVATE(device, G_UDEV_TYPE_DEVICE, GUdevDevicePrivate); +} + +static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo) +{ + GUdevDevice *device; + + g_return_val_if_fail(udevinfo != NULL, NULL); + + device = G_UDEV_DEVICE(g_object_new(G_UDEV_TYPE_DEVICE, NULL)); + device->priv->udevinfo = udevinfo; + return device; +} + +const gchar *g_udev_device_get_property(GUdevDevice *udev, const gchar *property) +{ + GUdevDeviceInfo* udevinfo; + + g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL); + g_return_val_if_fail(property != NULL, NULL); + + udevinfo = udev->priv->udevinfo; + g_return_val_if_fail(udevinfo != NULL, NULL); + + if (g_strcmp0(property, "BUSNUM") == 0) { + return udevinfo->sbus; + } else if (g_strcmp0(property, "DEVNUM") == 0) { + return udevinfo->saddr; + } else if (g_strcmp0(property, "DEVTYPE") == 0) { + return "usb_device"; + } + + g_warn_if_reached(); + return NULL; +} + +const gchar *g_udev_device_get_sysfs_attr(GUdevDevice *udev, const gchar *attr) +{ + GUdevDeviceInfo* udevinfo; + + g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL); + g_return_val_if_fail(attr != NULL, NULL); + + udevinfo = udev->priv->udevinfo; + g_return_val_if_fail(udevinfo != NULL, NULL); + + + if (g_strcmp0(attr, "bDeviceClass") == 0) { + return udevinfo->sclass; + } + g_warn_if_reached(); + return NULL; +} + +#ifdef DEBUG_GUDEV_DEVICE_LISTS +static void g_udev_device_print_list(GList *l, const gchar *msg) +{ + GList *it; + + for (it = g_list_first(l); it != NULL; it=g_list_next(it)) { + g_udev_device_print(it->data, msg); + } +} +#endif + +static void g_udev_device_print(GUdevDevice *udev, const gchar *msg) +{ + GUdevDeviceInfo* udevinfo; + + g_return_if_fail(G_UDEV_DEVICE(udev)); + + udevinfo = udev->priv->udevinfo; + g_return_if_fail(udevinfo != NULL); + + SPICE_DEBUG("%s: %d.%d 0x%04x:0x%04x class %d", msg, + udevinfo->bus, udevinfo->addr, + udevinfo->vid, udevinfo->pid, udevinfo->class); +} diff --git a/gtk/win-usb-dev.h b/gtk/win-usb-dev.h new file mode 100644 index 0000000..b5c4fce --- /dev/null +++ b/gtk/win-usb-dev.h @@ -0,0 +1,110 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2012 Red Hat, Inc. + + Red Hat Authors: + Arnon Gilboa <agilboa@xxxxxxxxxx> + Uri Lublin <uril@xxxxxxxxxx> + + 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 __WIN_USB_DEV_H__ +#define __WIN_USB_DEV_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* GUdevDevice */ + +#define G_UDEV_TYPE_DEVICE (g_udev_device_get_type()) +#define G_UDEV_DEVICE(o) (G_TYPE_CHECK_INSTANCE_CAST((o), G_UDEV_TYPE_DEVICE, GUdevDevice)) +#define G_UDEV_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_DEVICE, GUdevDeviceClass)) +#define G_UDEV_IS_DEVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UDEV_TYPE_DEVICE)) +#define G_UDEV_IS_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), G_UDEV_TYPE_DEVICE)) +#define G_UDEV_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_UDEV_TYPE_DEVICE, GUdevDeviceClass)) + +typedef struct _GUdevDevice GUdevDevice; +typedef struct _GUdevDeviceClass GUdevDeviceClass; +typedef struct _GUdevDevicePrivate GUdevDevicePrivate; + +struct _GUdevDevice +{ + GObject parent; + GUdevDevicePrivate *priv; +}; + +struct _GUdevDeviceClass +{ + GObjectClass parent_class; +}; + +/* GUdevClient */ + +#define G_UDEV_TYPE_CLIENT (g_udev_client_get_type()) +#define G_UDEV_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), G_UDEV_TYPE_CLIENT, GUdevClient)) +#define G_UDEV_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_CLIENT, GUdevClientClass)) +#define G_UDEV_IS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), G_UDEV_TYPE_CLIENT)) +#define G_UDEV_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), G_UDEV_TYPE_CLIENT)) +#define G_UDEV_CLIENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_UDEV_TYPE_CLIENT, GUdevClientClass)) + +typedef struct _GUdevClient GUdevClient; +typedef struct _GUdevClientClass GUdevClientClass; +typedef struct _GUdevClientPrivate GUdevClientPrivate; + +struct _GUdevClient +{ + GObject parent; + + GUdevClientPrivate *priv; +}; + +struct _GUdevClientClass +{ + GObjectClass parent_class; + + /* signals */ + void (*uevent)(GUdevClient *client, const gchar *action, GUdevDevice *device); +}; + +GType g_udev_client_get_type(void) G_GNUC_CONST; +GUdevClient *g_udev_client_new(const gchar* const *subsystems); +GList *g_udev_client_query_by_subsystem(GUdevClient *client, const gchar *subsystem); + +GType g_udev_device_get_type(void) G_GNUC_CONST; +const gchar *g_udev_device_get_property(GUdevDevice *udev, const gchar *property); +const gchar *g_udev_device_get_sysfs_attr(GUdevDevice *udev, const gchar *attr); + +GQuark g_udev_client_error_quark(void); +#define G_UDEV_CLIENT_ERROR g_udev_client_error_quark() + +/** + * GUdevClientError: + * @G_UDEV_CLIENT_ERROR_FAILED: generic error code + * @G_UDEV_CLIENT_LIBUSB_FAILED: a libusb call failed + * @G_UDEV_CLIENT_WINAPI_FAILED: a winapi call failed + * + * Error codes returned by spice-client API. + */ +typedef enum +{ + G_UDEV_CLIENT_ERROR_FAILED = 1, + G_UDEV_CLIENT_LIBUSB_FAILED, + G_UDEV_CLIENT_WINAPI_FAILED +} GUdevClientError; + + +G_END_DECLS + +#endif /* __WIN_USB_DEV_H__ */ -- 1.7.7.6 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel