Home | History | Annotate | Download | only in gamepad
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h"
      6 
      7 #include <dinput.h>
      8 #include <dinputd.h>
      9 
     10 #include "base/debug/trace_event.h"
     11 #include "base/strings/stringprintf.h"
     12 #include "base/win/windows_version.h"
     13 #include "content/common/gamepad_hardware_buffer.h"
     14 #include "content/common/gamepad_messages.h"
     15 
     16 // This was removed from the Windows 8 SDK for some reason.
     17 // We need it so we can get state for axes without worrying if they
     18 // exist.
     19 #ifndef DIDFT_OPTIONAL
     20 #define DIDFT_OPTIONAL 0x80000000
     21 #endif
     22 
     23 namespace content {
     24 
     25 using namespace WebKit;
     26 
     27 namespace {
     28 
     29 // See http://goo.gl/5VSJR. These are not available in all versions of the
     30 // header, but they can be returned from the driver, so we define our own
     31 // versions here.
     32 static const BYTE kDeviceSubTypeGamepad = 1;
     33 static const BYTE kDeviceSubTypeWheel = 2;
     34 static const BYTE kDeviceSubTypeArcadeStick = 3;
     35 static const BYTE kDeviceSubTypeFlightStick = 4;
     36 static const BYTE kDeviceSubTypeDancePad = 5;
     37 static const BYTE kDeviceSubTypeGuitar = 6;
     38 static const BYTE kDeviceSubTypeGuitarAlternate = 7;
     39 static const BYTE kDeviceSubTypeDrumKit = 8;
     40 static const BYTE kDeviceSubTypeGuitarBass = 11;
     41 static const BYTE kDeviceSubTypeArcadePad = 19;
     42 
     43 float NormalizeXInputAxis(SHORT value) {
     44   return ((value + 32768.f) / 32767.5f) - 1.f;
     45 }
     46 
     47 const WebUChar* const GamepadSubTypeName(BYTE sub_type) {
     48   switch (sub_type) {
     49     case kDeviceSubTypeGamepad: return L"GAMEPAD";
     50     case kDeviceSubTypeWheel: return L"WHEEL";
     51     case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK";
     52     case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK";
     53     case kDeviceSubTypeDancePad: return L"DANCE_PAD";
     54     case kDeviceSubTypeGuitar: return L"GUITAR";
     55     case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE";
     56     case kDeviceSubTypeDrumKit: return L"DRUM_KIT";
     57     case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS";
     58     case kDeviceSubTypeArcadePad: return L"ARCADE_PAD";
     59     default: return L"<UNKNOWN>";
     60   }
     61 }
     62 
     63 bool GetDirectInputVendorProduct(IDirectInputDevice8* gamepad,
     64                                  std::string* vendor,
     65                                  std::string* product) {
     66   DIPROPDWORD prop;
     67   prop.diph.dwSize = sizeof(DIPROPDWORD);
     68   prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
     69   prop.diph.dwObj = 0;
     70   prop.diph.dwHow = DIPH_DEVICE;
     71 
     72   if (FAILED(gamepad->GetProperty(DIPROP_VIDPID, &prop.diph)))
     73     return false;
     74   *vendor = base::StringPrintf("%04x", LOWORD(prop.dwData));
     75   *product = base::StringPrintf("%04x", HIWORD(prop.dwData));
     76   return true;
     77 }
     78 
     79 // Sets the deadzone value for all axes of a gamepad.
     80 // deadzone values range from 0 (no deadzone) to 10,000 (entire range
     81 // is dead).
     82 bool SetDirectInputDeadZone(IDirectInputDevice8* gamepad,
     83                             int deadzone) {
     84   DIPROPDWORD prop;
     85   prop.diph.dwSize = sizeof(DIPROPDWORD);
     86   prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
     87   prop.diph.dwObj = 0;
     88   prop.diph.dwHow = DIPH_DEVICE;
     89   prop.dwData = deadzone;
     90   return SUCCEEDED(gamepad->SetProperty(DIPROP_DEADZONE, &prop.diph));
     91 }
     92 
     93 struct InternalDirectInputDevice {
     94   IDirectInputDevice8* gamepad;
     95   GamepadStandardMappingFunction mapper;
     96   wchar_t id[WebGamepad::idLengthCap];
     97   GUID guid;
     98 };
     99 
    100 struct EnumDevicesContext {
    101   IDirectInput8* directinput_interface;
    102   std::vector<InternalDirectInputDevice>* directinput_devices;
    103 };
    104 
    105 // We define our own data format structure to attempt to get as many
    106 // axes as possible.
    107 struct JoyData {
    108   long axes[10];
    109   char buttons[24];
    110   DWORD pov;  // Often used for D-pads.
    111 };
    112 
    113 BOOL CALLBACK DirectInputEnumDevicesCallback(const DIDEVICEINSTANCE* instance,
    114                                              void* context) {
    115   EnumDevicesContext* ctxt = reinterpret_cast<EnumDevicesContext*>(context);
    116   IDirectInputDevice8* gamepad;
    117 
    118   if (FAILED(ctxt->directinput_interface->CreateDevice(instance->guidInstance,
    119                                                        &gamepad,
    120                                                        NULL)))
    121     return DIENUM_CONTINUE;
    122 
    123   gamepad->Acquire();
    124 
    125 #define MAKE_AXIS(i) \
    126   {0, FIELD_OFFSET(JoyData, axes) + 4 * i, \
    127    DIDFT_AXIS | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0}
    128 #define MAKE_BUTTON(i) \
    129   {&GUID_Button, FIELD_OFFSET(JoyData, buttons) + i, \
    130    DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0}
    131 #define MAKE_POV() \
    132   {&GUID_POV, FIELD_OFFSET(JoyData, pov), DIDFT_POV | DIDFT_OPTIONAL, 0}
    133   DIOBJECTDATAFORMAT rgodf[] = {
    134     MAKE_AXIS(0),
    135     MAKE_AXIS(1),
    136     MAKE_AXIS(2),
    137     MAKE_AXIS(3),
    138     MAKE_AXIS(4),
    139     MAKE_AXIS(5),
    140     MAKE_AXIS(6),
    141     MAKE_AXIS(7),
    142     MAKE_AXIS(8),
    143     MAKE_AXIS(9),
    144     MAKE_BUTTON(0),
    145     MAKE_BUTTON(1),
    146     MAKE_BUTTON(2),
    147     MAKE_BUTTON(3),
    148     MAKE_BUTTON(4),
    149     MAKE_BUTTON(5),
    150     MAKE_BUTTON(6),
    151     MAKE_BUTTON(7),
    152     MAKE_BUTTON(8),
    153     MAKE_BUTTON(9),
    154     MAKE_BUTTON(10),
    155     MAKE_BUTTON(11),
    156     MAKE_BUTTON(12),
    157     MAKE_BUTTON(13),
    158     MAKE_BUTTON(14),
    159     MAKE_BUTTON(15),
    160     MAKE_BUTTON(16),
    161     MAKE_POV(),
    162   };
    163 #undef MAKE_AXIS
    164 #undef MAKE_BUTTON
    165 #undef MAKE_POV
    166 
    167   DIDATAFORMAT df = {
    168     sizeof (DIDATAFORMAT),
    169     sizeof (DIOBJECTDATAFORMAT),
    170     DIDF_ABSAXIS,
    171     sizeof (JoyData),
    172     sizeof (rgodf) / sizeof (rgodf[0]),
    173     rgodf
    174   };
    175 
    176   // If we can't set the data format on the device, don't add it to our
    177   // list, since we won't know how to read data from it.
    178   if (FAILED(gamepad->SetDataFormat(&df))) {
    179     gamepad->Release();
    180     return DIENUM_CONTINUE;
    181   }
    182 
    183   InternalDirectInputDevice device;
    184   device.guid = instance->guidInstance;
    185   device.gamepad = gamepad;
    186   std::string vendor;
    187   std::string product;
    188   if (!GetDirectInputVendorProduct(gamepad, &vendor, &product)) {
    189     gamepad->Release();
    190     return DIENUM_CONTINUE;
    191   }
    192 
    193   // Set the dead zone to 10% of the axis length for all axes. This
    194   // gives us a larger space for what's "neutral" so the controls don't
    195   // slowly drift.
    196   SetDirectInputDeadZone(gamepad, 1000);
    197   device.mapper = GetGamepadStandardMappingFunction(vendor, product);
    198   if (device.mapper) {
    199     base::swprintf(device.id,
    200                    WebGamepad::idLengthCap,
    201                    L"STANDARD GAMEPAD (%ls)",
    202                    instance->tszProductName);
    203     ctxt->directinput_devices->push_back(device);
    204   } else {
    205     gamepad->Release();
    206   }
    207   return DIENUM_CONTINUE;
    208 }
    209 
    210 }  // namespace
    211 
    212 GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin()
    213     : xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))),
    214       xinput_available_(GetXInputDllFunctions()) {
    215   // TODO(teravest): http://crbug.com/260187
    216   if (base::win::GetVersion() > base::win::VERSION_XP) {
    217     directinput_available_ = SUCCEEDED(DirectInput8Create(
    218         GetModuleHandle(NULL),
    219         DIRECTINPUT_VERSION,
    220         IID_IDirectInput8,
    221         reinterpret_cast<void**>(&directinput_interface_),
    222         NULL));
    223   } else {
    224     directinput_available_ = false;
    225   }
    226   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i)
    227     pad_state_[i].status = DISCONNECTED;
    228 }
    229 
    230 GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() {
    231   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    232     if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
    233       pad_state_[i].directinput_gamepad->Release();
    234   }
    235 }
    236 
    237 int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const {
    238   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    239     if (pad_state_[i].status == DISCONNECTED)
    240       return i;
    241   }
    242   return -1;
    243 }
    244 
    245 bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const {
    246   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    247     if (pad_state_[i].status == XINPUT_CONNECTED &&
    248         pad_state_[i].xinput_index == index)
    249       return true;
    250   }
    251   return false;
    252 }
    253 
    254 bool GamepadPlatformDataFetcherWin::HasDirectInputGamepad(
    255     const GUID& guid) const {
    256   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    257     if (pad_state_[i].status == DIRECTINPUT_CONNECTED &&
    258         pad_state_[i].guid == guid)
    259       return true;
    260   }
    261   return false;
    262 }
    263 
    264 void GamepadPlatformDataFetcherWin::EnumerateDevices(
    265     WebGamepads* pads) {
    266   TRACE_EVENT0("GAMEPAD", "EnumerateDevices");
    267 
    268   // Mark all disconnected pads DISCONNECTED.
    269   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    270     if (!pads->items[i].connected)
    271       pad_state_[i].status = DISCONNECTED;
    272   }
    273 
    274   for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) {
    275     if (HasXInputGamepad(i))
    276       continue;
    277     int pad_index = FirstAvailableGamepadId();
    278     if (pad_index == -1)
    279       return;  // We can't add any more gamepads.
    280     WebGamepad& pad = pads->items[pad_index];
    281     if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) {
    282       pad_state_[pad_index].status = XINPUT_CONNECTED;
    283       pad_state_[pad_index].xinput_index = i;
    284     }
    285   }
    286 
    287   if (directinput_available_) {
    288     struct EnumDevicesContext context;
    289     std::vector<InternalDirectInputDevice> directinput_gamepads;
    290     context.directinput_interface = directinput_interface_;
    291     context.directinput_devices = &directinput_gamepads;
    292 
    293     directinput_interface_->EnumDevices(
    294         DI8DEVCLASS_GAMECTRL,
    295         &DirectInputEnumDevicesCallback,
    296         &context,
    297         DIEDFL_ATTACHEDONLY);
    298     for (size_t i = 0; i < directinput_gamepads.size(); ++i) {
    299       if (HasDirectInputGamepad(directinput_gamepads[i].guid)) {
    300         directinput_gamepads[i].gamepad->Release();
    301         continue;
    302       }
    303       int pad_index = FirstAvailableGamepadId();
    304       if (pad_index == -1)
    305         return;
    306       WebGamepad& pad = pads->items[pad_index];
    307       pad.connected = true;
    308       wcscpy_s(pad.id, WebGamepad::idLengthCap, directinput_gamepads[i].id);
    309       PadState& state = pad_state_[pad_index];
    310       state.status = DIRECTINPUT_CONNECTED;
    311       state.guid = directinput_gamepads[i].guid;
    312       state.directinput_gamepad = directinput_gamepads[i].gamepad;
    313       state.mapper = directinput_gamepads[i].mapper;
    314     }
    315   }
    316 }
    317 
    318 
    319 void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads,
    320                                                    bool devices_changed_hint) {
    321   TRACE_EVENT0("GAMEPAD", "GetGamepadData");
    322 
    323   if (!xinput_available_ && !directinput_available_) {
    324     pads->length = 0;
    325     return;
    326   }
    327 
    328   // A note on XInput devices:
    329   // If we got notification that system devices have been updated, then
    330   // run GetCapabilities to update the connected status and the device
    331   // identifier. It can be slow to do to both GetCapabilities and
    332   // GetState on unconnected devices, so we want to avoid a 2-5ms pause
    333   // here by only doing this when the devices are updated (despite
    334   // documentation claiming it's OK to call it any time).
    335   if (devices_changed_hint)
    336     EnumerateDevices(pads);
    337 
    338   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    339     WebGamepad& pad = pads->items[i];
    340     if (pad_state_[i].status == XINPUT_CONNECTED)
    341       GetXInputPadData(i, &pad);
    342     else if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
    343       GetDirectInputPadData(i, &pad);
    344   }
    345   pads->length = WebGamepads::itemsLengthCap;
    346 }
    347 
    348 bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity(
    349     int i,
    350     WebGamepad* pad) const {
    351   DCHECK(pad);
    352   TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i);
    353   XINPUT_CAPABILITIES caps;
    354   DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps);
    355   if (res == ERROR_DEVICE_NOT_CONNECTED) {
    356     pad->connected = false;
    357     return false;
    358   } else {
    359     pad->connected = true;
    360     base::swprintf(pad->id,
    361                    WebGamepad::idLengthCap,
    362                    L"Xbox 360 Controller (XInput STANDARD %ls)",
    363                    GamepadSubTypeName(caps.SubType));
    364     return true;
    365   }
    366 }
    367 
    368 void GamepadPlatformDataFetcherWin::GetXInputPadData(
    369     int i,
    370     WebGamepad* pad) {
    371   // We rely on device_changed and GetCapabilities to tell us that
    372   // something's been connected, but we will mark as disconnected if
    373   // GetState returns that we've lost the pad.
    374   if (!pad->connected)
    375     return;
    376 
    377   XINPUT_STATE state;
    378   memset(&state, 0, sizeof(XINPUT_STATE));
    379   TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i);
    380   DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state);
    381   TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i);
    382 
    383   if (dwResult == ERROR_SUCCESS) {
    384     pad->timestamp = state.dwPacketNumber;
    385     pad->buttonsLength = 0;
    386 #define ADD(b) pad->buttons[pad->buttonsLength++] = \
    387   ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0);
    388     ADD(XINPUT_GAMEPAD_A);
    389     ADD(XINPUT_GAMEPAD_B);
    390     ADD(XINPUT_GAMEPAD_X);
    391     ADD(XINPUT_GAMEPAD_Y);
    392     ADD(XINPUT_GAMEPAD_LEFT_SHOULDER);
    393     ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER);
    394     pad->buttons[pad->buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0;
    395     pad->buttons[pad->buttonsLength++] = state.Gamepad.bRightTrigger / 255.0;
    396     ADD(XINPUT_GAMEPAD_BACK);
    397     ADD(XINPUT_GAMEPAD_START);
    398     ADD(XINPUT_GAMEPAD_LEFT_THUMB);
    399     ADD(XINPUT_GAMEPAD_RIGHT_THUMB);
    400     ADD(XINPUT_GAMEPAD_DPAD_UP);
    401     ADD(XINPUT_GAMEPAD_DPAD_DOWN);
    402     ADD(XINPUT_GAMEPAD_DPAD_LEFT);
    403     ADD(XINPUT_GAMEPAD_DPAD_RIGHT);
    404 #undef ADD
    405     pad->axesLength = 0;
    406     // XInput are +up/+right, -down/-left, we want -up/-left.
    407     pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX);
    408     pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY);
    409     pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX);
    410     pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY);
    411   } else {
    412     pad->connected = false;
    413   }
    414 }
    415 
    416 void GamepadPlatformDataFetcherWin::GetDirectInputPadData(
    417     int index,
    418     WebGamepad* pad) {
    419   if (!pad->connected)
    420     return;
    421 
    422   IDirectInputDevice8* gamepad = pad_state_[index].directinput_gamepad;
    423   if (FAILED(gamepad->Poll())) {
    424     // Polling didn't work, try acquiring the gamepad.
    425     if (FAILED(gamepad->Acquire())) {
    426       pad->buttonsLength = 0;
    427       pad->axesLength = 0;
    428       return;
    429     }
    430     // Try polling again.
    431     if (FAILED(gamepad->Poll())) {
    432       pad->buttonsLength = 0;
    433       pad->axesLength = 0;
    434       return;
    435     }
    436   }
    437   JoyData state;
    438   if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) {
    439     pad->connected = false;
    440     return;
    441   }
    442 
    443   WebGamepad raw;
    444   raw.connected = true;
    445   for (int i = 0; i < 16; i++)
    446     raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0;
    447 
    448   // We map the POV (often a D-pad) into the buttons 16-19.
    449   // DirectInput gives pov measurements in hundredths of degrees,
    450   // clockwise from "North".
    451   // We use 22.5 degree slices so we can handle diagonal D-raw presses.
    452   static const int arc_segment = 2250;  // 22.5 degrees = 1/16 circle
    453   if (state.pov > arc_segment && state.pov < 7 * arc_segment)
    454     raw.buttons[19] = 1.0;
    455   else
    456     raw.buttons[19] = 0.0;
    457 
    458   if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment)
    459     raw.buttons[17] = 1.0;
    460   else
    461     raw.buttons[17] = 0.0;
    462 
    463   if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment)
    464     raw.buttons[18] = 1.0;
    465   else
    466     raw.buttons[18] = 0.0;
    467 
    468   if (state.pov < 3 * arc_segment ||
    469       (state.pov > 13 * arc_segment && state.pov < 36000))
    470     raw.buttons[16] = 1.0;
    471   else
    472     raw.buttons[16] = 0.0;
    473 
    474   for (int i = 0; i < 10; i++)
    475     raw.axes[i] = state.axes[i];
    476   pad_state_[index].mapper(raw, pad);
    477 }
    478 
    479 bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() {
    480   xinput_get_capabilities_ = NULL;
    481   xinput_get_state_ = NULL;
    482   xinput_enable_ = reinterpret_cast<XInputEnableFunc>(
    483       xinput_dll_.GetFunctionPointer("XInputEnable"));
    484   if (!xinput_enable_)
    485     return false;
    486   xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>(
    487       xinput_dll_.GetFunctionPointer("XInputGetCapabilities"));
    488   if (!xinput_get_capabilities_)
    489     return false;
    490   xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>(
    491       xinput_dll_.GetFunctionPointer("XInputGetState"));
    492   if (!xinput_get_state_)
    493     return false;
    494   xinput_enable_(true);
    495   return true;
    496 }
    497 
    498 }  // namespace content
    499