Browse Source

input: new windows input manager based on raw input

The new implementation uses a message-only window to register raw input events.

XInput is still used when possible, but raw input is being used to handle device detection.  (XInput is only enabled when an XInput device is plugged in, which also prevents us from having to load and poll the XInput library unnecessarily.)

For raw devices, the Windows HID parser library is used to parse the raw data.  Unfortunately, the Windows 7.1 SDK does not ship hid.lib (which is in the WDK 7.1.0), so I am dynamically loading hid.dll to prevent pulling in a dependency on the WDK.  (But perhaps we can ship hid.lib in the thirdparty libraries?)

Input devices other than XInput gamepads and the 3D mouse have not been tested very well yet.  Adding keyboard and mouse support is still a TODO.

This also splits out the Windows implementation of InputDeviceManager into a separate subclass; it is intended that the other implementations will follow suit.
rdb 8 years ago
parent
commit
8bd617c4ee

+ 5 - 3
dtool/src/parser-inc/XInput.h

@@ -1,3 +1,5 @@
-struct XINPUT_STATE;
-struct XINPUT_CAPABILITIES;
-struct XINPUT_VIBRATION;
+typedef struct _XINPUT_STATE XINPUT_STATE, *PXINPUT_STATE;
+typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES;
+typedef struct _XINPUT_VIBRATION XINPUT_VIBRATION, *PXINPUT_VIBRATION;
+typedef struct _XINPUT_GAMEPAD XINPUT_GAMEPAD, *PXINPUT_GAMEPAD;
+typedef struct _XINPUT_KEYSTROKE XINPUT_KEYSTROKE, *PXINPUT_KEYSTROKE;

+ 6 - 1
panda/src/device/inputDevice.cxx

@@ -13,6 +13,10 @@
 
 
 #include "inputDevice.h"
 #include "inputDevice.h"
 
 
+#if defined(_MSC_VER) && _MSC_VER < 1700
+#define fma(a, b, c) ((a) * (b) + (c))
+#endif
+
 TypeHandle InputDevice::_type_handle;
 TypeHandle InputDevice::_type_handle;
 
 
 /**
 /**
@@ -29,7 +33,8 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) :
   _event_sequence(0),
   _event_sequence(0),
   _enable_pointer_events(false),
   _enable_pointer_events(false),
   _battery_level(-1),
   _battery_level(-1),
-  _max_battery_level(-1)
+  _max_battery_level(-1),
+  _lock("InputDevice")
 {
 {
   _button_events = new ButtonEventList;
   _button_events = new ButtonEventList;
 }
 }

+ 5 - 3
panda/src/device/inputDeviceManager.I

@@ -16,8 +16,10 @@
  */
  */
 INLINE InputDeviceManager *InputDeviceManager::
 INLINE InputDeviceManager *InputDeviceManager::
 get_global_ptr() {
 get_global_ptr() {
-  if (_global_ptr == (InputDeviceManager *)NULL) {
-    _global_ptr = new InputDeviceManager;
+  if (_global_ptr != nullptr) {
+    return _global_ptr;
+  } else {
+    make_global_ptr();
+    return _global_ptr;
   }
   }
-  return _global_ptr;
 }
 }

+ 14 - 54
panda/src/device/inputDeviceManager.cxx

@@ -14,6 +14,7 @@
 #include "inputDeviceManager.h"
 #include "inputDeviceManager.h"
 #include "ioKitInputDevice.h"
 #include "ioKitInputDevice.h"
 #include "linuxJoystickDevice.h"
 #include "linuxJoystickDevice.h"
+#include "winInputDeviceManager.h"
 #include "throw_event.h"
 #include "throw_event.h"
 
 
 #ifdef PHAVE_LINUX_INPUT_H
 #ifdef PHAVE_LINUX_INPUT_H
@@ -23,12 +24,6 @@
 #include <sys/ioctl.h>
 #include <sys/ioctl.h>
 #endif
 #endif
 
 
-static ConfigVariableDouble xinput_detection_delay
-("xinput-detection-delay", 0.5,
- PRC_DESC("How many seconds to wait between each check to see whether "
-          "an XInput has been connected.  This check is not done every "
-          "frame in order to prevent slowdown."));
-
 InputDeviceManager *InputDeviceManager::_global_ptr = NULL;
 InputDeviceManager *InputDeviceManager::_global_ptr = NULL;
 
 
 /**
 /**
@@ -80,35 +75,6 @@ InputDeviceManager() : _inotify_fd(-1) {
     return;
     return;
   }
   }
 }
 }
-#elif defined(_WIN32)
-InputDeviceManager::
-InputDeviceManager() :
-  _xinput_device0(0),
-  _xinput_device1(1),
-  _xinput_device2(2),
-  _xinput_device3(3),
-  _last_detection(0.0) {
-
-  // XInput provides four device slots, so we simply create four XInputDevice
-  // objects that are bound to the lifetime of the input manager.
-  _xinput_device0.local_object();
-  _xinput_device1.local_object();
-  _xinput_device2.local_object();
-  _xinput_device3.local_object();
-
-  if (_xinput_device0.is_connected()) {
-    _connected_devices.add_device(&_xinput_device0);
-  }
-  if (_xinput_device1.is_connected()) {
-    _connected_devices.add_device(&_xinput_device1);
-  }
-  if (_xinput_device2.is_connected()) {
-    _connected_devices.add_device(&_xinput_device2);
-  }
-  if (_xinput_device3.is_connected()) {
-    _connected_devices.add_device(&_xinput_device3);
-  }
-}
 #elif defined(__APPLE__)
 #elif defined(__APPLE__)
 InputDeviceManager::
 InputDeviceManager::
 InputDeviceManager() {
 InputDeviceManager() {
@@ -153,7 +119,7 @@ InputDeviceManager() {
 }
 }
 #else
 #else
 InputDeviceManager::
 InputDeviceManager::
-InputDeviceManager() {
+InputDeviceManager() : _lock("InputDeviceManager") {
 }
 }
 #endif
 #endif
 
 
@@ -174,6 +140,18 @@ InputDeviceManager::
 #endif
 #endif
 }
 }
 
 
+/**
+ * Creates the global input manager.
+ */
+void InputDeviceManager::
+make_global_ptr() {
+#ifdef _WIN32
+  _global_ptr = new WinInputDeviceManager;
+#else
+  _global_ptr = new InputDeviceManager;
+#endif
+}
+
 #ifdef PHAVE_LINUX_INPUT_H
 #ifdef PHAVE_LINUX_INPUT_H
 /**
 /**
  * Checks whether the event device with the given index is accessible, and if
  * Checks whether the event device with the given index is accessible, and if
@@ -455,24 +433,6 @@ update() {
     ptr += sizeof(inotify_event) + event->len;
     ptr += sizeof(inotify_event) + event->len;
   }
   }
 #endif
 #endif
-
-#ifdef _WIN32
-  // XInput doesn't provide a very good hot-plugging interface.  We just check
-  // whether it's connected every so often.  Perhaps we can switch to using
-  // RegisterDeviceNotification in the future.
-  double time_now = ClockObject::get_global_clock()->get_real_time();
-  LightMutexHolder holder(_update_lock);
-
-  if (time_now - _last_detection > xinput_detection_delay.get_value()) {
-    // I've heard this can be quite slow if no device is detected.  We
-    // should probably move it to a thread.
-    _xinput_device0.detect(this);
-    _xinput_device1.detect(this);
-    _xinput_device2.detect(this);
-    _xinput_device3.detect(this);
-    _last_detection = time_now;
-  }
-#endif
 }
 }
 
 
 #if defined(__APPLE__) && !defined(CPPPARSER)
 #if defined(__APPLE__) && !defined(CPPPARSER)

+ 6 - 13
panda/src/device/inputDeviceManager.h

@@ -21,6 +21,7 @@
 
 
 #ifdef _WIN32
 #ifdef _WIN32
 #include "xinputDevice.h"
 #include "xinputDevice.h"
+class WinRawInputDevice;
 #endif
 #endif
 
 
 #ifdef __APPLE__
 #ifdef __APPLE__
@@ -32,10 +33,12 @@
  * when a device has been hot-plugged.
  * when a device has been hot-plugged.
  */
  */
 class EXPCL_PANDA_DEVICE InputDeviceManager {
 class EXPCL_PANDA_DEVICE InputDeviceManager {
-private:
+protected:
   InputDeviceManager();
   InputDeviceManager();
   ~InputDeviceManager();
   ~InputDeviceManager();
 
 
+  static void make_global_ptr();
+
 #ifdef PHAVE_LINUX_INPUT_H
 #ifdef PHAVE_LINUX_INPUT_H
   InputDevice *consider_add_evdev_device(int index);
   InputDevice *consider_add_evdev_device(int index);
   InputDevice *consider_add_js_device(int index);
   InputDevice *consider_add_js_device(int index);
@@ -48,14 +51,14 @@ PUBLISHED:
   void add_device(InputDevice *device);
   void add_device(InputDevice *device);
   void remove_device(InputDevice *device);
   void remove_device(InputDevice *device);
 
 
-  void update();
+  virtual void update();
 
 
   INLINE static InputDeviceManager *get_global_ptr();
   INLINE static InputDeviceManager *get_global_ptr();
 
 
   // The set of all currently connected devices.
   // The set of all currently connected devices.
   MAKE_PROPERTY(devices, get_devices);
   MAKE_PROPERTY(devices, get_devices);
 
 
-private:
+protected:
   LightMutex _lock;
   LightMutex _lock;
 
 
 #ifdef PHAVE_LINUX_INPUT_H
 #ifdef PHAVE_LINUX_INPUT_H
@@ -65,16 +68,6 @@ private:
   InputDeviceSet _inactive_devices;
   InputDeviceSet _inactive_devices;
 #endif
 #endif
 
 
-#ifdef _WIN32
-  // There are always exactly four of these in existence.
-  LightMutex _update_lock;
-  XInputDevice _xinput_device0;
-  XInputDevice _xinput_device1;
-  XInputDevice _xinput_device2;
-  XInputDevice _xinput_device3;
-  double _last_detection;
-#endif
-
 #if defined(__APPLE__) && !defined(CPPPARSER)
 #if defined(__APPLE__) && !defined(CPPPARSER)
   IOHIDManagerRef _hid_manager;
   IOHIDManagerRef _hid_manager;
 
 

+ 2 - 2
panda/src/device/inputDeviceNode.cxx

@@ -61,11 +61,11 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &,
   }
   }
 
 
   // calculate the battery level in percent and set a warning if the level is to low
   // calculate the battery level in percent and set a warning if the level is to low
