Bug 996078 - Replace Windows Gamepad DirectInput backend with Raw Input. r=jimm
authorTed Mielczarek <ted@mielczarek.org>
Mon, 14 Apr 2014 13:18:51 -0400
changeset 181094 79c804f8760d3d0a54fc3dfcaed9f7cc194f4598
parent 181093 dd434e37123903399b6c45febac237718f182f0e
child 181095 2462b87f45bbe13836511cdfe7de387d4f1406ad
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjimm
bugs996078
milestone32.0a1
Bug 996078 - Replace Windows Gamepad DirectInput backend with Raw Input. r=jimm
configure.in
hal/windows/WindowsGamepad.cpp
toolkit/library/libxul.mk
--- a/configure.in
+++ b/configure.in
@@ -5959,28 +5959,16 @@ MOZ_ARG_DISABLE_BOOL(gamepad,
     MOZ_GAMEPAD=1)
 
 if test "$MOZ_GAMEPAD"; then
     case "$OS_TARGET" in
     Darwin)
         MOZ_GAMEPAD_BACKEND=cocoa
         ;;
     WINNT)
-        if test -z "$MOZ_HAS_WINSDK_WITH_D3D"; then
-            if test -n "$MOZ_DIRECTX_SDK_PATH" ; then
-                if ! test -f "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_DIRECTX_SDK_CPU_SUFFIX/dxguid.lib ; then
-                   MOZ_GAMEPAD=
-                fi
-            elif test "$GCC" != "yes"; then
-                MOZ_GAMEPAD=
-            fi
-        fi
-        if test -z "$MOZ_GAMEPAD"; then
-           AC_MSG_ERROR([Couldn't find the DirectX SDK, needed for gamepad support. Please install it or, reconfigure with --disable-gamepad to disable gamepad support.])
-        fi
         MOZ_GAMEPAD_BACKEND=windows
         ;;
     Linux)
         MOZ_CHECK_HEADER([linux/joystick.h])
         if test "$ac_cv_header_linux_joystick_h" != "yes"; then
           AC_MSG_ERROR([Can't find header linux/joystick.h, needed for gamepad support. Please install Linux kernel headers or reconfigure with --disable-gamepad to disable gamepad support.])
         fi
         MOZ_GAMEPAD_BACKEND=linux
--- a/hal/windows/WindowsGamepad.cpp
+++ b/hal/windows/WindowsGamepad.cpp
@@ -1,733 +1,752 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <algorithm>
 #include <cstddef>
 
-#include <stdio.h>
 #ifndef UNICODE
 #define UNICODE
 #endif
 #include <windows.h>
-#define DIRECTINPUT_VERSION 0x0800
-#include <dinput.h>
+#include <hidsdi.h>
+#include <stdio.h>
 
 #include "nsIComponentManager.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
-#include "nsThreadUtils.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/dom/GamepadService.h"
-#include "mozilla/Mutex.h"
 #include "mozilla/Services.h"
 
 namespace {
 
 using mozilla::dom::GamepadService;
-using mozilla::Mutex;
-using mozilla::MutexAutoLock;
+using mozilla::ArrayLength;
+
+// USB HID usage tables, page 1 (Hat switch)
+const unsigned kUsageDpad = 0x39;
+// USB HID usage tables, page 1, 0x30 = X
+const unsigned kFirstAxis = 0x30;
 
-const LONG kMaxAxisValue = 65535;
-const DWORD BUTTON_DOWN_MASK = 0x80;
+// USB HID usage tables
+const unsigned kDesktopUsagePage = 0x1;
+const unsigned kButtonUsagePage = 0x9;
+
+// Arbitrary. In practice 10 buttons/6 axes is the near maximum.
+const unsigned kMaxButtons = 32;
+const unsigned kMaxAxes = 32;
+
 // Multiple devices-changed notifications can be sent when a device
 // is connected, because USB devices consist of multiple logical devices.
 // Therefore, we wait a bit after receiving one before looking for
 // device changes.
 const uint32_t kDevicesChangedStableDelay = 200;
 
+const struct {
+  int usagePage;
+  int usage;
+} kUsagePages[] = {
+  // USB HID usage tables, page 1
+  { kDesktopUsagePage, 4 },  // Joystick
+  { kDesktopUsagePage, 5 }   // Gamepad
+};
+
+enum GamepadType {
+  kNoGamepad = 0,
+  kRawInputGamepad
+};
+
 class WindowsGamepadService;
 WindowsGamepadService* gService = nullptr;
 
-typedef struct {
-  float x,y;
-} HatState;
+struct Gamepad {
+  GamepadType type;
+
+  // Handle to raw input device
+  HANDLE handle;
+
+  // ID from the GamepadService, also used as the index into
+  // WindowsGamepadService::mGamepads.
+  int id;
 
-struct Gamepad {
-  // From DirectInput, unique to this device+computer combination.
-  GUID guidInstance;
-  // The ID assigned by the base GamepadService
-  int globalID;
-  // A somewhat unique string consisting of the USB vendor/product IDs,
-  // and the controller name.
-  char idstring[128];
-  // USB vendor and product IDs
-  int vendorID;
-  int productID;
   // Information about the physical device.
-  int numAxes;
-  int numHats;
-  int numButtons;
-  // The human-readable device name.
-  char name[128];
-  // The DirectInput device.
-  nsRefPtr<IDirectInputDevice8> device;
-  // A handle that DirectInput signals when there is new data from
-  // the device.
-  HANDLE event;
-  // The state of any POV hats on the device.
-  HatState hatState[4];
+  unsigned numAxes;
+  unsigned numButtons;
+  bool hasDpad;
+  HIDP_VALUE_CAPS dpadCaps;
+
+  bool buttons[kMaxButtons];
+  struct {
+    HIDP_VALUE_CAPS caps;
+    double value;
+  } axes[kMaxAxes];
+
   // Used during rescan to find devices that were disconnected.
   bool present;
-  // Passed back from the main thread to indicate a device can
-  // now be removed.
-  bool remove;
 };
 
-// Given DWORD |hatPos| representing the position of the POV hat per:
-// http://msdn.microsoft.com/en-us/library/ee418260%28v=VS.85%29.aspx
-// fill |axes| with the position of the x and y axes.
-//
-//XXX: ostensibly the values could be arbitrary degrees for a hat with
-// full rotation, but we'll punt on that for now. This should handle
-// 8-way D-pads exposed as POV hats.
-static void
-HatPosToAxes(DWORD hatPos, HatState& axes) {
-  // hatPos is in hundredths of a degree clockwise from north.
-  if (LOWORD(hatPos) == 0xFFFF) {
-    // centered
-    axes.x = axes.y = 0.0;
-  }
-  else if (hatPos == 0)  {
-    // Up
-    axes.x = 0.0;
-    axes.y = -1.0;
-  }
-  else if (hatPos == 45 * DI_DEGREES) {
-    // Up-right
-    axes.x = 1.0;
-    axes.y = -1.0;
+bool
+GetPreparsedData(HANDLE handle, nsTArray<uint8_t>& data)
+{
+  UINT size;
+  if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0) {
+    return false;
   }
-  else if (hatPos == 90 * DI_DEGREES) {
-    // Right
-    axes.x = 1.0;
-    axes.y = 0.0;
-  }
-  else if (hatPos == 135 * DI_DEGREES) {
-    // Down-right
-    axes.x = 1.0;
-    axes.y = 1.0;
-  }
-  else if (hatPos == 180 * DI_DEGREES) {
-    // Down
-    axes.x = 0.0;
-    axes.y = 1.0;
+  data.SetLength(size);
+  return GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA,
+                               data.Elements(), &size) > 0;
+}
+
+/*
+ * Given an axis value and a minimum and maximum range,
+ * scale it to be in the range -1.0 .. 1.0.
+ */
+double
+ScaleAxis(ULONG value, LONG min, LONG max)
+{
+  return  2.0 * (value - min) / (max - min) - 1.0;
+}
+
+/*
+ * Given a value from a d-pad (POV hat in USB HID terminology),
+ * represent it as 4 buttons, one for each cardinal direction.
+ */
+void
+UnpackDpad(LONG dpad_value, const Gamepad* gamepad, bool buttons[kMaxButtons])
+{
+  const unsigned kUp = gamepad->numButtons - 4;
+  const unsigned kDown = gamepad->numButtons - 3;
+  const unsigned kLeft = gamepad->numButtons - 2;
+  const unsigned kRight = gamepad->numButtons - 1;
+
+  // Different controllers have different ways of representing
+  // "nothing is pressed", but they're all outside the range of values.
+  if (dpad_value < gamepad->dpadCaps.LogicalMin
+      || dpad_value > gamepad->dpadCaps.LogicalMax) {
+    // Nothing is pressed.
+    return;
   }
-  else if (hatPos == 225 * DI_DEGREES) {
-    // Down-left
-    axes.x = -1.0;
-    axes.y = 1.0;
+
+  // Normalize value to start at 0.
+  int value = dpad_value - gamepad->dpadCaps.LogicalMin;
+
+  // Value will be in the range 0-7. The value represents the
+  // position of the d-pad around a circle, with 0 being straight up,
+  // 2 being right, 4 being straight down, and 6 being left.
+  if (value < 2 || value > 6) {
+    buttons[kUp] = true;
+  }
+  if (value > 2 && value < 6) {
+    buttons[kDown] = true;
+  }
+  if (value > 4) {
+    buttons[kLeft] = true;
   }
-  else if (hatPos == 270 * DI_DEGREES) {
-    // Left
-    axes.x = -1.0;
-    axes.y = 0.0;
+  if (value > 0 && value < 4) {
+    buttons[kRight] = true;
   }
-  else if (hatPos == 315 * DI_DEGREES) {
-    // Up-left
-    axes.x = -1.0;
-    axes.y = -1.0;
+}
+
+/*
+ * Return true if this USB HID usage page and usage are of a type we
+ * know how to handle.
+ */
+bool
+SupportedUsage(USHORT page, USHORT usage)
+{
+  for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) {
+    if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) {
+      return true;
+    }
   }
+  return false;
 }
 
 class Observer : public nsIObserver {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   Observer(WindowsGamepadService& svc) : mSvc(svc),
-                                         mObserving(true) {
+                                         mObserving(true)
+  {
     nsresult rv;
     mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
     nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
     observerService->AddObserver(this,
                                  NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
                                  false);
   }
 
-  void Stop() {
+  void Stop()
+  {
     if (mTimer) {
       mTimer->Cancel();
     }
     if (mObserving) {
       nsCOMPtr<nsIObserverService> observerService =
         mozilla::services::GetObserverService();
       observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
       mObserving = false;
     }
   }
 
-  virtual ~Observer() {
+  virtual ~Observer()
+  {
     Stop();
   }
 
-  void SetDeviceChangeTimer() {
-    // set stable timer, since we will get multiple devices-changed
+  void SetDeviceChangeTimer()
+  {
+    // Set stable timer, since we will get multiple devices-changed
     // notifications at once
     if (mTimer) {
       mTimer->Cancel();
       mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT);
     }
   }
 
 private:
   // Gamepad service owns us, we just hold a reference back to it.
   WindowsGamepadService& mSvc;
   nsCOMPtr<nsITimer> mTimer;
   bool mObserving;
 };
 
 NS_IMPL_ISUPPORTS(Observer, nsIObserver);
 
+class HIDLoader {
+public:
+  HIDLoader() : mModule(LoadLibraryW(L"hid.dll")),
+                mHidD_GetProductString(nullptr),
+                mHidP_GetCaps(nullptr),
+                mHidP_GetButtonCaps(nullptr),
+                mHidP_GetValueCaps(nullptr),
+                mHidP_GetUsages(nullptr),
+                mHidP_GetUsageValue(nullptr),
+                mHidP_GetScaledUsageValue(nullptr)
+  {
+    if (mModule) {
+      mHidD_GetProductString = reinterpret_cast<decltype(HidD_GetProductString)*>(GetProcAddress(mModule, "HidD_GetProductString"));
+      mHidP_GetCaps = reinterpret_cast<decltype(HidP_GetCaps)*>(GetProcAddress(mModule, "HidP_GetCaps"));
+      mHidP_GetButtonCaps = reinterpret_cast<decltype(HidP_GetButtonCaps)*>(GetProcAddress(mModule, "HidP_GetButtonCaps"));
+      mHidP_GetValueCaps = reinterpret_cast<decltype(HidP_GetValueCaps)*>(GetProcAddress(mModule, "HidP_GetValueCaps"));
+      mHidP_GetUsages = reinterpret_cast<decltype(HidP_GetUsages)*>(GetProcAddress(mModule, "HidP_GetUsages"));
+      mHidP_GetUsageValue = reinterpret_cast<decltype(HidP_GetUsageValue)*>(GetProcAddress(mModule, "HidP_GetUsageValue"));
+      mHidP_GetScaledUsageValue = reinterpret_cast<decltype(HidP_GetScaledUsageValue)*>(GetProcAddress(mModule, "HidP_GetScaledUsageValue"));
+    }
+  }
+
+  ~HIDLoader() {
+    if (mModule) {
+      FreeLibrary(mModule);
+    }
+  }
+
+  operator bool() {
+    return mModule &&
+      mHidD_GetProductString &&
+      mHidP_GetCaps &&
+      mHidP_GetButtonCaps &&
+      mHidP_GetValueCaps &&
+      mHidP_GetUsages &&
+      mHidP_GetUsageValue &&
+      mHidP_GetScaledUsageValue;
+  }
+
+  decltype(HidD_GetProductString) *mHidD_GetProductString;
+  decltype(HidP_GetCaps) *mHidP_GetCaps;
+  decltype(HidP_GetButtonCaps) *mHidP_GetButtonCaps;
+  decltype(HidP_GetValueCaps) *mHidP_GetValueCaps;
+  decltype(HidP_GetUsages) *mHidP_GetUsages;
+  decltype(HidP_GetUsageValue) *mHidP_GetUsageValue;
+  decltype(HidP_GetScaledUsageValue) *mHidP_GetScaledUsageValue;
+
+private:
+  HMODULE mModule;
+};
+
 class WindowsGamepadService {
 public:
   WindowsGamepadService();
-  virtual ~WindowsGamepadService() {
+  virtual ~WindowsGamepadService()
+  {
     Cleanup();
-    CloseHandle(mThreadExitEvent);
-    CloseHandle(mThreadRescanEvent);
-    if (dinput) {
-      dinput->Release();
-      dinput = nullptr;
-    }
   }
 
   enum DeviceChangeType {
     DeviceChangeNotification,
     DeviceChangeStable
   };
   void DevicesChanged(DeviceChangeType type);
   void Startup();
   void Shutdown();
-  void SetGamepadID(int localID, int globalID);
-  void RemoveGamepad(int localID);
+  // Parse gamepad input from a WM_INPUT message.
+  bool HandleRawInput(HRAWINPUT handle);
 
 private:
   void ScanForDevices();
+  // Look for connected raw input devices.
+  void ScanForRawInputDevices();
+  // Get information about a raw input gamepad.
+  bool GetRawGamepad(HANDLE handle);
   void Cleanup();
-  void CleanupGamepad(Gamepad& gamepad);
-  // Callback for enumerating axes on a device
-  static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
-                                           LPVOID pvRef);
-  // Callback for enumerating devices via DInput
-  static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef);
-  // Thread function to wait on device events
-  static DWORD WINAPI DInputThread(LPVOID arg);
-
-  // Used to signal the background thread to exit.
-  HANDLE mThreadExitEvent;
-  // Used to signal the background thread to rescan devices.
-  HANDLE mThreadRescanEvent;
-  HANDLE mThread;
 
   // List of connected devices.
   nsTArray<Gamepad> mGamepads;
-  // Used to lock mutation of mGamepads.
-  Mutex mMutex;
-  // List of event handles used for signaling.
-  nsTArray<HANDLE> mEvents;
-
-  LPDIRECTINPUT8 dinput;
 
   nsRefPtr<Observer> mObserver;
+
+  HIDLoader mHID;
 };
 
-// Used to post events from the background thread to the foreground thread.
-class GamepadEvent : public nsRunnable {
-public:
-  typedef enum {
-    Axis,
-    Button,
-    HatX,
-    HatY,
-    HatXY,
-    Unknown
-  } Type;
 
-  GamepadEvent(const Gamepad& gamepad,
-               Type type,
-               int which,
-               DWORD data) : mGlobalID(gamepad.globalID),
-                             mGamepadAxes(gamepad.numAxes),
-                             mType(type),
-                             mWhich(which),
-                             mData(data) {
-  }
-
-  NS_IMETHOD Run() {
-    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (!gamepadsvc) {
-      return NS_OK;
-    }
+WindowsGamepadService::WindowsGamepadService()
+{
+  mObserver = new Observer(*this);
+}
 
-    switch (mType) {
-    case Button:
-      gamepadsvc->NewButtonEvent(mGlobalID, mWhich, mData & BUTTON_DOWN_MASK);
-      break;
-    case Axis: {
-      float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f;
-      gamepadsvc->NewAxisMoveEvent(mGlobalID, mWhich, adjustedData);
-    }
-    case HatX:
-    case HatY:
-    case HatXY: {
-      // Synthesize 2 axes per POV hat for convenience.
-      HatState hatState;
-      HatPosToAxes(mData, hatState);
-      int xAxis = mGamepadAxes + 2 * mWhich;
-      int yAxis = mGamepadAxes + 2 * mWhich + 1;
-      //TODO: ostensibly we could not fire an event if one axis hasn't
-      // changed, but it's a pain to track that.
-      if (mType == HatX || mType == HatXY) {
-        gamepadsvc->NewAxisMoveEvent(mGlobalID, xAxis, hatState.x);
-      }
-      if (mType == HatY || mType == HatXY) {
-        gamepadsvc->NewAxisMoveEvent(mGlobalID, yAxis, hatState.y);
-      }
-      break;
-    }
-    case Unknown:
-      break;
-    }
-    return NS_OK;
+void
+WindowsGamepadService::ScanForRawInputDevices()
+{
+  if (!mHID) {
+    return;
   }
 
-  int mGlobalID;
-  int mGamepadAxes;
-  // Type of event
-  Type mType;
-  // Which button/axis is involved
-  int mWhich;
-  // Data specific to event
-  DWORD mData;
-};
-
-class GamepadChangeEvent : public nsRunnable {
-public:
-  enum Type {
-    Added,
-    Removed
-  };
-  GamepadChangeEvent(Gamepad& gamepad,
-                     int localID,
-                     Type type) : mLocalID(localID),
-                                  mName(gamepad.idstring),
-                                  mGlobalID(gamepad.globalID),
-                                  mGamepadButtons(gamepad.numButtons),
-                                  mGamepadAxes(gamepad.numAxes),
-                                  mGamepadHats(gamepad.numHats),
-                                  mType(type) {
+  UINT numDevices;
+  if (GetRawInputDeviceList(nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST))
+      == -1) {
+    return;
+  }
+  nsTArray<RAWINPUTDEVICELIST> devices(numDevices);
+  devices.SetLength(numDevices);
+  if (GetRawInputDeviceList(devices.Elements(), &numDevices,
+                            sizeof(RAWINPUTDEVICELIST)) == -1) {
+    return;
   }
 
-  NS_IMETHOD Run() {
-    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (!gamepadsvc) {
-      return NS_OK;
+  for (unsigned i = 0; i < devices.Length(); i++) {
+    if (devices[i].dwType == RIM_TYPEHID) {
+      GetRawGamepad(devices[i].hDevice);
     }
-    if (mType == Added) {
-      int globalID = gamepadsvc->AddGamepad(mName.get(),
-                                            mozilla::dom::NoMapping,
-                                            mGamepadButtons,
-                                            mGamepadAxes +
-                                            mGamepadHats*2);
-      if (gService) {
-        gService->SetGamepadID(mLocalID, globalID);
-      }
-    } else {
-      gamepadsvc->RemoveGamepad(mGlobalID);
-      if (gService) {
-        gService->RemoveGamepad(mLocalID);
-      }
-    }
-    return NS_OK;
+  }
+}
+
+void
+WindowsGamepadService::ScanForDevices()
+{
+  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+    mGamepads[i].present = false;
   }
 
-private:
-  // ID in WindowsGamepadService::mGamepads
-  int mLocalID;
-  nsCString mName;
-  int mGamepadButtons;
-  int mGamepadAxes;
-  int mGamepadHats;
-  // ID from GamepadService
-  uint32_t mGlobalID;
-  Type mType;
-};
+  if (mHID) {
+    ScanForRawInputDevices();
+  }
 
-WindowsGamepadService::WindowsGamepadService()
-  : mThreadExitEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
-    mThreadRescanEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
-    mThread(nullptr),
-    mMutex("Windows Gamepad Service"),
-    dinput(nullptr) {
-  mObserver = new Observer(*this);
-  // Initialize DirectInput
-  CoInitialize(nullptr);
-  if (CoCreateInstance(CLSID_DirectInput8,
-                       nullptr,
-                       CLSCTX_INPROC_SERVER,
-                       IID_IDirectInput8W,
-                       (LPVOID*)&dinput) == S_OK) {
-    if (dinput->Initialize(GetModuleHandle(nullptr),
-                           DIRECTINPUT_VERSION) != DI_OK) {
-      dinput->Release();
-      dinput = nullptr;
+  nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+  if (!gamepadsvc) {
+    return;
+  }
+  // Look for devices that are no longer present and remove them.
+  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+    if (!mGamepads[i].present) {
+      gamepadsvc->RemoveGamepad(mGamepads[i].id);
+      mGamepads.RemoveElementAt(i);
     }
   }
 }
 
-// static
-BOOL CALLBACK
-WindowsGamepadService::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
-                                           LPVOID pvRef) {
-  // Ensure that all axes are using the same range.
-  Gamepad* gamepad = reinterpret_cast<Gamepad*>(pvRef);
-  DIPROPRANGE dp;
-  dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
-  dp.diph.dwSize = sizeof(DIPROPRANGE);
-  dp.diph.dwHow = DIPH_BYID;
-  dp.diph.dwObj = lpddoi->dwType;
-  dp.lMin = 0;
-  dp.lMax = kMaxAxisValue;
-  gamepad->device->SetProperty(DIPROP_RANGE, &dp.diph);
-  return DIENUM_CONTINUE;
-}
+// Used to sort a list of axes by HID usage.
+class HidValueComparator {
+public:
+  bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
+  {
+    return c1.UsagePage == c2.UsagePage && c1.Range.UsageMin == c2.Range.UsageMin;
+  }
+  bool LessThan(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
+  {
+    if (c1.UsagePage == c2.UsagePage) {
+      return c1.Range.UsageMin < c2.Range.UsageMin;
+    }
+    return c1.UsagePage < c2.UsagePage;
+  }
+};
 
-// static
-BOOL CALLBACK
-WindowsGamepadService::EnumCallback(LPCDIDEVICEINSTANCE lpddi,
-                                    LPVOID pvRef) {
-  WindowsGamepadService* self =
-    reinterpret_cast<WindowsGamepadService*>(pvRef);
-  // See if this device is already present in our list.
-  {
-    MutexAutoLock lock(self->mMutex);
-    for (unsigned int i = 0; i < self->mGamepads.Length(); i++) {
-      if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance,
-                 sizeof(GUID)) == 0) {
-        self->mGamepads[i].present = true;
-        return DIENUM_CONTINUE;
-      }
+bool
+WindowsGamepadService::GetRawGamepad(HANDLE handle)
+{
+  if (!mHID) {
+    return false;
+  }
+
+  for (unsigned i = 0; i < mGamepads.Length(); i++) {
+    if (mGamepads[i].type == kRawInputGamepad && mGamepads[i].handle == handle) {
+      mGamepads[i].present = true;
+      return true;
     }
   }
 
-  Gamepad gamepad;
-  memset(&gamepad, 0, sizeof(Gamepad));
-  if (self->dinput->CreateDevice(lpddi->guidInstance,
-                                 getter_AddRefs(gamepad.device),
-                                 nullptr)
-      == DI_OK) {
-    gamepad.present = true;
-    memcpy(&gamepad.guidInstance, &lpddi->guidInstance, sizeof(GUID));
+  RID_DEVICE_INFO rdi = {};
+  UINT size = rdi.cbSize = sizeof(RID_DEVICE_INFO);
+  if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) < 0) {
+    return false;
+  }
+  // Ensure that this is a device we care about
+  if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) {
+    return false;
+  }
+
+  Gamepad gamepad = {};
+
+  // Device name is a mostly-opaque string.
+  if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) < 0) {
+    return false;
+  }
 
-    DIDEVICEINSTANCE info;
-    info.dwSize = sizeof(DIDEVICEINSTANCE);
-    if (gamepad.device->GetDeviceInfo(&info) == DI_OK) {
-      WideCharToMultiByte(CP_UTF8, 0, info.tszProductName, -1,
-                          gamepad.name, sizeof(gamepad.name), nullptr, nullptr);
-    }
-    // Get vendor id and product id
-    DIPROPDWORD dp;
-    dp.diph.dwSize = sizeof(DIPROPDWORD);
-    dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
-    dp.diph.dwObj = 0;
-    dp.diph.dwHow = DIPH_DEVICE;
-    if (gamepad.device->GetProperty(DIPROP_VIDPID, &dp.diph) == DI_OK) {
-      sprintf(gamepad.idstring, "%x-%x-%s",
-              LOWORD(dp.dwData), HIWORD(dp.dwData), gamepad.name);
-    }
-    DIDEVCAPS caps;
-    caps.dwSize = sizeof(DIDEVCAPS);
-    if (gamepad.device->GetCapabilities(&caps) == DI_OK) {
-      gamepad.numAxes = caps.dwAxes;
-      gamepad.numHats = caps.dwPOVs;
-      gamepad.numButtons = caps.dwButtons;
-      //XXX: handle polled devices?
-      // (caps.dwFlags & DIDC_POLLEDDATAFORMAT || caps.dwFlags & DIDC_POLLEDDEVICE)
+  nsTArray<wchar_t> devname(size);
+  devname.SetLength(size);
+  if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(), &size)
+      <= 0) {
+    return false;
+  }
+
+  // Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
+  // device names containing "IG_" are XInput controllers. Ignore those
+  // devices since we'll handle them with XInput.
+  if (wcsstr(devname.Elements(), L"IG_")) {
+    return false;
+  }
+
+  // Product string is a human-readable name.
+  // Per http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681%28v=vs.85%29.aspx
+  // "For USB devices, the maximum string length is 126 wide characters (not including the terminating NULL character)."
+  wchar_t name[128] = { 0 };
+  size = sizeof(name);
+  nsTArray<char> gamepad_name;
+  HANDLE hid_handle = CreateFile(devname.Elements(), GENERIC_READ | GENERIC_WRITE,
+    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
+  if (hid_handle) {
+    if (mHID.mHidD_GetProductString(hid_handle, &name, size)) {
+      int bytes = WideCharToMultiByte(CP_UTF8, 0, name, -1, nullptr, 0, nullptr,
+                                      nullptr);
+      gamepad_name.SetLength(bytes);
+      WideCharToMultiByte(CP_UTF8, 0, name, -1, gamepad_name.Elements(),
+                          bytes, nullptr, nullptr);
     }
-    // Set min/max range for all axes on the device.
-    gamepad.device->EnumObjects(EnumObjectsCallback, &gamepad, DIDFT_AXIS);
-    // Set up structure for setting buffer size for buffered data
-    dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
-    dp.diph.dwSize = sizeof(DIPROPDWORD);
-    dp.diph.dwObj = 0;
-    dp.diph.dwHow = DIPH_DEVICE;
-    dp.dwData = 64; // arbitrary
-    // Create event so DInput can signal us when there's new data.
-    gamepad.event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
-    // Set data format, event notification, and acquire device
-    if (gamepad.device->SetDataFormat(&c_dfDIJoystick) == DI_OK &&
-        gamepad.device->SetProperty(DIPROP_BUFFERSIZE, &dp.diph) == DI_OK &&
-        gamepad.device->SetEventNotification(gamepad.event) == DI_OK &&
-        gamepad.device->Acquire() == DI_OK) {
-      MutexAutoLock lock(self->mMutex);
-      self->mGamepads.AppendElement(gamepad);
-      // Inform the GamepadService
-      int localID = self->mGamepads.Length() - 1;
-      nsRefPtr<GamepadChangeEvent> event =
-        new GamepadChangeEvent(self->mGamepads[localID],
-                               localID,
-                               GamepadChangeEvent::Added);
-      NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-    }
-    else {
-      if (gamepad.device) {
-        gamepad.device->SetEventNotification(nullptr);
-      }
-      CloseHandle(gamepad.event);
-    }
+    CloseHandle(hid_handle);
+  }
+  if (gamepad_name.Length() == 0 || !gamepad_name[0]) {
+    const char kUnknown[] = "Unknown Gamepad";
+    gamepad_name.SetLength(ArrayLength(kUnknown));
+    strcpy_s(gamepad_name.Elements(), gamepad_name.Length(), kUnknown);
+  }
+
+  char gamepad_id[256] = { 0 };
+  _snprintf_s(gamepad_id, _TRUNCATE, "%04x-%04x-%s", rdi.hid.dwVendorId,
+              rdi.hid.dwProductId, gamepad_name.Elements());
+
+  nsTArray<uint8_t> preparsedbytes;
+  if (!GetPreparsedData(handle, preparsedbytes)) {
+    return false;
+  }
+
+  PHIDP_PREPARSED_DATA parsed =
+    reinterpret_cast<PHIDP_PREPARSED_DATA>(preparsedbytes.Elements());
+  HIDP_CAPS caps;
+  if (mHID.mHidP_GetCaps(parsed, &caps) != HIDP_STATUS_SUCCESS) {
+    return false;
+  }
+
+  // Enumerate buttons.
+  USHORT count = caps.NumberInputButtonCaps;
+  nsTArray<HIDP_BUTTON_CAPS> buttonCaps(count);
+  buttonCaps.SetLength(count);
+  if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count, parsed)
+      != HIDP_STATUS_SUCCESS) {
+    return false;
   }
-  return DIENUM_CONTINUE;
-}
+  for (unsigned i = 0; i < count; i++) {
+    // Each buttonCaps is typically a range of buttons.
+    gamepad.numButtons +=
+      buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1;
+  }
+  gamepad.numButtons = std::min(gamepad.numButtons, kMaxButtons);
 
-void
-WindowsGamepadService::ScanForDevices() {
-  {
-    MutexAutoLock lock(mMutex);
-    for (int i = mGamepads.Length() - 1; i >= 0; i--) {
-      if (mGamepads[i].remove) {
-
-        // Main thread has already handled this, safe to remove.
-        CleanupGamepad(mGamepads[i]);
-        mGamepads.RemoveElementAt(i);
-      } else {
-        mGamepads[i].present = false;
-      }
+  // Enumerate value caps, which represent axes and d-pads.
+  count = caps.NumberInputValueCaps;
+  nsTArray<HIDP_VALUE_CAPS> valueCaps(count);
+  valueCaps.SetLength(count);
+  if (mHID.mHidP_GetValueCaps(HidP_Input, valueCaps.Elements(), &count, parsed)
+      != HIDP_STATUS_SUCCESS) {
+    return false;
+  }
+  nsTArray<HIDP_VALUE_CAPS> axes;
+  // Sort the axes by usagePage and usage to expose a consistent ordering.
+  HidValueComparator comparator;
+  for (unsigned i = 0; i < count; i++) {
+    if (valueCaps[i].UsagePage == kDesktopUsagePage
+        && valueCaps[i].Range.UsageMin == kUsageDpad
+        // Don't know how to handle d-pads that return weird values.
+        && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7
+        // Can't overflow buttons
+        && gamepad.numButtons + 4 < kMaxButtons) {
+      // d-pad gets special handling.
+      // Ostensibly HID devices can expose multiple d-pads, but this
+      // doesn't happen in practice.
+      gamepad.hasDpad = true;
+      gamepad.dpadCaps = valueCaps[i];
+      // Expose d-pad as 4 additional buttons.
+      gamepad.numButtons += 4;
+    } else {
+      axes.InsertElementSorted(valueCaps[i], comparator);
     }
   }
 
-  dinput->EnumDevices(DI8DEVCLASS_GAMECTRL,
-                      (LPDIENUMDEVICESCALLBACK)EnumCallback,
-                      this,
-                      DIEDFL_ATTACHEDONLY);
+  gamepad.numAxes = std::min(axes.Length(), kMaxAxes);
+  for (unsigned i = 0; i < gamepad.numAxes; i++) {
+    if (i >= kMaxAxes) {
+      break;
+    }
+    gamepad.axes[i].caps = axes[i];
+  }
+  gamepad.type = kRawInputGamepad;
+  gamepad.handle = handle;
+  gamepad.present = true;
+
+  nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+  if (!gamepadsvc) {
+    return false;
+  }
+
+  gamepad.id = gamepadsvc->AddGamepad(gamepad_id,
+                                      mozilla::dom::NoMapping,
+                                      gamepad.numButtons,
+                                      gamepad.numAxes);
+  mGamepads.AppendElement(gamepad);
+  return true;
+}
+
+bool
+WindowsGamepadService::HandleRawInput(HRAWINPUT handle)
+{
+  if (!mHID) {
+    return false;
+  }
+  nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+  if (!gamepadsvc) {
+    return false;
+  }
 
-  // Look for devices that are no longer present and inform the main thread.
-  {
-    MutexAutoLock lock(mMutex);
-    for (int i = mGamepads.Length() - 1; i >= 0; i--) {
-      if (!mGamepads[i].present) {
-        nsRefPtr<GamepadChangeEvent> event =
-          new GamepadChangeEvent(mGamepads[i],
-                                 i,
-                                 GamepadChangeEvent::Removed);
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      }
+  // First, get data from the handle
+  UINT size;
+  GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
+  nsTArray<uint8_t> data(size);
+  data.SetLength(size);
+  if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size,
+                      sizeof(RAWINPUTHEADER)) < 0) {
+    return false;
+  }
+  PRAWINPUT raw = reinterpret_cast<PRAWINPUT>(data.Elements());
+
+  Gamepad* gamepad = nullptr;
+  for (unsigned i = 0; i < mGamepads.Length(); i++) {
+    if (mGamepads[i].type == kRawInputGamepad
+        && mGamepads[i].handle == raw->header.hDevice) {
+      gamepad = &mGamepads[i];
+      break;
     }
+  }
+  if (gamepad == nullptr) {
+    return false;
+  }
 
-    mEvents.Clear();
-    for (unsigned int i = 0; i < mGamepads.Length(); i++) {
-      mEvents.AppendElement(mGamepads[i].event);
+  // Second, get the preparsed data
+  nsTArray<uint8_t> parsedbytes;
+  if (!GetPreparsedData(raw->header.hDevice, parsedbytes)) {
+    return false;
+  }
+  PHIDP_PREPARSED_DATA parsed =
+    reinterpret_cast<PHIDP_PREPARSED_DATA>(parsedbytes.Elements());
+
+  // Get all the pressed buttons.
+  nsTArray<USAGE> usages(gamepad->numButtons);
+  usages.SetLength(gamepad->numButtons);
+  ULONG usageLength = gamepad->numButtons;
+  if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(),
+                     &usageLength, parsed, (PCHAR)raw->data.hid.bRawData,
+                     raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
+    return false;
+  }
+
+  bool buttons[kMaxButtons] = { false };
+  usageLength = std::min<ULONG>(usageLength, kMaxButtons);
+  for (unsigned i = 0; i < usageLength; i++) {
+    buttons[usages[i] - 1] = true;
+  }
+
+  if (gamepad->hasDpad) {
+    // Get d-pad position as 4 buttons.
+    ULONG value;
+    if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
+      UnpackDpad(static_cast<LONG>(value), gamepad, buttons);
     }
   }
 
-
-  // These events must be the  last elements in the array, so that
-  // the other elements match mGamepads in order.
-  mEvents.AppendElement(mThreadRescanEvent);
-  mEvents.AppendElement(mThreadExitEvent);
-}
-
-// static
-DWORD WINAPI
-WindowsGamepadService::DInputThread(LPVOID arg) {
-  WindowsGamepadService* self = reinterpret_cast<WindowsGamepadService*>(arg);
-  self->ScanForDevices();
+  for (unsigned i = 0; i < gamepad->numButtons; i++) {
+    if (gamepad->buttons[i] != buttons[i]) {
+      gamepadsvc->NewButtonEvent(gamepad->id, i, buttons[i]);
+      gamepad->buttons[i] = buttons[i];
+    }
+  }
 
-  while (true) {
-    DWORD result = WaitForMultipleObjects(self->mEvents.Length(),
-                                          self->mEvents.Elements(),
-                                          FALSE,
-                                          INFINITE);
-    if (result == WAIT_FAILED ||
-        result == WAIT_OBJECT_0 + self->mEvents.Length() - 1) {
-      // error, or the main thread signaled us to exit
-      break;
+  // Get all axis values.
+  for (unsigned i = 0; i < gamepad->numAxes; i++) {
+    double new_value;
+    if (gamepad->axes[i].caps.LogicalMin < 0) {
+LONG value;
+      if (mHID.mHidP_GetScaledUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage,
+                                   0, gamepad->axes[i].caps.Range.UsageMin,
+                                   &value, parsed,
+                                   (PCHAR)raw->data.hid.bRawData,
+                                   raw->data.hid.dwSizeHid)
+          != HIDP_STATUS_SUCCESS) {
+        continue;
+      }
+      new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
+                            gamepad->axes[i].caps.LogicalMax);
     }
-
-    unsigned int i = result - WAIT_OBJECT_0;
-
-    if (i == self->mEvents.Length() - 2) {
-      // Main thread is signaling for a device rescan.
-      self->ScanForDevices();
-      continue;
-    }
-
-    {
-      MutexAutoLock lock(self->mMutex);
-      if (i >= self->mGamepads.Length()) {
-        // Something would be terribly wrong here, possibly we got
-        // a WAIT_ABANDONED_x result.
+    else {
+      ULONG value;
+      if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
+                             gamepad->axes[i].caps.Range.UsageMin, &value,
+                             parsed, (PCHAR)raw->data.hid.bRawData,
+                             raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
         continue;
       }
 
-      // first query for the number of items in the buffer
-      DWORD items = INFINITE;
-      nsRefPtr<IDirectInputDevice8> device = self->mGamepads[i].device;
-      if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
-                                nullptr,
-                                &items,
-                                DIGDD_PEEK)== DI_OK) {
-        while (items > 0) {
-          // now read each buffered event
-          //TODO: read more than one event at a time
-          DIDEVICEOBJECTDATA data;
-          DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA);
-          if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
-                                    &data, &readCount, 0) == DI_OK) {
-            //TODO: data.dwTimeStamp
-            GamepadEvent::Type type = GamepadEvent::Unknown;
-            int which;
-            if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) {
-              type = GamepadEvent::Button;
-              which = data.dwOfs - DIJOFS_BUTTON0;
-            }
-            else if(data.dwOfs >= DIJOFS_X  && data.dwOfs < DIJOFS_SLIDER(2)) {
-              // axis/slider
-              type = GamepadEvent::Axis;
-              which = (data.dwOfs - DIJOFS_X) / sizeof(LONG);
-            }
-            else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) {
-              HatState hatState;
-              HatPosToAxes(data.dwData, hatState);
-              which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD);
-              // Only send out axis move events for the axes that moved
-              // in this hat move.
-              if (hatState.x != self->mGamepads[i].hatState[which].x) {
-                type = GamepadEvent::HatX;
-              }
-              if (hatState.y != self->mGamepads[i].hatState[which].y) {
-                if (type == GamepadEvent::HatX) {
-                  type = GamepadEvent::HatXY;
-                }
-                else {
-                  type = GamepadEvent::HatY;
-                }
-              }
-              self->mGamepads[i].hatState[which].x = hatState.x;
-              self->mGamepads[i].hatState[which].y = hatState.y;
-            }
-
-            if (type != GamepadEvent::Unknown) {
-              nsRefPtr<GamepadEvent> event =
-                new GamepadEvent(self->mGamepads[i], type, which, data.dwData);
-              NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-            }
-          }
-          items--;
-        }
-      }
+      new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
+                            gamepad->axes[i].caps.LogicalMax);
+    }
+    if (gamepad->axes[i].value != new_value) {
+      gamepadsvc->NewAxisMoveEvent(gamepad->id, i, new_value);
+      gamepad->axes[i].value = new_value;
     }
   }
-  return 0;
+
+  return true;
 }
 
 void
-WindowsGamepadService::Startup() {
-  mThread = CreateThread(nullptr,
-                         0,
-                         DInputThread,
-                         this,
-                         0,
-                         nullptr);
+WindowsGamepadService::Startup()
+{
+  ScanForDevices();
 }
 
 void
-WindowsGamepadService::Shutdown() {
-  if (mThread) {
-    SetEvent(mThreadExitEvent);
-    WaitForSingleObject(mThread, INFINITE);
-    CloseHandle(mThread);
-  }
+WindowsGamepadService::Shutdown()
+{
   Cleanup();
 }
 
-// This method is called from the main thread.
 void
-WindowsGamepadService::SetGamepadID(int localID, int globalID) {
-  MutexAutoLock lock(mMutex);
-  mGamepads[localID].globalID = globalID;
-}
-
-// This method is called from the main thread.
-void WindowsGamepadService::RemoveGamepad(int localID) {
-  MutexAutoLock lock(mMutex);
-  mGamepads[localID].remove = true;
-  // Signal background thread to remove device.
-  DevicesChanged(DeviceChangeStable);
-}
-
-void
-WindowsGamepadService::Cleanup() {
-  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
-    CleanupGamepad(mGamepads[i]);
-  }
+WindowsGamepadService::Cleanup()
+{
   mGamepads.Clear();
 }
 
 void
-WindowsGamepadService::CleanupGamepad(Gamepad& gamepad) {
-  gamepad.device->Unacquire();
-  gamepad.device->SetEventNotification(nullptr);
-  CloseHandle(gamepad.event);
-}
-
-void
-WindowsGamepadService::DevicesChanged(DeviceChangeType type) {
+WindowsGamepadService::DevicesChanged(DeviceChangeType type)
+{
   if (type == DeviceChangeNotification) {
     mObserver->SetDeviceChangeTimer();
   } else if (type == DeviceChangeStable) {
-    SetEvent(mThreadRescanEvent);
+    ScanForDevices();
   }
 }
 
 NS_IMETHODIMP
 Observer::Observe(nsISupports* aSubject,
                   const char* aTopic,
-                  const char16_t* aData) {
+                  const char16_t* aData)
+{
   if (strcmp(aTopic, "timer-callback") == 0) {
     mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable);
   } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
     Stop();
   }
   return NS_OK;
 }
 
 HWND sHWnd = nullptr;
 
+bool
+RegisterRawInput(HWND hwnd, bool enable)
+{
+  nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages));
+  rid.SetLength(ArrayLength(kUsagePages));
+
+  for (unsigned i = 0; i < rid.Length(); i++) {
+    rid[i].usUsagePage = kUsagePages[i].usagePage;
+    rid[i].usUsage = kUsagePages[i].usage;
+    rid[i].dwFlags =
+      enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE;
+    rid[i].hwndTarget = hwnd;
+  }
+
+  if (!RegisterRawInputDevices(rid.Elements(), rid.Length(),
+                               sizeof(RAWINPUTDEVICE))) {
+    return false;
+  }
+  return true;
+}
+
 static
 LRESULT CALLBACK
-GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
+GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
   const unsigned int DBT_DEVICEARRIVAL        = 0x8000;
   const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
   const unsigned int DBT_DEVNODES_CHANGED     = 0x7;
 
-  if (msg == WM_DEVICECHANGE &&
-      (wParam == DBT_DEVICEARRIVAL ||
-       wParam == DBT_DEVICEREMOVECOMPLETE ||
-       wParam == DBT_DEVNODES_CHANGED)) {
+  switch (msg) {
+  case WM_DEVICECHANGE:
+    if (wParam == DBT_DEVICEARRIVAL ||
+        wParam == DBT_DEVICEREMOVECOMPLETE ||
+        wParam == DBT_DEVNODES_CHANGED) {
+      if (gService) {
+        gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification);
+      }
+    }
+    break;
+  case WM_INPUT:
     if (gService) {
-      gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification);
+      gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
     }
+    break;
   }
   return DefWindowProc(hwnd, msg, wParam, lParam);
 }
 
 } // namespace
 
 namespace mozilla {
 namespace hal_impl {
 
 void StartMonitoringGamepadStatus()
 {
-  if (gService)
+  if (gService) {
     return;
+  }
 
   gService = new WindowsGamepadService();
   gService->Startup();
 
   if (sHWnd == nullptr) {
     WNDCLASSW wc;
     HMODULE hSelf = GetModuleHandle(nullptr);
 
@@ -737,25 +756,28 @@ void StartMonitoringGamepadStatus()
       wc.lpfnWndProc = GamepadWindowProc;
       wc.lpszClassName = L"MozillaGamepadClass";
       RegisterClassW(&wc);
     }
 
     sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
                           0, 0, 0, 0, 0,
                           nullptr, nullptr, hSelf, nullptr);
+    RegisterRawInput(sHWnd, true);
   }
 }
 
 void StopMonitoringGamepadStatus()
 {
-  if (!gService)
+  if (!gService) {
     return;
+  }
 
   if (sHWnd) {
+    RegisterRawInput(sHWnd, false);
     DestroyWindow(sHWnd);
     sHWnd = nullptr;
   }
 
   gService->Shutdown();
   delete gService;
   gService = nullptr;
 }
