hal/windows/WindowsGamepad.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 #include <algorithm>
     6 #include <cstddef>
     8 #include <stdio.h>
     9 #ifndef UNICODE
    10 #define UNICODE
    11 #endif
    12 #include <windows.h>
    13 #define DIRECTINPUT_VERSION 0x0800
    14 #include <dinput.h>
    16 #include "nsIComponentManager.h"
    17 #include "nsIObserver.h"
    18 #include "nsIObserverService.h"
    19 #include "nsITimer.h"
    20 #include "nsTArray.h"
    21 #include "nsThreadUtils.h"
    22 #include "mozilla/dom/GamepadService.h"
    23 #include "mozilla/Mutex.h"
    24 #include "mozilla/Services.h"
    26 namespace {
    28 using mozilla::dom::GamepadService;
    29 using mozilla::Mutex;
    30 using mozilla::MutexAutoLock;
    32 const unsigned kMaxAxes = 32;
    33 const LONG kMaxAxisValue = 65535;
    34 const DWORD BUTTON_DOWN_MASK = 0x80;
    35 // Multiple devices-changed notifications can be sent when a device
    36 // is connected, because USB devices consist of multiple logical devices.
    37 // Therefore, we wait a bit after receiving one before looking for
    38 // device changes.
    39 const uint32_t kDevicesChangedStableDelay = 200;
    41 class WindowsGamepadService;
    42 WindowsGamepadService* gService = nullptr;
    44 typedef struct {
    45   float x,y;
    46 } HatState;
    48 struct Gamepad {
    49   // From DirectInput, unique to this device+computer combination.
    50   GUID guidInstance;
    51   // The ID assigned by the base GamepadService
    52   int globalID;
    53   // A somewhat unique string consisting of the USB vendor/product IDs,
    54   // and the controller name.
    55   char idstring[128];
    56   // USB vendor and product IDs
    57   int vendorID;
    58   int productID;
    59   // Information about the physical device.
    60   DWORD axes[kMaxAxes];
    61   int numAxes;
    62   int numHats;
    63   int numButtons;
    64   // The human-readable device name.
    65   char name[128];
    66   // The DirectInput device.
    67   nsRefPtr<IDirectInputDevice8> device;
    68   // A handle that DirectInput signals when there is new data from
    69   // the device.
    70   HANDLE event;
    71   // The state of any POV hats on the device.
    72   HatState hatState[4];
    73   // Used during rescan to find devices that were disconnected.
    74   bool present;
    75   // Passed back from the main thread to indicate a device can
    76   // now be removed.
    77   bool remove;
    78 };
    80 // Given DWORD |hatPos| representing the position of the POV hat per:
    81 // http://msdn.microsoft.com/en-us/library/ee418260%28v=VS.85%29.aspx
    82 // fill |axes| with the position of the x and y axes.
    83 //
    84 //XXX: ostensibly the values could be arbitrary degrees for a hat with
    85 // full rotation, but we'll punt on that for now. This should handle
    86 // 8-way D-pads exposed as POV hats.
    87 static void
    88 HatPosToAxes(DWORD hatPos, HatState& axes) {
    89   // hatPos is in hundredths of a degree clockwise from north.
    90   if (LOWORD(hatPos) == 0xFFFF) {
    91     // centered
    92     axes.x = axes.y = 0.0;
    93   }
    94   else if (hatPos == 0)  {
    95     // Up
    96     axes.x = 0.0;
    97     axes.y = -1.0;
    98   }
    99   else if (hatPos == 45 * DI_DEGREES) {
   100     // Up-right
   101     axes.x = 1.0;
   102     axes.y = -1.0;
   103   }
   104   else if (hatPos == 90 * DI_DEGREES) {
   105     // Right
   106     axes.x = 1.0;
   107     axes.y = 0.0;
   108   }
   109   else if (hatPos == 135 * DI_DEGREES) {
   110     // Down-right
   111     axes.x = 1.0;
   112     axes.y = 1.0;
   113   }
   114   else if (hatPos == 180 * DI_DEGREES) {
   115     // Down
   116     axes.x = 0.0;
   117     axes.y = 1.0;
   118   }
   119   else if (hatPos == 225 * DI_DEGREES) {
   120     // Down-left
   121     axes.x = -1.0;
   122     axes.y = 1.0;
   123   }
   124   else if (hatPos == 270 * DI_DEGREES) {
   125     // Left
   126     axes.x = -1.0;
   127     axes.y = 0.0;
   128   }
   129   else if (hatPos == 315 * DI_DEGREES) {
   130     // Up-left
   131     axes.x = -1.0;
   132     axes.y = -1.0;
   133   }
   134 }
   136 class Observer : public nsIObserver {
   137 public:
   138   NS_DECL_ISUPPORTS
   139   NS_DECL_NSIOBSERVER
   141   Observer(WindowsGamepadService& svc) : mSvc(svc),
   142                                          mObserving(true) {
   143     nsresult rv;
   144     mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
   145     nsCOMPtr<nsIObserverService> observerService =
   146       mozilla::services::GetObserverService();
   147     observerService->AddObserver(this,
   148                                  NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
   149                                  false);
   150   }
   152   void Stop() {
   153     if (mTimer) {
   154       mTimer->Cancel();
   155     }
   156     if (mObserving) {
   157       nsCOMPtr<nsIObserverService> observerService =
   158         mozilla::services::GetObserverService();
   159       observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
   160       mObserving = false;
   161     }
   162   }
   164   virtual ~Observer() {
   165     Stop();
   166   }
   168   void SetDeviceChangeTimer() {
   169     // set stable timer, since we will get multiple devices-changed
   170     // notifications at once
   171     if (mTimer) {
   172       mTimer->Cancel();
   173       mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT);
   174     }
   175   }
   177 private:
   178   // Gamepad service owns us, we just hold a reference back to it.
   179   WindowsGamepadService& mSvc;
   180   nsCOMPtr<nsITimer> mTimer;
   181   bool mObserving;
   182 };
   184 NS_IMPL_ISUPPORTS(Observer, nsIObserver);
   186 class WindowsGamepadService {
   187 public:
   188   WindowsGamepadService();
   189   virtual ~WindowsGamepadService() {
   190     Cleanup();
   191     CloseHandle(mThreadExitEvent);
   192     CloseHandle(mThreadRescanEvent);
   193     if (dinput) {
   194       dinput->Release();
   195       dinput = nullptr;
   196     }
   197   }
   199   enum DeviceChangeType {
   200     DeviceChangeNotification,
   201     DeviceChangeStable
   202   };
   203   void DevicesChanged(DeviceChangeType type);
   204   void Startup();
   205   void Shutdown();
   206   void SetGamepadID(int localID, int globalID);
   207   void RemoveGamepad(int localID);
   209 private:
   210   void ScanForDevices();
   211   void Cleanup();
   212   void CleanupGamepad(Gamepad& gamepad);
   213   // Callback for enumerating axes on a device
   214   static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
   215                                            LPVOID pvRef);
   216   // Callback for enumerating devices via DInput
   217   static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef);
   218   // Thread function to wait on device events
   219   static DWORD WINAPI DInputThread(LPVOID arg);
   221   // Used to signal the background thread to exit.
   222   HANDLE mThreadExitEvent;
   223   // Used to signal the background thread to rescan devices.
   224   HANDLE mThreadRescanEvent;
   225   HANDLE mThread;
   227   // List of connected devices.
   228   nsTArray<Gamepad> mGamepads;
   229   // Used to lock mutation of mGamepads.
   230   Mutex mMutex;
   231   // List of event handles used for signaling.
   232   nsTArray<HANDLE> mEvents;
   234   LPDIRECTINPUT8 dinput;
   236   nsRefPtr<Observer> mObserver;
   237 };
   239 // Used to post events from the background thread to the foreground thread.
   240 class GamepadEvent : public nsRunnable {
   241 public:
   242   typedef enum {
   243     Axis,
   244     Button,
   245     HatX,
   246     HatY,
   247     HatXY,
   248     Unknown
   249   } Type;
   251   GamepadEvent(const Gamepad& gamepad,
   252                Type type,
   253                int which,
   254                DWORD data) : mGlobalID(gamepad.globalID),
   255                              mGamepadAxes(gamepad.numAxes),
   256                              mType(type),
   257                              mWhich(which),
   258                              mData(data) {
   259   }
   261   NS_IMETHOD Run() {
   262     nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
   263     if (!gamepadsvc) {
   264       return NS_OK;
   265     }
   267     switch (mType) {
   268     case Button:
   269       gamepadsvc->NewButtonEvent(mGlobalID, mWhich, mData & BUTTON_DOWN_MASK);
   270       break;
   271     case Axis: {
   272       float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f;
   273       gamepadsvc->NewAxisMoveEvent(mGlobalID, mWhich, adjustedData);
   274     }
   275     case HatX:
   276     case HatY:
   277     case HatXY: {
   278       // Synthesize 2 axes per POV hat for convenience.
   279       HatState hatState;
   280       HatPosToAxes(mData, hatState);
   281       int xAxis = mGamepadAxes + 2 * mWhich;
   282       int yAxis = mGamepadAxes + 2 * mWhich + 1;
   283       //TODO: ostensibly we could not fire an event if one axis hasn't
   284       // changed, but it's a pain to track that.
   285       if (mType == HatX || mType == HatXY) {
   286         gamepadsvc->NewAxisMoveEvent(mGlobalID, xAxis, hatState.x);
   287       }
   288       if (mType == HatY || mType == HatXY) {
   289         gamepadsvc->NewAxisMoveEvent(mGlobalID, yAxis, hatState.y);
   290       }
   291       break;
   292     }
   293     case Unknown:
   294       break;
   295     }
   296     return NS_OK;
   297   }
   299   int mGlobalID;
   300   int mGamepadAxes;
   301   // Type of event
   302   Type mType;
   303   // Which button/axis is involved
   304   int mWhich;
   305   // Data specific to event
   306   DWORD mData;
   307 };
   309 class GamepadChangeEvent : public nsRunnable {
   310 public:
   311   enum Type {
   312     Added,
   313     Removed
   314   };
   315   GamepadChangeEvent(Gamepad& gamepad,
   316                      int localID,
   317                      Type type) : mLocalID(localID),
   318                                   mName(gamepad.idstring),
   319                                   mGlobalID(gamepad.globalID),
   320                                   mGamepadButtons(gamepad.numButtons),
   321                                   mGamepadAxes(gamepad.numAxes),
   322                                   mGamepadHats(gamepad.numHats),
   323                                   mType(type) {
   324   }
   326   NS_IMETHOD Run() {
   327     nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
   328     if (!gamepadsvc) {
   329       return NS_OK;
   330     }
   331     if (mType == Added) {
   332       int globalID = gamepadsvc->AddGamepad(mName.get(),
   333                                             mozilla::dom::NoMapping,
   334                                             mGamepadButtons,
   335                                             mGamepadAxes +
   336                                             mGamepadHats*2);
   337       if (gService) {
   338         gService->SetGamepadID(mLocalID, globalID);
   339       }
   340     } else {
   341       gamepadsvc->RemoveGamepad(mGlobalID);
   342       if (gService) {
   343         gService->RemoveGamepad(mLocalID);
   344       }
   345     }
   346     return NS_OK;
   347   }
   349 private:
   350   // ID in WindowsGamepadService::mGamepads
   351   int mLocalID;
   352   nsCString mName;
   353   int mGamepadButtons;
   354   int mGamepadAxes;
   355   int mGamepadHats;
   356   // ID from GamepadService
   357   uint32_t mGlobalID;
   358   Type mType;
   359 };
   361 WindowsGamepadService::WindowsGamepadService()
   362   : mThreadExitEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
   363     mThreadRescanEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
   364     mThread(nullptr),
   365     mMutex("Windows Gamepad Service"),
   366     dinput(nullptr) {
   367   mObserver = new Observer(*this);
   368   // Initialize DirectInput
   369   CoInitialize(nullptr);
   370   if (CoCreateInstance(CLSID_DirectInput8,
   371                        nullptr,
   372                        CLSCTX_INPROC_SERVER,
   373                        IID_IDirectInput8W,
   374                        (LPVOID*)&dinput) == S_OK) {
   375     if (dinput->Initialize(GetModuleHandle(nullptr),
   376                            DIRECTINPUT_VERSION) != DI_OK) {
   377       dinput->Release();
   378       dinput = nullptr;
   379     }
   380   }
   381 }
   383 // static
   384 BOOL CALLBACK
   385 WindowsGamepadService::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
   386                                            LPVOID pvRef) {
   387   // Ensure that all axes are using the same range.
   388   Gamepad* gamepad = reinterpret_cast<Gamepad*>(pvRef);
   389   DIPROPRANGE dp;
   390   dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
   391   dp.diph.dwSize = sizeof(DIPROPRANGE);
   392   dp.diph.dwHow = DIPH_BYID;
   393   dp.diph.dwObj = lpddoi->dwType;
   394   dp.lMin = 0;
   395   dp.lMax = kMaxAxisValue;
   396   gamepad->device->SetProperty(DIPROP_RANGE, &dp.diph);
   397   // Find what the dwOfs of this object in the c_dfDIJoystick data format is.
   398   for (DWORD i = 0; i < c_dfDIJoystick.dwNumObjs; i++) {
   399     if (c_dfDIJoystick.rgodf[i].pguid &&
   400         IsEqualGUID(*c_dfDIJoystick.rgodf[i].pguid, lpddoi->guidType) &&
   401         gamepad->numAxes < kMaxAxes) {
   402       gamepad->axes[gamepad->numAxes] = c_dfDIJoystick.rgodf[i].dwOfs;
   403       gamepad->numAxes++;
   404       break;
   405     }
   406   }
   407   return DIENUM_CONTINUE;
   408 }
   410 // static
   411 BOOL CALLBACK
   412 WindowsGamepadService::EnumCallback(LPCDIDEVICEINSTANCE lpddi,
   413                                     LPVOID pvRef) {
   414   WindowsGamepadService* self =
   415     reinterpret_cast<WindowsGamepadService*>(pvRef);
   416   // See if this device is already present in our list.
   417   {
   418     MutexAutoLock lock(self->mMutex);
   419     for (unsigned int i = 0; i < self->mGamepads.Length(); i++) {
   420       if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance,
   421                  sizeof(GUID)) == 0) {
   422         self->mGamepads[i].present = true;
   423         return DIENUM_CONTINUE;
   424       }
   425     }
   426   }
   428   Gamepad gamepad = {};
   429   if (self->dinput->CreateDevice(lpddi->guidInstance,
   430                                  getter_AddRefs(gamepad.device),
   431                                  nullptr)
   432       == DI_OK) {
   433     gamepad.present = true;
   434     memcpy(&gamepad.guidInstance, &lpddi->guidInstance, sizeof(GUID));
   436     DIDEVICEINSTANCE info;
   437     info.dwSize = sizeof(DIDEVICEINSTANCE);
   438     if (gamepad.device->GetDeviceInfo(&info) == DI_OK) {
   439       WideCharToMultiByte(CP_UTF8, 0, info.tszProductName, -1,
   440                           gamepad.name, sizeof(gamepad.name), nullptr, nullptr);
   441     }
   442     // Get vendor id and product id
   443     DIPROPDWORD dp;
   444     dp.diph.dwSize = sizeof(DIPROPDWORD);
   445     dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
   446     dp.diph.dwObj = 0;
   447     dp.diph.dwHow = DIPH_DEVICE;
   448     if (gamepad.device->GetProperty(DIPROP_VIDPID, &dp.diph) == DI_OK) {
   449       sprintf(gamepad.idstring, "%x-%x-%s",
   450               LOWORD(dp.dwData), HIWORD(dp.dwData), gamepad.name);
   451     }
   452     DIDEVCAPS caps;
   453     caps.dwSize = sizeof(DIDEVCAPS);
   454     if (gamepad.device->GetCapabilities(&caps) == DI_OK) {
   455       gamepad.numHats = caps.dwPOVs;
   456       gamepad.numButtons = caps.dwButtons;
   457       //XXX: handle polled devices?
   458       // (caps.dwFlags & DIDC_POLLEDDATAFORMAT || caps.dwFlags & DIDC_POLLEDDEVICE)
   459     }
   460     // Set min/max range for all axes on the device.
   461     // Axes will be gathered in EnumObjectsCallback.
   462     gamepad.numAxes = 0;
   463     gamepad.device->EnumObjects(EnumObjectsCallback, &gamepad, DIDFT_AXIS);
   464     // Set up structure for setting buffer size for buffered data
   465     dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
   466     dp.diph.dwSize = sizeof(DIPROPDWORD);
   467     dp.diph.dwObj = 0;
   468     dp.diph.dwHow = DIPH_DEVICE;
   469     dp.dwData = 64; // arbitrary
   470     // Create event so DInput can signal us when there's new data.
   471     gamepad.event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
   472     // Set data format, event notification, and acquire device
   473     if (gamepad.device->SetDataFormat(&c_dfDIJoystick) == DI_OK &&
   474         gamepad.device->SetProperty(DIPROP_BUFFERSIZE, &dp.diph) == DI_OK &&
   475         gamepad.device->SetEventNotification(gamepad.event) == DI_OK &&
   476         gamepad.device->Acquire() == DI_OK) {
   477       MutexAutoLock lock(self->mMutex);
   478       self->mGamepads.AppendElement(gamepad);
   479       // Inform the GamepadService
   480       int localID = self->mGamepads.Length() - 1;
   481       nsRefPtr<GamepadChangeEvent> event =
   482         new GamepadChangeEvent(self->mGamepads[localID],
   483                                localID,
   484                                GamepadChangeEvent::Added);
   485       NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   486     }
   487     else {
   488       if (gamepad.device) {
   489         gamepad.device->SetEventNotification(nullptr);
   490       }
   491       CloseHandle(gamepad.event);
   492     }
   493   }
   494   return DIENUM_CONTINUE;
   495 }
   497 void
   498 WindowsGamepadService::ScanForDevices() {
   499   {
   500     MutexAutoLock lock(mMutex);
   501     for (int i = mGamepads.Length() - 1; i >= 0; i--) {
   502       if (mGamepads[i].remove) {
   504         // Main thread has already handled this, safe to remove.
   505         CleanupGamepad(mGamepads[i]);
   506         mGamepads.RemoveElementAt(i);
   507       } else {
   508         mGamepads[i].present = false;
   509       }
   510     }
   511   }
   513   dinput->EnumDevices(DI8DEVCLASS_GAMECTRL,
   514                       (LPDIENUMDEVICESCALLBACK)EnumCallback,
   515                       this,
   516                       DIEDFL_ATTACHEDONLY);
   518   // Look for devices that are no longer present and inform the main thread.
   519   {
   520     MutexAutoLock lock(mMutex);
   521     for (int i = mGamepads.Length() - 1; i >= 0; i--) {
   522       if (!mGamepads[i].present) {
   523         nsRefPtr<GamepadChangeEvent> event =
   524           new GamepadChangeEvent(mGamepads[i],
   525                                  i,
   526                                  GamepadChangeEvent::Removed);
   527         NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   528       }
   529     }
   531     mEvents.Clear();
   532     for (unsigned int i = 0; i < mGamepads.Length(); i++) {
   533       mEvents.AppendElement(mGamepads[i].event);
   534     }
   535   }
   538   // These events must be the  last elements in the array, so that
   539   // the other elements match mGamepads in order.
   540   mEvents.AppendElement(mThreadRescanEvent);
   541   mEvents.AppendElement(mThreadExitEvent);
   542 }
   544 // static
   545 DWORD WINAPI
   546 WindowsGamepadService::DInputThread(LPVOID arg) {
   547   WindowsGamepadService* self = reinterpret_cast<WindowsGamepadService*>(arg);
   548   self->ScanForDevices();
   550   while (true) {
   551     DWORD result = WaitForMultipleObjects(self->mEvents.Length(),
   552                                           self->mEvents.Elements(),
   553                                           FALSE,
   554                                           INFINITE);
   555     if (result == WAIT_FAILED ||
   556         result == WAIT_OBJECT_0 + self->mEvents.Length() - 1) {
   557       // error, or the main thread signaled us to exit
   558       break;
   559     }
   561     unsigned int i = result - WAIT_OBJECT_0;
   563     if (i == self->mEvents.Length() - 2) {
   564       // Main thread is signaling for a device rescan.
   565       self->ScanForDevices();
   566       continue;
   567     }
   569     {
   570       MutexAutoLock lock(self->mMutex);
   571       if (i >= self->mGamepads.Length()) {
   572         // Something would be terribly wrong here, possibly we got
   573         // a WAIT_ABANDONED_x result.
   574         continue;
   575       }
   577       // first query for the number of items in the buffer
   578       DWORD items = INFINITE;
   579       nsRefPtr<IDirectInputDevice8> device = self->mGamepads[i].device;
   580       if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
   581                                 nullptr,
   582                                 &items,
   583                                 DIGDD_PEEK)== DI_OK) {
   584         while (items > 0) {
   585           // now read each buffered event
   586           //TODO: read more than one event at a time
   587           DIDEVICEOBJECTDATA data;
   588           DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA);
   589           if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
   590                                     &data, &readCount, 0) == DI_OK) {
   591             //TODO: data.dwTimeStamp
   592             GamepadEvent::Type type = GamepadEvent::Unknown;
   593             int which = -1;
   594             if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) {
   595               type = GamepadEvent::Button;
   596               which = data.dwOfs - DIJOFS_BUTTON0;
   597             }
   598             else if(data.dwOfs >= DIJOFS_X  && data.dwOfs < DIJOFS_SLIDER(2)) {
   599               // axis/slider
   600               type = GamepadEvent::Axis;
   601               for (int a = 0; a < self->mGamepads[i].numAxes; a++) {
   602                 if (self->mGamepads[i].axes[a] == data.dwOfs) {
   603                   which = a;
   604                   break;
   605                 }
   606               }
   607             }
   608             else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) {
   609               HatState hatState;
   610               HatPosToAxes(data.dwData, hatState);
   611               which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD);
   612               // Only send out axis move events for the axes that moved
   613               // in this hat move.
   614               if (hatState.x != self->mGamepads[i].hatState[which].x) {
   615                 type = GamepadEvent::HatX;
   616               }
   617               if (hatState.y != self->mGamepads[i].hatState[which].y) {
   618                 if (type == GamepadEvent::HatX) {
   619                   type = GamepadEvent::HatXY;
   620                 }
   621                 else {
   622                   type = GamepadEvent::HatY;
   623                 }
   624               }
   625               self->mGamepads[i].hatState[which].x = hatState.x;
   626               self->mGamepads[i].hatState[which].y = hatState.y;
   627             }
   629             if (type != GamepadEvent::Unknown && which != -1) {
   630               nsRefPtr<GamepadEvent> event =
   631                 new GamepadEvent(self->mGamepads[i], type, which, data.dwData);
   632               NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   633             }
   634           }
   635           items--;
   636         }
   637       }
   638     }
   639   }
   640   return 0;
   641 }
   643 void
   644 WindowsGamepadService::Startup() {
   645   mThread = CreateThread(nullptr,
   646                          0,
   647                          DInputThread,
   648                          this,
   649                          0,
   650                          nullptr);
   651 }
   653 void
   654 WindowsGamepadService::Shutdown() {
   655   if (mThread) {
   656     SetEvent(mThreadExitEvent);
   657     WaitForSingleObject(mThread, INFINITE);
   658     CloseHandle(mThread);
   659   }
   660   Cleanup();
   661 }
   663 // This method is called from the main thread.
   664 void
   665 WindowsGamepadService::SetGamepadID(int localID, int globalID) {
   666   MutexAutoLock lock(mMutex);
   667   mGamepads[localID].globalID = globalID;
   668 }
   670 // This method is called from the main thread.
   671 void WindowsGamepadService::RemoveGamepad(int localID) {
   672   MutexAutoLock lock(mMutex);
   673   mGamepads[localID].remove = true;
   674   // Signal background thread to remove device.
   675   DevicesChanged(DeviceChangeStable);
   676 }
   678 void
   679 WindowsGamepadService::Cleanup() {
   680   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
   681     CleanupGamepad(mGamepads[i]);
   682   }
   683   mGamepads.Clear();
   684 }
   686 void
   687 WindowsGamepadService::CleanupGamepad(Gamepad& gamepad) {
   688   gamepad.device->Unacquire();
   689   gamepad.device->SetEventNotification(nullptr);
   690   CloseHandle(gamepad.event);
   691 }
   693 void
   694 WindowsGamepadService::DevicesChanged(DeviceChangeType type) {
   695   if (type == DeviceChangeNotification) {
   696     mObserver->SetDeviceChangeTimer();
   697   } else if (type == DeviceChangeStable) {
   698     SetEvent(mThreadRescanEvent);
   699   }
   700 }
   702 NS_IMETHODIMP
   703 Observer::Observe(nsISupports* aSubject,
   704                   const char* aTopic,
   705                   const char16_t* aData) {
   706   if (strcmp(aTopic, "timer-callback") == 0) {
   707     mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable);
   708   } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
   709     Stop();
   710   }
   711   return NS_OK;
   712 }
   714 HWND sHWnd = nullptr;
   716 static
   717 LRESULT CALLBACK
   718 GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
   719   const unsigned int DBT_DEVICEARRIVAL        = 0x8000;
   720   const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
   721   const unsigned int DBT_DEVNODES_CHANGED     = 0x7;
   723   if (msg == WM_DEVICECHANGE &&
   724       (wParam == DBT_DEVICEARRIVAL ||
   725        wParam == DBT_DEVICEREMOVECOMPLETE ||
   726        wParam == DBT_DEVNODES_CHANGED)) {
   727     if (gService) {
   728       gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification);
   729     }
   730   }
   731   return DefWindowProc(hwnd, msg, wParam, lParam);
   732 }
   734 } // namespace
   736 namespace mozilla {
   737 namespace hal_impl {
   739 void StartMonitoringGamepadStatus()
   740 {
   741   if (gService)
   742     return;
   744   gService = new WindowsGamepadService();
   745   gService->Startup();
   747   if (sHWnd == nullptr) {
   748     WNDCLASSW wc;
   749     HMODULE hSelf = GetModuleHandle(nullptr);
   751     if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
   752       ZeroMemory(&wc, sizeof(WNDCLASSW));
   753       wc.hInstance = hSelf;
   754       wc.lpfnWndProc = GamepadWindowProc;
   755       wc.lpszClassName = L"MozillaGamepadClass";
   756       RegisterClassW(&wc);
   757     }
   759     sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
   760                           0, 0, 0, 0, 0,
   761                           nullptr, nullptr, hSelf, nullptr);
   762   }
   763 }
   765 void StopMonitoringGamepadStatus()
   766 {
   767   if (!gService)
   768     return;
   770   if (sHWnd) {
   771     DestroyWindow(sHWnd);
   772     sHWnd = nullptr;
   773   }
   775   gService->Shutdown();
   776   delete gService;
   777   gService = nullptr;
   778 }
   780 } // namespace hal_impl
   781 } // namespace mozilla

mercurial