-  if (_device->has_battery()) {
+  /*if (_device->has_battery()) {
     short bl = _device->get_battery_level();
     short bl = _device->get_battery_level();
     short bl_percent = bl / (_device->get_max_battery_level() / (short)100);
     short bl_percent = bl / (_device->get_max_battery_level() / (short)100);
     if (bl_percent <= low_battery_level) {
     if (bl_percent <= low_battery_level) {
       output.set_data(_low_battery_event_output, EventParameter(1));
       output.set_data(_low_battery_event_output, EventParameter(1));
     }
     }
-  }
+  }*/
 }
 }

+ 2 - 0
panda/src/device/p3device_composite1.cxx

@@ -12,3 +12,5 @@
 #include "inputDeviceSet.cxx"
 #include "inputDeviceSet.cxx"
 #include "ioKitInputDevice.cxx"
 #include "ioKitInputDevice.cxx"
 #include "linuxJoystickDevice.cxx"
 #include "linuxJoystickDevice.cxx"
+#include "winInputDeviceManager.cxx"
+#include "winRawInputDevice.cxx"

+ 384 - 0
panda/src/device/winInputDeviceManager.cxx

@@ -0,0 +1,384 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file winInputDeviceManager.cxx
+ * @author rdb
+ * @date 2018-01-21
+ */
+
+#include "winInputDeviceManager.h"
+#include "winRawInputDevice.h"
+#include "throw_event.h"
+
+#if defined(_WIN32) && !defined(CPPPARSER)
+
+/**
+ * Initializes the input device manager by scanning which devices are currently
+ * connected and setting up any platform-dependent structures necessary for
+ * listening for future device connect events.
+ */
+WinInputDeviceManager::
+WinInputDeviceManager() :
+  _xinput_device0(0),
+  _xinput_device1(1),
+  _xinput_device2(2),
+  _xinput_device3(3),
+  _message_hwnd(nullptr) {
+
+  // XInput provides four device slots, so we simply create four XInputDevice
+  // objects that are bound to the lifetime of the input manager.
+  _xinput_device0.local_object();
+  _xinput_device1.local_object();
+  _xinput_device2.local_object();
+  _xinput_device3.local_object();
+
+// This function is only available in Vista and later, so we use a wrapper.
+  HMODULE module = LoadLibraryA("cfgmgr32.dll");
+  if (module) {
+    _CM_Get_DevNode_PropertyW = (pCM_Get_DevNode_Property)GetProcAddress(module, "CM_Get_DevNode_PropertyW");
+  } else {
+    _CM_Get_DevNode_PropertyW = nullptr;
+  }
+
+  // Now create a message-only window for the raw input.
+  WNDCLASSEX wc = {};
+  wc.cbSize = sizeof(WNDCLASSEX);
+  wc.lpfnWndProc = window_proc;
+  wc.hInstance = GetModuleHandle(nullptr);
+  wc.lpszClassName = "InputDeviceManager";
+  if (!RegisterClassEx(&wc)) {
+    device_cat.warning()
+      << "Failed to register message-only window class.\n";
+    return;
+   }
+
+  _message_hwnd = CreateWindowEx(0, wc.lpszClassName, "InputDeviceManager", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
+  if (!_message_hwnd) {
+    device_cat.warning()
+      << "Failed to create message-only window.\n";
+    return;
+  }
+
+  // Now listen for raw input devices using the created message loop.
+  RAWINPUTDEVICE rid[3];
+  rid[0].usUsagePage = 1;
+  rid[0].usUsage = 4; // Joysticks
+  rid[0].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
+  rid[0].hwndTarget = _message_hwnd;
+  rid[1].usUsagePage = 1;
+  rid[1].usUsage = 5; // Gamepads
+  rid[1].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
+  rid[1].hwndTarget = _message_hwnd;
+  rid[2].usUsagePage = 1;
+  rid[2].usUsage = 8; // Multi-axis controllers (including 3D mice)
+  rid[2].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
+  rid[2].hwndTarget = _message_hwnd;
+  if (!RegisterRawInputDevices(rid, 3, sizeof(RAWINPUTDEVICE))) {
+    device_cat.warning()
+      << "Failed to register raw input devices.\n";
+  }
+}
+
+/**
+ * Closes any resources that the device manager was using to listen for events.
+ */
+WinInputDeviceManager::
+~WinInputDeviceManager() {
+  if (_message_hwnd != nullptr) {
+    DestroyWindow(_message_hwnd);
+    _message_hwnd = nullptr;
+  }
+}
+
+/**
+ * Called by the raw input device destructor.
+ */
+void WinInputDeviceManager::
+device_destroyed(WinRawInputDevice *device) {
+  LightMutexHolder holder(_lock);
+  // It shouldn't be in here, but let's check to be sure.
+  if (device->_handle != nullptr) {
+    _raw_devices.erase(device->_handle);
+  }
+
+  _raw_devices_by_path.erase(device->_path);
+}
+
+/**
+ * Called upon receiving a WM_INPUT message.
+ */
+void WinInputDeviceManager::
+on_input(HRAWINPUT handle) {
+  UINT size;
+  if (GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) < 0) {
+    return;
+  }
+
+  PRAWINPUT data = (PRAWINPUT)alloca(size);
+  if (GetRawInputData(handle, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)) <= 0) {
+    return;
+  }
+
+  // Look up the device in the map.
+  PT(WinRawInputDevice) device;
+  {
+    LightMutexHolder holder(_lock);
+    auto it = _raw_devices.find(data->header.hDevice);
+    if (it != _raw_devices.end()) {
+      device = it->second;
+    }
+  }
+  if (device != nullptr) {
+    device->on_input(data);
+  }
+}
+
+/**
+ * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_ARRIVAL.
+ */
+void WinInputDeviceManager::
+on_input_device_arrival(HANDLE handle) {
+  // Get the device path.
+  UINT size;
+  if (GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, nullptr, &size) != 0) {
+    return;
+  }
+
+  char *path = (char *)alloca(size);
+  if (path == nullptr ||
+      GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, (void *)path, &size) < 0) {
+    return;
+  }
+
+  if (device_cat.is_debug()) {
+    device_cat.debug()
+      << "GIDC_ARRIVAL: " << path << "\n";
+  }
+
+  // Get the device info.
+  RID_DEVICE_INFO info;
+  info.cbSize = sizeof(RID_DEVICE_INFO);
+  size = sizeof(RID_DEVICE_INFO);
+  if (GetRawInputDeviceInfoA(handle, RIDI_DEVICEINFO, &info, &size) <= 0) {
+    return;
+  }
+
+  // Strip the \\?\ prefix from the path.
+  while (path[0] == '\\' || path[0] == '?' || path[0] == '.') {
+    ++path;
+  }
+
+  // Now, replace # with \\ in the path, but only up to three components.
+  char *p = path;
+  int i = 0;
+  while (*p != '\0') {
+    if (*p == '#') {
+      if (i++ < 2) {
+        *p = '\\';
+      } else {
+        *p = '\0';
+        break;
+      }
+    }
+    ++p;
+  }
+
+  // Find the device node, which will be something like "HID\VID_0123..."
+  // Then we walk the device tree upward to get the USB node, which which will
+  // be something like a "USB\VID..." node, from which we can fetch the real
+  // USB device information (such as the product name).
+  string name, manufacturer;
+  DEVINST inst;
+  CONFIGRET ret = CM_Locate_DevNodeA(&inst, (DEVINSTID_A)path, CM_LOCATE_DEVNODE_PHANTOM);
+  if (ret == CR_SUCCESS) {
+    char buffer[4096];
+    ULONG buflen = 4096;
+    if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) {
+      name.assign(buffer);
+    }
+    buflen = 4096;
+    if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) {
+      if (strcmp(buffer, "(Standard system devices)") != 0) {
+        manufacturer.assign(buffer);
+      }
+    }
+
+    // Now walk the device tree upwards so fetch the bus-reported name of the
+    // parent USB device, which we prefer over the regular device description
+    // that is probably boring like "HID-compliant game controller".
+    DEVINST cur = inst;
+    DEVINST parent;
+    while (CM_Get_Parent(&parent, cur, 0) == CR_SUCCESS) {
+      buflen = 4096;
+      string dev_class;
+      if (CM_Get_DevNode_Registry_Property(parent, CM_DRP_CLASS, 0, buffer, &buflen, 0) == CR_SUCCESS) {
+        if (strcmp(buffer, "USB") == 0) {
+          // This is some generic USB device, like a hub.  We've gone too far.
+          break;
+        }
+        dev_class.assign(buffer);
+      }
+      cur = parent;
+
+      // While we're at it, maybe this one defines a manufacturer?
+      buflen = 4096;
+      if (manufacturer.empty() &&
+          CM_Get_DevNode_Registry_Property(cur, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) {
+        if (strcmp(buffer, "(Standard system devices)") != 0) {
+          manufacturer.assign(buffer);
+        }
+      }
+
+      // If it's a generic HID device, take the name from the USB bus.
+      // See devpkey.h for the available property keys.
+      static const DEVPROPKEY bus_reported_device_desc = {
+        {0x540b947e, 0x8b40, 0x45bc, {0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2}},
+        4,
+      };
+      DEVPROPTYPE type;
+      buflen = 4096;
+      if (dev_class == "HIDClass" && _CM_Get_DevNode_PropertyW != nullptr &&
+          _CM_Get_DevNode_PropertyW(cur, &bus_reported_device_desc, &type, (PBYTE)buffer, &buflen, 0) == CR_SUCCESS &&
+          type == DEVPROP_TYPE_STRING) {
+        TextEncoder encoder;
+        name.assign(encoder.encode_wtext((wchar_t *)buffer));
+        break;
+      } else {
+        buflen = 4096;
+        if (CM_Get_DevNode_Registry_Property(cur, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) {
+          // We'll pass if it has this awfully boring name.  Is there a
+          // language-independent way to check this?
+          if (strcmp(buffer, "USB Input Device") != 0) {
+            name.assign(buffer);
+          }
+        }
+      }
+    }
+  } else if (device_cat.is_debug()) {
+    // No big deal, we just won't be able to get the name.
+    device_cat.debug()
+      << "Could not locate device node " << path << " (" << ret << ")\n";
+  }
+
+  // Is this an XInput device?  If so, handle it via XInput, which allows us
+  // to handle independent left/right triggers as well as vibration output.
+  if (info.dwType == RIM_TYPEHID && strstr(path, "&IG_") != nullptr) {
+    // This is a device we should handle via the XInput API.  Check which of
+    // the four players was the lucky one.
+    WinRawInputDevice idev(this, path);
+    if (_xinput_device0.check_arrival(info, inst, name, manufacturer)) {
+      add_device(&_xinput_device0);
+    }
+    if (_xinput_device1.check_arrival(info, inst, name, manufacturer)) {
+      add_device(&_xinput_device1);
+    }
+    if (_xinput_device2.check_arrival(info, inst, name, manufacturer)) {
+      add_device(&_xinput_device2);
+    }
+    if (_xinput_device3.check_arrival(info, inst, name, manufacturer)) {
+      add_device(&_xinput_device3);
+    }
+    return;
+  }
+
+  LightMutexHolder holder(_lock);
+
+  // Do we have a device by this path already?  This can happen if the
+  // user keeps around a pointer to a disconnected device in the hope that
+  // it will reconnect later.
+  PT(WinRawInputDevice) device;
+  auto it = _raw_devices_by_path.find(path);
+  if (it != _raw_devices_by_path.end()) {
+    device = it->second;
+  } else {
+    device = new WinRawInputDevice(this, path);
+    _raw_devices_by_path[path] = device;
+  }
+
+  if (device->on_arrival(handle, info, move(name))) {
+    _raw_devices[handle] = device;
+    _connected_devices.add_device(device);
+
+    if (device_cat.is_debug()) {
+      device_cat.debug()
+        << "Discovered input device " << *device << "\n";
+    }
+    throw_event("connect-device", device.p());
+  }
+}
+
+/**
+ * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_REMOVAL.
+ */
+void WinInputDeviceManager::
+on_input_device_removal(HANDLE handle) {
+  // The handle will probably no longer be valid after this, so find the
+  // device and remove it from _raw_devices.  However, we keep it in
+  // _raw_devices_by_path in case there's still a pointer around to it.
+
+  // We keep the pointer outside the lock because the input device
+  // destructor calls back to InputDeviceManager.
+  PT(WinRawInputDevice) device;
+  {
+    LightMutexHolder holder(_lock);
+    auto it = _raw_devices.find(handle);
+    if (it != _raw_devices.end()) {
+      device = move(it->second);
+      _raw_devices.erase(it);
+      device->on_removal();
+
+      if (_connected_devices.remove_device(device)) {
+        throw_event("disconnect-device", device.p());
+      }
+      if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Removed input device " << *device << "\n";
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of the message loop.
+ */
+LRESULT WINAPI WinInputDeviceManager::
+window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
+  WinInputDeviceManager *mgr;
+  switch (msg) {
+  case WM_INPUT:
+    mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr();
+    if (mgr != nullptr) {
+      mgr->on_input((HRAWINPUT)lparam);
+    }
+    break;
+
+  case WM_INPUT_DEVICE_CHANGE:
+    switch (LOWORD(wparam)) {
+    case GIDC_ARRIVAL:
+      mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr();
+      if (mgr != nullptr) {
+        mgr->on_input_device_arrival((HANDLE)lparam);
+      }
+      break;
+
+    case GIDC_REMOVAL:
+      mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr();
+      if (mgr != nullptr) {
+        mgr->on_input_device_removal((HANDLE)lparam);
+      }
+      break;
+    }
+    break;
+
+  default:
+    break;
+  }
+  return DefWindowProcW(hwnd, msg, wparam, lparam);
+}
+
+#endif

+ 64 - 0
panda/src/device/winInputDeviceManager.h

@@ -0,0 +1,64 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file winInputDeviceManager.h
+ * @author rdb
+ * @date 2018-01-21
+ */
+
+#ifndef WININPUTDEVICEMANAGER_H
+#define WININPUTDEVICEMANAGER_H
+
+#include "inputDeviceManager.h"
+
+#if defined(_WIN32) && !defined(CPPPARSER)
+
+#include "xinputDevice.h"
+
+#include <CfgMgr32.h>
+#include <devpkey.h>
+
+class WinRawInputDevice;
+
+/**
+ * This is the Windows implementation of InputDeviceManager, managing both
+ * XInput controllers and raw input devices.
+ */
+class EXPCL_PANDA_DEVICE WinInputDeviceManager FINAL : public InputDeviceManager {
+private:
+  WinInputDeviceManager();
+  ~WinInputDeviceManager();
+
+public:
+  void device_destroyed(WinRawInputDevice *device);
+
+  void on_input(HRAWINPUT handle);
+  void on_input_device_arrival(HANDLE handle);
+  void on_input_device_removal(HANDLE handle);
+
+private:
+  // There are always exactly four of these in existence.
+  XInputDevice _xinput_device0;
+  XInputDevice _xinput_device1;
+  XInputDevice _xinput_device2;
+  XInputDevice _xinput_device3;
+
+  HWND _message_hwnd;
+  pmap<HANDLE, WinRawInputDevice *> _raw_devices;
+  pmap<string, WinRawInputDevice *> _raw_devices_by_path;
+
+  static LRESULT WINAPI window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+
+  typedef CONFIGRET (*pCM_Get_DevNode_Property)(DEVINST, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, PULONG, ULONG);
+  pCM_Get_DevNode_Property _CM_Get_DevNode_PropertyW;
+
+  friend class InputDeviceManager;
+};
+
+#endif
+#endif

+ 655 - 0
panda/src/device/winRawInputDevice.cxx

@@ -0,0 +1,655 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file winRawInputDevice.cxx
+ * @author rdb
+ * @date 2018-01-19
+ */
+
+#include "winRawInputDevice.h"
+#include "gamepadButton.h"
+#include "mouseButton.h"
+
+#if defined(_WIN32) && !defined(CPPPARSER)
+
+#include <CfgMgr32.h>
+#include <devpkey.h>
+
+// Copy definitions from hidusage.h, until we can drop support for the 7.1 SDK
+typedef USHORT USAGE, *PUSAGE;
+
+#define HID_USAGE_PAGE_UNDEFINED       ((USAGE) 0x00)
+#define HID_USAGE_PAGE_GENERIC         ((USAGE) 0x01)
+#define HID_USAGE_PAGE_SIMULATION      ((USAGE) 0x02)
+#define HID_USAGE_PAGE_VR              ((USAGE) 0x03)
+#define HID_USAGE_PAGE_SPORT           ((USAGE) 0x04)
+#define HID_USAGE_PAGE_GAME            ((USAGE) 0x05)
+#define HID_USAGE_PAGE_KEYBOARD        ((USAGE) 0x07)
+#define HID_USAGE_PAGE_LED             ((USAGE) 0x08)
+#define HID_USAGE_PAGE_BUTTON          ((USAGE) 0x09)
+
+#define HID_USAGE_GENERIC_POINTER      ((USAGE) 0x01)
+#define HID_USAGE_GENERIC_MOUSE        ((USAGE) 0x02)
+#define HID_USAGE_GENERIC_JOYSTICK     ((USAGE) 0x04)
+#define HID_USAGE_GENERIC_GAMEPAD      ((USAGE) 0x05)
+#define HID_USAGE_GENERIC_KEYBOARD     ((USAGE) 0x06)
+#define HID_USAGE_GENERIC_KEYPAD       ((USAGE) 0x07)
+#define HID_USAGE_GENERIC_SYSTEM_CTL   ((USAGE) 0x80)
+
+#define HID_USAGE_GENERIC_X            ((USAGE) 0x30)
+#define HID_USAGE_GENERIC_Y            ((USAGE) 0x31)
+#define HID_USAGE_GENERIC_Z            ((USAGE) 0x32)
+#define HID_USAGE_GENERIC_RX           ((USAGE) 0x33)
+#define HID_USAGE_GENERIC_RY           ((USAGE) 0x34)
+#define HID_USAGE_GENERIC_RZ           ((USAGE) 0x35)
+#define HID_USAGE_GENERIC_SLIDER       ((USAGE) 0x36)
+#define HID_USAGE_GENERIC_DIAL         ((USAGE) 0x37)
+#define HID_USAGE_GENERIC_WHEEL        ((USAGE) 0x38)
+#define HID_USAGE_GENERIC_HATSWITCH    ((USAGE) 0x39)
+
+// Copy definitions from hidpi.h, until we can drop support for the 7.1 SDK
+#define HIDP_STATUS_SUCCESS ((NTSTATUS)(0x11 << 16))
+
+typedef enum _HIDP_REPORT_TYPE {
+  HidP_Input,
+  HidP_Output,
+  HidP_Feature
+} HIDP_REPORT_TYPE;
+
+typedef struct _HIDP_BUTTON_CAPS {
+  USAGE UsagePage;
+  UCHAR ReportID;
+  BOOLEAN IsAlias;
+  USHORT BitField;
+  USHORT LinkCollection;
+  USAGE LinkUsage;
+  USAGE LinkUsagePage;
+  BOOLEAN IsRange;
+  BOOLEAN IsStringRange;
+  BOOLEAN IsDesignatorRange;
+  BOOLEAN IsAbsolute;
+  ULONG Reserved[10];
+  union {
+    struct {
+      USAGE UsageMin, UsageMax;
+      USHORT StringMin, StringMax;
+      USHORT DesignatorMin, DesignatorMax;
+      USHORT DataIndexMin, DataIndexMax;
+    } Range;
+    struct  {
+      USAGE Usage, Reserved1;
+      USHORT StringIndex, Reserved2;
+      USHORT DesignatorIndex, Reserved3;
+      USHORT DataIndex, Reserved4;
+    } NotRange;
+  };
+} HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS;
+
+typedef struct _HIDP_VALUE_CAPS {
+  USAGE UsagePage;
+  UCHAR ReportID;
+  BOOLEAN IsAlias;
+  USHORT BitField;
+  USHORT LinkCollection;
+  USAGE LinkUsage;
+  USAGE LinkUsagePage;
+  BOOLEAN IsRange;
+  BOOLEAN IsStringRange;
+  BOOLEAN IsDesignatorRange;
+  BOOLEAN IsAbsolute;
+  BOOLEAN HasNull;
+  UCHAR Reserved;
+  USHORT BitSize;
+  USHORT ReportCount;
+  USHORT Reserved2[5];
+  ULONG UnitsExp;
+  ULONG Units;
+  LONG LogicalMin, LogicalMax;
+  LONG PhysicalMin, PhysicalMax;
+  union {
+    struct {
+      USAGE UsageMin, UsageMax;
+      USHORT StringMin, StringMax;
+      USHORT DesignatorMin, DesignatorMax;
+      USHORT DataIndexMin, DataIndexMax;
+    } Range;
+    struct {
+      USAGE Usage, Reserved1;
+      USHORT StringIndex, Reserved2;
+      USHORT DesignatorIndex, Reserved3;
+      USHORT DataIndex, Reserved4;
+    } NotRange;
+  };
+} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS;
+
+typedef PUCHAR PHIDP_REPORT_DESCRIPTOR;
+typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
+
+typedef struct _HIDP_CAPS {
+  USAGE Usage;
+  USAGE UsagePage;
+  USHORT InputReportByteLength;
+  USHORT OutputReportByteLength;
+  USHORT FeatureReportByteLength;
+  USHORT Reserved[17];
+  USHORT NumberLinkCollectionNodes;
+  USHORT NumberInputButtonCaps;
+  USHORT NumberInputValueCaps;
+  USHORT NumberInputDataIndices;
+  USHORT NumberOutputButtonCaps;
+  USHORT NumberOutputValueCaps;
+  USHORT NumberOutputDataIndices;
+  USHORT NumberFeatureButtonCaps;
+  USHORT NumberFeatureValueCaps;
+  USHORT NumberFeatureDataIndices;
+} HIDP_CAPS, *PHIDP_CAPS;
+
+typedef struct _HIDP_DATA {
+  USHORT DataIndex;
+  USHORT Reserved;
+  union {
+    ULONG RawValue;
+    BOOLEAN On;
+  };
+} HIDP_DATA, *PHIDP_DATA;
+
+typedef LONG NTSTATUS;
+typedef NTSTATUS (*pHidP_GetCaps)(PHIDP_PREPARSED_DATA, PHIDP_CAPS);
+typedef NTSTATUS (*pHidP_GetButtonCaps)(HIDP_REPORT_TYPE, PHIDP_BUTTON_CAPS, PUSHORT, PHIDP_PREPARSED_DATA);
+typedef NTSTATUS (*pHidP_GetValueCaps)(HIDP_REPORT_TYPE, PHIDP_VALUE_CAPS, PUSHORT, PHIDP_PREPARSED_DATA);
+typedef NTSTATUS (*pHidP_GetData)(HIDP_REPORT_TYPE, PHIDP_DATA, PULONG, PHIDP_PREPARSED_DATA, PCHAR, ULONG);
+typedef ULONG (*pHidP_MaxDataListLength)(HIDP_REPORT_TYPE, PHIDP_PREPARSED_DATA);
+
+static pHidP_GetCaps _HidP_GetCaps = nullptr;
+static pHidP_GetButtonCaps _HidP_GetButtonCaps = nullptr;
+static pHidP_GetValueCaps _HidP_GetValueCaps = nullptr;
+static pHidP_GetData _HidP_GetData = nullptr;
+static pHidP_MaxDataListLength _HidP_MaxDataListLength = nullptr;
+
+/**
+ * Static method to initialize the HID parser library.  We load it dynamically
+ * because the Windows 7.1 SDK doesn't ship hid.lib.
+ */
+static bool init_hidp() {
+  HMODULE module = LoadLibraryA("hid.dll");
+  if (module) {
+    if (device_cat.is_debug()) {
+      device_cat.debug()
+        << "Successfully loaded hid.dll\n";
+    }
+
+    _HidP_GetCaps = (pHidP_GetCaps)GetProcAddress(module, "HidP_GetCaps");
+    _HidP_GetButtonCaps = (pHidP_GetButtonCaps)GetProcAddress(module, "HidP_GetButtonCaps");
+    _HidP_GetValueCaps = (pHidP_GetValueCaps)GetProcAddress(module, "HidP_GetValueCaps");
+    _HidP_GetData = (pHidP_GetData)GetProcAddress(module, "HidP_GetData");
+    _HidP_MaxDataListLength = (pHidP_MaxDataListLength)GetProcAddress(module, "HidP_MaxDataListLength");
+
+    if (_HidP_GetCaps == nullptr || _HidP_GetButtonCaps == nullptr ||
+        _HidP_GetValueCaps == nullptr || _HidP_GetData == nullptr ||
+        _HidP_MaxDataListLength == nullptr) {
+      device_cat.error()
+        << "Failed to locate function pointers in hid.dll\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  device_cat.error()
+    << "Failed to load hid.dll.\n";
+  return false;
+}
+
+/**
+ * Protected constructor.  Given a raw device handle.
+ */
+WinRawInputDevice::
+WinRawInputDevice(WinInputDeviceManager *manager, const char *path) :
+  _manager(manager),
+  _path(path),
+  _max_data_count(0),
+  _preparsed(nullptr) {
+}
+
+/**
+ *
+ */
+WinRawInputDevice::
+~WinRawInputDevice() {
+  // Unregister the device from the manager.
+  LightMutexHolder holder(_lock);
+  if (_manager != nullptr) {
+    _manager->device_destroyed(this);
+  }
+  if (_preparsed != nullptr) {
+    free(_preparsed);
+    _preparsed = nullptr;
+  }
+}
+
+/**
+ * Called by InputDeviceManager when this device is connected.  Returns true
+ * if the device was connected successfully.
+ */
+bool WinRawInputDevice::
+on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) {
+  LightMutexHolder holder(_lock);
+
+  _name = move(name);
+
+  switch (info.dwType) {
+  case RIM_TYPEMOUSE:
+    _device_class = DC_mouse;
+    break;
+
+  case RIM_TYPEKEYBOARD:
+    _device_class = DC_keyboard;
+    break;
+
+  case RIM_TYPEHID:
+    _vendor_id = info.hid.dwVendorId;
+    _product_id = info.hid.dwProductId;
+
+    // Gamepads
+    if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
+        info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) {
+      _device_class = DC_gamepad;
+
+    // Flight sticks
+    } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
+               info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) {
+      _device_class = DC_flight_stick;
+
+    // Mice
+    } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
+               info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) {
+      _device_class = DC_mouse;
+
+    // Keyboards
+    } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
+               info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) {
+      _device_class = DC_keyboard;
+
+    // 3Dconnexion SpaceNavigator and friends.
+    } else if (_vendor_id == 0x046d &&
+        (_product_id == 0xc623 ||
+         _product_id == 0xc625 ||
+         _product_id == 0xc626 ||
+         _product_id == 0xc627 ||
+         _product_id == 0xc628 ||
+         _product_id == 0xc629 ||
+         _product_id == 0xc62b)) {
+      _device_class = DC_3d_mouse;
+    }
+    break;
+
+  default:
+    return false;
+  }
+
+  // Initialize hid.dll, which provides the HID parser functions.
+  static bool hid_initialized = false;
+  if (!hid_initialized) {
+    if (!init_hidp()) {
+      return false;
+    }
+    hid_initialized = true;
+  }
+
+  // Get the "preparsed data", which we can parse with the HID parser API.
+  UINT size = 0;
+  if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0) {
+    return false;
+  }
+
+  PHIDP_PREPARSED_DATA buffer = (PHIDP_PREPARSED_DATA)malloc(size);
+  if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, buffer, &size) <= 0) {
+    return false;
+  }
+  _preparsed = buffer;
+
+  HIDP_CAPS caps;
+  if (_HidP_GetCaps(buffer, &caps) != HIDP_STATUS_SUCCESS) {
+    device_cat.warning()
+      << "Failed to get capabilities from HID preparsed data.\n";
+    return false;
+  }
+
+  // Prepare a mapping of data indices to button/control indices.
+  _indices.resize(caps.NumberInputDataIndices);
+
+  _buttons.clear();
+  _controls.clear();
+
+  USHORT num_button_caps = caps.NumberInputButtonCaps;
+  PHIDP_BUTTON_CAPS button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS));
+  _HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer);
+
+  for (USHORT i = 0; i < num_button_caps; ++i) {
+    HIDP_BUTTON_CAPS &cap = button_caps[i];
+    int upper = 0;
+    if (cap.IsRange) {
+      upper = (cap.Range.UsageMax - cap.Range.UsageMin);
+
+      if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Found button range: DataIndex=" << dec
+          << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
+          << ", ReportID=" << (int)cap.ReportID
+          << ", UsagePage=0x" << hex << cap.UsagePage
+          << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
+          << dec << "\n";
+      }
+    } else {
+      if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Found button: DataIndex=" << dec << cap.NotRange.DataIndex
+          << ", ReportID=" << dec << (int)cap.ReportID
+          << ", UsagePage=0x" << cap.UsagePage
+          << ", Usage=0x" << cap.NotRange.Usage
+          << dec << "\n";
+      }
+    }
+
+    // Windows will only tell us which buttons in a report are "on", so we
+    // need to keep track of which buttons exist in which report so that we
+    // can figure out which ones are off.
+    if (cap.ReportID >= _report_buttons.size()) {
+      _report_buttons.resize(cap.ReportID + 1);
+    }
+    for (int j = 0; j <= upper; ++j) {
+      USAGE usage = j + cap.Range.UsageMin;
+      USHORT data_index = j + cap.Range.DataIndexMin;
+      ButtonHandle handle = ButtonHandle::none();
+      switch (cap.UsagePage) {
+      case HID_USAGE_PAGE_BUTTON:
+        if (_device_class == DC_gamepad) {
+          static const ButtonHandle gamepad_buttons[] = {
+            ButtonHandle::none(),
+            GamepadButton::action_a(),
+            GamepadButton::action_b(),
+            GamepadButton::action_x(),
+            GamepadButton::action_y(),
+            GamepadButton::lshoulder(),
+            GamepadButton::rshoulder(),
+            GamepadButton::start(),
+            GamepadButton::back(),
+            GamepadButton::lstick(),
+            GamepadButton::rstick(),
+          };
+          if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) {
+            handle = gamepad_buttons[usage];
+          }
+        } else if (_device_class == DC_flight_stick) {
+          if (usage > 0) {
+            handle = GamepadButton::joystick(usage - 1);
+          }
+        } else if (_device_class == DC_mouse) {
+          // In Panda, wheel and right button are flipped around...
+          int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1);
+          handle = MouseButton::button(button);
+        }
+        break;
+      }
+
+      int button_index = _buttons.size();
+      _report_buttons[cap.ReportID].set_bit(button_index);
+      _indices[data_index] = Index::button(button_index);
+      _buttons.push_back(ButtonState(handle));
+    }
+  }
+
+  USHORT num_value_caps = caps.NumberInputValueCaps;
+  PHIDP_VALUE_CAPS value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS));
+  _HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer);
+
+  _hat_data_index = -1;
+
+  for (USHORT i = 0; i < num_value_caps; ++i) {
+    HIDP_VALUE_CAPS &cap = value_caps[i];
+    int upper = 0;
+    if (cap.IsRange) {
+      upper = (cap.Range.UsageMax - cap.Range.UsageMin);
+
+      if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Found value range: DataIndex=" << dec
+          << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
+          << ", ReportID=" << (int)cap.ReportID
+          << ", UsagePage=0x" << hex << cap.UsagePage
+          << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
+          << dec << ", LogicalMin=" << cap.LogicalMin
+          << ", LogicalMax=" << cap.LogicalMax << "\n";
+      }
+    } else {
+      if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Found value: DataIndex=" << dec << cap.NotRange.DataIndex
+          << ", ReportID=" << dec << (int)cap.ReportID
+          << ", UsagePage=0x" << hex << cap.UsagePage
+          << ", Usage=0x" << cap.NotRange.Usage
+          << dec << ", LogicalMin=" << cap.LogicalMin
+          << ", LogicalMax=" << cap.LogicalMax << "\n";
+      }
+    }
+
+    for (int j = 0; j <= upper; ++j) {
+      USAGE usage = j + cap.Range.UsageMin;
+      USHORT data_index = j + cap.Range.DataIndexMin;
+      bool is_signed = true;
+
+      // My gamepads give this odd invalid range.
+      if (cap.LogicalMin == 0 && cap.LogicalMax == -1) {
+        cap.LogicalMax = 65535;
+        is_signed = false;
+      }
+
+      ControlAxis axis = C_none;
+      switch (cap.UsagePage) {
+      case HID_USAGE_PAGE_GENERIC:
+        switch (usage) {
+          case HID_USAGE_GENERIC_X:
+          if (_device_class == DC_gamepad) {
+            axis = C_left_x;
+          } else if (_device_class == DC_flight_stick) {
+            axis = C_roll;
+          } else {
+            axis = C_x;
+          }
+          break;
+        case HID_USAGE_GENERIC_Y:
+          if (_device_class == DC_gamepad) {
+            axis = C_left_y;
+            swap(cap.LogicalMin, cap.LogicalMax);
+          } else if (_device_class == DC_flight_stick) {
+            axis = C_pitch;
+          } else {
+            axis = C_y;
+            swap(cap.LogicalMin, cap.LogicalMax);
+          }
+          break;
+        case HID_USAGE_GENERIC_Z:
+          if (_device_class == DC_gamepad) {
+            axis = C_left_trigger;
+          } else if (_device_class == DC_flight_stick) {
+            axis = C_throttle;
+          } else {
+            axis = C_z;
+            swap(cap.LogicalMin, cap.LogicalMax);
+          }
+          break;
+        case HID_USAGE_GENERIC_RX:
+          if (_device_class == DC_gamepad) {
+            axis = C_right_x;
+          } else {
+            axis = C_pitch;
+          }
+          break;
+        case HID_USAGE_GENERIC_RY:
+          if (_device_class == DC_gamepad) {
+            axis = C_right_y;
+          } else {
+            axis = C_roll;
+          }
+          swap(cap.LogicalMin, cap.LogicalMax);
+          break;
+        case HID_USAGE_GENERIC_RZ:
+          if (_device_class == DC_gamepad) {
+            axis = C_right_trigger;
+          } else {
+            // Flip to match Panda's convention for heading.
+            axis = C_yaw;
+            swap(cap.LogicalMin, cap.LogicalMax);
+          }
+          break;
+        case HID_USAGE_GENERIC_SLIDER:
+          // Flip to match Panda's convention for heading.
+          axis = C_rudder;
+          swap(cap.LogicalMin, cap.LogicalMax);
+          break;
+        case HID_USAGE_GENERIC_WHEEL:
+          axis = C_wheel;
+          break;
+        case HID_USAGE_GENERIC_HATSWITCH:
+          // This is handled specially.
+          _hat_data_index = data_index;
+          _hat_data_minimum = cap.LogicalMin;
+          continue;
+        }
+        break;
+      }
+
+      int control_index;
+      if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) {
+        // T.Flight Hotas X throttle is reversed and can go backwards.
+        control_index = add_control(axis, cap.LogicalMax, cap.LogicalMin, true);
+      } else if (!is_signed) {
+        // All axes on the weird XInput-style mappings go from -1 to 1
+        control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax, true);
+      } else {
+        control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax);
+      }
+      _indices[data_index] = Index::control(control_index, is_signed);
+    }
+  }
+
+  // Do we need to emulate a hat switch or directional pad?
+  if (_hat_data_index != -1) {
+    _hat_left_button = (int)_buttons.size();
+    if (_device_class == DC_gamepad) {
+      _buttons.push_back(ButtonState(GamepadButton::dpad_left()));
+      _buttons.push_back(ButtonState(GamepadButton::dpad_right()));
+      _buttons.push_back(ButtonState(GamepadButton::dpad_down()));
+      _buttons.push_back(ButtonState(GamepadButton::dpad_up()));
+    } else {
+      _buttons.push_back(ButtonState(GamepadButton::hat_left()));
+      _buttons.push_back(ButtonState(GamepadButton::hat_right()));
+      _buttons.push_back(ButtonState(GamepadButton::hat_down()));
+      _buttons.push_back(ButtonState(GamepadButton::hat_up()));
+    }
+  }
+
+  _max_data_count = _HidP_MaxDataListLength(HidP_Input, buffer);
+
+  _handle = handle;
+  _is_connected = true;
+  return true;
+}
+
+/**
+ * Called by InputDeviceManager when this device is removed.
+ */
+void WinRawInputDevice::
+on_removal() {
+  LightMutexHolder holder(_lock);
+  _is_connected = false;
+  _handle = nullptr;
+  if (_preparsed != nullptr) {
+    delete _preparsed;
+    _preparsed = nullptr;
+  }
+  _indices.clear();
+  _report_buttons.clear();
+}
+
+void WinRawInputDevice::
+on_input(PRAWINPUT input) {
+  nassertv(input != nullptr);
+  nassertv(_preparsed != nullptr);
+
+  BYTE *ptr = input->data.hid.bRawData;
+  if (input->data.hid.dwSizeHid == 0) {
+    return;
+  }
+
+  PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count);
+  nassertv(data != nullptr);
+  ULONG count;
+
+  LightMutexHolder holder(_lock);
+
+  for (DWORD i = 0; i < input->data.hid.dwCount; ++i) {
+    // The first byte is the report identifier.  We need it to figure out
+    // which buttons are off, since each report only contains the buttons that
+    // are "on".
+    UCHAR report_id = ptr[0];
+    BitArray unset_buttons = _report_buttons[report_id];
+
+    count = _max_data_count;
+    NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, (PCHAR)ptr, input->data.hid.dwSizeHid);
+    if (status == HIDP_STATUS_SUCCESS) {
+      for (ULONG di = 0; di < count; ++di) {
+        if (data[di].DataIndex != _hat_data_index) {
+          const Index &idx = _indices[data[di].DataIndex];
+          if (idx._control >= 0) {
+            if (idx._signed) {
+              control_changed(idx._control, (SHORT)data[di].RawValue);
+            } else {
+              control_changed(idx._control, data[di].RawValue);
+            }
+          }
+          if (idx._button >= 0) {
+            unset_buttons.clear_bit(idx._button);
+            button_changed(idx._button, (data[di].On != FALSE));
+          }
+        } else {
+          int value = (int)data[di].RawValue - _hat_data_minimum;
+          button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left
+          button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right
+          button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down
+          button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up
+        }
+      }
+
+      // Now unset the buttons in this report that aren't pressed.
+      int button_index = unset_buttons.get_lowest_on_bit();
+      while (button_index >= 0) {
+        button_changed(button_index, false);
+        unset_buttons.clear_bit(button_index);
+        button_index = unset_buttons.get_lowest_on_bit();
+      }
+    } else if (device_cat.is_spam()) {
+      device_cat.spam()
+        << "Failed to get data from raw device " << _path
+        << " (error 0x" << hex << (status & 0xffffffffu) << dec << ")\n";
+    }
+
+    ptr += input->data.hid.dwSizeHid;
+  }
+}
+
+/**
+ * Polls the input device for new activity, to ensure it contains the latest
+ * events.  This will only have any effect for some types of input devices;
+ * others may be updated automatically, and this method will be a no-op.
+ */
+void WinRawInputDevice::
+do_poll() {
+}
+
+#endif  // _WIN32

