Index: content/browser/gamepad/gamepad_platform_data_fetcher_win.cc |
diff --git a/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc b/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc |
index 937b9a2f070cdf74d2fc1b8c5a63bd805abfe40f..37af9797187a4ca99395e11af7bcea17cca770a4 100644 |
--- a/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc |
+++ b/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc |
@@ -4,10 +4,21 @@ |
#include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h" |
+#include <dinput.h> |
+#include <dinputd.h> |
+ |
#include "base/debug/trace_event.h" |
+#include "base/stringprintf.h" |
#include "content/common/gamepad_messages.h" |
#include "content/common/gamepad_hardware_buffer.h" |
+// This was removed from the Windows 8 SDK for some reason. |
+// We need it so we can get state for axes without worrying if they |
+// exist. |
+#ifndef DIDFT_OPTIONAL |
+#define DIDFT_OPTIONAL 0x80000000 |
+#endif |
+ |
namespace content { |
using namespace WebKit; |
@@ -28,7 +39,7 @@ static const BYTE kDeviceSubTypeDrumKit = 8; |
static const BYTE kDeviceSubTypeGuitarBass = 11; |
static const BYTE kDeviceSubTypeArcadePad = 19; |
-float NormalizeAxis(SHORT value) { |
+float NormalizeXInputAxis(SHORT value) { |
return ((value + 32768.f) / 32767.5f) - 1.f; |
} |
@@ -48,105 +59,418 @@ const WebUChar* const GamepadSubTypeName(BYTE sub_type) { |
} |
} |
+bool GetDirectInputVendorProduct(IDirectInputDevice8* gamepad, |
+ std::string* vendor, |
+ std::string* product) { |
+ DIPROPDWORD prop; |
+ prop.diph.dwSize = sizeof(DIPROPDWORD); |
+ prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); |
+ prop.diph.dwObj = 0; |
+ prop.diph.dwHow = DIPH_DEVICE; |
+ |
+ if (FAILED(gamepad->GetProperty(DIPROP_VIDPID, &prop.diph))) |
+ return false; |
+ *vendor = base::StringPrintf("%04x", LOWORD(prop.dwData)); |
+ *product = base::StringPrintf("%04x", HIWORD(prop.dwData)); |
+ return true; |
+} |
+ |
+// Sets the deadzone value for all axes of a gamepad. |
+// deadzone values range from 0 (no deadzone) to 10,000 (entire range |
+// is dead). |
+bool SetDirectInputDeadZone(IDirectInputDevice8* gamepad, |
+ int deadzone) { |
+ DIPROPDWORD prop; |
+ prop.diph.dwSize = sizeof(DIPROPDWORD); |
+ prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); |
+ prop.diph.dwObj = 0; |
+ prop.diph.dwHow = DIPH_DEVICE; |
+ prop.dwData = deadzone; |
+ return SUCCEEDED(gamepad->SetProperty(DIPROP_DEADZONE, &prop.diph)); |
+} |
+ |
+struct InternalDirectInputDevice { |
+ IDirectInputDevice8* gamepad; |
+ GamepadStandardMappingFunction mapper; |
+ wchar_t id[WebGamepad::idLengthCap]; |
+ GUID guid; |
+}; |
+ |
+struct EnumDevicesContext { |
+ IDirectInput8* directinput_interface; |
+ std::vector<InternalDirectInputDevice>* directinput_devices; |
+}; |
+ |
+// We define our own data format structure to attempt to get as many |
+// axes as possible. |
+struct JoyData { |
+ long axes[10]; |
+ char buttons[24]; |
+ DWORD pov; // Often used for D-pads. |
+}; |
+ |
+BOOL CALLBACK DirectInputEnumDevicesCallback(const DIDEVICEINSTANCE* instance, |
+ void* context) { |
+ EnumDevicesContext* ctxt = reinterpret_cast<EnumDevicesContext*>(context); |
+ IDirectInputDevice8* gamepad; |
+ |
+ if (FAILED(ctxt->directinput_interface->CreateDevice(instance->guidInstance, |
+ &gamepad, |
+ NULL))) |
+ return DIENUM_CONTINUE; |
+ |
+ gamepad->Acquire(); |
+ |
+#define MAKE_AXIS(i) \ |
+ {0, FIELD_OFFSET(JoyData, axes) + 4 * i, \ |
+ DIDFT_AXIS | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0} |
+#define MAKE_BUTTON(i) \ |
+ {&GUID_Button, FIELD_OFFSET(JoyData, buttons) + i, \ |
+ DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0} |
+#define MAKE_POV() \ |
+ {&GUID_POV, FIELD_OFFSET(JoyData, pov), DIDFT_POV | DIDFT_OPTIONAL, 0} |
+ DIOBJECTDATAFORMAT rgodf[] = { |
+ MAKE_AXIS(0), |
+ MAKE_AXIS(1), |
+ MAKE_AXIS(2), |
+ MAKE_AXIS(3), |
+ MAKE_AXIS(4), |
+ MAKE_AXIS(5), |
+ MAKE_AXIS(6), |
+ MAKE_AXIS(7), |
+ MAKE_AXIS(8), |
+ MAKE_AXIS(9), |
+ MAKE_BUTTON(0), |
+ MAKE_BUTTON(1), |
+ MAKE_BUTTON(2), |
+ MAKE_BUTTON(3), |
+ MAKE_BUTTON(4), |
+ MAKE_BUTTON(5), |
+ MAKE_BUTTON(6), |
+ MAKE_BUTTON(7), |
+ MAKE_BUTTON(8), |
+ MAKE_BUTTON(9), |
+ MAKE_BUTTON(10), |
+ MAKE_BUTTON(11), |
+ MAKE_BUTTON(12), |
+ MAKE_BUTTON(13), |
+ MAKE_BUTTON(14), |
+ MAKE_BUTTON(15), |
+ MAKE_BUTTON(16), |
+ MAKE_POV(), |
+ }; |
+#undef MAKE_AXIS |
+#undef MAKE_BUTTON |
+#undef MAKE_POV |
+ |
+ DIDATAFORMAT df = { |
+ sizeof (DIDATAFORMAT), |
+ sizeof (DIOBJECTDATAFORMAT), |
+ DIDF_ABSAXIS, |
+ sizeof (JoyData), |
+ sizeof (rgodf) / sizeof (rgodf[0]), |
+ rgodf |
+ }; |
+ |
+ // If we can't set the data format on the device, don't add it to our |
+ // list, since we won't know how to read data from it. |
+ if (FAILED(gamepad->SetDataFormat(&df))) { |
+ gamepad->Release(); |
+ return DIENUM_CONTINUE; |
+ } |
+ |
+ InternalDirectInputDevice device; |
+ device.guid = instance->guidInstance; |
+ device.gamepad = gamepad; |
+ std::string vendor; |
+ std::string product; |
+ if (!GetDirectInputVendorProduct(gamepad, &vendor, &product)) { |
+ gamepad->Release(); |
+ return DIENUM_CONTINUE; |
+ } |
+ |
+ // Set the dead zone to 10% of the axis length for all axes. This |
+ // gives us a larger space for what's "neutral" so the controls don't |
+ // slowly drift. |
+ SetDirectInputDeadZone(gamepad, 1000); |
+ device.mapper = GetGamepadStandardMappingFunction(vendor, product); |
+ if (device.mapper) { |
+ base::swprintf(device.id, |
+ WebGamepad::idLengthCap, |
+ L"STANDARD GAMEPAD (%ls)", |
+ instance->tszProductName); |
+ ctxt->directinput_devices->push_back(device); |
+ } else { |
+ gamepad->Release(); |
+ } |
+ return DIENUM_CONTINUE; |
+} |
+ |
} // namespace |
GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin() |
: xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))), |
- xinput_available_(GetXinputDllFunctions()) { |
+ xinput_available_(GetXInputDllFunctions()) { |
+ directinput_available_ = SUCCEEDED(DirectInput8Create( |
+ GetModuleHandle(NULL), |
+ DIRECTINPUT_VERSION, |
+ IID_IDirectInput8, |
+ reinterpret_cast<void**>(&directinput_interface_), |
+ NULL)); |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) |
+ pad_state_[i].status = DISCONNECTED; |
} |
GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() { |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ if (pad_state_[i].status == DIRECTINPUT_CONNECTED) |
+ pad_state_[i].directinput_gamepad->Release(); |
+ } |
} |
+int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const { |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ if (pad_state_[i].status == DISCONNECTED) |
+ return i; |
+ } |
+ return -1; |
+} |
+ |
+bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const { |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ if (pad_state_[i].status == XINPUT_CONNECTED && |
+ pad_state_[i].xinput_index == index) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool GamepadPlatformDataFetcherWin::HasDirectInputGamepad( |
+ const GUID& guid) const { |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ if (pad_state_[i].status == DIRECTINPUT_CONNECTED && |
+ pad_state_[i].guid == guid) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+void GamepadPlatformDataFetcherWin::EnumerateDevices( |
+ WebGamepads* pads) { |
+ TRACE_EVENT0("GAMEPAD", "EnumerateDevices"); |
+ |
+ // Mark all disconnected pads DISCONNECTED. |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ if (!pads->items[i].connected) |
+ pad_state_[i].status = DISCONNECTED; |
+ } |
+ |
+ for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) { |
+ if (HasXInputGamepad(i)) |
+ continue; |
+ int pad_index = FirstAvailableGamepadId(); |
+ if (pad_index == -1) |
+ return; // We can't add any more gamepads. |
+ WebGamepad& pad = pads->items[pad_index]; |
+ if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) { |
+ pad_state_[pad_index].status = XINPUT_CONNECTED; |
+ pad_state_[pad_index].xinput_index = i; |
+ } |
+ } |
+ |
+ if (directinput_available_) { |
+ struct EnumDevicesContext context; |
+ std::vector<InternalDirectInputDevice> directinput_gamepads; |
+ context.directinput_interface = directinput_interface_; |
+ context.directinput_devices = &directinput_gamepads; |
+ |
+ directinput_interface_->EnumDevices( |
+ DI8DEVCLASS_GAMECTRL, |
+ &DirectInputEnumDevicesCallback, |
+ &context, |
+ DIEDFL_ATTACHEDONLY); |
+ for (size_t i = 0; i < directinput_gamepads.size(); ++i) { |
+ if (HasDirectInputGamepad(directinput_gamepads[i].guid)) { |
+ directinput_gamepads[i].gamepad->Release(); |
+ continue; |
+ } |
+ int pad_index = FirstAvailableGamepadId(); |
+ if (pad_index == -1) |
+ return; |
+ WebGamepad& pad = pads->items[pad_index]; |
+ pad.connected = true; |
+ wcscpy_s(pad.id, WebGamepad::idLengthCap, directinput_gamepads[i].id); |
+ PadState& state = pad_state_[pad_index]; |
+ state.status = DIRECTINPUT_CONNECTED; |
+ state.guid = directinput_gamepads[i].guid; |
+ state.directinput_gamepad = directinput_gamepads[i].gamepad; |
+ state.mapper = directinput_gamepads[i].mapper; |
+ } |
+ } |
+} |
+ |
+ |
void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads, |
- bool devices_changed_hint) { |
+ bool devices_changed_hint) { |
TRACE_EVENT0("GAMEPAD", "GetGamepadData"); |
- // If there's no XInput DLL on the system, early out so that we don't |
- // call any other XInput functions. |
- if (!xinput_available_) { |
+ if (!xinput_available_ && !directinput_available_) { |
pads->length = 0; |
return; |
} |
- pads->length = WebGamepads::itemsLengthCap; |
- |
+ // A note on XInput devices: |
// If we got notification that system devices have been updated, then |
// run GetCapabilities to update the connected status and the device |
// identifier. It can be slow to do to both GetCapabilities and |
// GetState on unconnected devices, so we want to avoid a 2-5ms pause |
// here by only doing this when the devices are updated (despite |
// documentation claiming it's OK to call it any time). |
- if (devices_changed_hint) { |
- for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
- WebGamepad& pad = pads->items[i]; |
- TRACE_EVENT1("GAMEPAD", "GetCapabilities", "id", i); |
- XINPUT_CAPABILITIES caps; |
- DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps); |
- if (res == ERROR_DEVICE_NOT_CONNECTED) { |
- pad.connected = false; |
- } else { |
- pad.connected = true; |
- base::swprintf(pad.id, |
- WebGamepad::idLengthCap, |
- L"Xbox 360 Controller (XInput STANDARD %ls)", |
- GamepadSubTypeName(caps.SubType)); |
- } |
- } |
- } |
+ if (devices_changed_hint) |
+ EnumerateDevices(pads); |
- // We've updated the connection state if necessary, now update the actual |
- // data for the devices that are connected. |
- for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
WebGamepad& pad = pads->items[i]; |
+ if (pad_state_[i].status == XINPUT_CONNECTED) |
+ GetXInputPadData(i, &pad); |
+ else if (pad_state_[i].status == DIRECTINPUT_CONNECTED) |
+ GetDirectInputPadData(i, &pad); |
+ } |
+ pads->length = WebGamepads::itemsLengthCap; |
+} |
- // We rely on device_changed and GetCapabilities to tell us that |
- // something's been connected, but we will mark as disconnected if |
- // GetState returns that we've lost the pad. |
- if (!pad.connected) |
- continue; |
+bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity( |
+ int i, |
+ WebGamepad* pad) const { |
+ DCHECK(pad); |
+ TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i); |
+ XINPUT_CAPABILITIES caps; |
+ DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps); |
+ if (res == ERROR_DEVICE_NOT_CONNECTED) { |
+ pad->connected = false; |
+ return false; |
+ } else { |
+ pad->connected = true; |
+ base::swprintf(pad->id, |
+ WebGamepad::idLengthCap, |
+ L"Xbox 360 Controller (XInput STANDARD %ls)", |
+ GamepadSubTypeName(caps.SubType)); |
+ return true; |
+ } |
+} |
+ |
+void GamepadPlatformDataFetcherWin::GetXInputPadData( |
+ int i, |
+ WebGamepad* pad) { |
+ // We rely on device_changed and GetCapabilities to tell us that |
+ // something's been connected, but we will mark as disconnected if |
+ // GetState returns that we've lost the pad. |
+ if (!pad->connected) |
+ return; |
+ |
+ XINPUT_STATE state; |
+ memset(&state, 0, sizeof(XINPUT_STATE)); |
+ TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i); |
+ DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state); |
+ TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i); |
- XINPUT_STATE state; |
- memset(&state, 0, sizeof(XINPUT_STATE)); |
- TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i); |
- DWORD dwResult = xinput_get_state_(i, &state); |
- TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i); |
- |
- if (dwResult == ERROR_SUCCESS) { |
- pad.timestamp = state.dwPacketNumber; |
- pad.buttonsLength = 0; |
-#define ADD(b) pad.buttons[pad.buttonsLength++] = \ |
- ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0); |
- ADD(XINPUT_GAMEPAD_A); |
- ADD(XINPUT_GAMEPAD_B); |
- ADD(XINPUT_GAMEPAD_X); |
- ADD(XINPUT_GAMEPAD_Y); |
- ADD(XINPUT_GAMEPAD_LEFT_SHOULDER); |
- ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER); |
- pad.buttons[pad.buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0; |
- pad.buttons[pad.buttonsLength++] = state.Gamepad.bRightTrigger / 255.0; |
- ADD(XINPUT_GAMEPAD_BACK); |
- ADD(XINPUT_GAMEPAD_START); |
- ADD(XINPUT_GAMEPAD_LEFT_THUMB); |
- ADD(XINPUT_GAMEPAD_RIGHT_THUMB); |
- ADD(XINPUT_GAMEPAD_DPAD_UP); |
- ADD(XINPUT_GAMEPAD_DPAD_DOWN); |
- ADD(XINPUT_GAMEPAD_DPAD_LEFT); |
- ADD(XINPUT_GAMEPAD_DPAD_RIGHT); |
+ if (dwResult == ERROR_SUCCESS) { |
+ pad->timestamp = state.dwPacketNumber; |
+ pad->buttonsLength = 0; |
+#define ADD(b) pad->buttons[pad->buttonsLength++] = \ |
+ ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0); |
+ ADD(XINPUT_GAMEPAD_A); |
+ ADD(XINPUT_GAMEPAD_B); |
+ ADD(XINPUT_GAMEPAD_X); |
+ ADD(XINPUT_GAMEPAD_Y); |
+ ADD(XINPUT_GAMEPAD_LEFT_SHOULDER); |
+ ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER); |
+ pad->buttons[pad->buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0; |
+ pad->buttons[pad->buttonsLength++] = state.Gamepad.bRightTrigger / 255.0; |
+ ADD(XINPUT_GAMEPAD_BACK); |
+ ADD(XINPUT_GAMEPAD_START); |
+ ADD(XINPUT_GAMEPAD_LEFT_THUMB); |
+ ADD(XINPUT_GAMEPAD_RIGHT_THUMB); |
+ ADD(XINPUT_GAMEPAD_DPAD_UP); |
+ ADD(XINPUT_GAMEPAD_DPAD_DOWN); |
+ ADD(XINPUT_GAMEPAD_DPAD_LEFT); |
+ ADD(XINPUT_GAMEPAD_DPAD_RIGHT); |
#undef ADD |
- pad.axesLength = 0; |
- // XInput are +up/+right, -down/-left, we want -up/-left. |
- pad.axes[pad.axesLength++] = NormalizeAxis(state.Gamepad.sThumbLX); |
- pad.axes[pad.axesLength++] = -NormalizeAxis(state.Gamepad.sThumbLY); |
- pad.axes[pad.axesLength++] = NormalizeAxis(state.Gamepad.sThumbRX); |
- pad.axes[pad.axesLength++] = -NormalizeAxis(state.Gamepad.sThumbRY); |
- } else { |
- pad.connected = false; |
+ pad->axesLength = 0; |
+ // XInput are +up/+right, -down/-left, we want -up/-left. |
+ pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX); |
+ pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY); |
+ pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX); |
+ pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY); |
+ } else { |
+ pad->connected = false; |
+ } |
+} |
+ |
+void GamepadPlatformDataFetcherWin::GetDirectInputPadData( |
+ int index, |
+ WebGamepad* pad) { |
+ if (!pad->connected) |
+ return; |
+ |
+ IDirectInputDevice8* gamepad = pad_state_[index].directinput_gamepad; |
+ if (FAILED(gamepad->Poll())) { |
+ // Polling didn't work, try acquiring the gamepad. |
+ if (FAILED(gamepad->Acquire())) { |
+ pad->buttonsLength = 0; |
+ pad->axesLength = 0; |
+ return; |
} |
+ // Try polling again. |
+ if (FAILED(gamepad->Poll())) { |
+ pad->buttonsLength = 0; |
+ pad->axesLength = 0; |
+ return; |
+ } |
+ } |
+ JoyData state; |
+ if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) { |
+ pad->connected = false; |
+ return; |
} |
+ |
+ WebGamepad raw; |
+ raw.connected = true; |
+ for (int i = 0; i < 16; i++) |
+ raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0; |
+ |
+ // We map the POV (often a D-pad) into the buttons 16-19. |
+ // DirectInput gives pov measurements in hundredths of degrees, |
+ // clockwise from "North". |
+ // We use 22.5 degree slices so we can handle diagonal D-raw presses. |
+ static const int arc_segment = 2250; // 22.5 degrees = 1/16 circle |
+ if (state.pov > arc_segment && state.pov < 7 * arc_segment) |
+ raw.buttons[19] = 1.0; |
+ else |
+ raw.buttons[19] = 0.0; |
+ |
+ if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment) |
+ raw.buttons[17] = 1.0; |
+ else |
+ raw.buttons[17] = 0.0; |
+ |
+ if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment) |
+ raw.buttons[18] = 1.0; |
+ else |
+ raw.buttons[18] = 0.0; |
+ |
+ if (state.pov < 3 * arc_segment || |
+ (state.pov > 13 * arc_segment && state.pov < 36000)) |
+ raw.buttons[16] = 1.0; |
+ else |
+ raw.buttons[16] = 0.0; |
+ |
+ for (int i = 0; i < 10; i++) |
+ raw.axes[i] = state.axes[i]; |
+ pad_state_[index].mapper(raw, pad); |
} |
-bool GamepadPlatformDataFetcherWin::GetXinputDllFunctions() { |
+bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() { |
xinput_get_capabilities_ = NULL; |
xinput_get_state_ = NULL; |
xinput_enable_ = static_cast<XInputEnableFunc>( |