Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/HidInputDevice.cpp
3185 views
1
// This file in particular along with its header is public domain, use it for whatever you want.
2
3
#include <windows.h>
4
#include <hidsdi.h>
5
#include <setupapi.h>
6
#include <initguid.h>
7
#include <vector>
8
9
#include "Windows/HidInputDevice.h"
10
#include "Common/CommonTypes.h"
11
#include "Common/TimeUtil.h"
12
#include "Common/Math/math_util.h"
13
#include "Common/Log.h"
14
#include "Common/Input/InputState.h"
15
#include "Common/Common.h"
16
#include "Common/System/NativeApp.h"
17
#include "Common/System/OSD.h"
18
19
constexpr u8 LED_R = 0x05;
20
constexpr u8 LED_G = 0x10;
21
constexpr u8 LED_B = 0x40;
22
23
enum HIDButton : u32 {
24
PS_DPAD_UP = 1, // These dpad ones are not real, we convert from hat switch format.
25
PS_DPAD_DOWN = 2,
26
PS_DPAD_LEFT = 4,
27
PS_DPAD_RIGHT = 8,
28
PS_BTN_SQUARE = 16,
29
PS_BTN_CROSS = 32,
30
PS_BTN_TRIANGLE = 64,
31
PS_BTN_CIRCLE = 128,
32
33
PS_BTN_L1 = (1 << 8),
34
PS_BTN_R1 = (1 << 9),
35
PS_BTN_L2 = (1 << 10),
36
PS_BTN_R2 = (1 << 11),
37
PS_BTN_SHARE = (1 << 12),
38
PS_BTN_OPTIONS = (1 << 13),
39
PS_BTN_L3 = (1 << 14),
40
PS_BTN_R3 = (1 << 15),
41
PS_BTN_PS_BUTTON = (1 << 16),
42
PS_BTN_TOUCHPAD = (1 << 17),
43
44
SWITCH_PRO_BTN_Y = (1 << 0),
45
SWITCH_PRO_BTN_X = (1 << 1),
46
SWITCH_PRO_BTN_B = (1 << 2),
47
SWITCH_PRO_BTN_A = (1 << 3),
48
SWITCH_PRO_BTN_R1 = (1 << 6),
49
SWITCH_PRO_BTN_R2 = (1 << 7),
50
SWITCH_PRO_BTN_L3 = (1 << 11),
51
SWITCH_PRO_BTN_R3 = (1 << 10),
52
SWITCH_PRO_BTN_SHARE = (1 << 8),
53
SWITCH_PRO_BTN_OPTIONS = (1 << 9),
54
SWITCH_PRO_BTN_PS_BUTTON = (1 << 12),
55
SWITCH_PRO_BTN_CAPTURE = (1 << 13),
56
SWITCH_PRO_DPAD_DOWN = (1 << 16),
57
SWITCH_PRO_DPAD_UP = (1 << 17),
58
SWITCH_PRO_DPAD_RIGHT = (1 << 18),
59
SWITCH_PRO_DPAD_LEFT = (1 << 19),
60
SWITCH_PRO_BTN_L1 = (1 << 22),
61
SWITCH_PRO_BTN_L2 = (1 << 23),
62
};
63
64
struct ButtonInputMapping {
65
HIDButton button;
66
InputKeyCode keyCode;
67
};
68
69
static const ButtonInputMapping g_psInputMappings[] = {
70
{PS_DPAD_UP, NKCODE_DPAD_UP},
71
{PS_DPAD_DOWN, NKCODE_DPAD_DOWN},
72
{PS_DPAD_LEFT, NKCODE_DPAD_LEFT},
73
{PS_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
74
{PS_BTN_SQUARE, NKCODE_BUTTON_4},
75
{PS_BTN_TRIANGLE, NKCODE_BUTTON_3},
76
{PS_BTN_CIRCLE, NKCODE_BUTTON_1},
77
{PS_BTN_CROSS, NKCODE_BUTTON_2},
78
{PS_BTN_PS_BUTTON, NKCODE_HOME},
79
{PS_BTN_SHARE, NKCODE_BUTTON_9},
80
{PS_BTN_OPTIONS, NKCODE_BUTTON_10},
81
{PS_BTN_L1, NKCODE_BUTTON_7},
82
{PS_BTN_R1, NKCODE_BUTTON_8},
83
// {PS_BTN_L2, NKCODE_BUTTON_L2}, // These are done by the analog triggers.
84
// {PS_BTN_R2, NKCODE_BUTTON_R2},
85
{PS_BTN_L3, NKCODE_BUTTON_THUMBL},
86
{PS_BTN_R3, NKCODE_BUTTON_THUMBR},
87
};
88
89
static const ButtonInputMapping g_switchProInputMappings[] = {
90
{SWITCH_PRO_DPAD_UP, NKCODE_DPAD_UP},
91
{SWITCH_PRO_DPAD_DOWN, NKCODE_DPAD_DOWN},
92
{SWITCH_PRO_DPAD_LEFT, NKCODE_DPAD_LEFT},
93
{SWITCH_PRO_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
94
{SWITCH_PRO_BTN_Y, NKCODE_BUTTON_4},
95
{SWITCH_PRO_BTN_X, NKCODE_BUTTON_1},
96
{SWITCH_PRO_BTN_B, NKCODE_BUTTON_2},
97
{SWITCH_PRO_BTN_A, NKCODE_BUTTON_3},
98
{SWITCH_PRO_BTN_PS_BUTTON, NKCODE_HOME},
99
{SWITCH_PRO_BTN_SHARE, NKCODE_BUTTON_9},
100
{SWITCH_PRO_BTN_OPTIONS, NKCODE_BUTTON_10},
101
{SWITCH_PRO_BTN_L1, NKCODE_BUTTON_7},
102
{SWITCH_PRO_BTN_R1, NKCODE_BUTTON_8},
103
{SWITCH_PRO_BTN_L2, NKCODE_BUTTON_L2}, // No analog triggers.
104
{SWITCH_PRO_BTN_R2, NKCODE_BUTTON_R2},
105
{SWITCH_PRO_BTN_L3, NKCODE_BUTTON_THUMBL},
106
{SWITCH_PRO_BTN_R3, NKCODE_BUTTON_THUMBR},
107
};
108
109
enum PSStickAxis : u32 {
110
PS_STICK_LX = 0,
111
PS_STICK_LY = 1,
112
PS_STICK_RX = 2,
113
PS_STICK_RY = 3,
114
};
115
116
struct PSStickMapping {
117
PSStickAxis stickAxis;
118
InputAxis inputAxis;
119
};
120
121
// This is the same mapping as DInput etc.
122
static const PSStickMapping g_psStickMappings[] = {
123
{PS_STICK_LX, JOYSTICK_AXIS_X},
124
{PS_STICK_LY, JOYSTICK_AXIS_Y},
125
{PS_STICK_RX, JOYSTICK_AXIS_Z},
126
{PS_STICK_RY, JOYSTICK_AXIS_RX},
127
};
128
129
enum PSTriggerAxis : u32 {
130
PS_TRIGGER_L2 = 0,
131
PS_TRIGGER_R2 = 1,
132
};
133
134
struct PSTriggerMapping {
135
PSTriggerAxis triggerAxis;
136
InputAxis inputAxis;
137
};
138
139
static const PSTriggerMapping g_psTriggerMappings[] = {
140
{PS_TRIGGER_L2, JOYSTICK_AXIS_LTRIGGER},
141
{PS_TRIGGER_R2, JOYSTICK_AXIS_RTRIGGER},
142
};
143
144
enum class SwitchProSubCmd {
145
SET_INPUT_MODE = 0x03,
146
SET_LOW_POWER_STATE = 0x08,
147
SPI_FLASH_READ = 0x10,
148
SET_LIGHTS = 0x30, // LEDs on controller
149
SET_HOME_LIGHT = 0x38,
150
ENABLE_IMU = 0x40,
151
SET_IMU_SENS = 0x41,
152
ENABLE_VIBRATION = 0x48,
153
};
154
155
constexpr int SwitchPro_INPUT_REPORT_LEN = 362;
156
constexpr int SwitchPro_OUTPUT_REPORT_LEN = 49;
157
constexpr int SwitchPro_RUMBLE_REPORT_LEN = 64;
158
159
static const u8 g_switchProCmdBufHeader[] = {0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
160
161
struct HIDControllerInfo {
162
u16 vendorId;
163
u16 productId;
164
HIDControllerType type;
165
const char *name;
166
};
167
168
constexpr u16 SONY_VID = 0x054C;
169
constexpr u16 NINTENDO_VID = 0x57e;
170
constexpr u16 SWITCH_PRO_PID = 0x2009;
171
constexpr u16 DS4_WIRELESS = 0x0BA0;
172
constexpr u16 PS_CLASSIC = 0x0CDA;
173
174
// We pick a few ones from here to support, let's add more later.
175
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/DS4Devices.cs#L126
176
static const HIDControllerInfo g_psInfos[] = {
177
{SONY_VID, 0x05C4, HIDControllerType::DS4, "DS4 v.1"},
178
{SONY_VID, 0x09CC, HIDControllerType::DS4, "DS4 v.2"},
179
{SONY_VID, 0x0CE6, HIDControllerType::DS5, "DualSense"},
180
{SONY_VID, PS_CLASSIC, HIDControllerType::DS4, "PS Classic"},
181
{NINTENDO_VID, SWITCH_PRO_PID, HIDControllerType::SwitchPro, "Switch Pro"},
182
// {PSSubType::DS4, DS4_WIRELESS},
183
// {PSSubType::DS5, DUALSENSE_WIRELESS},
184
// {PSSubType::DS5, DUALSENSE_EDGE_WIRELESS},
185
};
186
187
static bool IsSupportedGamepad(HANDLE handle, USHORT *pidOut, HIDControllerType *subType) {
188
HIDD_ATTRIBUTES attr{sizeof(HIDD_ATTRIBUTES)};
189
if (!HidD_GetAttributes(handle, &attr)) {
190
return false;
191
}
192
for (const auto &info : g_psInfos) {
193
if (attr.VendorID == info.vendorId && attr.ProductID == info.productId) {
194
*pidOut = attr.ProductID;
195
*subType = info.type;
196
return true;
197
}
198
}
199
return false;
200
}
201
202
template<class T>
203
static bool WriteReport(HANDLE handle, const T &report) {
204
DWORD written;
205
bool result = WriteFile(handle, &report, sizeof(report), &written, NULL);
206
if (!result) {
207
u32 errorCode = GetLastError();
208
209
if (errorCode == ERROR_INVALID_PARAMETER) {
210
if (!HidD_SetOutputReport(handle, (PVOID)&report, sizeof(T))) {
211
errorCode = GetLastError();
212
}
213
}
214
215
WARN_LOG(Log::UI, "Failed initializing: %08x", errorCode);
216
return false;
217
}
218
return true;
219
}
220
221
struct DualSenseOutputReport {
222
u8 reportId;
223
u8 flags1;
224
u8 flags2;
225
u8 rumbleRight;
226
u8 rumbleLeft;
227
u8 pad[2];
228
u8 muteLED;
229
u8 micMute;
230
u8 other[32];
231
u8 enableBrightness;
232
u8 fade;
233
u8 brightness;
234
u8 playerLights;
235
u8 lightbarRed;
236
u8 lightbarGreen;
237
u8 lightbarBlue;
238
};
239
static_assert(sizeof(DualSenseOutputReport) == 48);
240
241
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs#L905
242
243
// Sends initialization packet to DualSense
244
static bool InitializeDualSense(HANDLE handle, int outReportSize) {
245
if (outReportSize != sizeof(DualSenseOutputReport)) {
246
return false;
247
}
248
249
DualSenseOutputReport report{};
250
report.reportId = 2;
251
report.flags1 = 0x0C;
252
report.flags2 = 0x15;
253
report.muteLED = 1;
254
report.playerLights = 1;
255
report.enableBrightness = 1;
256
report.brightness = 0; // 0 = high, 1 = medium, 2 = low
257
report.lightbarRed = LED_R;
258
report.lightbarGreen = LED_G;
259
report.lightbarBlue = LED_B;
260
return WriteReport(handle, report);
261
}
262
263
static bool ShutdownDualsense(HANDLE handle, int outReportSize) {
264
if (outReportSize != sizeof(DualSenseOutputReport)) {
265
return false;
266
}
267
268
DualSenseOutputReport report{};
269
report.reportId = 2;
270
report.flags1 = 0x0C;
271
report.flags2 = 0x15;
272
report.muteLED = 1;
273
report.playerLights = 0;
274
report.enableBrightness = 1;
275
report.brightness = 2; // 0 = high, 1 = medium, 2 = low
276
report.lightbarRed = 0;
277
report.lightbarGreen = 0;
278
report.lightbarBlue = 0;
279
return WriteReport(handle, report);
280
}
281
282
enum class DS4FeatureBits : u8 {
283
VOL_L = 0x10,
284
VOL_R = 0x20,
285
MIC_VOL = 0x40,
286
SPEAKER_VOL = 0x80,
287
RUMBLE = 0x1,
288
LIGHTBAR = 0x2,
289
FLASH = 0x4,
290
};
291
ENUM_CLASS_BITOPS(DS4FeatureBits);
292
293
struct DualshockOutputReport {
294
u8 reportID;
295
u8 featureBits;
296
u8 two;
297
u8 pad;
298
u8 rumbleRight;
299
u8 rumbleLeft;
300
u8 ledR;
301
u8 ledG;
302
u8 ledB;
303
u8 padding[23];
304
};
305
static_assert(sizeof(DualshockOutputReport) == 32);
306
307
static bool InitializeDualShock(HANDLE handle, int outReportSize) {
308
if (outReportSize != sizeof(DualshockOutputReport)) {
309
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
310
return false;
311
}
312
313
DualshockOutputReport report{};
314
report.reportID = 0x05; // Report ID (DS4 output)
315
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
316
report.two = 2;
317
318
// Rumble
319
report.rumbleRight = 0x00; // Right (weak)
320
report.rumbleLeft = 0x00; // Left (strong)
321
322
// Lightbar (RGB)
323
report.ledR = LED_R;
324
report.ledG = LED_G;
325
report.ledB = LED_B;
326
327
return WriteReport(handle, report);
328
}
329
330
static bool ShutdownDualShock(HANDLE handle, int outReportSize) {
331
if (outReportSize != sizeof(DualshockOutputReport)) {
332
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
333
return false;
334
}
335
336
DualshockOutputReport report{};
337
report.reportID = 0x05; // Report ID (DS4 output)
338
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
339
report.two = 2;
340
341
// Rumble
342
report.rumbleRight = 0x00; // Right (weak)
343
report.rumbleLeft = 0x00; // Left (strong)
344
345
// Lightbar (RGB)
346
report.ledR = 0;
347
report.ledG = 0;
348
report.ledB = 0;
349
350
return WriteReport(handle, report);
351
}
352
353
HANDLE OpenFirstHIDController(HIDControllerType *subType, int *reportSize, int *outReportSize) {
354
GUID hidGuid;
355
HidD_GetHidGuid(&hidGuid);
356
357
HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&hidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
358
if (deviceInfoSet == INVALID_HANDLE_VALUE)
359
return nullptr;
360
361
SP_DEVICE_INTERFACE_DATA interfaceData;
362
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
363
364
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, nullptr, &hidGuid, i, &interfaceData); ++i) {
365
DWORD requiredSize = 0;
366
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &interfaceData, nullptr, 0, &requiredSize, nullptr);
367
368
std::vector<BYTE> buffer(requiredSize);
369
auto* detailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buffer.data());
370
detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
371
372
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &interfaceData, detailData, requiredSize, nullptr, nullptr)) {
373
HANDLE handle = CreateFile(detailData->DevicePath, GENERIC_READ | GENERIC_WRITE,
374
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
375
if (handle != INVALID_HANDLE_VALUE) {
376
USHORT pid;
377
if (IsSupportedGamepad(handle, &pid, subType)) {
378
INFO_LOG(Log::UI, "Found supported gamepad. PID: %04x", pid);
379
HIDP_CAPS caps;
380
PHIDP_PREPARSED_DATA preparsedData;
381
382
HidD_GetPreparsedData(handle, &preparsedData);
383
HidP_GetCaps(preparsedData, &caps);
384
HidD_FreePreparsedData(preparsedData);
385
386
*reportSize = caps.InputReportByteLength;
387
*outReportSize = caps.OutputReportByteLength;
388
389
INFO_LOG(Log::UI, "Initializing gamepad. out report size=%d", outReportSize);
390
bool result;
391
switch (*subType) {
392
case HIDControllerType::DS5:
393
result = InitializeDualSense(handle, *outReportSize);
394
break;
395
case HIDControllerType::DS4:
396
result = InitializeDualShock(handle, *outReportSize);
397
break;
398
case HIDControllerType::SwitchPro:
399
result = true; // InitializeSwitchPro(handle, *outReportSize);
400
break;
401
}
402
403
if (!result) {
404
ERROR_LOG(Log::UI, "Controller initialization failed");
405
}
406
407
SetupDiDestroyDeviceInfoList(deviceInfoSet);
408
409
return handle;
410
}
411
CloseHandle(handle);
412
}
413
}
414
}
415
SetupDiDestroyDeviceInfoList(deviceInfoSet);
416
return nullptr;
417
}
418
419
void HidInputDevice::AddSupportedDevices(std::set<u32> *deviceVIDPIDs) {
420
for (const auto &info : g_psInfos) {
421
const u32 vidpid = MAKELONG(info.vendorId, info.productId);
422
deviceVIDPIDs->insert(vidpid);
423
}
424
}
425
426
static u32 DecodeHatSwitch(u8 dpad) {
427
u32 buttons = 0;
428
if (dpad == 0 || dpad == 1 || dpad == 7) {
429
buttons |= PS_DPAD_UP;
430
}
431
if (dpad == 1 || dpad == 2 || dpad == 3) {
432
buttons |= PS_DPAD_RIGHT;
433
}
434
if (dpad == 3 || dpad == 4 || dpad == 5) {
435
buttons |= PS_DPAD_DOWN;
436
}
437
if (dpad == 5 || dpad == 6 || dpad == 7) {
438
buttons |= PS_DPAD_LEFT;
439
}
440
return buttons;
441
}
442
443
struct DualShockInputReport {
444
u8 lx;
445
u8 ly;
446
u8 rx;
447
u8 ry;
448
u8 buttons[3]; // note, starts at 5 so not aligned
449
u8 l2_analog;
450
u8 r2_analog;
451
u8 pad[2];
452
u8 battery;
453
// Then there's motion and all kinds of stuff.
454
};
455
456
bool ReadDualShockInput(HANDLE handle, HIDControllerState *state) {
457
BYTE inputReport[64]{}; // 64-byte input report for DS4
458
DWORD bytesRead = 0;
459
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
460
u32 error = GetLastError();
461
return false;
462
}
463
DualShockInputReport report{};
464
static_assert(sizeof(report) < sizeof(inputReport));
465
if (bytesRead < 14) {
466
return false;
467
}
468
469
// OK, check the first byte to figure out what we're dealing with here.
470
int offset = 1;
471
int reportId;
472
if (inputReport[0] == 0xA1) {
473
// 2-byte bluetooth frame
474
offset = 2;
475
reportId = inputReport[1];
476
} else {
477
offset = 1;
478
reportId = inputReport[0];
479
}
480
// const bool isBluetooth = (reportId == 0x11 || reportId == 0x31);
481
482
memcpy(&report, inputReport + offset, sizeof(report));
483
484
// Center the sticks.
485
state->stickAxes[PS_STICK_LX] = report.lx - 128;
486
state->stickAxes[PS_STICK_LY] = report.ly - 128;
487
state->stickAxes[PS_STICK_RX] = report.rx - 128;
488
state->stickAxes[PS_STICK_RY] = report.ry - 128;
489
490
// Copy over the triggers.
491
state->triggerAxes[PS_TRIGGER_L2] = report.l2_analog;
492
state->triggerAxes[PS_TRIGGER_R2] = report.r2_analog;
493
494
u32 buttons{};
495
int frameCounter = report.buttons[2] >> 2;
496
report.buttons[2] &= 3;
497
memcpy(&buttons, &report.buttons[0], 3);
498
499
// Clear out and re-fill the DPAD, it works differently somehow
500
buttons &= ~0xF;
501
buttons |= DecodeHatSwitch(report.buttons[0] & 0xF);
502
503
state->buttons = buttons;
504
return true;
505
}
506
507
// So strange that this is different!
508
struct DualSenseInputReport {
509
u8 lx;
510
u8 ly;
511
u8 rx;
512
u8 ry;
513
514
u8 l2_analog;
515
u8 r2_analog;
516
517
u8 frameCounter;
518
519
u8 buttons[3];
520
521
// TODO: More stuff (battery, tilt, etc).
522
};
523
524
bool ReadDualSenseInput(HANDLE handle, HIDControllerState *state) {
525
BYTE inputReport[64]{}; // 64-byte input report for DS4
526
DWORD bytesRead = 0;
527
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
528
const u32 error = GetLastError();
529
return false;
530
}
531
532
DualSenseInputReport report{};
533
static_assert(sizeof(report) < sizeof(inputReport));
534
if (bytesRead < 14) {
535
return false;
536
}
537
538
// OK, check the first byte to figure out what we're dealing with here.
539
int offset = 1;
540
if (inputReport[0] != 1) {
541
// Wrong data
542
return false;
543
}
544
// const bool isBluetooth = (reportId == 0x11 || reportId == 0x31);
545
546
memcpy(&report, inputReport + offset, sizeof(report));
547
548
// Center the sticks.
549
state->stickAxes[PS_STICK_LX] = report.lx - 128;
550
state->stickAxes[PS_STICK_LY] = report.ly - 128;
551
state->stickAxes[PS_STICK_RX] = report.rx - 128;
552
state->stickAxes[PS_STICK_RY] = report.ry - 128;
553
554
// Copy over the triggers.
555
state->triggerAxes[PS_TRIGGER_L2] = report.l2_analog;
556
state->triggerAxes[PS_TRIGGER_R2] = report.r2_analog;
557
558
u32 buttons{};
559
report.buttons[2] &= 3; // Remove noise
560
memcpy(&buttons, &report.buttons[0], 3);
561
562
// Clear out and re-fill the DPAD, it works differently somehow
563
buttons &= ~0xF;
564
buttons |= DecodeHatSwitch(report.buttons[0] & 0xF);
565
566
state->buttons = buttons;
567
return true;
568
}
569
570
void HidInputDevice::Init() {}
571
void HidInputDevice::Shutdown() {
572
if (controller_) {
573
switch (subType_) {
574
case HIDControllerType::DS4:
575
ShutdownDualShock(controller_, outReportSize_);
576
break;
577
case HIDControllerType::DS5:
578
ShutdownDualsense(controller_, outReportSize_);
579
break;
580
}
581
CloseHandle(controller_);
582
controller_ = nullptr;
583
}
584
}
585
586
struct SwitchProInputReport {
587
u8 reportId;
588
u8 padding;
589
u8 battery;
590
u8 buttons[3];
591
u8 lStick[3]; // 2 12-bit values.
592
u8 rStick[3]; // 2 12-bit values.
593
// Next up is gyro and all sorts of stuff we don't care about right now.
594
};
595
596
static void DecodeSwitchProStick(const u8 *stickData, s8 *outX, s8 *outY) {
597
int x = ((stickData[1] & 0xF) << 8) | (stickData[0]);
598
int y = (stickData[1] >> 4) | (stickData[2] << 4);
599
600
// For some reason the values are not really centered. Let's approximate.
601
// We probably should add some low level calibration?
602
x = (x - 2048) / 12;
603
y = (y - 1950) / 12;
604
605
*outX = (s8)clamp_value(x, -128, 127);
606
*outY = (s8)clamp_value(y, -128, 127);
607
// INFO_LOG(Log::sceCtrl, "Switch Pro input: x=%d, y=%d, cx=%d, cy=%d", x, y, *outX, *outY);
608
}
609
610
bool ReadSwitchProInput(HANDLE handle, HIDControllerState *state) {
611
BYTE inputReport[SwitchPro_INPUT_REPORT_LEN]{}; // 64-byte input report for Switch Pro
612
DWORD bytesRead = 0;
613
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
614
u32 error = GetLastError();
615
return false;
616
}
617
618
if (inputReport[0] != 0x30) {
619
// Not a Switch Pro controller input report.
620
return false;
621
}
622
623
SwitchProInputReport report{};
624
memcpy(&report, inputReport, sizeof(report));
625
626
u32 buttons = 0;
627
memcpy(&state->buttons, &report.buttons[0], 3);
628
// INFO_LOG(Log::sceCtrl, "Switch Pro input: buttons=%08x", state->buttons);
629
630
DecodeSwitchProStick(report.lStick, &state->stickAxes[PS_STICK_LX], &state->stickAxes[PS_STICK_LY]);
631
DecodeSwitchProStick(report.rStick, &state->stickAxes[PS_STICK_RX], &state->stickAxes[PS_STICK_RY]);
632
return true;
633
}
634
635
void HidInputDevice::ReleaseAllKeys(const ButtonInputMapping *buttonMappings, int count) {
636
for (int i = 0; i < count; i++) {
637
const auto &mapping = buttonMappings[i];
638
KeyInput key;
639
key.deviceId = DEVICE_ID_XINPUT_0 + pad_;
640
key.flags = KEY_UP;
641
key.keyCode = mapping.keyCode;
642
NativeKey(key);
643
}
644
645
static const InputAxis allAxes[6] = {
646
JOYSTICK_AXIS_X,
647
JOYSTICK_AXIS_Y,
648
JOYSTICK_AXIS_Z,
649
JOYSTICK_AXIS_RX,
650
JOYSTICK_AXIS_LTRIGGER,
651
JOYSTICK_AXIS_RTRIGGER,
652
};
653
654
for (const auto axisId : allAxes) {
655
AxisInput axis;
656
axis.deviceId = DEVICE_ID_XINPUT_0 + pad_;
657
axis.axisId = axisId;
658
axis.value = 0;
659
NativeAxis(&axis, 1);
660
}
661
}
662
663
InputDeviceID HidInputDevice::DeviceID(int pad) {
664
return DEVICE_ID_PAD_0 + pad;
665
}
666
667
int HidInputDevice::UpdateState() {
668
if (!controller_) {
669
// Poll for controllers from time to time.
670
if (pollCount_ == 0) {
671
pollCount_ = POLL_FREQ;
672
HANDLE newController = OpenFirstHIDController(&subType_, &reportSize_, &outReportSize_);
673
if (newController) {
674
controller_ = newController;
675
}
676
} else {
677
pollCount_--;
678
}
679
}
680
681
if (controller_) {
682
HIDControllerState state{};
683
bool result = false;
684
const ButtonInputMapping *buttonMappings = g_psInputMappings;
685
u32 buttonMappingsSize = sizeof(g_psInputMappings) / sizeof(ButtonInputMapping);
686
if (subType_ == HIDControllerType::DS4) {
687
result = ReadDualShockInput(controller_, &state);
688
} else if (subType_ == HIDControllerType::DS5) {
689
result = ReadDualSenseInput(controller_, &state);
690
} else if (subType_ == HIDControllerType::SwitchPro) {
691
result = ReadSwitchProInput(controller_, &state);
692
buttonMappings = g_switchProInputMappings;
693
buttonMappingsSize = sizeof(g_switchProInputMappings) / sizeof(ButtonInputMapping);
694
}
695
696
if (result) {
697
const InputDeviceID deviceID = DeviceID(pad_);
698
// Process the input and generate input events.
699
const u32 downMask = state.buttons & (~prevState_.buttons);
700
const u32 upMask = (~state.buttons) & prevState_.buttons;
701
702
for (u32 i = 0; i < buttonMappingsSize; i++) {
703
const ButtonInputMapping &mapping = buttonMappings[i];
704
if (downMask & mapping.button) {
705
KeyInput key;
706
key.deviceId = deviceID;
707
key.flags = KEY_DOWN;
708
key.keyCode = mapping.keyCode;
709
NativeKey(key);
710
}
711
if (upMask & mapping.button) {
712
KeyInput key;
713
key.deviceId = deviceID;
714
key.flags = KEY_UP;
715
key.keyCode = mapping.keyCode;
716
NativeKey(key);
717
}
718
}
719
720
for (const auto &mapping : g_psStickMappings) {
721
if (state.stickAxes[mapping.stickAxis] != prevState_.stickAxes[mapping.stickAxis]) {
722
AxisInput axis;
723
axis.deviceId = deviceID;
724
axis.axisId = mapping.inputAxis;
725
axis.value = (float)state.stickAxes[mapping.stickAxis] * (1.0f / 128.0f);
726
NativeAxis(&axis, 1);
727
}
728
}
729
730
for (const auto &mapping : g_psTriggerMappings) {
731
if (state.triggerAxes[mapping.triggerAxis] != prevState_.triggerAxes[mapping.triggerAxis]) {
732
AxisInput axis;
733
axis.deviceId = deviceID;
734
axis.axisId = mapping.inputAxis;
735
axis.value = (float)state.triggerAxes[mapping.triggerAxis] * (1.0f / 255.0f);
736
NativeAxis(&axis, 1);
737
}
738
}
739
740
prevState_ = state;
741
return UPDATESTATE_NO_SLEEP; // The ReadFile sleeps for us, effectively.
742
} else {
743
// might have been disconnected. retry later.
744
ReleaseAllKeys(buttonMappings, buttonMappingsSize);
745
CloseHandle(controller_);
746
controller_ = NULL;
747
pollCount_ = POLL_FREQ;
748
}
749
}
750
return 0;
751
}
752
753