+ 85 - 0
panda/src/device/winRawInputDevice.h

@@ -0,0 +1,85 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file winRawInputDevice.h
+ * @author rdb
+ * @date 2018-01-19
+ */
+
+#ifndef WINRAWINPUTDEVICE_H
+#define WINRAWINPUTDEVICE_H
+
+#include "pandabase.h"
+#include "inputDevice.h"
+#include "bitArray.h"
+
+#if defined(_WIN32) && !defined(CPPPARSER)
+
+class WinInputDeviceManager;
+
+/**
+ * This implementation of InputDevice uses the Win32 raw input API and the HID
+ * parser library to support a wide range of devices.
+ */
+class EXPCL_PANDA_DEVICE WinRawInputDevice FINAL : public InputDevice {
+public:
+  WinRawInputDevice(WinInputDeviceManager *manager, const char *path);
+  ~WinRawInputDevice();
+
+  bool on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name);
+  void on_removal();
+  void on_input(PRAWINPUT input);
+
+private:
+  virtual void do_poll();
+
+private:
+  const string _path;
+  HANDLE _handle;
+  DWORD _size;
+  void *_preparsed;
+  ULONG _max_data_count;
+  ULONG _max_usage_count;
+
+  // Indexed by report ID
+  pvector<BitArray> _report_buttons;
+
+  // Either a button index or a control index.
+  struct Index {
+    Index() : _button(-1), _control(-1) {}
+
+    static Index button(int index) {
+      Index idx;
+      idx._button = index;
+      return idx;
+    }
+    static Index control(int index, bool is_signed=true) {
+      Index idx;
+      idx._control = index;
+      idx._signed = is_signed;
+      return idx;
+    }
+
+    int _button;
+    int _control;
+    bool _signed;
+  };
+
+  // Maps a "data index" to either button index or control index.
+  pvector<Index> _indices;
+  int _hat_data_index;
+  int _hat_data_minimum;
+  int _hat_left_button;
+
+  WinInputDeviceManager *_manager;
+  friend class WinInputDeviceManager;
+};
+
+#endif  // _WIN32
+
+#endif

