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 "base/debug/trace_event.h"
      8 #include "base/strings/stringprintf.h"
      9 #include "base/win/windows_version.h"
     10 #include "content/common/gamepad_hardware_buffer.h"
     11 #include "content/common/gamepad_messages.h"
     12 
     13 namespace content {
     14 
     15 using namespace blink;
     16 
     17 namespace {
     18 
     19 // See http://goo.gl/5VSJR. These are not available in all versions of the
     20 // header, but they can be returned from the driver, so we define our own
     21 // versions here.
     22 static const BYTE kDeviceSubTypeGamepad = 1;
     23 static const BYTE kDeviceSubTypeWheel = 2;
     24 static const BYTE kDeviceSubTypeArcadeStick = 3;
     25 static const BYTE kDeviceSubTypeFlightStick = 4;
     26 static const BYTE kDeviceSubTypeDancePad = 5;
     27 static const BYTE kDeviceSubTypeGuitar = 6;
     28 static const BYTE kDeviceSubTypeGuitarAlternate = 7;
     29 static const BYTE kDeviceSubTypeDrumKit = 8;
     30 static const BYTE kDeviceSubTypeGuitarBass = 11;
     31 static const BYTE kDeviceSubTypeArcadePad = 19;
     32 
     33 float NormalizeXInputAxis(SHORT value) {
     34   return ((value + 32768.f) / 32767.5f) - 1.f;
     35 }
     36 
     37 const WebUChar* const GamepadSubTypeName(BYTE sub_type) {
     38   switch (sub_type) {
     39     case kDeviceSubTypeGamepad: return L"GAMEPAD";
     40     case kDeviceSubTypeWheel: return L"WHEEL";
     41     case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK";
     42     case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK";
     43     case kDeviceSubTypeDancePad: return L"DANCE_PAD";
     44     case kDeviceSubTypeGuitar: return L"GUITAR";
     45     case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE";
     46     case kDeviceSubTypeDrumKit: return L"DRUM_KIT";
     47     case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS";
     48     case kDeviceSubTypeArcadePad: return L"ARCADE_PAD";
     49     default: return L"<UNKNOWN>";
     50   }
     51 }
     52 
     53 }  // namespace
     54 
     55 GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin()
     56     : xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))),
     57       xinput_available_(GetXInputDllFunctions()) {
     58   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i)
     59     pad_state_[i].status = DISCONNECTED;
     60 
     61   raw_input_fetcher_.reset(new RawInputDataFetcher());
     62   raw_input_fetcher_->StartMonitor();
     63 }
     64 
     65 GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() {
     66   raw_input_fetcher_->StopMonitor();
     67 }
     68 
     69 int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const {
     70   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
     71     if (pad_state_[i].status == DISCONNECTED)
     72       return i;
     73   }
     74   return -1;
     75 }
     76 
     77 bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const {
     78   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
     79     if (pad_state_[i].status == XINPUT_CONNECTED &&
     80         pad_state_[i].xinput_index == index)
     81       return true;
     82   }
     83   return false;
     84 }
     85 
     86 bool GamepadPlatformDataFetcherWin::HasRawInputGamepad(
     87     const HANDLE handle) const {
     88   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
     89     if (pad_state_[i].status == RAWINPUT_CONNECTED &&
     90         pad_state_[i].raw_input_handle == handle)
     91       return true;
     92   }
     93   return false;
     94 }
     95 
     96 void GamepadPlatformDataFetcherWin::EnumerateDevices(
     97     WebGamepads* pads) {
     98   TRACE_EVENT0("GAMEPAD", "EnumerateDevices");
     99 
    100   // Mark all disconnected pads DISCONNECTED.
    101   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    102     if (!pads->items[i].connected)
    103       pad_state_[i].status = DISCONNECTED;
    104   }
    105 
    106   for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) {
    107     if (HasXInputGamepad(i))
    108       continue;
    109     int pad_index = FirstAvailableGamepadId();
    110     if (pad_index == -1)
    111       return;  // We can't add any more gamepads.
    112     WebGamepad& pad = pads->items[pad_index];
    113     if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) {
    114       pad_state_[pad_index].status = XINPUT_CONNECTED;
    115       pad_state_[pad_index].xinput_index = i;
    116       pad_state_[pad_index].mapper = NULL;
    117       pads->length++;
    118     }
    119   }
    120 
    121   if (raw_input_fetcher_->Available()) {
    122     std::vector<RawGamepadInfo*> raw_inputs =
    123         raw_input_fetcher_->EnumerateDevices();
    124     for (size_t i = 0; i < raw_inputs.size(); ++i) {
    125       RawGamepadInfo* gamepad = raw_inputs[i];
    126       if (HasRawInputGamepad(gamepad->handle))
    127         continue;
    128       int pad_index = FirstAvailableGamepadId();
    129       if (pad_index == -1)
    130         return;
    131       WebGamepad& pad = pads->items[pad_index];
    132       pad.connected = true;
    133       PadState& state = pad_state_[pad_index];
    134       state.status = RAWINPUT_CONNECTED;
    135       state.raw_input_handle = gamepad->handle;
    136 
    137       std::string vendor = base::StringPrintf("%04x", gamepad->vendor_id);
    138       std::string product = base::StringPrintf("%04x", gamepad->product_id);
    139       state.mapper = GetGamepadStandardMappingFunction(vendor, product);
    140 
    141       swprintf(pad.id, WebGamepad::idLengthCap,
    142         L"%ls (%lsVendor: %04x Product: %04x)",
    143         gamepad->id, state.mapper ? L"STANDARD GAMEPAD " : L"",
    144         gamepad->vendor_id, gamepad->product_id);
    145 
    146       if (state.mapper)
    147         swprintf(pad.mapping, WebGamepad::mappingLengthCap, L"standard");
    148       else
    149         pad.mapping[0] = 0;
    150 
    151       pads->length++;
    152     }
    153   }
    154 }
    155 
    156 
    157 void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads,
    158                                                    bool devices_changed_hint) {
    159   TRACE_EVENT0("GAMEPAD", "GetGamepadData");
    160 
    161   if (!xinput_available_ &&
    162       !raw_input_fetcher_->Available()) {
    163     pads->length = 0;
    164     return;
    165   }
    166 
    167   // A note on XInput devices:
    168   // If we got notification that system devices have been updated, then
    169   // run GetCapabilities to update the connected status and the device
    170   // identifier. It can be slow to do to both GetCapabilities and
    171   // GetState on unconnected devices, so we want to avoid a 2-5ms pause
    172   // here by only doing this when the devices are updated (despite
    173   // documentation claiming it's OK to call it any time).
    174   if (devices_changed_hint)
    175     EnumerateDevices(pads);
    176 
    177   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
    178     // We rely on device_changed and GetCapabilities to tell us that
    179     // something's been connected, but we will mark as disconnected if
    180     // Get___PadState returns that we've lost the pad.
    181     if (!pads->items[i].connected)
    182       continue;
    183 
    184     if (pad_state_[i].status == XINPUT_CONNECTED)
    185       GetXInputPadData(i, &pads->items[i]);
    186     else if (pad_state_[i].status == RAWINPUT_CONNECTED)
    187       GetRawInputPadData(i, &pads->items[i]);
    188   }
    189 }
    190 
    191 void GamepadPlatformDataFetcherWin::PauseHint(bool pause) {
    192   if (pause)
    193     raw_input_fetcher_->StopMonitor();
    194   else
    195     raw_input_fetcher_->StartMonitor();
    196 }
    197 
    198 bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity(
    199     int i,
    200     WebGamepad* pad) const {
    201   DCHECK(pad);
    202   TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i);
    203   XINPUT_CAPABILITIES caps;
    204   DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps);
    205   if (res == ERROR_DEVICE_NOT_CONNECTED) {
    206     pad->connected = false;
    207     return false;
    208   } else {
    209     pad->connected = true;
    210     swprintf(pad->id,
    211              WebGamepad::idLengthCap,
    212              L"Xbox 360 Controller (XInput STANDARD %ls)",
    213              GamepadSubTypeName(caps.SubType));
    214     swprintf(pad->mapping, WebGamepad::mappingLengthCap, L"standard");
    215     return true;
    216   }
    217 }
    218 
    219 void GamepadPlatformDataFetcherWin::GetXInputPadData(
    220     int i,
    221     WebGamepad* pad) {
    222   XINPUT_STATE state;
    223   memset(&state, 0, sizeof(XINPUT_STATE));
    224   TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i);
    225   DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state);
    226   TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i);
    227 
    228   if (dwResult == ERROR_SUCCESS) {
    229     pad->timestamp = state.dwPacketNumber;
    230     pad->buttonsLength = 0;
    231 #define ADD(b) pad->buttons[pad->buttonsLength].pressed = \
    232   (state.Gamepad.wButtons & (b)) != 0; \
    233   pad->buttons[pad->buttonsLength++].value = \
    234   ((state.Gamepad.wButtons & (b)) ? 1.f : 0.f);
    235     ADD(XINPUT_GAMEPAD_A);
    236     ADD(XINPUT_GAMEPAD_B);
    237     ADD(XINPUT_GAMEPAD_X);
    238     ADD(XINPUT_GAMEPAD_Y);
    239     ADD(XINPUT_GAMEPAD_LEFT_SHOULDER);
    240     ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER);
    241     pad->buttons[pad->buttonsLength].pressed =
    242         state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    243     pad->buttons[pad->buttonsLength++].value =
    244         state.Gamepad.bLeftTrigger / 255.f;
    245     pad->buttons[pad->buttonsLength].pressed =
    246         state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    247     pad->buttons[pad->buttonsLength++].value =
    248         state.Gamepad.bRightTrigger / 255.f;
    249     ADD(XINPUT_GAMEPAD_BACK);
    250     ADD(XINPUT_GAMEPAD_START);
    251     ADD(XINPUT_GAMEPAD_LEFT_THUMB);
    252     ADD(XINPUT_GAMEPAD_RIGHT_THUMB);
    253     ADD(XINPUT_GAMEPAD_DPAD_UP);
    254     ADD(XINPUT_GAMEPAD_DPAD_DOWN);
    255     ADD(XINPUT_GAMEPAD_DPAD_LEFT);
    256     ADD(XINPUT_GAMEPAD_DPAD_RIGHT);
    257 #undef ADD
    258     pad->axesLength = 0;
    259     // XInput are +up/+right, -down/-left, we want -up/-left.
    260     pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX);
    261     pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY);
    262     pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX);
    263     pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY);
    264   } else {
    265     pad->connected = false;
    266   }
    267 }
    268 
    269 void GamepadPlatformDataFetcherWin::GetRawInputPadData(
    270     int index,
    271     WebGamepad* pad) {
    272   RawGamepadInfo* gamepad = raw_input_fetcher_->GetGamepadInfo(
    273       pad_state_[index].raw_input_handle);
    274   if (!gamepad) {
    275     pad->connected = false;
    276     return;
    277   }
    278 
    279   WebGamepad raw_pad = *pad;
    280 
    281   raw_pad.timestamp = gamepad->report_id;
    282   raw_pad.buttonsLength = gamepad->buttons_length;
    283   raw_pad.axesLength =  gamepad->axes_length;
    284 
    285   for (unsigned int i = 0; i < raw_pad.buttonsLength; i++) {
    286     raw_pad.buttons[i].pressed = gamepad->buttons[i];
    287     raw_pad.buttons[i].value = gamepad->buttons[i] ? 1.0 : 0.0;
    288   }
    289 
    290   for (unsigned int i = 0; i < raw_pad.axesLength; i++)
    291     raw_pad.axes[i] = gamepad->axes[i].value;
    292 
    293   // Copy to the current state to the output buffer, using the mapping
    294   // function, if there is one available.
    295   if (pad_state_[index].mapper)
    296     pad_state_[index].mapper(raw_pad, pad);
    297   else
    298     *pad = raw_pad;
    299 }
    300 
    301 bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() {
    302   xinput_get_capabilities_ = NULL;
    303   xinput_get_state_ = NULL;
    304   xinput_enable_ = reinterpret_cast<XInputEnableFunc>(
    305       xinput_dll_.GetFunctionPointer("XInputEnable"));
    306   if (!xinput_enable_)
    307     return false;
    308   xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>(
    309       xinput_dll_.GetFunctionPointer("XInputGetCapabilities"));
    310   if (!xinput_get_capabilities_)
    311     return false;
    312   xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>(
    313       xinput_dll_.GetFunctionPointer("XInputGetState"));
    314   if (!xinput_get_state_)
    315     return false;
    316   xinput_enable_(true);
    317   return true;
    318 }
    319 
    320 }  // namespace content
    321