2017-02-27 10:32:40 +00:00
# include "ppsspp_config.h"
# include <climits>
2014-09-05 21:31:25 +00:00
# include <algorithm>
2013-07-22 22:06:07 +00:00
2020-10-04 08:30:18 +00:00
# include "Common/System/NativeApp.h"
2018-03-22 21:14:19 +00:00
# include "Common/CommonWindows.h"
2016-12-05 15:51:28 +00:00
# include "Common/Log.h"
2022-07-04 19:49:38 +00:00
# include "Common/StringUtils.h"
2020-10-05 18:58:33 +00:00
# include "Common/TimeUtil.h"
2020-10-01 07:36:43 +00:00
# include "Common/Input/InputState.h"
# include "Common/Input/KeyCodes.h"
2013-05-17 10:31:06 +00:00
# include "XinputDevice.h"
2022-07-04 19:49:38 +00:00
# include "Core/Config.h"
2020-04-12 13:08:46 +00:00
# include "Core/Core.h"
2020-10-03 22:25:21 +00:00
# include "Core/KeyMap.h"
2020-04-12 13:08:46 +00:00
# include "Core/HLE/sceCtrl.h"
2013-05-17 10:31:06 +00:00
2013-07-22 22:06:07 +00:00
// Utilities to dynamically load XInput. Adapted from SDL.
2017-02-27 10:32:40 +00:00
# if !PPSSPP_PLATFORM(UWP)
2022-07-04 19:49:38 +00:00
struct XINPUT_CAPABILITIES_EX {
XINPUT_CAPABILITIES Capabilities ;
WORD vendorId ;
WORD productId ;
WORD revisionId ;
DWORD a4 ; //unknown
} ;
2013-07-22 22:06:07 +00:00
typedef DWORD ( WINAPI * XInputGetState_t ) ( DWORD dwUserIndex , XINPUT_STATE * pState ) ;
2020-04-12 13:08:46 +00:00
typedef DWORD ( WINAPI * XInputSetState_t ) ( DWORD dwUserIndex , XINPUT_VIBRATION * pVibration ) ;
2022-07-04 19:49:38 +00:00
typedef DWORD ( WINAPI * XInputGetCapabilitiesEx_t ) ( DWORD unknown , DWORD dwUserIndex , DWORD flags , XINPUT_CAPABILITIES_EX * pCapabilities ) ;
2013-07-22 22:06:07 +00:00
2020-10-05 18:58:33 +00:00
static XInputGetState_t PPSSPP_XInputGetState = nullptr ;
static XInputSetState_t PPSSPP_XInputSetState = nullptr ;
2022-07-04 19:49:38 +00:00
static XInputGetCapabilitiesEx_t PPSSPP_XInputGetCapabilitiesEx = nullptr ;
2013-07-22 22:06:07 +00:00
static DWORD PPSSPP_XInputVersion = 0 ;
2022-12-11 05:09:50 +00:00
static HMODULE s_pXInputDLL = nullptr ;
2013-07-22 22:06:07 +00:00
static int s_XInputDLLRefCount = 0 ;
static void UnloadXInputDLL ( ) ;
static int LoadXInputDLL ( ) {
DWORD version = 0 ;
if ( s_pXInputDLL ) {
s_XInputDLLRefCount + + ;
return 0 ; /* already loaded */
}
version = ( 1 < < 16 ) | 4 ;
2013-08-26 17:00:16 +00:00
s_pXInputDLL = LoadLibrary ( L " XInput1_4.dll " ) ; // 1.4 Ships with Windows 8.
2013-07-22 22:06:07 +00:00
if ( ! s_pXInputDLL ) {
version = ( 1 < < 16 ) | 3 ;
2013-08-26 17:00:16 +00:00
s_pXInputDLL = LoadLibrary ( L " XInput1_3.dll " ) ; // 1.3 Ships with Vista and Win7, can be installed as a restributable component.
2019-06-28 22:45:23 +00:00
if ( ! s_pXInputDLL ) {
version = ( 1 < < 16 ) | 0 ;
s_pXInputDLL = LoadLibrary ( L " XInput9_1_0.dll " ) ; // 1.0 ships with any Windows since WinXP
}
2013-07-22 22:06:07 +00:00
}
if ( ! s_pXInputDLL ) {
return - 1 ;
}
PPSSPP_XInputVersion = version ;
s_XInputDLLRefCount = 1 ;
2019-06-28 22:45:23 +00:00
/* 100 is the ordinal for _XInputGetStateEx, which returns the same struct as XinputGetState, but with extra data in wButtons for the guide button, we think...
Let ' s try the name first , though - then fall back to ordinal , then to a non - Ex version ( xinput9_1_0 . dll doesn ' t have Ex ) */
PPSSPP_XInputGetState = ( XInputGetState_t ) GetProcAddress ( ( HMODULE ) s_pXInputDLL , " XInputGetStateEx " ) ;
if ( ! PPSSPP_XInputGetState ) {
PPSSPP_XInputGetState = ( XInputGetState_t ) GetProcAddress ( ( HMODULE ) s_pXInputDLL , ( LPCSTR ) 100 ) ;
if ( ! PPSSPP_XInputGetState ) {
PPSSPP_XInputGetState = ( XInputGetState_t ) GetProcAddress ( ( HMODULE ) s_pXInputDLL , " XInputGetState " ) ;
}
}
if ( ! PPSSPP_XInputGetState ) {
2013-07-22 22:06:07 +00:00
UnloadXInputDLL ( ) ;
return - 1 ;
}
2020-04-12 13:08:46 +00:00
/* Let's try the name first, then fall back to a non-Ex version (xinput9_1_0.dll doesn't have Ex) */
PPSSPP_XInputSetState = ( XInputSetState_t ) GetProcAddress ( ( HMODULE ) s_pXInputDLL , " XInputSetStateEx " ) ;
if ( ! PPSSPP_XInputSetState ) {
PPSSPP_XInputSetState = ( XInputSetState_t ) GetProcAddress ( ( HMODULE ) s_pXInputDLL , " XInputSetState " ) ;
}
if ( ! PPSSPP_XInputSetState ) {
UnloadXInputDLL ( ) ;
return - 1 ;
}
2022-07-04 19:49:38 +00:00
if ( PPSSPP_XInputVersion > = ( ( 1 < < 16 ) | 4 ) ) {
PPSSPP_XInputGetCapabilitiesEx = ( XInputGetCapabilitiesEx_t ) GetProcAddress ( ( HMODULE ) s_pXInputDLL , ( LPCSTR ) 108 ) ;
}
2013-07-22 22:06:07 +00:00
return 0 ;
}
static void UnloadXInputDLL ( ) {
if ( s_pXInputDLL ) {
if ( - - s_XInputDLLRefCount = = 0 ) {
FreeLibrary ( s_pXInputDLL ) ;
2022-12-11 05:09:50 +00:00
s_pXInputDLL = nullptr ;
2013-07-22 22:06:07 +00:00
}
}
}
2012-11-12 15:38:21 +00:00
2017-02-27 10:32:40 +00:00
# else
static int LoadXInputDLL ( ) { return 0 ; }
static void UnloadXInputDLL ( ) { }
# define PPSSPP_XInputGetState XInputGetState
2020-04-12 13:08:46 +00:00
# define PPSSPP_XInputSetState XInputSetState
2017-02-27 10:32:40 +00:00
# endif
2012-11-14 01:15:54 +00:00
# ifndef XUSER_MAX_COUNT
# define XUSER_MAX_COUNT 4
# endif
2013-07-22 22:06:07 +00:00
// Undocumented. Steam annoyingly grabs this button though....
# define XINPUT_GUIDE_BUTTON 0x400
2013-07-06 17:08:59 +00:00
// Permanent map. Actual mapping happens elsewhere.
2023-05-26 16:40:13 +00:00
static const struct { int from ; InputKeyCode to ; } xinput_ctrl_map [ ] = {
2013-08-04 17:31:40 +00:00
{ XINPUT_GAMEPAD_A , NKCODE_BUTTON_A } ,
{ XINPUT_GAMEPAD_B , NKCODE_BUTTON_B } ,
{ XINPUT_GAMEPAD_X , NKCODE_BUTTON_X } ,
{ XINPUT_GAMEPAD_Y , NKCODE_BUTTON_Y } ,
{ XINPUT_GAMEPAD_BACK , NKCODE_BUTTON_SELECT } ,
{ XINPUT_GAMEPAD_START , NKCODE_BUTTON_START } ,
{ XINPUT_GAMEPAD_LEFT_SHOULDER , NKCODE_BUTTON_L1 } ,
{ XINPUT_GAMEPAD_RIGHT_SHOULDER , NKCODE_BUTTON_R1 } ,
{ XINPUT_GAMEPAD_LEFT_THUMB , NKCODE_BUTTON_THUMBL } ,
{ XINPUT_GAMEPAD_RIGHT_THUMB , NKCODE_BUTTON_THUMBR } ,
{ XINPUT_GAMEPAD_DPAD_UP , NKCODE_DPAD_UP } ,
{ XINPUT_GAMEPAD_DPAD_DOWN , NKCODE_DPAD_DOWN } ,
{ XINPUT_GAMEPAD_DPAD_LEFT , NKCODE_DPAD_LEFT } ,
{ XINPUT_GAMEPAD_DPAD_RIGHT , NKCODE_DPAD_RIGHT } ,
{ XINPUT_GUIDE_BUTTON , NKCODE_HOME } ,
2013-05-17 10:31:06 +00:00
} ;
2013-07-06 17:08:59 +00:00
2012-11-12 15:38:21 +00:00
XinputDevice : : XinputDevice ( ) {
2013-07-22 22:06:07 +00:00
if ( LoadXInputDLL ( ) ! = 0 ) {
2024-07-14 12:42:59 +00:00
WARN_LOG ( Log : : sceCtrl , " Failed to load XInput! DLL missing " ) ;
2013-07-22 22:06:07 +00:00
}
2017-04-02 21:34:20 +00:00
for ( size_t i = 0 ; i < ARRAY_SIZE ( check_delay ) ; + + i ) {
2017-04-13 05:02:50 +00:00
check_delay [ i ] = ( int ) i ;
2017-04-02 21:34:20 +00:00
}
2012-11-12 15:38:21 +00:00
}
2013-07-22 22:06:07 +00:00
XinputDevice : : ~ XinputDevice ( ) {
UnloadXInputDLL ( ) ;
}
2017-03-15 05:01:18 +00:00
int XinputDevice : : UpdateState ( ) {
2017-02-27 10:32:40 +00:00
# if !PPSSPP_PLATFORM(UWP)
2013-07-25 21:58:19 +00:00
if ( ! s_pXInputDLL )
return 0 ;
2017-02-27 10:32:40 +00:00
# endif
2013-07-25 21:58:19 +00:00
2017-02-19 14:02:17 +00:00
bool anySuccess = false ;
for ( int i = 0 ; i < XUSER_MAX_COUNT ; i + + ) {
2023-11-11 09:51:41 +00:00
XINPUT_STATE state { } ;
2017-02-19 14:02:17 +00:00
if ( check_delay [ i ] - - > 0 )
continue ;
DWORD dwResult = PPSSPP_XInputGetState ( i , & state ) ;
if ( dwResult = = ERROR_SUCCESS ) {
2023-12-20 09:35:02 +00:00
XINPUT_VIBRATION vibration { } ;
2020-04-12 13:08:46 +00:00
UpdatePad ( i , state , vibration ) ;
2017-02-19 14:02:17 +00:00
anySuccess = true ;
2017-04-02 21:34:20 +00:00
} else {
check_delay [ i ] = 30 ;
2017-02-19 14:02:17 +00:00
}
}
2014-05-19 21:29:35 +00:00
2017-04-02 21:34:20 +00:00
// If we get XInput, skip the others. This might not actually be a good idea,
// and was done to avoid conflicts between DirectInput and XInput.
2017-02-19 14:02:17 +00:00
return anySuccess ? UPDATESTATE_SKIP_PAD : 0 ;
}
2012-11-12 15:38:21 +00:00
2020-04-12 13:08:46 +00:00
void XinputDevice : : UpdatePad ( int pad , const XINPUT_STATE & state , XINPUT_VIBRATION & vibration ) {
2023-07-06 13:47:36 +00:00
if ( ! notified_ [ pad ] ) {
notified_ [ pad ] = true ;
2022-07-04 21:37:22 +00:00
# if !PPSSPP_PLATFORM(UWP)
2022-12-11 05:09:50 +00:00
XINPUT_CAPABILITIES_EX caps { } ;
2022-07-04 19:49:38 +00:00
if ( PPSSPP_XInputGetCapabilitiesEx ! = nullptr & & PPSSPP_XInputGetCapabilitiesEx ( 1 , pad , 0 , & caps ) = = ERROR_SUCCESS ) {
KeyMap : : NotifyPadConnected ( DEVICE_ID_XINPUT_0 + pad , StringFromFormat ( " Xbox 360 Pad: %d/%d " , caps . vendorId , caps . productId ) ) ;
} else {
2022-07-04 21:37:22 +00:00
# else
{
# endif
2022-07-04 19:49:38 +00:00
KeyMap : : NotifyPadConnected ( DEVICE_ID_XINPUT_0 + pad , " Xbox 360 Pad " ) ;
}
2012-11-12 15:38:21 +00:00
}
2017-03-15 05:01:18 +00:00
ApplyButtons ( pad , state ) ;
2020-04-12 13:08:46 +00:00
ApplyVibration ( pad , vibration ) ;
2013-07-08 00:18:02 +00:00
2023-08-31 09:41:34 +00:00
AxisInput axis [ 6 ] ;
int axisCount = 0 ;
for ( int i = 0 ; i < ARRAY_SIZE ( axis ) ; i + + ) {
axis [ i ] . deviceId = ( InputDeviceID ) ( DEVICE_ID_XINPUT_0 + pad ) ;
}
2023-07-06 13:47:36 +00:00
auto sendAxis = [ & ] ( InputAxis axisId , float value , int axisIndex ) {
if ( value ! = prevAxisValue_ [ pad ] [ axisIndex ] ) {
prevAxisValue_ [ pad ] [ axisIndex ] = value ;
2023-08-31 09:41:34 +00:00
axis [ axisCount ] . axisId = axisId ;
axis [ axisCount ] . value = value ;
axisCount + + ;
2023-07-06 13:47:36 +00:00
}
2021-05-23 20:21:45 +00:00
} ;
2023-07-06 13:47:36 +00:00
sendAxis ( JOYSTICK_AXIS_X , ( float ) state . Gamepad . sThumbLX / 32767.0f , 0 ) ;
sendAxis ( JOYSTICK_AXIS_Y , ( float ) state . Gamepad . sThumbLY / 32767.0f , 1 ) ;
sendAxis ( JOYSTICK_AXIS_Z , ( float ) state . Gamepad . sThumbRX / 32767.0f , 2 ) ;
sendAxis ( JOYSTICK_AXIS_RZ , ( float ) state . Gamepad . sThumbRY / 32767.0f , 3 ) ;
2023-11-11 05:20:01 +00:00
sendAxis ( JOYSTICK_AXIS_LTRIGGER , ( float ) state . Gamepad . bLeftTrigger / 255.0f , 4 ) ;
sendAxis ( JOYSTICK_AXIS_RTRIGGER , ( float ) state . Gamepad . bRightTrigger / 255.0f , 5 ) ;
2017-02-19 14:02:17 +00:00
2023-08-31 09:41:34 +00:00
if ( axisCount ) {
NativeAxis ( axis , axisCount ) ;
}
2017-04-02 21:34:20 +00:00
prevState [ pad ] = state ;
check_delay [ pad ] = 0 ;
2012-11-12 15:38:21 +00:00
}
2017-03-15 05:01:18 +00:00
void XinputDevice : : ApplyButtons ( int pad , const XINPUT_STATE & state ) {
2023-07-06 13:47:36 +00:00
const u32 buttons = state . Gamepad . wButtons ;
2013-07-06 17:08:59 +00:00
2023-07-06 13:47:36 +00:00
const u32 downMask = buttons & ( ~ prevButtons_ [ pad ] ) ;
const u32 upMask = ( ~ buttons ) & prevButtons_ [ pad ] ;
prevButtons_ [ pad ] = buttons ;
2013-07-06 17:08:59 +00:00
2023-11-11 09:51:41 +00:00
for ( int i = 0 ; i < ARRAY_SIZE ( xinput_ctrl_map ) ; i + + ) {
2013-07-06 17:08:59 +00:00
if ( downMask & xinput_ctrl_map [ i ] . from ) {
KeyInput key ;
2021-08-28 13:38:03 +00:00
key . deviceId = DEVICE_ID_XINPUT_0 + pad ;
2013-07-06 17:08:59 +00:00
key . flags = KEY_DOWN ;
key . keyCode = xinput_ctrl_map [ i ] . to ;
NativeKey ( key ) ;
}
if ( upMask & xinput_ctrl_map [ i ] . from ) {
KeyInput key ;
2021-08-28 13:38:03 +00:00
key . deviceId = DEVICE_ID_XINPUT_0 + pad ;
2013-07-06 17:08:59 +00:00
key . flags = KEY_UP ;
key . keyCode = xinput_ctrl_map [ i ] . to ;
NativeKey ( key ) ;
}
}
2013-05-17 10:31:06 +00:00
}
2020-04-12 13:08:46 +00:00
void XinputDevice : : ApplyVibration ( int pad , XINPUT_VIBRATION & vibration ) {
if ( PSP_IsInited ( ) ) {
2023-11-11 10:08:22 +00:00
newVibrationTime_ = time_now_d ( ) ;
2020-04-12 13:08:46 +00:00
// We have to run PPSSPP_XInputSetState at time intervals
2021-08-17 14:48:47 +00:00
// since it bugs otherwise with very high fast-forward speeds
2020-04-12 13:08:46 +00:00
// and freezes at constant vibration or no vibration at all.
2023-11-11 10:08:22 +00:00
if ( newVibrationTime_ - prevVibrationTime > = 1.0 / 64.0 ) {
2020-04-12 13:08:46 +00:00
if ( GetUIState ( ) = = UISTATE_INGAME ) {
vibration . wLeftMotorSpeed = sceCtrlGetLeftVibration ( ) ; // use any value between 0-65535 here
vibration . wRightMotorSpeed = sceCtrlGetRightVibration ( ) ; // use any value between 0-65535 here
} else {
vibration . wLeftMotorSpeed = 0 ;
vibration . wRightMotorSpeed = 0 ;
}
2020-10-05 18:58:33 +00:00
if ( prevVibration [ pad ] . wLeftMotorSpeed ! = vibration . wLeftMotorSpeed | | prevVibration [ pad ] . wRightMotorSpeed ! = vibration . wRightMotorSpeed ) {
2020-04-12 13:08:46 +00:00
PPSSPP_XInputSetState ( pad , & vibration ) ;
prevVibration [ pad ] = vibration ;
}
2023-11-11 10:08:22 +00:00
prevVibrationTime = newVibrationTime_ ;
2020-04-12 13:08:46 +00:00
}
} else {
DWORD dwResult = PPSSPP_XInputSetState ( pad , & vibration ) ;
if ( dwResult ! = ERROR_SUCCESS ) {
check_delay [ pad ] = 30 ;
}
}
}