+ 113 - 95
panda/src/device/xInputDevice.cxx

@@ -13,7 +13,8 @@
 
 
 #include "xInputDevice.h"
 #include "xInputDevice.h"
 
 
-#ifdef _WIN32
+#if defined(_WIN32) && !defined(CPPPARSER)
+
 #include "gamepadButton.h"
 #include "gamepadButton.h"
 #include "inputDeviceManager.h"
 #include "inputDeviceManager.h"
 #include "string_utils.h"
 #include "string_utils.h"
@@ -73,27 +74,44 @@ typedef struct _XINPUT_BATTERY_INFORMATION {
 } XINPUT_BATTERY_INFORMATION;
 } XINPUT_BATTERY_INFORMATION;
 
 
 // Undocumented, I figured out how this looks by trial and error.
 // Undocumented, I figured out how this looks by trial and error.
-struct XINPUT_BUSINFO {
+typedef struct _XINPUT_BUSINFO {
   WORD VendorID;
   WORD VendorID;
   WORD ProductID;
   WORD ProductID;
   WORD RevisionID;
   WORD RevisionID;
   WORD Unknown1; // Unknown - padding?
   WORD Unknown1; // Unknown - padding?
   DWORD InstanceID;
   DWORD InstanceID;
   DWORD Unknown2;
   DWORD Unknown2;
-  //WORD Unknown3;
-};
+  WORD Unknown3;
+} XINPUT_BUSINFO;
+
+typedef struct _XINPUT_CAPABILITIES_EX {
+  BYTE Type;
+  BYTE SubType;
+  WORD Flags;
+  XINPUT_GAMEPAD Gamepad;
+  XINPUT_VIBRATION Vibration;
+
+  // The following fields are undocumented.
+  WORD VendorID;
+  WORD ProductID;
+  WORD RevisionID;
+  WORD Unknown1;
+  WORD Unknown2;
+} XINPUT_CAPABILITIES_EX;
 
 
 typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *);
 typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *);
 typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *);
 typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *);
 typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *);
 typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *);
