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 #include @@ -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; }