Bug 980876 - Be smarter about sending gamepad updates from the background thread. r=smaug, a=sledru
authorTed Mielczarek <ted@mielczarek.org>
Mon, 07 Apr 2014 14:28:08 -0400
changeset 183670 7ccc27d5c8f4
parent 183669 ee6aea5824b7
child 183671 30c45853f8cb
push id3441
push userryanvm@gmail.com
push date2014-04-08 16:59 +0000
Treeherderresults
reviewerssmaug, sledru
bugs980876
milestone29.0
Bug 980876 - Be smarter about sending gamepad updates from the background thread. r=smaug, a=sledru
hal/windows/WindowsGamepad.cpp
--- a/hal/windows/WindowsGamepad.cpp
+++ b/hal/windows/WindowsGamepad.cpp
@@ -15,39 +15,45 @@
 
 #include "nsIComponentManager.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.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;
 
 const LONG kMaxAxisValue = 65535;
 const DWORD BUTTON_DOWN_MASK = 0x80;
 // 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;
 
+class WindowsGamepadService;
+WindowsGamepadService* gService = nullptr;
+
 typedef struct {
   float x,y;
 } HatState;
 
 struct Gamepad {
   // From DirectInput, unique to this device+computer combination.
   GUID guidInstance;
   // The ID assigned by the base GamepadService
-  int id;
+  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;
@@ -59,16 +65,19 @@ struct Gamepad {
   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];
   // 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
@@ -117,115 +126,16 @@ HatPosToAxes(DWORD hatPos, HatState& axe
   }
   else if (hatPos == 315 * DI_DEGREES) {
     // Up-left
     axes.x = -1.0;
     axes.y = -1.0;
   }
 }
 
-// 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) : mGamepad(gamepad),
-                             mType(type),
-                             mWhich(which),
-                             mData(data) {
-  }
-
-  NS_IMETHOD Run() {
-    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-
-    switch (mType) {
-    case Button:
-      gamepadsvc->NewButtonEvent(mGamepad.id, mWhich, mData & BUTTON_DOWN_MASK);
-      break;
-    case Axis: {
-      float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f;
-      gamepadsvc->NewAxisMoveEvent(mGamepad.id, mWhich, adjustedData);
-    }
-    case HatX:
-    case HatY:
-    case HatXY: {
-      // Synthesize 2 axes per POV hat for convenience.
-      HatState hatState;
-      HatPosToAxes(mData, hatState);
-      int xAxis = mGamepad.numAxes + 2 * mWhich;
-      int yAxis = mGamepad.numAxes + 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(mGamepad.id, xAxis, hatState.x);
-      }
-      if (mType == HatY || mType == HatXY) {
-        gamepadsvc->NewAxisMoveEvent(mGamepad.id, yAxis, hatState.y);
-      }
-      break;
-    }
-    case Unknown:
-      break;
-    }
-    return NS_OK;
-  }
-
-  const Gamepad& mGamepad;
-  // 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,
-                     Type type) : mGamepad(gamepad),
-                                  mID(gamepad.id),
-                                  mType(type) {
-  }
-
-  NS_IMETHOD Run() {
-    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (mType == Added) {
-      mGamepad.id = gamepadsvc->AddGamepad(mGamepad.idstring,
-                                           mozilla::dom::NoMapping,
-                                           mGamepad.numButtons,
-                                           mGamepad.numAxes +
-                                           mGamepad.numHats*2);
-    } else {
-      gamepadsvc->RemoveGamepad(mID);
-    }
-    return NS_OK;
-  }
-
-private:
-  Gamepad& mGamepad;
-  uint32_t mID;
-  Type mType;
-};
-
-class WindowsGamepadService;
-
 class Observer : public nsIObserver {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   Observer(WindowsGamepadService& svc) : mSvc(svc),
                                          mObserving(true) {
     nsresult rv;
@@ -286,16 +196,18 @@ public:
 
   enum DeviceChangeType {
     DeviceChangeNotification,
     DeviceChangeStable
   };
   void DevicesChanged(DeviceChangeType type);
   void Startup();
   void Shutdown();
+  void SetGamepadID(int localID, int globalID);
+  void RemoveGamepad(int localID);
 
 private:
   void ScanForDevices();
   void Cleanup();
   void CleanupGamepad(Gamepad& gamepad);
   // Callback for enumerating axes on a device
   static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
                                            LPVOID pvRef);
@@ -307,28 +219,147 @@ private:
   // 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;
 };
 
