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 <limits.h>
20 #include <algorithm>
21 #include <mmsystem.h>
22 #include <XInput.h>
23
24 #include "Common/Input/InputState.h"
25 #include "Common/Input/KeyCodes.h"
26 #include "Common/System/NativeApp.h"
27 #include "Core/Config.h"
28 #include "Core/HLE/sceCtrl.h"
29 #include "Core/KeyMap.h"
30 #include "Core/Reporting.h"
31 #include "Windows/DinputDevice.h"
32 #pragma comment(lib,"dinput8.lib")
33
34 //initialize static members of DinputDevice
35 unsigned int DinputDevice::pInstances = 0;
36 LPDIRECTINPUT8 DinputDevice::pDI = NULL;
37 std::vector<DIDEVICEINSTANCE> DinputDevice::devices;
38 bool DinputDevice::needsCheck_ = true;
39
40 // In order from 0. There can be 128, but most controllers do not have that many.
41 static const int dinput_buttons[] = {
42 NKCODE_BUTTON_1,
43 NKCODE_BUTTON_2,
44 NKCODE_BUTTON_3,
45 NKCODE_BUTTON_4,
46 NKCODE_BUTTON_5,
47 NKCODE_BUTTON_6,
48 NKCODE_BUTTON_7,
49 NKCODE_BUTTON_8,
50 NKCODE_BUTTON_9,
51 NKCODE_BUTTON_10,
52 NKCODE_BUTTON_11,
53 NKCODE_BUTTON_12,
54 NKCODE_BUTTON_13,
55 NKCODE_BUTTON_14,
56 NKCODE_BUTTON_15,
57 NKCODE_BUTTON_16,
58 };
59
60 static float NormalizedDeadzoneFilter(short value);
61
62 #define DIFF (JOY_POVRIGHT - JOY_POVFORWARD) / 2
63 #define JOY_POVFORWARD_RIGHT JOY_POVFORWARD + DIFF
64 #define JOY_POVRIGHT_BACKWARD JOY_POVRIGHT + DIFF
65 #define JOY_POVBACKWARD_LEFT JOY_POVBACKWARD + DIFF
66 #define JOY_POVLEFT_FORWARD JOY_POVLEFT + DIFF
67
68 struct XINPUT_DEVICE_NODE {
69 DWORD dwVidPid;
70 XINPUT_DEVICE_NODE* pNext;
71 };
72 XINPUT_DEVICE_NODE* g_pXInputDeviceList = NULL;
73
IsXInputDevice(const GUID * pGuidProductFromDirectInput)74 bool IsXInputDevice( const GUID* pGuidProductFromDirectInput ) {
75 XINPUT_DEVICE_NODE* pNode = g_pXInputDeviceList;
76 while( pNode )
77 {
78 if( pNode->dwVidPid == pGuidProductFromDirectInput->Data1 )
79 return true;
80 pNode = pNode->pNext;
81 }
82
83 return false;
84 }
85
getPDI()86 LPDIRECTINPUT8 DinputDevice::getPDI()
87 {
88 if (pDI == NULL)
89 {
90 if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&pDI, NULL)))
91 {
92 pDI = NULL;
93 }
94 }
95 return pDI;
96 }
97
DevicesCallback(LPCDIDEVICEINSTANCE lpddi,LPVOID pvRef)98 BOOL CALLBACK DinputDevice::DevicesCallback(
99 LPCDIDEVICEINSTANCE lpddi,
100 LPVOID pvRef
101 )
102 {
103 //check if a device with the same Instance guid is already saved
104 auto res = std::find_if(devices.begin(), devices.end(),
105 [lpddi](const DIDEVICEINSTANCE &to_consider){
106 return lpddi->guidInstance == to_consider.guidInstance;
107 });
108 if (res == devices.end()) //not yet in the devices list
109 {
110 // Ignore if device supports XInput
111 if (!IsXInputDevice(&lpddi->guidProduct)) {
112 devices.push_back(*lpddi);
113 }
114 }
115 return DIENUM_CONTINUE;
116 }
117
getDevices(bool refresh)118 void DinputDevice::getDevices(bool refresh)
119 {
120 if (refresh)
121 {
122 getPDI()->EnumDevices(DI8DEVCLASS_GAMECTRL, &DinputDevice::DevicesCallback, NULL, DIEDFL_ATTACHEDONLY);
123 }
124 }
125
DinputDevice(int devnum)126 DinputDevice::DinputDevice(int devnum) {
127 pInstances++;
128 pDevNum = devnum;
129 pJoystick = NULL;
130 memset(lastButtons_, 0, sizeof(lastButtons_));
131 memset(lastPOV_, 0, sizeof(lastPOV_));
132 last_lX_ = 0;
133 last_lY_ = 0;
134 last_lZ_ = 0;
135 last_lRx_ = 0;
136 last_lRy_ = 0;
137 last_lRz_ = 0;
138
139 if (getPDI() == NULL)
140 {
141 return;
142 }
143
144 if (devnum >= MAX_NUM_PADS)
145 {
146 return;
147 }
148
149 getDevices(false);
150 if ( (devnum >= (int)devices.size()) || FAILED(getPDI()->CreateDevice(devices.at(devnum).guidInstance, &pJoystick, NULL)))
151 {
152 return;
153 }
154
155 if (FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick2))) {
156 pJoystick->Release();
157 pJoystick = NULL;
158 return;
159 }
160
161 DIPROPRANGE diprg;
162 diprg.diph.dwSize = sizeof(DIPROPRANGE);
163 diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
164 diprg.diph.dwHow = DIPH_DEVICE;
165 diprg.diph.dwObj = 0;
166 diprg.lMin = -10000;
167 diprg.lMax = 10000;
168
169 analog = FAILED(pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph)) ? false : true;
170
171 // Other devices suffer if the deadzone is not set.
172 // TODO: The dead zone will be made configurable in the Control dialog.
173 DIPROPDWORD dipw;
174 dipw.diph.dwSize = sizeof(DIPROPDWORD);
175 dipw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
176 dipw.diph.dwHow = DIPH_DEVICE;
177 dipw.diph.dwObj = 0;
178 // dwData 10000 is deadzone(0% - 100%), multiply by config scalar
179 dipw.dwData = 0;
180
181 analog |= FAILED(pJoystick->SetProperty(DIPROP_DEADZONE, &dipw.diph)) ? false : true;
182 }
183
~DinputDevice()184 DinputDevice::~DinputDevice() {
185 if (pJoystick) {
186 pJoystick->Release();
187 pJoystick = NULL;
188 }
189
190 pInstances--;
191
192 //the whole instance counter is obviously highly thread-unsafe
193 //but I don't think creation and destruction operations will be
194 //happening at the same time and other values like pDI are
195 //unsafe as well anyway
196 if (pInstances == 0 && pDI) {
197 pDI->Release();
198 pDI = NULL;
199 }
200 }
201
SendNativeAxis(int deviceId,int value,int & lastValue,int axisId)202 void SendNativeAxis(int deviceId, int value, int &lastValue, int axisId) {
203 AxisInput axis;
204 axis.deviceId = deviceId;
205 axis.axisId = axisId;
206 axis.value = (float)value / 10000.0f; // Convert axis to normalised float
207 NativeAxis(axis);
208
209 lastValue = value;
210 }
211
ValueForAxisId(DIJOYSTATE2 & js,int axisId)212 static LONG *ValueForAxisId(DIJOYSTATE2 &js, int axisId) {
213 switch (axisId) {
214 case JOYSTICK_AXIS_X: return &js.lX;
215 case JOYSTICK_AXIS_Y: return &js.lY;
216 case JOYSTICK_AXIS_Z: return &js.lZ;
217 case JOYSTICK_AXIS_RX: return &js.lRx;
218 case JOYSTICK_AXIS_RY: return &js.lRy;
219 case JOYSTICK_AXIS_RZ: return &js.lRz;
220 default: return nullptr;
221 }
222 }
223
UpdateState()224 int DinputDevice::UpdateState() {
225 if (!pJoystick) return -1;
226
227 DIJOYSTATE2 js;
228
229 if (FAILED(pJoystick->Poll())) {
230 if(pJoystick->Acquire() == DIERR_INPUTLOST)
231 return -1;
232 }
233
234 if(FAILED(pJoystick->GetDeviceState(sizeof(DIJOYSTATE2), &js)))
235 return -1;
236
237 ApplyButtons(js);
238
239 if (analog) {
240 AxisInput axis;
241 axis.deviceId = DEVICE_ID_PAD_0 + pDevNum;
242
243 auto axesToSquare = KeyMap::MappedAxesForDevice(axis.deviceId);
244
245 SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X);
246 SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y);
247 SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lZ, last_lZ_, JOYSTICK_AXIS_Z);
248 SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRx, last_lRx_, JOYSTICK_AXIS_RX);
249 SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRy, last_lRy_, JOYSTICK_AXIS_RY);
250 SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRz, last_lRz_, JOYSTICK_AXIS_RZ);
251 }
252
253 //check if the values have changed from last time and skip polling the rest of the dinput devices if they did
254 //this doesn't seem to quite work if only the axis have changed
255 if ((memcmp(js.rgbButtons, pPrevState.rgbButtons, sizeof(BYTE) * 128) != 0)
256 || (memcmp(js.rgdwPOV, pPrevState.rgdwPOV, sizeof(DWORD) * 4) != 0)
257 || js.lVX != 0 || js.lVY != 0 || js.lVZ != 0 || js.lVRx != 0 || js.lVRy != 0 || js.lVRz != 0)
258 {
259 pPrevState = js;
260 return UPDATESTATE_SKIP_PAD;
261 }
262 return -1;
263 }
264
ApplyButtons(DIJOYSTATE2 & state)265 void DinputDevice::ApplyButtons(DIJOYSTATE2 &state) {
266 BYTE *buttons = state.rgbButtons;
267 u32 downMask = 0x80;
268
269 for (int i = 0; i < ARRAY_SIZE(dinput_buttons); ++i) {
270 if (state.rgbButtons[i] == lastButtons_[i]) {
271 continue;
272 }
273
274 bool down = (state.rgbButtons[i] & downMask) == downMask;
275 KeyInput key;
276 key.deviceId = DEVICE_ID_PAD_0 + pDevNum;
277 key.flags = down ? KEY_DOWN : KEY_UP;
278 key.keyCode = dinput_buttons[i];
279 NativeKey(key);
280
281 lastButtons_[i] = state.rgbButtons[i];
282 }
283
284 // Now the POV hat, which can technically go in any degree but usually does not.
285 if (LOWORD(state.rgdwPOV[0]) != lastPOV_[0]) {
286 KeyInput dpad[4];
287 for (int i = 0; i < 4; ++i) {
288 dpad[i].deviceId = DEVICE_ID_PAD_0 + pDevNum;
289 dpad[i].flags = KEY_UP;
290 }
291 dpad[0].keyCode = NKCODE_DPAD_UP;
292 dpad[1].keyCode = NKCODE_DPAD_LEFT;
293 dpad[2].keyCode = NKCODE_DPAD_DOWN;
294 dpad[3].keyCode = NKCODE_DPAD_RIGHT;
295
296 if (LOWORD(state.rgdwPOV[0]) != JOY_POVCENTERED) {
297 // These are the edges, so we use or.
298 if (state.rgdwPOV[0] >= JOY_POVLEFT_FORWARD || state.rgdwPOV[0] <= JOY_POVFORWARD_RIGHT) {
299 dpad[0].flags = KEY_DOWN;
300 }
301 if (state.rgdwPOV[0] >= JOY_POVBACKWARD_LEFT && state.rgdwPOV[0] <= JOY_POVLEFT_FORWARD) {
302 dpad[1].flags = KEY_DOWN;
303 }
304 if (state.rgdwPOV[0] >= JOY_POVRIGHT_BACKWARD && state.rgdwPOV[0] <= JOY_POVBACKWARD_LEFT) {
305 dpad[2].flags = KEY_DOWN;
306 }
307 if (state.rgdwPOV[0] >= JOY_POVFORWARD_RIGHT && state.rgdwPOV[0] <= JOY_POVRIGHT_BACKWARD) {
308 dpad[3].flags = KEY_DOWN;
309 }
310 }
311
312 NativeKey(dpad[0]);
313 NativeKey(dpad[1]);
314 NativeKey(dpad[2]);
315 NativeKey(dpad[3]);
316
317 lastPOV_[0] = LOWORD(state.rgdwPOV[0]);
318 }
319 }
320
getNumPads()321 size_t DinputDevice::getNumPads()
322 {
323 getDevices(needsCheck_);
324 needsCheck_ = false;
325 return devices.size();
326 }
327