+typedef DWORD (*pXInputGetCapabilitiesEx)(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX *);
 typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *);
 typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *);
 typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *);
 typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *);
 
 
-static pXInputGetState get_state = NULL;
-static pXInputSetState set_state = NULL;
-static pXInputGetCapabilities get_capabilities = NULL;
-static pXInputGetBatteryInformation get_battery_information = NULL;
-static pXInputGetBaseBusInformation get_base_bus_information = NULL;
+static pXInputGetState get_state = nullptr;
+static pXInputSetState set_state = nullptr;
+static pXInputGetCapabilities get_capabilities = nullptr;
+static pXInputGetCapabilitiesEx get_capabilities_ex = nullptr;
+static pXInputGetBatteryInformation get_battery_information = nullptr;
+static pXInputGetBaseBusInformation get_base_bus_information = nullptr;
 
 
 bool XInputDevice::_initialized = false;
 bool XInputDevice::_initialized = false;
 
 
@@ -108,26 +126,8 @@ XInputDevice(DWORD user_index) :
 
 
   nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT);
   nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT);
 
 
-  if (!_initialized) {
-    nassertv(init_xinput());
-  }
-
-  _name = "XInput Device #";
-  _name += format_string(user_index);
-
   _controls.resize(6);
   _controls.resize(6);
   _buttons.resize(16);
   _buttons.resize(16);