+// 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());
+
+    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;
+  }
+
+  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) {
+  }
+
+  NS_IMETHOD Run() {
+    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+    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;
+  }
+
+private:
+  // ID in WindowsGamepadService::mGamepads
+  int mLocalID;
+  nsCString mName;
+  int mGamepadButtons;
+  int mGamepadAxes;
+  int mGamepadHats;
+  // ID from GamepadService
+  uint32_t mGlobalID;
+  Type mType;
+};
+
 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,
@@ -360,21 +391,24 @@ WindowsGamepadService::EnumObjectsCallba
 
 // 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.
-  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;
+  {
+    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;
+      }
     }
   }
 
   Gamepad gamepad;
   memset(&gamepad, 0, sizeof(Gamepad));
   if (self->dinput->CreateDevice(lpddi->guidInstance,
                                  getter_AddRefs(gamepad.device),
                                  nullptr)
@@ -417,60 +451,76 @@ WindowsGamepadService::EnumCallback(LPCD
     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[self->mGamepads.Length() - 1],
+        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);
     }
   }
   return DIENUM_CONTINUE;
 }
 
 void
 WindowsGamepadService::ScanForDevices() {
-  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
-    mGamepads[i].present = false;
+  {
+    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;
+      }
+    }
   }
 
   dinput->EnumDevices(DI8DEVCLASS_GAMECTRL,
                       (LPDIENUMDEVICESCALLBACK)EnumCallback,
                       this,
                       DIEDFL_ATTACHEDONLY);
 
-  // Look for devices that were removed.
-  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
-    if (!mGamepads[i].present) {
-      nsRefPtr<GamepadChangeEvent> event =
-        new GamepadChangeEvent(mGamepads[i],
-                               GamepadChangeEvent::Removed);
-      NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      CleanupGamepad(mGamepads[i]);
-      mGamepads.RemoveElementAt(i);
+  // 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);
+      }
+    }
+
+    mEvents.Clear();
+    for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+      mEvents.AppendElement(mGamepads[i].event);
     }
   }
 
-  mEvents.Clear();
-  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
-    mEvents.AppendElement(mGamepads[i].event);
-  }
 
   // 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
@@ -493,76 +543,79 @@ WindowsGamepadService::DInputThread(LPVO
     unsigned int i = result - WAIT_OBJECT_0;
 
     if (i == self->mEvents.Length() - 2) {
       // Main thread is signaling for a device rescan.
       self->ScanForDevices();
       continue;
     }
 
-    if (i >= self->mGamepads.Length()) {
-      // Something would be terribly wrong here, possibly we got
-      // a WAIT_ABANDONED_x result.
-      continue;
-    }
+    {
+      MutexAutoLock lock(self->mMutex);
+      if (i >= self->mGamepads.Length()) {
+        // Something would be terribly wrong here, possibly we got
+        // a WAIT_ABANDONED_x result.
+        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;
+      // 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);
+            }
           }
-          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--;
         }
-        items--;
       }
     }
   }
   return 0;
 }
 
 void
 WindowsGamepadService::Startup() {
@@ -579,16 +632,31 @@ WindowsGamepadService::Shutdown() {
   if (mThread) {
     SetEvent(mThreadExitEvent);
     WaitForSingleObject(mThread, INFINITE);
     CloseHandle(mThread);
   }
   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]);
   }
   mGamepads.Clear();
 }
 
@@ -615,17 +683,16 @@ Observer::Observe(nsISupports* aSubject,
   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;
 }
 
-WindowsGamepadService* gService = nullptr;
 HWND sHWnd = nullptr;
 
 static
 LRESULT CALLBACK
 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;