--- a/toolkit/library/libxul.mk
+++ b/toolkit/library/libxul.mk
@@ -209,23 +209,16 @@ endif
 ifeq ($(OS_ARCH),WINNT)
 OS_LIBS += $(call EXPAND_LIBNAME,shell32 ole32 version winspool comdlg32 imm32 msimg32 shlwapi psapi ws2_32 dbghelp rasapi32 rasdlg iphlpapi uxtheme setupapi secur32 sensorsapi portabledeviceguids windowscodecs wininet wbemuuid wintrust)
 ifdef ACCESSIBILITY
 OS_LIBS += $(call EXPAND_LIBNAME,oleacc)
 endif
 ifdef MOZ_METRO
 OS_LIBS += $(call EXPAND_LIBNAME,uiautomationcore runtimeobject)
 endif
-ifdef MOZ_GAMEPAD
-ifdef MOZ_HAS_WINSDK_WITH_D3D
-OS_LIBS += $(call EXPAND_LIBNAME,dxguid dinput8)
-else
-OS_LIBS += $(call EXPAND_LIBNAME_PATH,dxguid dinput8, '$(subst \,/,$(MOZ_DIRECTX_SDK_PATH))/Lib/$(MOZ_DIRECTX_SDK_CPU_SUFFIX)')
-endif
-endif
 endif # WINNT
 
 ifdef MOZ_JPROF
 EXTRA_DSO_LDOPTS += -ljprof
 endif
 
 ifdef MOZ_ENABLE_QT
 EXTRA_DSO_LDOPTS += $(MOZ_QT_LDFLAGS) $(XEXT_LIBS)