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