-
-  // Check if the device is connected.  If so, initialize it.
-  XINPUT_CAPABILITIES caps;
-  XINPUT_STATE state;
-  if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS &&
-      get_state(_index, &state) == ERROR_SUCCESS) {
-    _is_connected = true;
-    init_device(caps, state);
-  } else {
-    _is_connected = false;
-  }
 }
 }
 
 
 /**
 /**
@@ -138,17 +138,79 @@ XInputDevice::
   do_set_vibration(0, 0);
   do_set_vibration(0, 0);
 }
 }
 
 
+/**
+ * Called when a new input device arrives in the InputDeviceManager.  This
+ * method checks whether it matches this XInput device.
+ */
+bool XInputDevice::
+check_arrival(const RID_DEVICE_INFO &info, DEVINST inst,
+              const string &name, const string &manufacturer) {
+  LightMutexHolder holder(_lock);
+  if (_is_connected) {
+    return false;
+  }
+
+  if (!_initialized) {
+    nassertr_always(init_xinput(), false);
+  }
+
+  XINPUT_CAPABILITIES_EX caps = {0};
+  XINPUT_STATE state;
+  if ((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) != ERROR_SUCCESS) &&
+       get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) != ERROR_SUCCESS) {
+    return false;
+  }
+
+  // Extra check for VID/PID if we have it, just to be sure.
+  if ((caps.VendorID != 0 && caps.VendorID != info.hid.dwVendorId) ||
+      (caps.ProductID != 0 && caps.ProductID != info.hid.dwProductId)) {
+    return false;
+  }
+
+  // Yes, take the name and manufacturer.
+  if (!name.empty()) {
+    _name = name;
+  } else {
+    _name = "XInput Device #";
+    _name += format_string(_index + 1);
+  }
+  _manufacturer = manufacturer;
+
+  if (inst && caps.ProductID == 0 && caps.RevisionID != 0) {
+    // XInput does not report a product ID for the Xbox 360 wireless adapter.
+    // Instead, we check that the RevisionID matches.
+    char buffer[4096];
+    ULONG buflen = sizeof(buffer);
+    if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_HARDWAREID, 0, buffer, &buflen, 0) == CR_SUCCESS) {
+      string ids(buffer, buflen);
+      char revstr[16];
+      sprintf(revstr, "REV_%04x", caps.RevisionID);
+      if (ids.find(revstr) == string::npos) {
+        return false;
+      }
+    }
+  }
+
+  _is_connected = true;
+  init_device(caps, state);
+  _vendor_id = info.hid.dwVendorId;
+  _product_id = info.hid.dwProductId;
+  return true;
+}
+
 /**
 /**
  * Called periodically by the InputDeviceManager to detect whether the device
  * Called periodically by the InputDeviceManager to detect whether the device
  * is currently connected.
  * is currently connected.
+ * Returns true if the device wasn't connected, but now is.
  */
  */
 void XInputDevice::
 void XInputDevice::
 detect(InputDeviceManager *mgr) {
 detect(InputDeviceManager *mgr) {
   bool connected = false;
   bool connected = false;
 
 
-  XINPUT_CAPABILITIES caps;
+  XINPUT_CAPABILITIES_EX caps = {0};
   XINPUT_STATE state;
   XINPUT_STATE state;
-  if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS &&
+  if (((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) == ERROR_SUCCESS) ||
+       get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) == ERROR_SUCCESS) &&
       get_state(_index, &state) == ERROR_SUCCESS) {
       get_state(_index, &state) == ERROR_SUCCESS) {
     connected = true;
     connected = true;
   } else {
   } else {
@@ -203,9 +265,9 @@ init_xinput() {
     // Undocumented version (XInputGetStateEx) that includes a
     // Undocumented version (XInputGetStateEx) that includes a
     // state bit for the guide button.
     // state bit for the guide button.
     get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100));
     get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100));
-    if (get_state == NULL) {
+    if (get_state == nullptr) {
       get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState");
       get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState");
-      if (get_state == NULL) {
+      if (get_state == nullptr) {
         device_cat.error()
         device_cat.error()
           << "Failed to find function XInputGetState in " << dll_name << ".\n";
           << "Failed to find function XInputGetState in " << dll_name << ".\n";
         return false;
         return false;
@@ -213,14 +275,14 @@ init_xinput() {
     }
     }
 
 
     set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState");
     set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState");
-    if (set_state == NULL) {
+    if (set_state == nullptr) {
       device_cat.error()
       device_cat.error()
         << "Failed to find function XInputSetState in " << dll_name << ".\n";
         << "Failed to find function XInputSetState in " << dll_name << ".\n";
       return false;
       return false;
     }
     }
 
 
     get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities");
     get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities");
-    if (get_capabilities == NULL) {
+    if (get_capabilities == nullptr) {
       device_cat.error()
       device_cat.error()
         << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n";
         << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n";
       return false;
       return false;
@@ -228,6 +290,7 @@ init_xinput() {
 
 
     get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation");
     get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation");
     get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104));
     get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104));
+    get_capabilities_ex = (pXInputGetCapabilitiesEx)GetProcAddress(module, MAKEINTRESOURCE(108));
     return true;
     return true;
   }
   }
 
 
