Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(112)

Unified Diff: content/browser/gamepad/gamepad_platform_data_fetcher_win.cc

Issue 12260011: Support DirectInput gamepads on Windows. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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>(
« no previous file with comments | « content/browser/gamepad/gamepad_platform_data_fetcher_win.h ('k') | content/browser/gamepad/gamepad_standard_mappings_win.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698