Remove Windows-specific part of hotplug detection from usb-device-manager and win-usb-dev and place it into USB backend module. Connect the hotplug/unplug detection in Windows to the same callbacks already used in Linux. This removes significant part of Windows-specific code and simpifies things. Note regarding 'redirecting' property of usb-device-manager: - Previously usb-device-manager under Windows maintained 'redirection' property in win-usb-dev - Now it maintains its own property in both Windows and Linux (for GUI and single redirect at a time) - The USB backend maintains its own 'redirecting' field in Windows to prevent update of device list when libusb opens the device (device redirection causes temporary removal of target device) Signed-off-by: Yuri Benditovich <yuri.benditovich@xxxxxxxxxx> --- meson.build | 2 +- src/meson.build | 4 +- src/usb-backend.c | 360 ++++++++++++++++++++++---------- src/usb-backend.h | 8 - src/usb-device-manager.c | 61 +----- src/win-usb-dev.c | 436 --------------------------------------- src/win-usb-dev.h | 84 -------- 7 files changed, 255 insertions(+), 700 deletions(-) delete mode 100644 src/win-usb-dev.c delete mode 100644 src/win-usb-dev.h diff --git a/meson.build b/meson.build index 54160f9..1f6ea6d 100644 --- a/meson.build +++ b/meson.build @@ -117,7 +117,7 @@ endforeach deps = [] if host_machine.system() == 'windows' - deps += ['libws2_32', 'libgdi32'] + deps += ['libws2_32', 'libgdi32', 'libcomctl32'] endif foreach dep : deps diff --git a/src/meson.build b/src/meson.build index a51d0a9..adcfaec 100644 --- a/src/meson.build +++ b/src/meson.build @@ -158,9 +158,7 @@ endif if spice_gtk_has_usbredir and host_machine.system() == 'windows' spice_client_glib_sources += ['usbdk_api.c', - 'usbdk_api.h', - 'win-usb-dev.c', - 'win-usb-dev.h'] + 'usbdk_api.h'] endif # diff --git a/src/usb-backend.c b/src/usb-backend.c index a2c502d..9964c4f 100644 --- a/src/usb-backend.c +++ b/src/usb-backend.c @@ -32,6 +32,9 @@ #include <libusb.h> #include <string.h> #include <fcntl.h> +#ifdef G_OS_WIN32 +#include <commctrl.h> +#endif #include "usbredirhost.h" #include "usbredirparser.h" #include "spice-util.h" @@ -55,6 +58,11 @@ struct _SpiceUsbBackend usb_hot_plug_callback hotplug_callback; void *hotplug_user_data; libusb_hotplug_callback_handle hotplug_handle; +#ifdef G_OS_WIN32 + HANDLE hWnd; + libusb_device **libusb_device_list; + gint redirecting; +#endif }; struct _SpiceUsbBackendChannel @@ -66,9 +74,229 @@ struct _SpiceUsbBackendChannel int rules_count; SpiceUsbBackendDevice *attached; SpiceUsbredirChannel *user_data; + SpiceUsbBackend *backend; GError **error; }; +static gboolean fill_usb_info(SpiceUsbBackendDevice *bdev) +{ + UsbDeviceInformation *info = &bdev->device_info; + + struct libusb_device_descriptor desc; + libusb_device *libdev = bdev->libusb_device; + libusb_get_device_descriptor(libdev, &desc); + info->bus = libusb_get_bus_number(libdev); + info->address = libusb_get_device_address(libdev); + if (info->address == 0xff || /* root hub (HCD) */ + info->address <= 1 || /* root hub or bad address */ + (desc.bDeviceClass == LIBUSB_CLASS_HUB) /*hub*/) { + return FALSE; + } + + info->vid = desc.idVendor; + info->pid = desc.idProduct; + info->class = desc.bDeviceClass; + info->subclass = desc.bDeviceSubClass; + info->protocol = desc.bDeviceProtocol; + + return TRUE; +} + +static SpiceUsbBackendDevice *allocate_backend_device(libusb_device *libdev) +{ + SpiceUsbBackendDevice *dev = g_new0(SpiceUsbBackendDevice, 1); + dev->ref_count = 1; + dev->libusb_device = libdev; + if (!fill_usb_info(dev)) { + g_clear_pointer(&dev, g_free); + } + return dev; +} + +static int LIBUSB_CALL hotplug_callback(libusb_context *ctx, + libusb_device *device, + libusb_hotplug_event event, + void *user_data) +{ + SpiceUsbBackend *be = (SpiceUsbBackend *)user_data; + if (be->hotplug_callback) { + SpiceUsbBackendDevice *dev; + gboolean val = event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED; + dev = allocate_backend_device(device); + if (dev) { + SPICE_DEBUG("created dev %p, usblib dev %p", dev, device); + libusb_ref_device(device); + be->hotplug_callback(be->hotplug_user_data, dev, val); + spice_usb_backend_device_unref(dev); + } + } + return 0; +} + +#ifdef G_OS_WIN32 +/* Windows-specific: get notification on device change */ + +static gboolean is_same_libusb_dev(libusb_device *dev1, + UsbDeviceInformation *info1, + libusb_device *dev2) +{ + struct libusb_device_descriptor desc; + info1->bus = libusb_get_bus_number(dev1); + info1->address = libusb_get_device_address(dev1); + libusb_get_device_descriptor(dev1, &desc); + info1->vid = desc.idVendor; + info1->pid = desc.idProduct; + if (dev2 == NULL) { + return FALSE; + } + libusb_get_device_descriptor(dev2, &desc); + return info1->bus == libusb_get_bus_number(dev2) && + info1->address == libusb_get_device_address(dev2) && + info1->vid == desc.idVendor && + info1->pid == desc.idProduct; +} + +static int compare_dev_list(SpiceUsbBackend *be, + libusb_device **l1, + libusb_device **l2, + gboolean value) +{ + int res = 0; + while (*l1) { + gboolean found = 0; + uint32_t n = 0; + UsbDeviceInformation info1; + while (!found) { + found = is_same_libusb_dev(*l1, &info1, l2[n]); + if (l2[n] == NULL) { + break; + } + n++; + } + if (!found) { + SPICE_DEBUG("%s %04X:%04X at %d:%d", value ? "adding" : "removing", + info1.vid, info1.pid, info1.bus, info1.address); + hotplug_callback(NULL, *l1, + value ? LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED : LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + be); + res++; + } + l1++; + } + return res; +} + +static LRESULT subclass_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, + UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + SpiceUsbBackend *be = (SpiceUsbBackend *)dwRefData; + if (uMsg == WM_DEVICECHANGE && !be->redirecting) { + libusb_device **new_list = NULL; + libusb_get_device_list(be->libusb_context, &new_list); + if (new_list) { + int res = compare_dev_list(be, be->libusb_device_list, new_list, FALSE); + res += compare_dev_list(be, new_list, be->libusb_device_list, TRUE); + if (res) { + /* there were changes in device tree */ + libusb_free_device_list(be->libusb_device_list, TRUE); + be->libusb_device_list = new_list; + } else { + libusb_free_device_list(new_list, TRUE); + } + } else { + g_warn_if_reached(); + } + } + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +static void disable_hotplug_support(SpiceUsbBackend *be) +{ + if (be->hWnd) { + DestroyWindow(be->hWnd); + be->hWnd = NULL; + } + if (be->libusb_device_list) { + libusb_free_device_list(be->libusb_device_list, TRUE); + be->libusb_device_list = NULL; + } +} + +static int enable_hotplug_support(SpiceUsbBackend *be, const char **error_on_enable) +{ + long win_err; + libusb_device **dev_list = NULL; + libusb_device *dummy = NULL; + + libusb_get_device_list(be->libusb_context, &dev_list); + if (!dev_list) { + *error_on_enable = "Getting device list"; + goto error; + } + be->hWnd = CreateWindow("Static", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + if (!be->hWnd) { + *error_on_enable = "CreateWindow"; + goto error; + } + if (!SetWindowSubclass(be->hWnd, subclass_proc, 0, (DWORD_PTR)be)) { + *error_on_enable = "SetWindowSubclass"; + goto error; + } + be->hotplug_handle = 1; + be->libusb_device_list = dev_list; + + compare_dev_list(be, be->libusb_device_list, &dummy, TRUE); + + return LIBUSB_SUCCESS; +error: + win_err = GetLastError(); + if (!win_err) { + win_err = -1; + } + g_warning("%s failed: %ld", *error_on_enable, win_err); + if (dev_list) { + libusb_free_device_list(dev_list, TRUE); + } + return win_err; +} + +static void set_redirecting(SpiceUsbBackend *be, gboolean on) +{ + gboolean no_redir; + if (on) { + g_atomic_int_inc(&be->redirecting); + } else { + no_redir = g_atomic_int_dec_and_test(&be->redirecting); + if (no_redir && be->hWnd) { + PostMessage(be->hWnd, WM_DEVICECHANGE, 0, 0); + } + } +} + +#else +/* Linux-specific: use hot callback from libusb */ + +static void disable_hotplug_support(SpiceUsbBackend *be) +{ + libusb_hotplug_deregister_callback(be->libusb_context, be->hotplug_handle); +} + +static int enable_hotplug_support(SpiceUsbBackend *be, const char **error_on_enable) +{ + int rc = 0; + rc = libusb_hotplug_register_callback(be->libusb_context, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, + hotplug_callback, be, &be->hotplug_handle); + *error_on_enable = libusb_strerror(rc); + return rc; +} + +#define set_redirecting(...) + +#endif + /* lock functions for usbredirhost and usbredirparser */ static void *usbredir_alloc_lock(void) { GMutex *mutex; @@ -131,41 +359,6 @@ gboolean spice_usb_backend_device_isoch(SpiceUsbBackendDevice *dev) return isoc_found; } -static gboolean fill_usb_info(SpiceUsbBackendDevice *bdev) -{ - UsbDeviceInformation *info = &bdev->device_info; - - struct libusb_device_descriptor desc; - libusb_device *libdev = bdev->libusb_device; - libusb_get_device_descriptor(libdev, &desc); - info->bus = libusb_get_bus_number(libdev); - info->address = libusb_get_device_address(libdev); - if (info->address == 0xff || /* root hub (HCD) */ - info->address <= 1 || /* root hub or bad address */ - (desc.bDeviceClass == LIBUSB_CLASS_HUB) /*hub*/) { - return FALSE; - } - - info->vid = desc.idVendor; - info->pid = desc.idProduct; - info->class = desc.bDeviceClass; - info->subclass = desc.bDeviceSubClass; - info->protocol = desc.bDeviceProtocol; - - return TRUE; -} - -static SpiceUsbBackendDevice *allocate_backend_device(libusb_device *libdev) -{ - SpiceUsbBackendDevice *dev = g_new0(SpiceUsbBackendDevice, 1); - dev->ref_count = 1; - dev->libusb_device = libdev; - if (!fill_usb_info(dev)) { - g_clear_pointer(&dev, g_free); - } - return dev; -} - static gboolean is_channel_ready(SpiceUsbredirChannel *channel) { return spice_channel_get_state(SPICE_CHANNEL(channel)) == SPICE_CHANNEL_STATE_READY; @@ -237,31 +430,11 @@ void spice_usb_backend_interrupt_event_handler(SpiceUsbBackend *be) } } -static int LIBUSB_CALL hotplug_callback(libusb_context *ctx, - libusb_device *device, - libusb_hotplug_event event, - void *user_data) -{ - SpiceUsbBackend *be = (SpiceUsbBackend *)user_data; - if (be->hotplug_callback) { - SpiceUsbBackendDevice *dev; - gboolean val = event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED; - dev = allocate_backend_device(device); - if (dev) { - SPICE_DEBUG("created dev %p, usblib dev %p", dev, device); - libusb_ref_device(device); - be->hotplug_callback(be->hotplug_user_data, dev, val); - spice_usb_backend_device_unref(dev); - } - } - return 0; -} - void spice_usb_backend_deregister_hotplug(SpiceUsbBackend *be) { g_return_if_fail(be != NULL); if (be->hotplug_handle) { - libusb_hotplug_deregister_callback(be->libusb_context, be->hotplug_handle); + disable_hotplug_support(be); be->hotplug_handle = 0; } be->hotplug_callback = NULL; @@ -272,17 +445,15 @@ gboolean spice_usb_backend_register_hotplug(SpiceUsbBackend *be, usb_hot_plug_callback proc) { int rc; + const char *desc; g_return_val_if_fail(be != NULL, FALSE); be->hotplug_callback = proc; be->hotplug_user_data = user_data; - rc = libusb_hotplug_register_callback(be->libusb_context, - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, - LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY, - LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, - hotplug_callback, be, &be->hotplug_handle); + + rc = enable_hotplug_support(be, &desc); + if (rc != LIBUSB_SUCCESS) { - const char *desc = libusb_strerror(rc); g_warning("Error initializing USB hotplug support: %s [%i]", desc, rc); be->hotplug_callback = NULL; return FALSE; @@ -301,45 +472,6 @@ void spice_usb_backend_delete(SpiceUsbBackend *be) SPICE_DEBUG("%s <<", __FUNCTION__); } -SpiceUsbBackendDevice **spice_usb_backend_get_device_list(SpiceUsbBackend *be) -{ - LOUD_DEBUG("%s >>", __FUNCTION__); - libusb_device **devlist = NULL, **dev; - SpiceUsbBackendDevice *d, **list; - - int n = 0, index; - - if (be && be->libusb_context) { - libusb_get_device_list(be->libusb_context, &devlist); - } - - /* add all the libusb device that not present in our list */ - for (dev = devlist; dev && *dev; dev++) { - n++; - } - - list = g_new0(SpiceUsbBackendDevice*, n + 1); - - index = 0; - - for (dev = devlist; dev && *dev; dev++) { - d = allocate_backend_device(*dev); - if (!d) { - libusb_unref_device(*dev); - } else { - SPICE_DEBUG("created dev %p, usblib dev %p", d, *dev); - list[index++] = d; - } - } - - if (devlist) { - libusb_free_device_list(devlist, 0); - } - - LOUD_DEBUG("%s <<", __FUNCTION__); - return list; -} - const UsbDeviceInformation* spice_usb_backend_device_get_info(SpiceUsbBackendDevice *dev) { return &dev->device_info; @@ -350,18 +482,6 @@ gconstpointer spice_usb_backend_device_get_libdev(SpiceUsbBackendDevice *dev) return dev->libusb_device; } -void spice_usb_backend_free_device_list(SpiceUsbBackendDevice **devlist) -{ - LOUD_DEBUG("%s >>", __FUNCTION__); - SpiceUsbBackendDevice **dev; - for (dev = devlist; *dev; dev++) { - SpiceUsbBackendDevice *d = *dev; - spice_usb_backend_device_unref(d); - } - g_free(devlist); - LOUD_DEBUG("%s <<", __FUNCTION__); -} - SpiceUsbBackendDevice *spice_usb_backend_device_ref(SpiceUsbBackendDevice *dev) { LOUD_DEBUG("%s >> %p", __FUNCTION__, dev); @@ -529,12 +649,23 @@ gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, SpiceUsbBackendDevice *dev, GError **error) { + int rc; SPICE_DEBUG("%s >> ch %p, dev %p (was attached %p)", __FUNCTION__, ch, dev, ch->attached); g_return_val_if_fail(dev != NULL, FALSE); libusb_device_handle *handle = NULL; - int rc = libusb_open(dev->libusb_device, &handle); + + /* + Under Windows we need to avoid updating + list of devices when we are acquiring the device + */ + set_redirecting(ch->backend, TRUE); + + rc = libusb_open(dev->libusb_device, &handle); + + set_redirecting(ch->backend, FALSE); + if (rc) { const char *desc = libusb_strerror(rc); g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, @@ -582,6 +713,7 @@ SpiceUsbBackendChannel *spice_usb_backend_channel_new(SpiceUsbBackend *be, SPICE_DEBUG("%s >>", __FUNCTION__); ch->user_data = SPICE_USBREDIR_CHANNEL(user_data); if (be->libusb_context) { + ch->backend = be; ch->usbredirhost = usbredirhost_open_full( be->libusb_context, NULL, diff --git a/src/usb-backend.h b/src/usb-backend.h index cbb73c2..6da3981 100644 --- a/src/usb-backend.h +++ b/src/usb-backend.h @@ -56,14 +56,6 @@ enum { SpiceUsbBackend *spice_usb_backend_new(GError **error); void spice_usb_backend_delete(SpiceUsbBackend *context); -/* -returns newly-allocated null-terminated list of -SpiceUsbBackendDevice pointers. -The caller must call spice_usb_backend_free_device_list -after it finishes list processing -*/ -SpiceUsbBackendDevice **spice_usb_backend_get_device_list(SpiceUsbBackend *backend); -void spice_usb_backend_free_device_list(SpiceUsbBackendDevice **devlist); gboolean spice_usb_backend_handle_events(SpiceUsbBackend *be); void spice_usb_backend_interrupt_event_handler(SpiceUsbBackend *be); gboolean spice_usb_backend_register_hotplug(SpiceUsbBackend *be, diff --git a/src/usb-device-manager.c b/src/usb-device-manager.c index 9aba275..9300ad2 100644 --- a/src/usb-device-manager.c +++ b/src/usb-device-manager.c @@ -29,7 +29,6 @@ #ifdef G_OS_WIN32 #include <windows.h> #include "usbdk_api.h" -#include "win-usb-dev.h" #endif #include "channel-usbredir-priv.h" @@ -101,12 +100,10 @@ struct _SpiceUsbDeviceManagerPrivate { struct usbredirfilter_rule *redirect_on_connect_rules; int auto_conn_filter_rules_count; int redirect_on_connect_rules_count; + gboolean redirecting; #ifdef G_OS_WIN32 - GUdevClient *udev; usbdk_api_wrapper *usbdk_api; HANDLE usbdk_hider_handle; -#else - gboolean redirecting; /* Handled by GUdevClient in the gudev case */ #endif GPtrArray *devices; GPtrArray *channels; @@ -139,16 +136,9 @@ static void channel_destroy(SpiceSession *session, SpiceChannel *channel, gpointer user_data); static void channel_event(SpiceChannel *channel, SpiceChannelEvent event, gpointer user_data); -#ifdef G_OS_WIN32 -static void spice_usb_device_manager_uevent_cb(GUdevClient *client, - SpiceUsbBackendDevice *udevice, - gboolean add, - gpointer user_data); -#else static void spice_usb_device_manager_hotplug_cb(void *user_data, SpiceUsbBackendDevice *dev, gboolean added); -#endif static void spice_usb_device_manager_check_redir_on_connect( SpiceUsbDeviceManager *self, SpiceChannel *channel); @@ -190,11 +180,7 @@ G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device, static void _set_redirecting(SpiceUsbDeviceManager *self, gboolean is_redirecting) { -#ifdef G_OS_WIN32 - g_object_set(self->priv->udev, "redirecting", is_redirecting, NULL); -#else self->priv->redirecting = is_redirecting; -#endif } #else @@ -215,10 +201,6 @@ gboolean spice_usb_device_manager_is_redirecting(SpiceUsbDeviceManager *self) { #ifndef USE_USBREDIR return FALSE; -#elif defined(G_OS_WIN32) - gboolean redirecting; - g_object_get(self->priv->udev, "redirecting", &redirecting, NULL); - return redirecting; #else return self->priv->redirecting; #endif @@ -267,29 +249,18 @@ static gboolean spice_usb_device_manager_initable_init(GInitable *initable, GList *list; GList *it; - /* Start listening for usb devices plug / unplug */ -#ifdef G_OS_WIN32 - priv->udev = g_udev_client_new(); - if (priv->udev == NULL) { - g_warning("Error initializing GUdevClient"); - return FALSE; - } - priv->context = g_udev_client_get_context(priv->udev); - g_signal_connect(G_OBJECT(priv->udev), "uevent", - G_CALLBACK(spice_usb_device_manager_uevent_cb), self); - /* Do coldplug (detection of already connected devices) */ - g_udev_client_report_devices(priv->udev); -#else /* Initialize libusb */ priv->context = spice_usb_backend_new(err); if (!priv->context) { return FALSE; } + /* Start listening for usb devices plug / unplug */ if (!spice_usb_backend_register_hotplug(priv->context, self, spice_usb_device_manager_hotplug_cb)) { return FALSE; } +#ifndef G_OS_WIN32 spice_usb_device_manager_start_event_listening(self, NULL); #endif @@ -324,8 +295,9 @@ static void spice_usb_device_manager_dispose(GObject *gobject) g_warn_if_reached(); g_atomic_int_set(&priv->event_thread_run, FALSE); } - spice_usb_backend_deregister_hotplug(priv->context); #endif + spice_usb_backend_deregister_hotplug(priv->context); + if (priv->event_thread) { g_warn_if_fail(g_atomic_int_get(&priv->event_thread_run) == FALSE); g_atomic_int_set(&priv->event_thread_run, FALSE); @@ -350,14 +322,10 @@ static void spice_usb_device_manager_finalize(GObject *gobject) if (priv->devices) { g_ptr_array_unref(priv->devices); } -#ifdef G_OS_WIN32 - g_clear_object(&priv->udev); -#endif g_return_if_fail(priv->event_thread == NULL); -#ifndef G_OS_WIN32 - if (priv->context) + if (priv->context) { spice_usb_backend_delete(priv->context); -#endif + } free(priv->auto_conn_filter_rules); free(priv->redirect_on_connect_rules); #ifdef G_OS_WIN32 @@ -891,20 +859,6 @@ static void spice_usb_device_manager_remove_dev(SpiceUsbDeviceManager *self, spice_usb_device_unref(device); } -#ifdef G_OS_WIN32 -static void spice_usb_device_manager_uevent_cb(GUdevClient *client, - SpiceUsbBackendDevice *bdev, - gboolean add, - gpointer user_data) -{ - SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data); - - if (add) - spice_usb_device_manager_add_dev(self, bdev); - else - spice_usb_device_manager_remove_dev(self, bdev); -} -#else struct hotplug_idle_cb_args { SpiceUsbDeviceManager *self; SpiceUsbBackendDevice *device; @@ -940,7 +894,6 @@ static void spice_usb_device_manager_hotplug_cb(void *user_data, args->added = added; g_idle_add(spice_usb_device_manager_hotplug_idle_cb, args); } -#endif // USE_USBREDIR static void spice_usb_device_manager_channel_connect_cb( GObject *gobject, GAsyncResult *channel_res, gpointer user_data) diff --git a/src/win-usb-dev.c b/src/win-usb-dev.c deleted file mode 100644 index c2a5115..0000000 --- a/src/win-usb-dev.c +++ /dev/null @@ -1,436 +0,0 @@ -/* -*- 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/>. -*/ - -#include "config.h" - -#include <windows.h> -#include "win-usb-dev.h" -#include "spice-marshal.h" -#include "spice-util.h" - -enum { - PROP_0, - PROP_REDIRECTING -}; - -struct _GUdevClientPrivate { - SpiceUsbBackend *ctx; - GList *udev_list; - HWND hwnd; - gboolean redirecting; -}; - -#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_ADD_PRIVATE(GUdevClient) - G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, g_udev_client_initable_iface_init)) - -enum -{ - UEVENT_SIGNAL, - LAST_SIGNAL, -}; - -static guint signals[LAST_SIGNAL] = { 0, }; -static GUdevClient *singleton = NULL; - -static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); - -//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(SpiceUsbBackendDevice *dev, 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(void) -{ - if (singleton != NULL) - return g_object_ref(singleton); - - singleton = g_initable_new(G_UDEV_TYPE_CLIENT, NULL, NULL, NULL); - return singleton; -} - -SpiceUsbBackend *g_udev_client_get_context(GUdevClient *client) -{ - return client->priv->ctx; -} - -/* - * 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) -{ - SpiceUsbBackendDevice **lusb_list, **dev; - GUdevClientPrivate *priv; - 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); - - lusb_list = spice_usb_backend_get_device_list(priv->ctx); - if (!lusb_list) { - g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED, - "%s: Error getting USB device list", name); - return -4; - } - - n = 0; - for (dev = lusb_list; *dev; dev++) { - *devs = g_list_prepend(*devs, spice_usb_backend_device_ref(*dev)); - n++; - } - spice_usb_backend_free_device_list(lusb_list); - - return n; -} - -static void unreference_backend_device(gpointer data) -{ - spice_usb_backend_device_unref((SpiceUsbBackendDevice *)data); -} - -static void g_udev_client_free_device_list(GList **devs) -{ - g_return_if_fail(devs != NULL); - if (*devs) { - g_list_free_full(*devs, unreference_backend_device); - *devs = NULL; - } -} - - -static gboolean -g_udev_client_initable_init(GInitable *initable, GCancellable *cancellable, - GError **err) -{ - GUdevClient *self; - GUdevClientPrivate *priv; - WNDCLASS wcls; - - 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; - - priv->ctx = spice_usb_backend_new(err); - if (!priv->ctx) { - return FALSE; - } - -#ifdef G_OS_WIN32 -#if LIBUSB_API_VERSION >= 0x01000106 - libusb_set_option(priv->ctx, LIBUSB_OPTION_USE_USBDK); -#endif -#endif - - /* get initial device list */ - if (g_udev_client_list_devices(self, &priv->udev_list, err, __FUNCTION__) < 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: - return FALSE; -} - -static void g_udev_client_initable_iface_init(GInitableIface *iface) -{ - iface->init = g_udev_client_initable_init; -} - -static void report_one_device(gpointer data, gpointer self) -{ - g_signal_emit(self, signals[UEVENT_SIGNAL], 0, data, TRUE); -} - -void g_udev_client_report_devices(GUdevClient *self) -{ - g_list_foreach(self->priv->udev_list, report_one_device, self); -} - -static void g_udev_client_init(GUdevClient *self) -{ - self->priv = g_udev_client_get_instance_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 backend context */ - g_warn_if_fail(priv->ctx != NULL); - spice_usb_backend_delete(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_get_property(GObject *gobject, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GUdevClient *self = G_UDEV_CLIENT(gobject); - GUdevClientPrivate *priv = self->priv; - - switch (prop_id) { - case PROP_REDIRECTING: - g_value_set_boolean(value, priv->redirecting); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); - break; - } -} - -static void handle_dev_change(GUdevClient *self); - -static void g_udev_client_set_property(GObject *gobject, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GUdevClient *self = G_UDEV_CLIENT(gobject); - GUdevClientPrivate *priv = self->priv; - gboolean old_val; - - switch (prop_id) { - case PROP_REDIRECTING: - old_val = priv->redirecting; - priv->redirecting = g_value_get_boolean(value); - if (old_val && !priv->redirecting) { - /* This is a redirection completion case. - Inject hotplug event in case we missed device changes - during redirection processing. */ - handle_dev_change(self); - } - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); - break; - } -} - -static void g_udev_client_class_init(GUdevClientClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS(klass); - GParamSpec *pspec; - - gobject_class->finalize = g_udev_client_finalize; - gobject_class->get_property = g_udev_client_get_property; - gobject_class->set_property = g_udev_client_set_property; - - 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__POINTER_BOOLEAN, - G_TYPE_NONE, - 2, - G_TYPE_POINTER, - G_TYPE_BOOLEAN); - - /** - * GUdevClient::redirecting: - * - * This property indicates when a redirection operation - * is in progress on a device. It's set back to FALSE - * once the device is fully redirected to the guest. - */ - pspec = g_param_spec_boolean("redirecting", "Redirecting", - "USB redirection operation is in progress", - FALSE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - - g_object_class_install_property(gobject_class, PROP_REDIRECTING, pspec); -} - -/* comparing bus:addr and vid:pid */ -static gint compare_usb_devices(gconstpointer a, gconstpointer b) -{ - const UsbDeviceInformation *a_info, *b_info; - gboolean same_bus, same_addr, same_vid, same_pid; - a_info = spice_usb_backend_device_get_info((SpiceUsbBackendDevice *)a); - b_info = spice_usb_backend_device_get_info((SpiceUsbBackendDevice *)b); - - - same_bus = (a_info->bus == b_info->bus); - same_addr = (a_info->address == b_info->address); - same_vid = (a_info->vid == b_info->vid); - same_pid = (a_info->pid == b_info->pid); - - return (same_bus && same_addr && same_vid && same_pid) ? 0 : -1; -} - -static void update_device_list(GUdevClient *self, GList *new_device_list) -{ - GList *dev; - - for (dev = g_list_first(new_device_list); dev != NULL; dev = g_list_next(dev)) { - GList *found = g_list_find_custom(self->priv->udev_list, dev->data, compare_usb_devices); - if (found != NULL) { - /* keep old existing libusb_device in the new list, when - usb-dev-manager will maintain its own list of libusb_device, - these lists will be completely consistent */ - SpiceUsbBackendDevice *temp = found->data; - found->data = dev->data; - dev->data = temp; - } - } - - g_udev_client_free_device_list(&self->priv->udev_list); - self->priv->udev_list = new_device_list; -} - -static void notify_dev_state_change(GUdevClient *self, - GList *old_list, - GList *new_list, - gboolean add) -{ - GList *dev; - - for (dev = g_list_first(old_list); dev != NULL; dev = g_list_next(dev)) { - GList *found = g_list_find_custom(new_list, dev->data, compare_usb_devices); - if (found == NULL) { - g_udev_device_print(dev->data, add ? "add" : "remove"); - g_signal_emit(self, signals[UEVENT_SIGNAL], 0, dev->data, add); - } - } -} - -static void handle_dev_change(GUdevClient *self) -{ - GUdevClientPrivate *priv = self->priv; - GError *err = NULL; - GList *now_devs = NULL; - - if (priv->redirecting == TRUE) { - /* On Windows, querying USB device list may return inconsistent results - if performed in parallel to redirection flow. - A simulated hotplug event will be injected after redirection - completion in order to process real device list changes that may - had taken place during redirection process. */ - return; - } - - if(g_udev_client_list_devices(self, &now_devs, &err, __FUNCTION__) < 0) { - g_warning("could not retrieve device list"); - return; - } - - g_udev_device_print_list(now_devs, "handle_dev_change: current list:"); - g_udev_device_print_list(priv->udev_list, "handle_dev_change: previous list:"); - - /* Unregister devices that are not present anymore */ - notify_dev_state_change(self, priv->udev_list, now_devs, FALSE); - - /* Register newly inserted devices */ - notify_dev_state_change(self, now_devs, priv->udev_list, TRUE); - - /* keep most recent info: free previous list, and keep current list */ - update_device_list(self, now_devs); -} - -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); -} - -#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(SpiceUsbBackendDevice *dev, const gchar *msg) -{ - const UsbDeviceInformation *info = spice_usb_backend_device_get_info(dev); - - SPICE_DEBUG("%s: %d.%d 0x%04x:0x%04x class %d", msg, - info->bus, info->address, - info->vid, info->pid, info->class); -} diff --git a/src/win-usb-dev.h b/src/win-usb-dev.h deleted file mode 100644 index fdfe73a..0000000 --- a/src/win-usb-dev.h +++ /dev/null @@ -1,84 +0,0 @@ -/* -*- 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 <gio/gio.h> -#include "usb-backend.h" - -G_BEGIN_DECLS - -/* 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, SpiceUsbBackendDevice *device, gboolean add); -}; - -GType g_udev_client_get_type(void) G_GNUC_CONST; -GUdevClient *g_udev_client_new(void); -SpiceUsbBackend *g_udev_client_get_context(GUdevClient *client); -void g_udev_client_report_devices(GUdevClient *client); - -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__ */ -- 2.17.1 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel