Yet another xinput implementation for the xbox360 controller
Bruno Jesus
00cpxxx at gmail.com
Sat Aug 27 03:41:26 CDT 2016
On Sat, Aug 27, 2016 at 3:05 AM, Bruno Jesus <00cpxxx at gmail.com> wrote:
> If you want force feedback remember to disable the (js) joystick in
> the control panel. Also if you have other controller brands it must
> have at least 6 axis, 11 buttons and 1 POV to work, but the mapping
> has to be redone. If it has less buttons you can relax the checks in
> dinput_is_good.
>
> Last but not least I know I should have used dinput to map the axes
> values for me but I was tired of reading dinput docs and it was easy
> enough to convert manually.
Just replying to myself here. I updated the patch to work with
non-xbox controllers, just tested with a 2 axes, 8 button controller
and a ps2 + usb adapter. Broforce with 3 players working fine =)
-------------- next part --------------
diff --git a/dlls/xinput1_3/Makefile.in b/dlls/xinput1_3/Makefile.in
index cf8f730..37621fa 100644
--- a/dlls/xinput1_3/Makefile.in
+++ b/dlls/xinput1_3/Makefile.in
@@ -1,5 +1,6 @@
MODULE = xinput1_3.dll
IMPORTLIB = xinput
+IMPORTS = uuid dxguid dinput dinput8 ole32
C_SRCS = \
xinput1_3_main.c
diff --git a/dlls/xinput1_3/xinput1_3_main.c b/dlls/xinput1_3/xinput1_3_main.c
index 63f725b..0015372 100644
--- a/dlls/xinput1_3/xinput1_3_main.c
+++ b/dlls/xinput1_3/xinput1_3_main.c
@@ -1,6 +1,8 @@
/*
* The Wine project - Xinput Joystick Library
+ *
* Copyright 2008 Andrew Fenn
+ * Copyright 2016 Bruno Jesus
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -17,6 +19,7 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
+#define COBJMACROS
#include "config.h"
#include <assert.h>
#include <stdarg.h>
@@ -27,10 +30,349 @@
#include "winbase.h"
#include "winerror.h"
+#include "initguid.h"
#include "xinput.h"
+#include "dinput.h"
+#include "dinputd.h"
WINE_DEFAULT_DEBUG_CHANNEL(xinput);
+struct CapsFlags
+{
+ BOOL wireless, jedi, pov;
+ int axes, buttons;
+};
+
+static struct ControllerMap
+{
+ LPDIRECTINPUTDEVICE8A device;
+ BOOL connected, acquired;
+ struct CapsFlags caps;
+ XINPUT_STATE state;
+ XINPUT_VIBRATION vibration;
+ BOOL vibration_dirty;
+
+ DIEFFECT effect_data;
+ LPDIRECTINPUTEFFECT effect_instance;
+} controllers[XUSER_MAX_COUNT];
+
+static struct
+{
+ LPDIRECTINPUT8A iface;
+ BOOL enabled;
+ int mapped;
+} dinput;
+
+#define STARTUP_DINPUT if (!dinput.iface) dinput_start();
+
+/* ========================= Internal functions ============================= */
+
+static BOOL dinput_is_good(const LPDIRECTINPUTDEVICE8A device, struct CapsFlags *caps)
+{
+ HRESULT hr;
+ DIPROPDWORD property;
+ DIDEVCAPS dinput_caps;
+ static const struct
+ {
+ unsigned long vidpid;
+ BOOL wireless;
+ } data_list[] = {
+ /* Microsoft known controllers for Xbox 360 */
+ { MAKELONG(0x045e, 0x028e), 0 /* wired controller*/ },
+ { MAKELONG(0x045e, 0x0291), 1 /* wireless receiver */ },
+ { MAKELONG(0x045e, 0x0719), 1 /* wireless controller */ }
+ };
+ int i;
+
+ property.diph.dwSize = sizeof(property);
+ property.diph.dwHeaderSize = sizeof(property.diph);
+ property.diph.dwObj = 0;
+ property.diph.dwHow = DIPH_DEVICE;
+
+ hr = IDirectInputDevice_GetProperty(device, DIPROP_VIDPID, &property.diph);
+ if (FAILED(hr))
+ return FALSE;
+
+ dinput_caps.dwSize = sizeof(dinput_caps);
+ hr = IDirectInputDevice_GetCapabilities(device, &dinput_caps);
+ if (FAILED(hr))
+ return FALSE;
+
+ if (dinput_caps.dwAxes < 2 || dinput_caps.dwButtons < 8)
+ return FALSE;
+
+ caps->jedi = !!(dinput_caps.dwFlags & DIDC_FORCEFEEDBACK);
+ caps->axes = dinput_caps.dwAxes;
+ caps->buttons = dinput_caps.dwButtons;
+
+ for (i = 0; i < sizeof(data_list) / sizeof(data_list[0]); i++)
+ if (property.dwData == data_list[i].vidpid)
+ {
+ /* check if it has force feedback to ignore (js) driver joysticks */
+ if (!caps->jedi)
+ continue;
+
+ caps->pov = TRUE;
+ caps->wireless = data_list[i].wireless;
+ TRACE("Xbox 360 controller detected\n");
+ return TRUE;
+ }
+
+ if (dinput_caps.dwAxes == 6 && dinput_caps.dwButtons == 11 && dinput_caps.dwPOVs == 1)
+ TRACE("This controller has the same number of buttons/axes from xbox 360, may work...\n");
+ else
+ FIXME("This is not a known xbox controller, using anyway. Expect problems!\n");
+
+ caps->pov = !!dinput_caps.dwPOVs;
+ caps->wireless = FALSE;
+ return TRUE;
+}
+
+static BOOL dinput_set_range(const LPDIRECTINPUTDEVICE8A device)
+{
+ HRESULT hr;
+ DIPROPRANGE property;
+
+ property.diph.dwSize = sizeof(property);
+ property.diph.dwHeaderSize = sizeof(property.diph);
+ property.diph.dwHow = DIPH_DEVICE;
+ property.diph.dwObj = 0;
+ property.lMin = -32767;
+ property.lMax = +32767;
+
+ hr = IDirectInputDevice_SetProperty(device, DIPROP_RANGE, &property.diph);
+ if (FAILED(hr))
+ {
+ WARN("Failed to set axis range (0x%x)\n", hr);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void dinput_joystate_to_xinput(DIJOYSTATE2 *js, XINPUT_GAMEPAD *gamepad, struct CapsFlags *caps)
+{
+ static const int xbox_buttons[] = {
+ XINPUT_GAMEPAD_A,
+ XINPUT_GAMEPAD_B,
+ XINPUT_GAMEPAD_X,
+ XINPUT_GAMEPAD_Y,
+ XINPUT_GAMEPAD_LEFT_SHOULDER,
+ XINPUT_GAMEPAD_RIGHT_SHOULDER,
+ XINPUT_GAMEPAD_BACK,
+ XINPUT_GAMEPAD_START,
+ 0, /* xbox key not used */
+ XINPUT_GAMEPAD_LEFT_THUMB,
+ XINPUT_GAMEPAD_RIGHT_THUMB
+ };
+ int i, buttons;
+
+ gamepad->wButtons = 0x0000;
+ /* First the D-Pad which is recognized as a POV in dinput */
+ if (caps->pov)
+ {
+ switch (js->rgdwPOV[0])
+ {
+ case 0 : gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_UP; break;
+ case 4500 : gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_UP; /* fall through */
+ case 9000 : gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT; break;
+ case 13500: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT; /* fall through */
+ case 18000: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN; break;
+ case 22500: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN; /* fall through */
+ case 27000: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT; break;
+ case 31500: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_UP;
+ }
+ }
+
+ /* Buttons */
+ buttons = min(caps->buttons, sizeof(xbox_buttons) / sizeof(*xbox_buttons));
+ for (i = 0; i < buttons; i++)
+ if (js->rgbButtons[i] & 0x80)
+ gamepad->wButtons |= xbox_buttons[i];
+
+ /* Axes */
+ gamepad->sThumbLX = js->lX;
+ gamepad->sThumbLY = -js->lY;
+ if (caps->axes >= 4)
+ {
+ gamepad->sThumbRX = js->lRx;
+ gamepad->sThumbRY = -js->lRy;
+ }
+ else
+ gamepad->sThumbRX = gamepad->sThumbRY = 0;
+
+ /* Both triggers */
+ if (caps->axes >= 6)
+ {
+ gamepad->bLeftTrigger = (255 * (js->lZ + 32767)) / 32767;
+ gamepad->bRightTrigger = (255 * (js->lRz + 32767)) / 32767;
+ }
+ else
+ gamepad->bLeftTrigger = gamepad->bRightTrigger = 0;
+}
+
+static void dinput_fill_effect(DIEFFECT *effect)
+{
+ static DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
+ static LONG direction[2] = {0, 0};
+
+ effect->dwSize = sizeof(effect);
+ effect->dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
+ effect->dwDuration = INFINITE;
+ effect->dwGain = 0;
+ effect->dwTriggerButton = DIEB_NOTRIGGER;
+ effect->cAxes = sizeof(axes) / sizeof(axes[0]);
+ effect->rgdwAxes = axes;
+ effect->rglDirection = direction;
+}
+
+static void dinput_send_effect(int index, int power)
+{
+ HRESULT hr;
+ DIPERIODIC periodic;
+ DIEFFECT *effect = &controllers[index].effect_data;
+ LPDIRECTINPUTEFFECT *instance = &controllers[index].effect_instance;
+
+ if (!*instance)
+ dinput_fill_effect(effect);
+
+ effect->cbTypeSpecificParams = sizeof(periodic);
+ effect->lpvTypeSpecificParams = &periodic;
+
+ periodic.dwMagnitude = power;
+ periodic.dwPeriod = DI_SECONDS; /* 1 second */
+ periodic.lOffset = 0;
+ periodic.dwPhase = 0;
+
+ if (!*instance)
+ {
+ hr = IDirectInputDevice8_CreateEffect(controllers[index].device, &GUID_Square,
+ effect, instance, NULL);
+ if (FAILED(hr))
+ {
+ WARN("Failed to create effect (0x%x)\n", hr);
+ return;
+ }
+ if (!*instance)
+ {
+ WARN("Effect not returned???\n");
+ return;
+ }
+
+ hr = IDirectInputEffect_SetParameters(*instance, effect, DIEP_AXES | DIEP_DIRECTION | DIEP_NODOWNLOAD);
+ if (FAILED(hr))
+ {
+ IUnknown_Release(*instance);
+ *instance = NULL;
+ WARN("Failed to configure effect (0x%x)\n", hr);
+ return;
+ }
+ }
+
+ hr = IDirectInputEffect_SetParameters(*instance, effect, DIEP_TYPESPECIFICPARAMS | DIEP_START);
+ if (FAILED(hr))
+ {
+ WARN("Failed to play effect (0x%x)\n", hr);
+ return;
+ }
+}
+
+static BOOL CALLBACK dinput_enum_callback(const DIDEVICEINSTANCEA *instance, void *context)
+{
+ LPDIRECTINPUTDEVICE8A device;
+ HRESULT hr;
+
+ if (dinput.mapped == sizeof(controllers) / sizeof(*controllers))
+ return DIENUM_STOP;
+
+ hr = IDirectInput_CreateDevice(dinput.iface, &instance->guidInstance, &device, NULL);
+ if (FAILED(hr))
+ return DIENUM_CONTINUE;
+
+ if (!dinput_is_good(device, &controllers[dinput.mapped].caps))
+ {
+ IDirectInput_Release(device);
+ return DIENUM_CONTINUE;
+ }
+
+ if (!dinput_set_range(device))
+ {
+ IDirectInput_Release(device);
+ return DIENUM_CONTINUE;
+ }
+
+ controllers[dinput.mapped].connected = TRUE;
+ controllers[dinput.mapped].device = device;
+ dinput.mapped++;
+
+ return DIENUM_CONTINUE;
+}
+
+static void dinput_start(void)
+{
+ HRESULT hr;
+
+ hr = DirectInput8Create(GetModuleHandleA(NULL), 0x0800, &IID_IDirectInput8A,
+ (void **)&dinput.iface, NULL);
+ if (FAILED(hr))
+ {
+ ERR("Failed to create dinput8 interface, no xinput controller support (0x%x)\n", hr);
+ return;
+ }
+
+ hr = IDirectInput8_EnumDevices(dinput.iface, DI8DEVCLASS_GAMECTRL,
+ dinput_enum_callback, NULL, DIEDFL_ATTACHEDONLY);
+ if (FAILED(hr))
+ {
+ ERR("Failed to enumerate dinput8 devices, no xinput controller support (0x%x)\n", hr);
+ return;
+ }
+
+ dinput.enabled = TRUE;
+}
+
+static void dinput_update(int index)
+{
+ HRESULT hr;
+ DIJOYSTATE2 data;
+ XINPUT_GAMEPAD gamepad;
+
+ if (dinput.enabled)
+ {
+ if (!controllers[index].acquired)
+ {
+ IDirectInputDevice8_SetDataFormat(controllers[index].device, &c_dfDIJoystick2);
+ hr = IDirectInputDevice8_Acquire(controllers[index].device);
+ if (FAILED(hr))
+ {
+ WARN("Failed to acquire game controller (0x%x)\n", hr);
+ return;
+ }
+ controllers[index].acquired = TRUE;
+ }
+
+ IDirectInputDevice8_Poll(controllers[index].device);
+ hr = IDirectInputDevice_GetDeviceState(controllers[index].device, sizeof(data), &data);
+ if (FAILED(hr))
+ {
+ if (hr == DIERR_INPUTLOST)
+ controllers[index].acquired = FALSE;
+ WARN("Failed to get game controller state (0x%x)\n", hr);
+ return;
+ }
+ dinput_joystate_to_xinput(&data, &gamepad, &controllers[index].caps);
+ }
+ else
+ memset(&gamepad, 0, sizeof(gamepad));
+
+ if (memcmp(&controllers[index].state.Gamepad, &gamepad, sizeof(gamepad)))
+ {
+ controllers[index].state.Gamepad = gamepad;
+ controllers[index].state.dwPacketNumber++;
+ }
+}
+
+/* ============================ Dll Functions =============================== */
+
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
{
switch(reason)
@@ -46,87 +388,163 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
void WINAPI XInputEnable(BOOL enable)
{
- /* Setting to false will stop messages from XInputSetState being sent
- to the controllers. Setting to true will send the last vibration
- value (sent to XInputSetState) to the controller and allow messages to
- be sent */
- FIXME("(%d) Stub!\n", enable);
+ TRACE("(%d)\n", enable);
+
+ STARTUP_DINPUT
+
+ if((dinput.enabled = enable))
+ {
+ int i;
+ /* Apply the last vibration status that was sent to the controller
+ * while xinput was disabled. */
+ for (i = 0; i < sizeof(controllers) / sizeof(*controllers); i++)
+ {
+ if (controllers[i].connected && controllers[i].vibration_dirty)
+ XInputSetState(i, &controllers[i].vibration);
+ }
+ }
}
-DWORD WINAPI XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration)
+DWORD WINAPI XInputSetState(DWORD index, XINPUT_VIBRATION* vibration)
{
- FIXME("(%d %p) Stub!\n", dwUserIndex, pVibration);
+ TRACE("(%u %p)\n", index, vibration);
- if (dwUserIndex < XUSER_MAX_COUNT)
- {
+ STARTUP_DINPUT
+
+ if (index >= XUSER_MAX_COUNT)
+ return ERROR_BAD_ARGUMENTS;
+ if (!controllers[index].connected)
return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
+
+ /* Check if we really have to do all the process */
+ if (!controllers[index].vibration_dirty &&
+ !memcmp(&controllers[index].vibration, vibration, sizeof(*vibration)))
+ return ERROR_SUCCESS;
+
+ controllers[index].vibration = *vibration;
+ controllers[index].vibration_dirty = !dinput.enabled;
+
+ if (dinput.enabled && controllers[index].caps.jedi)
+ {
+ int power;
+ /* FIXME: we can't set the speed of each motor so do an average */
+ power = DI_FFNOMINALMAX * (vibration->wLeftMotorSpeed + vibration->wRightMotorSpeed) / 2 / 0xFFFF;
+
+ TRACE("Vibration left/right speed %d/%d translated to %d\n\n",
+ vibration->wLeftMotorSpeed, vibration->wRightMotorSpeed, power);
+ dinput_send_effect(index, power);
}
- return ERROR_BAD_ARGUMENTS;
+
+ return ERROR_SUCCESS;
}
-DWORD WINAPI DECLSPEC_HOTPATCH XInputGetState(DWORD dwUserIndex, XINPUT_STATE* pState)
+DWORD WINAPI DECLSPEC_HOTPATCH XInputGetState(DWORD index, XINPUT_STATE* state)
{
- static int warn_once;
+ TRACE("(%u %p)\n", index, state);
- if (!warn_once++)
- FIXME("(%u %p)\n", dwUserIndex, pState);
+ STARTUP_DINPUT
- if (dwUserIndex < XUSER_MAX_COUNT)
- {
+ if (index >= XUSER_MAX_COUNT)
+ return ERROR_BAD_ARGUMENTS;
+ if (!controllers[index].connected)
return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
- }
- return ERROR_BAD_ARGUMENTS;
+
+ dinput_update(index);
+ *state = controllers[index].state;
+
+ return ERROR_SUCCESS;
}
-DWORD WINAPI XInputGetKeystroke(DWORD dwUserIndex, DWORD dwReserve, PXINPUT_KEYSTROKE pKeystroke)
+DWORD WINAPI XInputGetKeystroke(DWORD index, DWORD reserved, PXINPUT_KEYSTROKE key)
{
- FIXME("(%d %d %p) Stub!\n", dwUserIndex, dwReserve, pKeystroke);
+ TRACE("(%u %d %p) stub!\n", index, reserved, key);
- if (dwUserIndex < XUSER_MAX_COUNT)
- {
+ STARTUP_DINPUT
+
+ if (index >= XUSER_MAX_COUNT)
+ return ERROR_BAD_ARGUMENTS;
+ if (!controllers[index].connected)
return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
- }
- return ERROR_BAD_ARGUMENTS;
+
+ return ERROR_NOT_SUPPORTED;
}
-DWORD WINAPI XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES* pCapabilities)
+/* Not defined anywhere ??? */
+#define XINPUT_CAPS_FFB_SUPPORTED 0x0001
+#define XINPUT_CAPS_WIRELESS 0x0002
+#define XINPUT_CAPS_NO_NAVIGATION 0x0010
+
+DWORD WINAPI XInputGetCapabilities(DWORD index, DWORD flags, XINPUT_CAPABILITIES* capabilities)
{
- static int warn_once;
+ TRACE("(%u %d %p)\n", index, flags, capabilities);
- if (!warn_once++)
- FIXME("(%d %d %p)\n", dwUserIndex, dwFlags, pCapabilities);
+ STARTUP_DINPUT
- if (dwUserIndex < XUSER_MAX_COUNT)
- {
+ if (index >= XUSER_MAX_COUNT || (flags && (flags & ~XINPUT_FLAG_GAMEPAD)))
+ return ERROR_BAD_ARGUMENTS;
+ if (!controllers[index].connected)
return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
- }
- return ERROR_BAD_ARGUMENTS;
+
+ capabilities->Type = XINPUT_DEVTYPE_GAMEPAD;
+ capabilities->SubType = XINPUT_DEVSUBTYPE_GAMEPAD;
+
+ capabilities->Flags = 0;
+ if (!controllers[index].caps.jedi)
+ capabilities->Flags |= XINPUT_CAPS_FFB_SUPPORTED;
+ if (controllers[index].caps.wireless)
+ capabilities->Flags |= XINPUT_CAPS_WIRELESS;
+ if (!controllers[index].caps.pov)
+ capabilities->Flags |= XINPUT_CAPS_NO_NAVIGATION;
+
+ dinput_update(index);
+
+ capabilities->Vibration = controllers[index].vibration;
+ capabilities->Gamepad = controllers[index].state.Gamepad;
+
+ return ERROR_SUCCESS;
}
-DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD dwUserIndex, GUID* pDSoundRenderGuid, GUID* pDSoundCaptureGuid)
+DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD index, GUID* dsound_render_guid, GUID* dsound_capture_guid)
{
- FIXME("(%d %p %p) Stub!\n", dwUserIndex, pDSoundRenderGuid, pDSoundCaptureGuid);
+ TRACE("(%u %p %p) Stub!\n", index, dsound_render_guid, dsound_capture_guid);
- if (dwUserIndex < XUSER_MAX_COUNT)
- {
+ STARTUP_DINPUT
+
+ if (index >= XUSER_MAX_COUNT)
+ return ERROR_BAD_ARGUMENTS;
+ if (!controllers[index].connected)
return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
- }
- return ERROR_BAD_ARGUMENTS;
+
+ return ERROR_NOT_SUPPORTED;
}
-DWORD WINAPI XInputGetBatteryInformation(DWORD dwUserIndex, BYTE deviceType, XINPUT_BATTERY_INFORMATION* pBatteryInfo)
+DWORD WINAPI XInputGetBatteryInformation(DWORD index, BYTE type, XINPUT_BATTERY_INFORMATION* battery)
{
- FIXME("(%d %u %p) Stub!\n", dwUserIndex, deviceType, pBatteryInfo);
+ TRACE("(%u %u %p) Stub!\n", index, type, battery);
- if (dwUserIndex < XUSER_MAX_COUNT)
- {
+ STARTUP_DINPUT
+
+ if (index >= XUSER_MAX_COUNT)
+ return ERROR_BAD_ARGUMENTS;
+ if (!controllers[index].connected)
return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
+ if (type != BATTERY_DEVTYPE_GAMEPAD && type != BATTERY_DEVTYPE_HEADSET)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (!controllers[index].caps.wireless)
+ {
+ battery->BatteryType = BATTERY_TYPE_WIRED;
+ battery->BatteryLevel = BATTERY_LEVEL_FULL;
}
- return ERROR_BAD_ARGUMENTS;
+ else
+ {
+ static int once;
+ if (!once++)
+ FIXME("Reporting fake battery values\n");
+
+ battery->BatteryType = BATTERY_TYPE_NIMH;
+ battery->BatteryLevel = BATTERY_LEVEL_MEDIUM;
+ }
+
+ return ERROR_SUCCESS;
}
More information about the wine-devel
mailing list