Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/DinputDevice.cpp
5654 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "stdafx.h"
19
#include <initguid.h>
20
#include <cstddef>
21
#include <limits.h>
22
#include <algorithm>
23
#include <mmsystem.h>
24
#include <XInput.h>
25
#include <wrl/client.h>
26
27
#include <wbemidl.h>
28
#include <comdef.h>
29
#include <set>
30
#include "Common/Input/InputState.h"
31
#include "Common/Input/KeyCodes.h"
32
#include "Common/StringUtils.h"
33
#include "Common/System/NativeApp.h"
34
#include "Core/KeyMap.h"
35
#include "Windows/DinputDevice.h"
36
#include "Windows/Hid/HidInputDevice.h"
37
38
#pragma comment(lib,"dinput8.lib")
39
40
using Microsoft::WRL::ComPtr;
41
42
// static members of DinputDevice
43
unsigned int DinputDevice::pInstances = 0;
44
Microsoft::WRL::ComPtr<IDirectInput8> DinputDevice::pDI;
45
std::vector<DIDEVICEINSTANCE> DinputDevice::devices;
46
std::set<u32> DinputDevice::ignoreDevices_;
47
48
bool DinputDevice::needsCheck_ = true;
49
50
// In order from 0. There can be 128, but most controllers do not have that many.
51
static const InputKeyCode dinput_buttons[] = {
52
NKCODE_BUTTON_1,
53
NKCODE_BUTTON_2,
54
NKCODE_BUTTON_3,
55
NKCODE_BUTTON_4,
56
NKCODE_BUTTON_5,
57
NKCODE_BUTTON_6,
58
NKCODE_BUTTON_7,
59
NKCODE_BUTTON_8,
60
NKCODE_BUTTON_9,
61
NKCODE_BUTTON_10,
62
NKCODE_BUTTON_11,
63
NKCODE_BUTTON_12,
64
NKCODE_BUTTON_13,
65
NKCODE_BUTTON_14,
66
NKCODE_BUTTON_15,
67
NKCODE_BUTTON_16,
68
};
69
70
#define DIFF (JOY_POVRIGHT - JOY_POVFORWARD) / 2
71
#define JOY_POVFORWARD_RIGHT JOY_POVFORWARD + DIFF
72
#define JOY_POVRIGHT_BACKWARD JOY_POVRIGHT + DIFF
73
#define JOY_POVBACKWARD_LEFT JOY_POVBACKWARD + DIFF
74
#define JOY_POVLEFT_FORWARD JOY_POVLEFT + DIFF
75
76
static std::set<u32> DetectXInputVIDPIDs();
77
78
LPDIRECTINPUT8 DinputDevice::getPDI()
79
{
80
if (pDI == nullptr)
81
{
82
if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&pDI, NULL)))
83
{
84
pDI = nullptr;
85
}
86
}
87
return pDI.Get();
88
}
89
90
BOOL CALLBACK DinputDevice::DevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) {
91
//check if a device with the same Instance guid is already saved
92
auto res = std::find_if(devices.begin(), devices.end(),
93
[lpddi](const DIDEVICEINSTANCE &to_consider){
94
return lpddi->guidInstance == to_consider.guidInstance;
95
});
96
if (res == devices.end()) {
97
// not yet in the devices list
98
// Ignore if device supports XInput - we'll get the input through there instead.
99
const u32 vidpid = lpddi->guidProduct.Data1;
100
const bool isXinputDevice = ignoreDevices_.find(vidpid) != ignoreDevices_.end();
101
if (!isXinputDevice) {
102
devices.push_back(*lpddi);
103
}
104
}
105
return DIENUM_CONTINUE;
106
}
107
108
void DinputDevice::getDevices(bool refresh) {
109
if (refresh) {
110
// We don't want duplicate reporting from XInput devices through DInput.
111
ignoreDevices_ = DetectXInputVIDPIDs();
112
HidInputDevice::AddSupportedDevices(&ignoreDevices_);
113
getPDI()->EnumDevices(DI8DEVCLASS_GAMECTRL, &DinputDevice::DevicesCallback, NULL, DIEDFL_ATTACHEDONLY);
114
}
115
}
116
117
DinputDevice::DinputDevice(int devnum) {
118
pInstances++;
119
pDevNum = devnum;
120
pJoystick = nullptr;
121
last_lX_ = 0;
122
last_lY_ = 0;
123
last_lZ_ = 0;
124
last_lRx_ = 0;
125
last_lRy_ = 0;
126
last_lRz_ = 0;
127
128
if (!getPDI()) {
129
return;
130
}
131
132
if (devnum >= MAX_NUM_PADS) {
133
return;
134
}
135
136
getDevices(needsCheck_);
137
if ( (devnum >= (int)devices.size()) || FAILED(getPDI()->CreateDevice(devices.at(devnum).guidInstance, &pJoystick, NULL)))
138
{
139
return;
140
}
141
142
wchar_t guid[64];
143
if (StringFromGUID2(devices.at(devnum).guidProduct, guid, ARRAY_SIZE(guid)) != 0) {
144
KeyMap::NotifyPadConnected(DEVICE_ID_PAD_0 + pDevNum, StringFromFormat("%S: %S", devices.at(devnum).tszProductName, guid));
145
}
146
147
if (FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick2))) {
148
pJoystick = nullptr;
149
return;
150
}
151
152
DIPROPRANGE diprg;
153
diprg.diph.dwSize = sizeof(DIPROPRANGE);
154
diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
155
diprg.diph.dwHow = DIPH_DEVICE;
156
diprg.diph.dwObj = 0;
157
diprg.lMin = -10000;
158
diprg.lMax = 10000;
159
160
analog = FAILED(pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph)) ? false : true;
161
162
// Other devices suffer if the deadzone is not set.
163
// TODO: The dead zone will be made configurable in the Control dialog.
164
DIPROPDWORD dipw;
165
dipw.diph.dwSize = sizeof(DIPROPDWORD);
166
dipw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
167
dipw.diph.dwHow = DIPH_DEVICE;
168
dipw.diph.dwObj = 0;
169
// dwData 10000 is deadzone(0% - 100%), multiply by config scalar
170
dipw.dwData = 0;
171
172
analog |= FAILED(pJoystick->SetProperty(DIPROP_DEADZONE, &dipw.diph)) ? false : true;
173
}
174
175
DinputDevice::~DinputDevice() {
176
KeyMap::NotifyPadDisconnected(DEVICE_ID_PAD_0 + pDevNum);
177
ReleaseAllKeys();
178
179
if (pJoystick) {
180
pJoystick = nullptr;
181
}
182
183
pInstances--;
184
185
//the whole instance counter is obviously highly thread-unsafe
186
//but I don't think creation and destruction operations will be
187
//happening at the same time and other values like pDI are
188
//unsafe as well anyway
189
if (pInstances == 0 && pDI) {
190
pDI = nullptr;
191
}
192
}
193
194
void DinputDevice::ReleaseAllKeys() {
195
KeyInput key;
196
key.deviceId = DEVICE_ID_PAD_0 + pDevNum;
197
key.flags = KeyInputFlags::UP;
198
for (int i = 0; i < ARRAY_SIZE(dinput_buttons); ++i) {
199
if (lastButtons_[i] != 0) {
200
key.keyCode = dinput_buttons[i];
201
NativeKey(key);
202
lastButtons_[i] = 0;
203
}
204
}
205
206
// Release DPad
207
static const InputKeyCode dpadCodes[] = {
208
NKCODE_DPAD_UP,
209
NKCODE_DPAD_DOWN,
210
NKCODE_DPAD_LEFT,
211
NKCODE_DPAD_RIGHT
212
};
213
for (int i = 0; i < ARRAY_SIZE(dpadCodes); ++i) {
214
key.keyCode = dpadCodes[i];
215
NativeKey(key);
216
}
217
218
// Release axes
219
static const InputAxis axes[] = {
220
JOYSTICK_AXIS_X,
221
JOYSTICK_AXIS_Y,
222
JOYSTICK_AXIS_Z,
223
JOYSTICK_AXIS_RX,
224
JOYSTICK_AXIS_RY,
225
JOYSTICK_AXIS_RZ
226
};
227
for (int i = 0; i < ARRAY_SIZE(axes); ++i) {
228
AxisInput axis;
229
axis.deviceId = DEVICE_ID_PAD_0 + pDevNum;
230
axis.axisId = axes[i];
231
axis.value = 0.0f;
232
NativeAxis(&axis, 1);
233
}
234
}
235
236
void SendNativeAxis(InputDeviceID deviceId, int value, int &lastValue, InputAxis axisId) {
237
if (value != lastValue) {
238
AxisInput axis;
239
axis.deviceId = deviceId;
240
axis.axisId = axisId;
241
axis.value = (float)value * (1.0f / 10000.0f); // Convert axis to normalised float
242
NativeAxis(&axis, 1);
243
}
244
lastValue = value;
245
}
246
247
static LONG *ValueForAxisId(DIJOYSTATE2 &js, int axisId) {
248
switch (axisId) {
249
case JOYSTICK_AXIS_X: return &js.lX;
250
case JOYSTICK_AXIS_Y: return &js.lY;
251
case JOYSTICK_AXIS_Z: return &js.lZ;
252
case JOYSTICK_AXIS_RX: return &js.lRx;
253
case JOYSTICK_AXIS_RY: return &js.lRy;
254
case JOYSTICK_AXIS_RZ: return &js.lRz;
255
default: return nullptr;
256
}
257
}
258
259
int DinputDevice::UpdateState() {
260
if (!pJoystick) return -1;
261
262
DIJOYSTATE2 js;
263
264
if (FAILED(pJoystick->Poll())) {
265
if(pJoystick->Acquire() == DIERR_INPUTLOST)
266
return -1;
267
}
268
269
if(FAILED(pJoystick->GetDeviceState(sizeof(DIJOYSTATE2), &js)))
270
return -1;
271
272
ApplyButtons(js);
273
274
if (analog) {
275
// TODO: Use the batched interface.
276
AxisInput axis;
277
axis.deviceId = DEVICE_ID_PAD_0 + pDevNum;
278
279
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X);
280
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y);
281
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lZ, last_lZ_, JOYSTICK_AXIS_Z);
282
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRx, last_lRx_, JOYSTICK_AXIS_RX);
283
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRy, last_lRy_, JOYSTICK_AXIS_RY);
284
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRz, last_lRz_, JOYSTICK_AXIS_RZ);
285
}
286
287
//check if the values have changed from last time and skip polling the rest of the dinput devices if they did
288
//this doesn't seem to quite work if only the axis have changed
289
if ((memcmp(js.rgbButtons, pPrevState.rgbButtons, sizeof(BYTE) * 128) != 0)
290
|| (memcmp(js.rgdwPOV, pPrevState.rgdwPOV, sizeof(DWORD) * 4) != 0)
291
|| js.lVX != 0 || js.lVY != 0 || js.lVZ != 0 || js.lVRx != 0 || js.lVRy != 0 || js.lVRz != 0)
292
{
293
pPrevState = js;
294
return InputDevice::UPDATESTATE_SKIP_PAD;
295
}
296
return -1;
297
}
298
299
void DinputDevice::ApplyButtons(DIJOYSTATE2 &state) {
300
BYTE *buttons = state.rgbButtons;
301
u32 downMask = 0x80;
302
303
for (int i = 0; i < ARRAY_SIZE(dinput_buttons); ++i) {
304
if (state.rgbButtons[i] == lastButtons_[i]) {
305
continue;
306
}
307
308
bool down = (state.rgbButtons[i] & downMask) == downMask;
309
KeyInput key;
310
key.deviceId = DEVICE_ID_PAD_0 + pDevNum;
311
key.flags = down ? KeyInputFlags::DOWN : KeyInputFlags::UP;
312
key.keyCode = dinput_buttons[i];
313
NativeKey(key);
314
315
lastButtons_[i] = state.rgbButtons[i];
316
}
317
318
// Now the POV hat, which can technically go in any degree but usually does not.
319
if (LOWORD(state.rgdwPOV[0]) != lastPOV_[0]) {
320
KeyInput dpad[4]{};
321
for (int i = 0; i < 4; ++i) {
322
dpad[i].deviceId = DEVICE_ID_PAD_0 + pDevNum;
323
dpad[i].flags = KeyInputFlags::UP;
324
}
325
dpad[0].keyCode = NKCODE_DPAD_UP;
326
dpad[1].keyCode = NKCODE_DPAD_LEFT;
327
dpad[2].keyCode = NKCODE_DPAD_DOWN;
328
dpad[3].keyCode = NKCODE_DPAD_RIGHT;
329
330
if (LOWORD(state.rgdwPOV[0]) != JOY_POVCENTERED) {
331
// These are the edges, so we use or.
332
if (state.rgdwPOV[0] >= JOY_POVLEFT_FORWARD || state.rgdwPOV[0] <= JOY_POVFORWARD_RIGHT) {
333
dpad[0].flags = KeyInputFlags::DOWN;
334
}
335
if (state.rgdwPOV[0] >= JOY_POVBACKWARD_LEFT && state.rgdwPOV[0] <= JOY_POVLEFT_FORWARD) {
336
dpad[1].flags = KeyInputFlags::DOWN;
337
}
338
if (state.rgdwPOV[0] >= JOY_POVRIGHT_BACKWARD && state.rgdwPOV[0] <= JOY_POVBACKWARD_LEFT) {
339
dpad[2].flags = KeyInputFlags::DOWN;
340
}
341
if (state.rgdwPOV[0] >= JOY_POVFORWARD_RIGHT && state.rgdwPOV[0] <= JOY_POVRIGHT_BACKWARD) {
342
dpad[3].flags = KeyInputFlags::DOWN;
343
}
344
}
345
346
NativeKey(dpad[0]);
347
NativeKey(dpad[1]);
348
NativeKey(dpad[2]);
349
NativeKey(dpad[3]);
350
351
lastPOV_[0] = LOWORD(state.rgdwPOV[0]);
352
}
353
}
354
355
size_t DinputDevice::getNumPads()
356
{
357
getDevices(needsCheck_);
358
needsCheck_ = false;
359
return devices.size();
360
}
361
362
static std::set<u32> DetectXInputVIDPIDs() {
363
std::set<u32> xinputVidPids;
364
365
ComPtr<IWbemLocator> pIWbemLocator;
366
if (FAILED(CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER,
367
IID_PPV_ARGS(&pIWbemLocator))))
368
return xinputVidPids;
369
370
ComPtr<IWbemServices> pIWbemServices;
371
if (FAILED(pIWbemLocator->ConnectServer(_bstr_t(L"root\\cimv2"), nullptr, nullptr, nullptr, 0,
372
nullptr, nullptr, &pIWbemServices)))
373
return xinputVidPids;
374
375
CoSetProxyBlanket(pIWbemServices.Get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
376
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE);
377
378
ComPtr<IEnumWbemClassObject> pEnumDevices;
379
if (FAILED(pIWbemServices->CreateInstanceEnum(_bstr_t(L"Win32_PNPEntity"), 0, nullptr, &pEnumDevices)))
380
return xinputVidPids;
381
382
IWbemClassObject* pDevices[32] = { 0 };
383
ULONG uReturned = 0;
384
385
while (SUCCEEDED(pEnumDevices->Next(10000, 32, pDevices, &uReturned)) && uReturned > 0) {
386
for (ULONG i = 0; i < uReturned; i++) {
387
VARIANT var{};
388
if (SUCCEEDED(pDevices[i]->Get(L"DeviceID", 0, &var, nullptr, nullptr)))
389
{
390
if (wcsstr(var.bstrVal, L"IG_"))
391
{
392
DWORD vid = 0, pid = 0;
393
const WCHAR *strVid = wcsstr(var.bstrVal, L"VID_");
394
const WCHAR *strPid = wcsstr(var.bstrVal, L"PID_");
395
396
if (strVid) swscanf_s(strVid, L"VID_%4x", &vid);
397
if (strPid) swscanf_s(strPid, L"PID_%4x", &pid);
398
399
const DWORD vidpid = MAKELONG(vid, pid);
400
xinputVidPids.insert((u32)vidpid);
401
}
402
VariantClear(&var);
403
}
404
pDevices[i]->Release();
405
}
406
}
407
408
return xinputVidPids;
409
}
410
411
DInputMetaDevice::DInputMetaDevice() {
412
//find all connected DInput devices of class GamePad
413
numDinputDevices_ = DinputDevice::getNumPads();
414
for (size_t i = 0; i < numDinputDevices_; i++) {
415
devices_.push_back(std::make_unique<DinputDevice>(static_cast<int>(i)));
416
}
417
}
418
419
int DInputMetaDevice::UpdateState() {
420
static const int CHECK_FREQUENCY = 71; // Just an arbitrary prime to try to not collide with other periodic checks.
421
if (checkCounter_++ > CHECK_FREQUENCY) {
422
const size_t newCount = DinputDevice::getNumPads();
423
if (newCount > numDinputDevices_) {
424
INFO_LOG(Log::System, "New controller device detected");
425
for (size_t i = numDinputDevices_; i < newCount; i++) {
426
devices_.push_back(std::make_unique<DinputDevice>(static_cast<int>(i)));
427
}
428
numDinputDevices_ = newCount;
429
}
430
checkCounter_ = 0;
431
}
432
433
for (const auto &device : devices_) {
434
if (device->UpdateState() == InputDevice::UPDATESTATE_SKIP_PAD)
435
return InputDevice::UPDATESTATE_SKIP_PAD;
436
}
437
return 0;
438
}
439
440