Chromium Code Reviews| 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 838a626d0184b093df64cb1ba6a2269dd9060022..04189a01a393acc1bfe21ac15ac1d235a92e68af 100644 |
| --- a/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc |
| +++ b/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc |
| @@ -4,7 +4,11 @@ |
| #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" |
| @@ -28,7 +32,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 +52,377 @@ 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]; |
| +}; |
| + |
| +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 = (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.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_(FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))), |
| - xinput_available_(GetXinputDllFunctions()) { |
| + xinput_available_(GetXInputDllFunctions()) { |
| + directinput_available_ = |
| + SUCCEEDED(DirectInput8Create(GetModuleHandle(NULL), |
| + DIRECTINPUT_VERSION, |
|
scottmg
2013/02/14 18:44:34
nit, parameter alignment
|
| + IID_IDirectInput8, |
| + (void **)&directinput_interface_, |
| + NULL)); |
| } |
| GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() { |
| } |
| +void GamepadPlatformDataFetcherWin::EnumerateDevices( |
| + WebGamepads* pads) { |
| + TRACE_EVENT0("GAMEPAD", "EnumerateDevices"); |
| + pad_state_.clear(); |
| + for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
| + PadState s; |
| + s.status = DISCONNECTED; |
| + pad_state_.push_back(s); |
| + } |
| + |
| + for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
| + WebGamepad &pad = pads->items[i]; |
| + if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) { |
| + pad_state_[i].status = XINPUT_CONNECTED; |
| + continue; |
| + } |
| + } |
| + |
| + 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); |
| + |
| + // Fill the "disconnected" pad state entries with our DirectInput |
| + // gamepads. |
| + unsigned pad_state_index = 0; |
| + unsigned directinput_index = 0; |
| + while (pad_state_index < WebGamepads::itemsLengthCap && |
| + directinput_index < directinput_gamepads.size()) { |
| + if (pad_state_[pad_state_index].status != DISCONNECTED) { |
| + ++pad_state_index; |
| + continue; |
| + } |
| + WebGamepad &pad = pads->items[pad_state_index]; |
| + pad.connected = true; |
| + wcscpy_s(pad.id, WebGamepad::idLengthCap, |
| + directinput_gamepads[directinput_index].id); |
| + PadState &state = pad_state_[pad_state_index]; |
| + state.status = DIRECTINPUT_CONNECTED; |
| + state.directinput_gamepad = |
| + directinput_gamepads[directinput_index].gamepad; |
| + state.mapper = directinput_gamepads[directinput_index].mapper; |
| + ++directinput_index; |
| + ++pad_state_index; |
| + } |
| + } |
| +} |
| + |
| + |
| 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) { |
| 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; |
| + } |
| +} |
| - 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); |
| +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_(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); |
| #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())) |
| + return; |
|
scottmg
2013/02/14 18:44:34
i think these failures need to set pad->connected
teravest
2013/02/19 17:17:24
Done.
|
| + // Try polling again. |
| + if (FAILED(gamepad->Poll())) |
| + 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>( |