CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/XinputDevice.cpp
Views: 1401
1
#include "ppsspp_config.h"
2
3
#include <climits>
4
#include <algorithm>
5
6
#include "Common/System/NativeApp.h"
7
#include "Common/CommonWindows.h"
8
#include "Common/Log.h"
9
#include "Common/StringUtils.h"
10
#include "Common/TimeUtil.h"
11
#include "Common/Input/InputState.h"
12
#include "Common/Input/KeyCodes.h"
13
#include "XinputDevice.h"
14
#include "Core/Config.h"
15
#include "Core/Core.h"
16
#include "Core/KeyMap.h"
17
#include "Core/HLE/sceCtrl.h"
18
19
// Utilities to dynamically load XInput. Adapted from SDL.
20
21
#if !PPSSPP_PLATFORM(UWP)
22
23
struct XINPUT_CAPABILITIES_EX {
24
XINPUT_CAPABILITIES Capabilities;
25
WORD vendorId;
26
WORD productId;
27
WORD revisionId;
28
DWORD a4; //unknown
29
};
30
31
typedef DWORD (WINAPI *XInputGetState_t) (DWORD dwUserIndex, XINPUT_STATE* pState);
32
typedef DWORD (WINAPI *XInputSetState_t) (DWORD dwUserIndex, XINPUT_VIBRATION* pVibration);
33
typedef DWORD (WINAPI *XInputGetCapabilitiesEx_t) (DWORD unknown, DWORD dwUserIndex, DWORD flags, XINPUT_CAPABILITIES_EX *pCapabilities);
34
35
static XInputGetState_t PPSSPP_XInputGetState = nullptr;
36
static XInputSetState_t PPSSPP_XInputSetState = nullptr;
37
static XInputGetCapabilitiesEx_t PPSSPP_XInputGetCapabilitiesEx = nullptr;
38
static DWORD PPSSPP_XInputVersion = 0;
39
static HMODULE s_pXInputDLL = nullptr;
40
static int s_XInputDLLRefCount = 0;
41
42
static void UnloadXInputDLL();
43
44
static int LoadXInputDLL() {
45
DWORD version = 0;
46
47
if (s_pXInputDLL) {
48
s_XInputDLLRefCount++;
49
return 0; /* already loaded */
50
}
51
52
version = (1 << 16) | 4;
53
s_pXInputDLL = LoadLibrary( L"XInput1_4.dll" ); // 1.4 Ships with Windows 8.
54
if (!s_pXInputDLL) {
55
version = (1 << 16) | 3;
56
s_pXInputDLL = LoadLibrary( L"XInput1_3.dll" ); // 1.3 Ships with Vista and Win7, can be installed as a restributable component.
57
if (!s_pXInputDLL) {
58
version = (1 << 16) | 0;
59
s_pXInputDLL = LoadLibrary( L"XInput9_1_0.dll" ); // 1.0 ships with any Windows since WinXP
60
}
61
}
62
if (!s_pXInputDLL) {
63
return -1;
64
}
65
66
PPSSPP_XInputVersion = version;
67
s_XInputDLLRefCount = 1;
68
69
/* 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...
70
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) */
71
PPSSPP_XInputGetState = (XInputGetState_t)GetProcAddress( (HMODULE)s_pXInputDLL, "XInputGetStateEx" );
72
if ( !PPSSPP_XInputGetState ) {
73
PPSSPP_XInputGetState = (XInputGetState_t)GetProcAddress( (HMODULE)s_pXInputDLL, (LPCSTR)100 );
74
if ( !PPSSPP_XInputGetState ) {
75
PPSSPP_XInputGetState = (XInputGetState_t)GetProcAddress( (HMODULE)s_pXInputDLL, "XInputGetState" );
76
}
77
}
78
79
if ( !PPSSPP_XInputGetState ) {
80
UnloadXInputDLL();
81
return -1;
82
}
83
84
/* Let's try the name first, then fall back to a non-Ex version (xinput9_1_0.dll doesn't have Ex) */
85
PPSSPP_XInputSetState = (XInputSetState_t)GetProcAddress((HMODULE)s_pXInputDLL, "XInputSetStateEx");
86
if (!PPSSPP_XInputSetState) {
87
PPSSPP_XInputSetState = (XInputSetState_t)GetProcAddress((HMODULE)s_pXInputDLL, "XInputSetState");
88
}
89
90
if (!PPSSPP_XInputSetState) {
91
UnloadXInputDLL();
92
return -1;
93
}
94
95
if (PPSSPP_XInputVersion >= ((1 << 16) | 4)) {
96
PPSSPP_XInputGetCapabilitiesEx = (XInputGetCapabilitiesEx_t)GetProcAddress((HMODULE)s_pXInputDLL, (LPCSTR)108);
97
}
98
99
return 0;
100
}
101
102
static void UnloadXInputDLL() {
103
if ( s_pXInputDLL ) {
104
if (--s_XInputDLLRefCount == 0) {
105
FreeLibrary( s_pXInputDLL );
106
s_pXInputDLL = nullptr;
107
}
108
}
109
}
110
111
#else
112
static int LoadXInputDLL() { return 0; }
113
static void UnloadXInputDLL() {}
114
#define PPSSPP_XInputGetState XInputGetState
115
#define PPSSPP_XInputSetState XInputSetState
116
#endif
117
118
#ifndef XUSER_MAX_COUNT
119
#define XUSER_MAX_COUNT 4
120
#endif
121
122
// Undocumented. Steam annoyingly grabs this button though....
123
#define XINPUT_GUIDE_BUTTON 0x400
124
125
126
// Permanent map. Actual mapping happens elsewhere.
127
static const struct { int from; InputKeyCode to; } xinput_ctrl_map[] = {
128
{XINPUT_GAMEPAD_A, NKCODE_BUTTON_A},
129
{XINPUT_GAMEPAD_B, NKCODE_BUTTON_B},
130
{XINPUT_GAMEPAD_X, NKCODE_BUTTON_X},
131
{XINPUT_GAMEPAD_Y, NKCODE_BUTTON_Y},
132
{XINPUT_GAMEPAD_BACK, NKCODE_BUTTON_SELECT},
133
{XINPUT_GAMEPAD_START, NKCODE_BUTTON_START},
134
{XINPUT_GAMEPAD_LEFT_SHOULDER, NKCODE_BUTTON_L1},
135
{XINPUT_GAMEPAD_RIGHT_SHOULDER, NKCODE_BUTTON_R1},
136
{XINPUT_GAMEPAD_LEFT_THUMB, NKCODE_BUTTON_THUMBL},
137
{XINPUT_GAMEPAD_RIGHT_THUMB, NKCODE_BUTTON_THUMBR},
138
{XINPUT_GAMEPAD_DPAD_UP, NKCODE_DPAD_UP},
139
{XINPUT_GAMEPAD_DPAD_DOWN, NKCODE_DPAD_DOWN},
140
{XINPUT_GAMEPAD_DPAD_LEFT, NKCODE_DPAD_LEFT},
141
{XINPUT_GAMEPAD_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
142
{XINPUT_GUIDE_BUTTON, NKCODE_HOME},
143
};
144
145
XinputDevice::XinputDevice() {
146
if (LoadXInputDLL() != 0) {
147
WARN_LOG(Log::sceCtrl, "Failed to load XInput! DLL missing");
148
}
149
150
for (size_t i = 0; i < ARRAY_SIZE(check_delay); ++i) {
151
check_delay[i] = (int)i;
152
}
153
}
154
155
XinputDevice::~XinputDevice() {
156
UnloadXInputDLL();
157
}
158
159
int XinputDevice::UpdateState() {
160
#if !PPSSPP_PLATFORM(UWP)
161
if (!s_pXInputDLL)
162
return 0;
163
#endif
164
165
bool anySuccess = false;
166
for (int i = 0; i < XUSER_MAX_COUNT; i++) {
167
XINPUT_STATE state{};
168
if (check_delay[i]-- > 0)
169
continue;
170
DWORD dwResult = PPSSPP_XInputGetState(i, &state);
171
if (dwResult == ERROR_SUCCESS) {
172
XINPUT_VIBRATION vibration{};
173
UpdatePad(i, state, vibration);
174
anySuccess = true;
175
} else {
176
check_delay[i] = 30;
177
}
178
}
179
180
// If we get XInput, skip the others. This might not actually be a good idea,
181
// and was done to avoid conflicts between DirectInput and XInput.
182
return anySuccess ? UPDATESTATE_SKIP_PAD : 0;
183
}
184
185
void XinputDevice::UpdatePad(int pad, const XINPUT_STATE &state, XINPUT_VIBRATION &vibration) {
186
if (!notified_[pad]) {
187
notified_[pad] = true;
188
#if !PPSSPP_PLATFORM(UWP)
189
XINPUT_CAPABILITIES_EX caps{};
190
if (PPSSPP_XInputGetCapabilitiesEx != nullptr && PPSSPP_XInputGetCapabilitiesEx(1, pad, 0, &caps) == ERROR_SUCCESS) {
191
KeyMap::NotifyPadConnected(DEVICE_ID_XINPUT_0 + pad, StringFromFormat("Xbox 360 Pad: %d/%d", caps.vendorId, caps.productId));
192
} else {
193
#else
194
{
195
#endif
196
KeyMap::NotifyPadConnected(DEVICE_ID_XINPUT_0 + pad, "Xbox 360 Pad");
197
}
198
}
199
ApplyButtons(pad, state);
200
ApplyVibration(pad, vibration);
201
202
AxisInput axis[6];
203
int axisCount = 0;
204
for (int i = 0; i < ARRAY_SIZE(axis); i++) {
205
axis[i].deviceId = (InputDeviceID)(DEVICE_ID_XINPUT_0 + pad);
206
}
207
auto sendAxis = [&](InputAxis axisId, float value, int axisIndex) {
208
if (value != prevAxisValue_[pad][axisIndex]) {
209
prevAxisValue_[pad][axisIndex] = value;
210
axis[axisCount].axisId = axisId;
211
axis[axisCount].value = value;
212
axisCount++;
213
}
214
};
215
216
sendAxis(JOYSTICK_AXIS_X, (float)state.Gamepad.sThumbLX / 32767.0f, 0);
217
sendAxis(JOYSTICK_AXIS_Y, (float)state.Gamepad.sThumbLY / 32767.0f, 1);
218
sendAxis(JOYSTICK_AXIS_Z, (float)state.Gamepad.sThumbRX / 32767.0f, 2);
219
sendAxis(JOYSTICK_AXIS_RZ, (float)state.Gamepad.sThumbRY / 32767.0f, 3);
220
sendAxis(JOYSTICK_AXIS_LTRIGGER, (float)state.Gamepad.bLeftTrigger / 255.0f, 4);
221
sendAxis(JOYSTICK_AXIS_RTRIGGER, (float)state.Gamepad.bRightTrigger / 255.0f, 5);
222
223
if (axisCount) {
224
NativeAxis(axis, axisCount);
225
}
226
227
prevState[pad] = state;
228
check_delay[pad] = 0;
229
}
230
231
void XinputDevice::ApplyButtons(int pad, const XINPUT_STATE &state) {
232
const u32 buttons = state.Gamepad.wButtons;
233
234
const u32 downMask = buttons & (~prevButtons_[pad]);
235
const u32 upMask = (~buttons) & prevButtons_[pad];
236
prevButtons_[pad] = buttons;
237
238
for (int i = 0; i < ARRAY_SIZE(xinput_ctrl_map); i++) {
239
if (downMask & xinput_ctrl_map[i].from) {
240
KeyInput key;
241
key.deviceId = DEVICE_ID_XINPUT_0 + pad;
242
key.flags = KEY_DOWN;
243
key.keyCode = xinput_ctrl_map[i].to;
244
NativeKey(key);
245
}
246
if (upMask & xinput_ctrl_map[i].from) {
247
KeyInput key;
248
key.deviceId = DEVICE_ID_XINPUT_0 + pad;
249
key.flags = KEY_UP;
250
key.keyCode = xinput_ctrl_map[i].to;
251
NativeKey(key);
252
}
253
}
254
}
255
256
257
void XinputDevice::ApplyVibration(int pad, XINPUT_VIBRATION &vibration) {
258
if (PSP_IsInited()) {
259
newVibrationTime_ = time_now_d();
260
// We have to run PPSSPP_XInputSetState at time intervals
261
// since it bugs otherwise with very high fast-forward speeds
262
// and freezes at constant vibration or no vibration at all.
263
if (newVibrationTime_ - prevVibrationTime >= 1.0 / 64.0) {
264
if (GetUIState() == UISTATE_INGAME) {
265
vibration.wLeftMotorSpeed = sceCtrlGetLeftVibration(); // use any value between 0-65535 here
266
vibration.wRightMotorSpeed = sceCtrlGetRightVibration(); // use any value between 0-65535 here
267
} else {
268
vibration.wLeftMotorSpeed = 0;
269
vibration.wRightMotorSpeed = 0;
270
}
271
272
if (prevVibration[pad].wLeftMotorSpeed != vibration.wLeftMotorSpeed || prevVibration[pad].wRightMotorSpeed != vibration.wRightMotorSpeed) {
273
PPSSPP_XInputSetState(pad, &vibration);
274
prevVibration[pad] = vibration;
275
}
276
prevVibrationTime = newVibrationTime_;
277
}
278
} else {
279
DWORD dwResult = PPSSPP_XInputSetState(pad, &vibration);
280
if (dwResult != ERROR_SUCCESS) {
281
check_delay[pad] = 30;
282
}
283
}
284
}
285
286