@@ -240,7 +303,8 @@ init_xinput() {
  * Initializes the device.  Called when the device was just connected.
  * Initializes the device.  Called when the device was just connected.
  */
  */
 void XInputDevice::
 void XInputDevice::
-init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
+init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) {
+  nassertv(_initialized);
   // It seems that the Xbox One controller is reported as having a DevType of
   // It seems that the Xbox One controller is reported as having a DevType of
   // zero, at least when I tested in with XInput 1.3 on Windows 7.
   // zero, at least when I tested in with XInput 1.3 on Windows 7.
   //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) {
   //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) {
@@ -275,8 +339,8 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
     set_control_map(1, C_throttle);
     set_control_map(1, C_throttle);
     set_control_map(2, C_roll);
     set_control_map(2, C_roll);
     set_control_map(3, C_pitch);
     set_control_map(3, C_pitch);
-    set_control_map(4, C_hat_x);
-    set_control_map(5, C_hat_y);
+    set_control_map(4, C_none);
+    set_control_map(5, C_none);
     break;
     break;
 
 
   case XINPUT_DEVSUBTYPE_DANCE_PAD:
   case XINPUT_DEVSUBTYPE_DANCE_PAD:
@@ -327,11 +391,13 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
   set_button_map(13, GamepadButton::action_x());
   set_button_map(13, GamepadButton::action_x());
   set_button_map(14, GamepadButton::action_y());
   set_button_map(14, GamepadButton::action_y());
 
 
-  if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) {
+
+  if (caps.Vibration.wLeftMotorSpeed != 0 ||
+      caps.Vibration.wRightMotorSpeed != 0) {
     _flags |= IDF_has_vibration;
     _flags |= IDF_has_vibration;
   }
   }
 
 
-  if (get_battery_information != NULL) {
+  if (get_battery_information != nullptr) {
     XINPUT_BATTERY_INFORMATION batt;
     XINPUT_BATTERY_INFORMATION batt;
     if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) {
     if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) {
       if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED &&
       if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED &&
@@ -344,54 +410,11 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
     }
     }
   }
   }
 
 
-  // Get information about the USB device.
-  // This is not documented at all.  I'm probably the first to try this.
-  XINPUT_BUSINFO businfo;
-  if (get_base_bus_information != NULL &&
-      get_base_bus_information(0, &businfo) == ERROR_SUCCESS) {
-    _vendor_id = businfo.VendorID;
-    _product_id = businfo.ProductID;
-
-    {
-      // Reformat the serial number into its original hex string form.
-      char sn[10];
-      sprintf_s(sn, 10, "%08X", businfo.InstanceID);
-      _serial_number.assign(sn, 8);
-    }
-
-    // Get information about the device from Windows.  For that, we'll
-    // first need to construct the device path.  Fortunately, we now have
-    // enough information to do so.
-    char path[32];
-    sprintf_s(path, 32, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID);
-
-    DEVINST inst;
-    if (CM_Locate_DevNodeA(&inst, path, 0) != 0) {
-      if (device_cat.is_debug()) {
-        device_cat.debug()
-          << "Could not locate device node " << path << "\n";
-      }
-    } else {
-      // Get the device properties we need.
-      char buffer[4096];
-      ULONG buflen = 4096;
-      if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) {
-        _name.assign(buffer);
-      }
-      buflen = 4096;
-      if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) {
-        _manufacturer.assign(buffer);
-      }
-    }
-  }
-
   WORD buttons = state.Gamepad.wButtons;
   WORD buttons = state.Gamepad.wButtons;
   WORD mask = 1;
   WORD mask = 1;
   for (int i = 0; i < 16; ++i) {
   for (int i = 0; i < 16; ++i) {
-    if (buttons & mask) {
-      // Set the state without triggering a button event.
-      _buttons[i].state = (buttons & mask) ? S_down : S_up;
-    }
+    // Set the state without triggering a button event.
+    _buttons[i].state = (buttons & mask) ? S_down : S_up;
     mask <<= 1;
     mask <<= 1;
     if (i == 10) {
     if (i == 10) {
       // XInput skips 0x0800.
       // XInput skips 0x0800.
@@ -417,6 +440,8 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
  */
  */
 void XInputDevice::
 void XInputDevice::
 do_set_vibration(double strong, double weak) {
 do_set_vibration(double strong, double weak) {
+  nassertv_always(_is_connected);
+
   XINPUT_VIBRATION vibration;
   XINPUT_VIBRATION vibration;
   vibration.wLeftMotorSpeed = strong * 0xffff;
   vibration.wLeftMotorSpeed = strong * 0xffff;
   vibration.wRightMotorSpeed = weak * 0xffff;
   vibration.wRightMotorSpeed = weak * 0xffff;
@@ -430,6 +455,11 @@ do_set_vibration(double strong, double weak) {
  */
  */
 void XInputDevice::
 void XInputDevice::
 do_poll() {
 do_poll() {
+  // Not sure why someone would call this on a disconnected device.
+  if (!_is_connected) {
+    return;
+  }
+
   XINPUT_STATE state;
   XINPUT_STATE state;
 
 
   if (get_state(_index, &state) != ERROR_SUCCESS) {
   if (get_state(_index, &state) != ERROR_SUCCESS) {
@@ -440,18 +470,6 @@ do_poll() {
       mgr->remove_device(this);
       mgr->remove_device(this);
     }
     }
     return;
     return;
-
-  } else if (!_is_connected) {
-    // Device was (re)connected.  It's a bit strange to call poll() on a
-    // disconnected device, but there's nothing stopping the user from
-    // doing so.
-    XINPUT_CAPABILITIES caps;
-    if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) {
-      _is_connected = true;
-      init_device(caps, state);
-      InputDeviceManager *mgr = InputDeviceManager::get_global_ptr();
-      mgr->add_device(this);
-    }
   }
   }
 
 
   if (state.dwPacketNumber == _last_packet) {
   if (state.dwPacketNumber == _last_packet) {

+ 9 - 3
panda/src/device/xInputDevice.h

@@ -17,13 +17,17 @@
 #include "pandabase.h"
 #include "pandabase.h"
 #include "inputDevice.h"
 #include "inputDevice.h"
 
 
-#ifdef _WIN32
+#if defined(_WIN32) && !defined(CPPPARSER)
+
+#include <CfgMgr32.h>
 
 
 class InputDeviceManager;
 class InputDeviceManager;
 
 
-typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES;
+typedef struct _XINPUT_CAPABILITIES_EX XINPUT_CAPABILITIES_EX;
 typedef struct _XINPUT_STATE XINPUT_STATE;
 typedef struct _XINPUT_STATE XINPUT_STATE;
 
 
+typedef struct tagRID_DEVICE_INFO RID_DEVICE_INFO;
+
 /**
 /**
  * This implementation of InputDevice uses Microsoft's XInput library to
  * This implementation of InputDevice uses Microsoft's XInput library to
  * interface with an Xbox 360 game controller.
  * interface with an Xbox 360 game controller.
@@ -33,11 +37,13 @@ public:
   XInputDevice(DWORD user_index);
   XInputDevice(DWORD user_index);
   ~XInputDevice();
   ~XInputDevice();
 
 
+  bool check_arrival(const RID_DEVICE_INFO &info, DEVINST inst,
+                     const string &name, const string &manufacturer);
   void detect(InputDeviceManager *mgr);
   void detect(InputDeviceManager *mgr);
   static bool init_xinput();
   static bool init_xinput();
 
 
 private:
 private:
-  void init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state);
+  void init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state);
   virtual void do_set_vibration(double strong, double weak);
   virtual void do_set_vibration(double strong, double weak);
   virtual void do_poll();
   virtual void do_poll();