mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
Split the Hid code up in a few files to make it more practical to work on
This commit is contained in:
+8
-2
@@ -2775,8 +2775,14 @@ set(WindowsFiles
|
||||
Windows/CaptureDevice.h
|
||||
Windows/DinputDevice.cpp
|
||||
Windows/DinputDevice.h
|
||||
Windows/HidInputDevice.h
|
||||
Windows/HidInputDevice.cpp
|
||||
Windows/Hid/HidInputDevice.h
|
||||
Windows/Hid/HidInputDevice.cpp
|
||||
Windows/Hid/DualSense.h
|
||||
Windows/Hid/DualSense.cpp
|
||||
Windows/Hid/DualShock.h
|
||||
Windows/Hid/DualShock.cpp
|
||||
Windows/Hid/SwitchPro.h
|
||||
Windows/Hid/SwitchPro.cpp
|
||||
Windows/EmuThread.cpp
|
||||
Windows/EmuThread.h
|
||||
Windows/GPU/D3D11Context.cpp
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "Common/System/NativeApp.h"
|
||||
#include "Core/KeyMap.h"
|
||||
#include "Windows/DinputDevice.h"
|
||||
#include "Windows/HidInputDevice.h"
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
|
||||
#pragma comment(lib,"dinput8.lib")
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// Some info from
|
||||
// https://controllers.fandom.com/wiki/Sony_DualSense
|
||||
|
||||
#include "Windows/Hid/DualShock.h"
|
||||
#include "Windows/Hid/DualSense.h"
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
#include "Windows/Hid/HidCommon.h"
|
||||
|
||||
struct DualSenseOutputReport {
|
||||
u8 reportId;
|
||||
u8 flags1;
|
||||
u8 flags2;
|
||||
u8 rumbleRight;
|
||||
u8 rumbleLeft;
|
||||
u8 pad[2];
|
||||
u8 muteLED;
|
||||
u8 micMute; // 10
|
||||
u8 other[32];
|
||||
u8 enableBrightness;
|
||||
u8 fade;
|
||||
u8 brightness;
|
||||
u8 playerLights;
|
||||
u8 lightbarRed;
|
||||
u8 lightbarGreen;
|
||||
u8 lightbarBlue;
|
||||
};
|
||||
static_assert(sizeof(DualSenseOutputReport) == 48);
|
||||
|
||||
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs#L905
|
||||
|
||||
// Sends initialization packet to DualSense
|
||||
bool InitializeDualSense(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualSenseOutputReport)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DualSenseOutputReport report{};
|
||||
report.reportId = 2;
|
||||
report.flags1 = 0x0C;
|
||||
report.flags2 = 0x15;
|
||||
report.muteLED = 1;
|
||||
report.playerLights = 1;
|
||||
report.enableBrightness = 1;
|
||||
report.brightness = 0; // 0 = high, 1 = medium, 2 = low
|
||||
report.lightbarRed = LED_R;
|
||||
report.lightbarGreen = LED_G;
|
||||
report.lightbarBlue = LED_B;
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
bool ShutdownDualsense(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualSenseOutputReport)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DualSenseOutputReport report{};
|
||||
report.reportId = 2;
|
||||
report.flags1 = 0x0C;
|
||||
report.flags2 = 0x15;
|
||||
report.muteLED = 1;
|
||||
report.playerLights = 0;
|
||||
report.enableBrightness = 1;
|
||||
report.brightness = 2; // 0 = high, 1 = medium, 2 = low
|
||||
report.lightbarRed = 0;
|
||||
report.lightbarGreen = 0;
|
||||
report.lightbarBlue = 0;
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
// So strange that this is different!
|
||||
struct DualSenseInputReport {
|
||||
u8 firstByte; // must be 1
|
||||
|
||||
u8 lx;
|
||||
u8 ly;
|
||||
u8 rx;
|
||||
u8 ry;
|
||||
|
||||
u8 l2_analog;
|
||||
u8 r2_analog;
|
||||
|
||||
u8 frameCounter; // 7
|
||||
|
||||
u8 buttons[3]; // 8-10
|
||||
u8 pad[5]; // 11,12,13,14,15
|
||||
|
||||
s16 gyroscope[3];
|
||||
s16 accelerometer[3];
|
||||
};
|
||||
|
||||
bool ReadDualSenseInput(HANDLE handle, HIDControllerState *state) {
|
||||
BYTE inputReport[64]{}; // 64-byte input report for DS4
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
|
||||
const u32 error = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
DualSenseInputReport report{};
|
||||
static_assert(sizeof(report) < sizeof(inputReport));
|
||||
if (bytesRead < 14) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, check the first byte to figure out what we're dealing with here.
|
||||
if (inputReport[0] != 1) {
|
||||
// Wrong data
|
||||
return false;
|
||||
}
|
||||
// const bool isBluetooth = (reportId == 0x11 || reportId == 0x31);
|
||||
|
||||
memcpy(&report, inputReport, sizeof(report));
|
||||
|
||||
// Center the sticks.
|
||||
state->stickAxes[HID_STICK_LX] = report.lx - 128;
|
||||
state->stickAxes[HID_STICK_LY] = report.ly - 128;
|
||||
state->stickAxes[HID_STICK_RX] = report.rx - 128;
|
||||
state->stickAxes[HID_STICK_RY] = report.ry - 128;
|
||||
|
||||
// Copy over the triggers.
|
||||
state->triggerAxes[HID_TRIGGER_L2] = report.l2_analog;
|
||||
state->triggerAxes[HID_TRIGGER_R2] = report.r2_analog;
|
||||
|
||||
const float accelScale = (1.0f / 8192.0f) * 9.81f;
|
||||
// We need to remap the axes a bit.
|
||||
state->accValid = true;
|
||||
state->accelerometer[0] = -report.accelerometer[2] * accelScale;
|
||||
state->accelerometer[1] = -report.accelerometer[0] * accelScale;
|
||||
state->accelerometer[2] = report.accelerometer[1] * accelScale;
|
||||
|
||||
u32 buttons{};
|
||||
report.buttons[2] &= 3; // Remove noise
|
||||
memcpy(&buttons, &report.buttons[0], 3);
|
||||
|
||||
// Clear out and re-fill the DPAD, it works differently somehow
|
||||
buttons &= ~0xF;
|
||||
buttons |= DecodePSHatSwitch(report.buttons[0] & 0xF);
|
||||
|
||||
state->buttons = buttons;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
|
||||
bool InitializeDualSense(HANDLE handle, int outReportSize);
|
||||
bool ShutdownDualsense(HANDLE handle, int outReportSize);
|
||||
bool ReadDualSenseInput(HANDLE handle, HIDControllerState *state);
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "Windows/Hid/DualShock.h"
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
#include "Windows/Hid/HidCommon.h"
|
||||
|
||||
static const ButtonInputMapping g_psInputMappings[] = {
|
||||
{PS_DPAD_UP, NKCODE_DPAD_UP},
|
||||
{PS_DPAD_DOWN, NKCODE_DPAD_DOWN},
|
||||
{PS_DPAD_LEFT, NKCODE_DPAD_LEFT},
|
||||
{PS_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
|
||||
{PS_BTN_SQUARE, NKCODE_BUTTON_4},
|
||||
{PS_BTN_TRIANGLE, NKCODE_BUTTON_3},
|
||||
{PS_BTN_CIRCLE, NKCODE_BUTTON_1},
|
||||
{PS_BTN_CROSS, NKCODE_BUTTON_2},
|
||||
{PS_BTN_PS_BUTTON, NKCODE_HOME},
|
||||
{PS_BTN_SHARE, NKCODE_BUTTON_9},
|
||||
{PS_BTN_OPTIONS, NKCODE_BUTTON_10},
|
||||
{PS_BTN_L1, NKCODE_BUTTON_7},
|
||||
{PS_BTN_R1, NKCODE_BUTTON_8},
|
||||
// {PS_BTN_L2, NKCODE_BUTTON_L2}, // These are done by the analog triggers.
|
||||
// {PS_BTN_R2, NKCODE_BUTTON_R2},
|
||||
{PS_BTN_L3, NKCODE_BUTTON_THUMBL},
|
||||
{PS_BTN_R3, NKCODE_BUTTON_THUMBR},
|
||||
};
|
||||
|
||||
void GetPSInputMappings(const ButtonInputMapping **mappings, size_t *size) {
|
||||
*mappings = g_psInputMappings;
|
||||
*size = ARRAY_SIZE(g_psInputMappings);
|
||||
}
|
||||
|
||||
enum class DS4FeatureBits : u8 {
|
||||
VOL_L = 0x10,
|
||||
VOL_R = 0x20,
|
||||
MIC_VOL = 0x40,
|
||||
SPEAKER_VOL = 0x80,
|
||||
RUMBLE = 0x1,
|
||||
LIGHTBAR = 0x2,
|
||||
FLASH = 0x4,
|
||||
};
|
||||
ENUM_CLASS_BITOPS(DS4FeatureBits);
|
||||
|
||||
struct DualshockOutputReport {
|
||||
u8 reportID;
|
||||
u8 featureBits;
|
||||
u8 two;
|
||||
u8 pad;
|
||||
u8 rumbleRight;
|
||||
u8 rumbleLeft;
|
||||
u8 ledR;
|
||||
u8 ledG;
|
||||
u8 ledB;
|
||||
u8 padding[23];
|
||||
};
|
||||
static_assert(sizeof(DualshockOutputReport) == 32);
|
||||
|
||||
bool InitializeDualShock(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualshockOutputReport)) {
|
||||
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
DualshockOutputReport report{};
|
||||
report.reportID = 0x05; // Report ID (DS4 output)
|
||||
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
|
||||
report.two = 2;
|
||||
|
||||
// Rumble
|
||||
report.rumbleRight = 0x00; // Right (weak)
|
||||
report.rumbleLeft = 0x00; // Left (strong)
|
||||
|
||||
// Lightbar (RGB)
|
||||
report.ledR = LED_R;
|
||||
report.ledG = LED_G;
|
||||
report.ledB = LED_B;
|
||||
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
bool ShutdownDualShock(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualshockOutputReport)) {
|
||||
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
DualshockOutputReport report{};
|
||||
report.reportID = 0x05; // Report ID (DS4 output)
|
||||
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
|
||||
report.two = 2;
|
||||
|
||||
// Rumble
|
||||
report.rumbleRight = 0x00; // Right (weak)
|
||||
report.rumbleLeft = 0x00; // Left (strong)
|
||||
|
||||
// Lightbar (RGB)
|
||||
report.ledR = 0;
|
||||
report.ledG = 0;
|
||||
report.ledB = 0;
|
||||
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
struct DualShockInputReport {
|
||||
u8 lx;
|
||||
u8 ly;
|
||||
u8 rx;
|
||||
u8 ry;
|
||||
u8 buttons[3]; // note, starts at 5 so not aligned
|
||||
u8 l2_analog;
|
||||
u8 r2_analog;
|
||||
u8 pad[2];
|
||||
u8 battery;
|
||||
// Then there's motion and all kinds of stuff.
|
||||
};
|
||||
|
||||
bool ReadDualShockInput(HANDLE handle, HIDControllerState *state) {
|
||||
BYTE inputReport[64]{}; // 64-byte input report for DS4
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
|
||||
u32 error = GetLastError();
|
||||
return false;
|
||||
}
|
||||
DualShockInputReport report{};
|
||||
static_assert(sizeof(report) < sizeof(inputReport));
|
||||
if (bytesRead < 14) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, check the first byte to figure out what we're dealing with here.
|
||||
int offset = 1;
|
||||
int reportId;
|
||||
if (inputReport[0] == 0xA1) {
|
||||
// 2-byte bluetooth frame
|
||||
offset = 2;
|
||||
reportId = inputReport[1];
|
||||
} else {
|
||||
offset = 1;
|
||||
reportId = inputReport[0];
|
||||
}
|
||||
// const bool isBluetooth = (reportId == 0x11 || reportId == 0x31);
|
||||
|
||||
memcpy(&report, inputReport + offset, sizeof(report));
|
||||
|
||||
// Center the sticks.
|
||||
state->stickAxes[HID_STICK_LX] = report.lx - 128;
|
||||
state->stickAxes[HID_STICK_LY] = report.ly - 128;
|
||||
state->stickAxes[HID_STICK_RX] = report.rx - 128;
|
||||
state->stickAxes[HID_STICK_RY] = report.ry - 128;
|
||||
|
||||
// Copy over the triggers.
|
||||
state->triggerAxes[HID_TRIGGER_L2] = report.l2_analog;
|
||||
state->triggerAxes[HID_TRIGGER_R2] = report.r2_analog;
|
||||
|
||||
state->accValid = false;
|
||||
|
||||
u32 buttons{};
|
||||
int frameCounter = report.buttons[2] >> 2;
|
||||
report.buttons[2] &= 3;
|
||||
memcpy(&buttons, &report.buttons[0], 3);
|
||||
|
||||
// Clear out and re-fill the DPAD, it works differently somehow
|
||||
buttons &= ~0xF;
|
||||
buttons |= DecodePSHatSwitch(report.buttons[0] & 0xF);
|
||||
|
||||
state->buttons = buttons;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
|
||||
enum PSDPadButton : u32 {
|
||||
PS_DPAD_UP = 1, // These dpad ones are not real, we convert from hat switch format.
|
||||
PS_DPAD_DOWN = 2,
|
||||
PS_DPAD_LEFT = 4,
|
||||
PS_DPAD_RIGHT = 8,
|
||||
};
|
||||
|
||||
enum PSHIDButton : u32 {
|
||||
PS_BTN_SQUARE = 16,
|
||||
PS_BTN_CROSS = 32,
|
||||
PS_BTN_TRIANGLE = 64,
|
||||
PS_BTN_CIRCLE = 128,
|
||||
|
||||
PS_BTN_L1 = (1 << 8),
|
||||
PS_BTN_R1 = (1 << 9),
|
||||
PS_BTN_L2 = (1 << 10),
|
||||
PS_BTN_R2 = (1 << 11),
|
||||
PS_BTN_SHARE = (1 << 12),
|
||||
PS_BTN_OPTIONS = (1 << 13),
|
||||
PS_BTN_L3 = (1 << 14),
|
||||
PS_BTN_R3 = (1 << 15),
|
||||
PS_BTN_PS_BUTTON = (1 << 16),
|
||||
PS_BTN_TOUCHPAD = (1 << 17),
|
||||
};
|
||||
|
||||
inline u32 DecodePSHatSwitch(u8 dpad) {
|
||||
u32 buttons = 0;
|
||||
if (dpad == 0 || dpad == 1 || dpad == 7) {
|
||||
buttons |= PS_DPAD_UP;
|
||||
}
|
||||
if (dpad == 1 || dpad == 2 || dpad == 3) {
|
||||
buttons |= PS_DPAD_RIGHT;
|
||||
}
|
||||
if (dpad == 3 || dpad == 4 || dpad == 5) {
|
||||
buttons |= PS_DPAD_DOWN;
|
||||
}
|
||||
if (dpad == 5 || dpad == 6 || dpad == 7) {
|
||||
buttons |= PS_DPAD_LEFT;
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
bool InitializeDualShock(HANDLE handle, int outReportSize);
|
||||
bool ShutdownDualShock(HANDLE handle, int outReportSize);
|
||||
bool ReadDualShockInput(HANDLE handle, HIDControllerState *state);
|
||||
void GetPSInputMappings(const ButtonInputMapping **mappings, size_t *size);
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <hidsdi.h>
|
||||
#include "Common/CommonWindows.h"
|
||||
|
||||
bool WriteReport(HANDLE handle, const u8 *data, size_t size) {
|
||||
DWORD written;
|
||||
bool result = WriteFile(handle, data, size, &written, NULL);
|
||||
if (!result) {
|
||||
u32 errorCode = GetLastError();
|
||||
if (errorCode == ERROR_INVALID_PARAMETER) {
|
||||
if (!HidD_SetOutputReport(handle, (PVOID)data, size)) {
|
||||
errorCode = GetLastError();
|
||||
}
|
||||
}
|
||||
WARN_LOG(Log::UI, "WriteReport: Failed initializing: %08x", errorCode);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
|
||||
constexpr u8 LED_R = 0x05;
|
||||
constexpr u8 LED_G = 0x10;
|
||||
constexpr u8 LED_B = 0x40;
|
||||
|
||||
enum HidStickAxis : u32 {
|
||||
HID_STICK_LX = 0,
|
||||
HID_STICK_LY = 1,
|
||||
HID_STICK_RX = 2,
|
||||
HID_STICK_RY = 3,
|
||||
};
|
||||
|
||||
enum HidTriggerAxis : u32 {
|
||||
HID_TRIGGER_L2 = 0,
|
||||
HID_TRIGGER_R2 = 1,
|
||||
};
|
||||
|
||||
bool WriteReport(HANDLE handle, const u8 *data, size_t size);
|
||||
|
||||
template<class T>
|
||||
inline bool WriteReport(HANDLE handle, const T &report) {
|
||||
return WriteReport(handle, (const u8 *)&report, sizeof(T));
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// This file in particular along with its header is public domain, use it for whatever you want.
|
||||
|
||||
#include <windows.h>
|
||||
#include <hidsdi.h>
|
||||
#include <setupapi.h>
|
||||
#include <initguid.h>
|
||||
#include <vector>
|
||||
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
#include "Windows/Hid/SwitchPro.h"
|
||||
#include "Windows/Hid/DualSense.h"
|
||||
#include "Windows/Hid/DualShock.h"
|
||||
#include "Windows/Hid/HidCommon.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/Math/math_util.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/Input/InputState.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/System/NativeApp.h"
|
||||
#include "Common/System/OSD.h"
|
||||
#include "Core/KeyMap.h"
|
||||
|
||||
struct HidStickMapping {
|
||||
HidStickAxis stickAxis;
|
||||
InputAxis inputAxis;
|
||||
};
|
||||
|
||||
// This is the same mapping as DInput etc.
|
||||
static const HidStickMapping g_psStickMappings[] = {
|
||||
{HID_STICK_LX, JOYSTICK_AXIS_X},
|
||||
{HID_STICK_LY, JOYSTICK_AXIS_Y},
|
||||
{HID_STICK_RX, JOYSTICK_AXIS_Z},
|
||||
{HID_STICK_RY, JOYSTICK_AXIS_RX},
|
||||
};
|
||||
|
||||
struct HidTriggerMapping {
|
||||
HidTriggerAxis triggerAxis;
|
||||
InputAxis inputAxis;
|
||||
};
|
||||
|
||||
static const HidTriggerMapping g_psTriggerMappings[] = {
|
||||
{HID_TRIGGER_L2, JOYSTICK_AXIS_LTRIGGER},
|
||||
{HID_TRIGGER_R2, JOYSTICK_AXIS_RTRIGGER},
|
||||
};
|
||||
|
||||
struct HIDControllerInfo {
|
||||
u16 vendorId;
|
||||
u16 productId;
|
||||
HIDControllerType type;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
constexpr u16 SONY_VID = 0x054C;
|
||||
constexpr u16 NINTENDO_VID = 0x57e;
|
||||
constexpr u16 SWITCH_PRO_PID = 0x2009;
|
||||
constexpr u16 DS4_WIRELESS = 0x0BA0;
|
||||
constexpr u16 PS_CLASSIC = 0x0CDA;
|
||||
|
||||
// We pick a few ones from here to support, let's add more later.
|
||||
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/DS4Devices.cs#L126
|
||||
static const HIDControllerInfo g_psInfos[] = {
|
||||
{SONY_VID, 0x05C4, HIDControllerType::DS4, "DS4 v.1"},
|
||||
{SONY_VID, 0x09CC, HIDControllerType::DS4, "DS4 v.2"},
|
||||
{SONY_VID, 0x0CE6, HIDControllerType::DS5, "DualSense"},
|
||||
{SONY_VID, PS_CLASSIC, HIDControllerType::DS4, "PS Classic"},
|
||||
{NINTENDO_VID, SWITCH_PRO_PID, HIDControllerType::SwitchPro, "Switch Pro"},
|
||||
// {PSSubType::DS4, DS4_WIRELESS},
|
||||
// {PSSubType::DS5, DUALSENSE_WIRELESS},
|
||||
// {PSSubType::DS5, DUALSENSE_EDGE_WIRELESS},
|
||||
};
|
||||
|
||||
static const HIDControllerInfo *GetGamepadInfo(HANDLE handle) {
|
||||
HIDD_ATTRIBUTES attr{sizeof(HIDD_ATTRIBUTES)};
|
||||
if (!HidD_GetAttributes(handle, &attr)) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const auto &info : g_psInfos) {
|
||||
if (attr.VendorID == info.vendorId && attr.ProductID == info.productId) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static HANDLE OpenFirstHIDController(HIDControllerType *subType, int *reportSize, int *outReportSize, const HIDControllerInfo **outInfo) {
|
||||
GUID hidGuid;
|
||||
HidD_GetHidGuid(&hidGuid);
|
||||
|
||||
HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&hidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (deviceInfoSet == INVALID_HANDLE_VALUE)
|
||||
return nullptr;
|
||||
|
||||
SP_DEVICE_INTERFACE_DATA interfaceData;
|
||||
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||||
|
||||
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, nullptr, &hidGuid, i, &interfaceData); ++i) {
|
||||
DWORD requiredSize = 0;
|
||||
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &interfaceData, nullptr, 0, &requiredSize, nullptr);
|
||||
|
||||
std::vector<BYTE> buffer(requiredSize);
|
||||
auto* detailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buffer.data());
|
||||
detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
|
||||
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &interfaceData, detailData, requiredSize, nullptr, nullptr)) {
|
||||
HANDLE handle = CreateFile(detailData->DevicePath, GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
const HIDControllerInfo *info = GetGamepadInfo(handle);
|
||||
*outInfo = info;
|
||||
if (info) {
|
||||
*subType = info->type;
|
||||
INFO_LOG(Log::UI, "Found supported gamepad. PID: %04x", info->productId);
|
||||
HIDP_CAPS caps;
|
||||
PHIDP_PREPARSED_DATA preparsedData;
|
||||
|
||||
HidD_GetPreparsedData(handle, &preparsedData);
|
||||
HidP_GetCaps(preparsedData, &caps);
|
||||
HidD_FreePreparsedData(preparsedData);
|
||||
|
||||
*reportSize = caps.InputReportByteLength;
|
||||
*outReportSize = caps.OutputReportByteLength;
|
||||
|
||||
INFO_LOG(Log::UI, "Initializing gamepad. out report size=%d", *outReportSize);
|
||||
bool result;
|
||||
switch (*subType) {
|
||||
case HIDControllerType::DS5:
|
||||
result = InitializeDualSense(handle, *outReportSize);
|
||||
break;
|
||||
case HIDControllerType::DS4:
|
||||
result = InitializeDualShock(handle, *outReportSize);
|
||||
break;
|
||||
case HIDControllerType::SwitchPro:
|
||||
result = InitializeSwitchPro(handle);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
ERROR_LOG(Log::UI, "Controller initialization failed");
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
|
||||
return handle;
|
||||
}
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HidInputDevice::AddSupportedDevices(std::set<u32> *deviceVIDPIDs) {
|
||||
for (const auto &info : g_psInfos) {
|
||||
const u32 vidpid = MAKELONG(info.vendorId, info.productId);
|
||||
deviceVIDPIDs->insert(vidpid);
|
||||
}
|
||||
}
|
||||
|
||||
void HidInputDevice::Init() {}
|
||||
void HidInputDevice::Shutdown() {
|
||||
if (controller_) {
|
||||
switch (subType_) {
|
||||
case HIDControllerType::DS4:
|
||||
ShutdownDualShock(controller_, outReportSize_);
|
||||
break;
|
||||
case HIDControllerType::DS5:
|
||||
ShutdownDualsense(controller_, outReportSize_);
|
||||
break;
|
||||
}
|
||||
CloseHandle(controller_);
|
||||
controller_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HidInputDevice::ReleaseAllKeys(const ButtonInputMapping *buttonMappings, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
const auto &mapping = buttonMappings[i];
|
||||
KeyInput key;
|
||||
key.deviceId = DEVICE_ID_XINPUT_0 + pad_;
|
||||
key.flags = KeyInputFlags::UP;
|
||||
key.keyCode = mapping.keyCode;
|
||||
NativeKey(key);
|
||||
}
|
||||
|
||||
static const InputAxis allAxes[6] = {
|
||||
JOYSTICK_AXIS_X,
|
||||
JOYSTICK_AXIS_Y,
|
||||
JOYSTICK_AXIS_Z,
|
||||
JOYSTICK_AXIS_RX,
|
||||
JOYSTICK_AXIS_LTRIGGER,
|
||||
JOYSTICK_AXIS_RTRIGGER,
|
||||
};
|
||||
|
||||
for (const auto axisId : allAxes) {
|
||||
AxisInput axis;
|
||||
axis.deviceId = DEVICE_ID_XINPUT_0 + pad_;
|
||||
axis.axisId = axisId;
|
||||
axis.value = 0;
|
||||
NativeAxis(&axis, 1);
|
||||
}
|
||||
}
|
||||
|
||||
InputDeviceID HidInputDevice::DeviceID(int pad) {
|
||||
return DEVICE_ID_PAD_0 + pad;
|
||||
}
|
||||
|
||||
int HidInputDevice::UpdateState() {
|
||||
const InputDeviceID deviceID = DeviceID(pad_);
|
||||
|
||||
if (!controller_) {
|
||||
// Poll for controllers from time to time.
|
||||
if (pollCount_ == 0) {
|
||||
pollCount_ = POLL_FREQ;
|
||||
const HIDControllerInfo *info{};
|
||||
HANDLE newController = OpenFirstHIDController(&subType_, &reportSize_, &outReportSize_, &info);
|
||||
if (newController) {
|
||||
controller_ = newController;
|
||||
if (info) {
|
||||
name_ = info->name;
|
||||
}
|
||||
KeyMap::NotifyPadConnected(deviceID, name_);
|
||||
}
|
||||
} else {
|
||||
pollCount_--;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller_) {
|
||||
HIDControllerState state{};
|
||||
bool result = false;
|
||||
const ButtonInputMapping *buttonMappings = nullptr;
|
||||
size_t buttonMappingsSize = 0;
|
||||
if (subType_ == HIDControllerType::DS4) {
|
||||
result = ReadDualShockInput(controller_, &state);
|
||||
GetPSInputMappings(&buttonMappings, &buttonMappingsSize);
|
||||
} else if (subType_ == HIDControllerType::DS5) {
|
||||
result = ReadDualSenseInput(controller_, &state);
|
||||
GetPSInputMappings(&buttonMappings, &buttonMappingsSize);
|
||||
} else if (subType_ == HIDControllerType::SwitchPro) {
|
||||
result = ReadSwitchProInput(controller_, &state);
|
||||
GetSwitchInputMappings(&buttonMappings, &buttonMappingsSize);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
// Process the input and generate input events.
|
||||
const u32 downMask = state.buttons & (~prevState_.buttons);
|
||||
const u32 upMask = (~state.buttons) & prevState_.buttons;
|
||||
|
||||
for (u32 i = 0; i < buttonMappingsSize; i++) {
|
||||
const ButtonInputMapping &mapping = buttonMappings[i];
|
||||
if (downMask & mapping.button) {
|
||||
KeyInput key;
|
||||
key.deviceId = deviceID;
|
||||
key.flags = KeyInputFlags::DOWN;
|
||||
key.keyCode = mapping.keyCode;
|
||||
NativeKey(key);
|
||||
}
|
||||
if (upMask & mapping.button) {
|
||||
KeyInput key;
|
||||
key.deviceId = deviceID;
|
||||
key.flags = KeyInputFlags::UP;
|
||||
key.keyCode = mapping.keyCode;
|
||||
NativeKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &mapping : g_psStickMappings) {
|
||||
if (state.stickAxes[mapping.stickAxis] != prevState_.stickAxes[mapping.stickAxis]) {
|
||||
AxisInput axis;
|
||||
axis.deviceId = deviceID;
|
||||
axis.axisId = mapping.inputAxis;
|
||||
axis.value = (float)state.stickAxes[mapping.stickAxis] * (1.0f / 128.0f);
|
||||
NativeAxis(&axis, 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &mapping : g_psTriggerMappings) {
|
||||
if (state.triggerAxes[mapping.triggerAxis] != prevState_.triggerAxes[mapping.triggerAxis]) {
|
||||
AxisInput axis;
|
||||
axis.deviceId = deviceID;
|
||||
axis.axisId = mapping.inputAxis;
|
||||
axis.value = (float)state.triggerAxes[mapping.triggerAxis] * (1.0f / 255.0f);
|
||||
NativeAxis(&axis, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.accValid) {
|
||||
NativeAccelerometer(state.accelerometer[0], state.accelerometer[1], state.accelerometer[2]);
|
||||
}
|
||||
|
||||
prevState_ = state;
|
||||
return UPDATESTATE_NO_SLEEP; // The ReadFile sleeps for us, effectively.
|
||||
} else {
|
||||
// might have been disconnected. retry later.
|
||||
KeyMap::NotifyPadDisconnected(deviceID);
|
||||
ReleaseAllKeys(buttonMappings, (int)buttonMappingsSize);
|
||||
CloseHandle(controller_);
|
||||
controller_ = NULL;
|
||||
pollCount_ = POLL_FREQ;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
// This file in particular along with its cpp file is public domain, use it for whatever you want.
|
||||
|
||||
// Internal common header for Hid input stuff.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Input/InputState.h"
|
||||
#include "Windows/InputDevice.h"
|
||||
#include <set>
|
||||
#include <windows.h>
|
||||
#include "Common/CommonWindows.h"
|
||||
|
||||
enum class HIDControllerType {
|
||||
DS4,
|
||||
@@ -26,7 +30,10 @@ struct HIDControllerState {
|
||||
float accelerometer[3]; // X, Y, Z
|
||||
};
|
||||
|
||||
struct ButtonInputMapping;
|
||||
struct ButtonInputMapping {
|
||||
u32 button;
|
||||
InputKeyCode keyCode;
|
||||
};
|
||||
|
||||
// Supports a few specific HID input devices, namely DualShock and DualSense.
|
||||
// More may be added later. Just picks the first one available, for now.
|
||||
@@ -44,7 +51,6 @@ private:
|
||||
void ReleaseAllKeys(const ButtonInputMapping *buttonMappings, int count);
|
||||
InputDeviceID DeviceID(int pad);
|
||||
HIDControllerState prevState_{};
|
||||
|
||||
HIDControllerType subType_{};
|
||||
HANDLE controller_;
|
||||
std::string name_;
|
||||
@@ -0,0 +1,120 @@
|
||||
#include "Common/Math/math_util.h"
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
#include "Windows/Hid/SwitchPro.h"
|
||||
#include "Windows/Hid/HidCommon.h"
|
||||
|
||||
enum HIDButton : u32 {
|
||||
SWITCH_PRO_BTN_Y = (1 << 0),
|
||||
SWITCH_PRO_BTN_X = (1 << 1),
|
||||
SWITCH_PRO_BTN_B = (1 << 2),
|
||||
SWITCH_PRO_BTN_A = (1 << 3),
|
||||
SWITCH_PRO_BTN_R1 = (1 << 6),
|
||||
SWITCH_PRO_BTN_R2 = (1 << 7),
|
||||
SWITCH_PRO_BTN_L3 = (1 << 11),
|
||||
SWITCH_PRO_BTN_R3 = (1 << 10),
|
||||
SWITCH_PRO_BTN_SHARE = (1 << 8),
|
||||
SWITCH_PRO_BTN_OPTIONS = (1 << 9),
|
||||
SWITCH_PRO_BTN_PS_BUTTON = (1 << 12),
|
||||
SWITCH_PRO_BTN_CAPTURE = (1 << 13),
|
||||
SWITCH_PRO_DPAD_DOWN = (1 << 16),
|
||||
SWITCH_PRO_DPAD_UP = (1 << 17),
|
||||
SWITCH_PRO_DPAD_RIGHT = (1 << 18),
|
||||
SWITCH_PRO_DPAD_LEFT = (1 << 19),
|
||||
SWITCH_PRO_BTN_L1 = (1 << 22),
|
||||
SWITCH_PRO_BTN_L2 = (1 << 23),
|
||||
};
|
||||
|
||||
static const ButtonInputMapping g_switchProInputMappings[] = {
|
||||
{SWITCH_PRO_DPAD_UP, NKCODE_DPAD_UP},
|
||||
{SWITCH_PRO_DPAD_DOWN, NKCODE_DPAD_DOWN},
|
||||
{SWITCH_PRO_DPAD_LEFT, NKCODE_DPAD_LEFT},
|
||||
{SWITCH_PRO_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
|
||||
{SWITCH_PRO_BTN_Y, NKCODE_BUTTON_4},
|
||||
{SWITCH_PRO_BTN_X, NKCODE_BUTTON_1},
|
||||
{SWITCH_PRO_BTN_B, NKCODE_BUTTON_2},
|
||||
{SWITCH_PRO_BTN_A, NKCODE_BUTTON_3},
|
||||
{SWITCH_PRO_BTN_PS_BUTTON, NKCODE_HOME},
|
||||
{SWITCH_PRO_BTN_SHARE, NKCODE_BUTTON_9},
|
||||
{SWITCH_PRO_BTN_OPTIONS, NKCODE_BUTTON_10},
|
||||
{SWITCH_PRO_BTN_L1, NKCODE_BUTTON_7},
|
||||
{SWITCH_PRO_BTN_R1, NKCODE_BUTTON_8},
|
||||
{SWITCH_PRO_BTN_L2, NKCODE_BUTTON_L2}, // No analog triggers.
|
||||
{SWITCH_PRO_BTN_R2, NKCODE_BUTTON_R2},
|
||||
{SWITCH_PRO_BTN_L3, NKCODE_BUTTON_THUMBL},
|
||||
{SWITCH_PRO_BTN_R3, NKCODE_BUTTON_THUMBR},
|
||||
};
|
||||
|
||||
void GetSwitchInputMappings(const ButtonInputMapping **mappings, size_t *size) {
|
||||
*mappings = g_switchProInputMappings;
|
||||
*size = ARRAY_SIZE(g_switchProInputMappings);
|
||||
}
|
||||
|
||||
enum class SwitchProSubCmd {
|
||||
SET_INPUT_MODE = 0x03,
|
||||
SET_LOW_POWER_STATE = 0x08,
|
||||
SPI_FLASH_READ = 0x10,
|
||||
SET_LIGHTS = 0x30, // LEDs on controller
|
||||
SET_HOME_LIGHT = 0x38,
|
||||
ENABLE_IMU = 0x40,
|
||||
SET_IMU_SENS = 0x41,
|
||||
ENABLE_VIBRATION = 0x48,
|
||||
};
|
||||
|
||||
constexpr int SwitchPro_INPUT_REPORT_LEN = 362;
|
||||
constexpr int SwitchPro_OUTPUT_REPORT_LEN = 49;
|
||||
constexpr int SwitchPro_RUMBLE_REPORT_LEN = 64;
|
||||
|
||||
static const u8 g_switchProCmdBufHeader[] = {0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
|
||||
|
||||
bool InitializeSwitchPro(HANDLE handle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct SwitchProInputReport {
|
||||
u8 reportId;
|
||||
u8 padding;
|
||||
u8 battery;
|
||||
u8 buttons[3];
|
||||
u8 lStick[3]; // 2 12-bit values.
|
||||
u8 rStick[3]; // 2 12-bit values.
|
||||
// Next up is gyro and all sorts of stuff we don't care about right now.
|
||||
};
|
||||
|
||||
static void DecodeSwitchProStick(const u8 *stickData, s8 *outX, s8 *outY) {
|
||||
int x = ((stickData[1] & 0xF) << 8) | (stickData[0]);
|
||||
int y = (stickData[1] >> 4) | (stickData[2] << 4);
|
||||
|
||||
// For some reason the values are not really centered. Let's approximate.
|
||||
// We probably should add some low level calibration?
|
||||
x = (x - 2048) / 12;
|
||||
y = (y - 1950) / 12;
|
||||
|
||||
*outX = (s8)clamp_value(x, -128, 127);
|
||||
*outY = (s8)clamp_value(y, -128, 127);
|
||||
// INFO_LOG(Log::sceCtrl, "Switch Pro input: x=%d, y=%d, cx=%d, cy=%d", x, y, *outX, *outY);
|
||||
}
|
||||
|
||||
bool ReadSwitchProInput(HANDLE handle, HIDControllerState *state) {
|
||||
BYTE inputReport[SwitchPro_INPUT_REPORT_LEN]{}; // 64-byte input report for Switch Pro
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
|
||||
u32 error = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputReport[0] != 0x30) {
|
||||
// Not a Switch Pro controller input report.
|
||||
return false;
|
||||
}
|
||||
|
||||
SwitchProInputReport report{};
|
||||
memcpy(&report, inputReport, sizeof(report));
|
||||
|
||||
u32 buttons = 0;
|
||||
memcpy(&state->buttons, &report.buttons[0], 3);
|
||||
// INFO_LOG(Log::sceCtrl, "Switch Pro input: buttons=%08x", state->buttons);
|
||||
|
||||
DecodeSwitchProStick(report.lStick, &state->stickAxes[HID_STICK_LX], &state->stickAxes[HID_STICK_LY]);
|
||||
DecodeSwitchProStick(report.rStick, &state->stickAxes[HID_STICK_RX], &state->stickAxes[HID_STICK_RY]);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
|
||||
#include "Windows/Hid/HidCommon.h"
|
||||
|
||||
bool InitializeSwitchPro(HANDLE handle);
|
||||
void GetSwitchInputMappings(const ButtonInputMapping **mappings, size_t *size);
|
||||
bool ReadSwitchProInput(HANDLE handle, HIDControllerState *state);
|
||||
@@ -1,779 +0,0 @@
|
||||
// This file in particular along with its header is public domain, use it for whatever you want.
|
||||
|
||||
#include <windows.h>
|
||||
#include <hidsdi.h>
|
||||
#include <setupapi.h>
|
||||
#include <initguid.h>
|
||||
#include <vector>
|
||||
|
||||
#include "Windows/HidInputDevice.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/Math/math_util.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/Input/InputState.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/System/NativeApp.h"
|
||||
#include "Common/System/OSD.h"
|
||||
#include "Core/KeyMap.h"
|
||||
|
||||
constexpr u8 LED_R = 0x05;
|
||||
constexpr u8 LED_G = 0x10;
|
||||
constexpr u8 LED_B = 0x40;
|
||||
|
||||
enum HIDButton : u32 {
|
||||
PS_DPAD_UP = 1, // These dpad ones are not real, we convert from hat switch format.
|
||||
PS_DPAD_DOWN = 2,
|
||||
PS_DPAD_LEFT = 4,
|
||||
PS_DPAD_RIGHT = 8,
|
||||
PS_BTN_SQUARE = 16,
|
||||
PS_BTN_CROSS = 32,
|
||||
PS_BTN_TRIANGLE = 64,
|
||||
PS_BTN_CIRCLE = 128,
|
||||
|
||||
PS_BTN_L1 = (1 << 8),
|
||||
PS_BTN_R1 = (1 << 9),
|
||||
PS_BTN_L2 = (1 << 10),
|
||||
PS_BTN_R2 = (1 << 11),
|
||||
PS_BTN_SHARE = (1 << 12),
|
||||
PS_BTN_OPTIONS = (1 << 13),
|
||||
PS_BTN_L3 = (1 << 14),
|
||||
PS_BTN_R3 = (1 << 15),
|
||||
PS_BTN_PS_BUTTON = (1 << 16),
|
||||
PS_BTN_TOUCHPAD = (1 << 17),
|
||||
|
||||
SWITCH_PRO_BTN_Y = (1 << 0),
|
||||
SWITCH_PRO_BTN_X = (1 << 1),
|
||||
SWITCH_PRO_BTN_B = (1 << 2),
|
||||
SWITCH_PRO_BTN_A = (1 << 3),
|
||||
SWITCH_PRO_BTN_R1 = (1 << 6),
|
||||
SWITCH_PRO_BTN_R2 = (1 << 7),
|
||||
SWITCH_PRO_BTN_L3 = (1 << 11),
|
||||
SWITCH_PRO_BTN_R3 = (1 << 10),
|
||||
SWITCH_PRO_BTN_SHARE = (1 << 8),
|
||||
SWITCH_PRO_BTN_OPTIONS = (1 << 9),
|
||||
SWITCH_PRO_BTN_PS_BUTTON = (1 << 12),
|
||||
SWITCH_PRO_BTN_CAPTURE = (1 << 13),
|
||||
SWITCH_PRO_DPAD_DOWN = (1 << 16),
|
||||
SWITCH_PRO_DPAD_UP = (1 << 17),
|
||||
SWITCH_PRO_DPAD_RIGHT = (1 << 18),
|
||||
SWITCH_PRO_DPAD_LEFT = (1 << 19),
|
||||
SWITCH_PRO_BTN_L1 = (1 << 22),
|
||||
SWITCH_PRO_BTN_L2 = (1 << 23),
|
||||
};
|
||||
|
||||
struct ButtonInputMapping {
|
||||
HIDButton button;
|
||||
InputKeyCode keyCode;
|
||||
};
|
||||
|
||||
static const ButtonInputMapping g_psInputMappings[] = {
|
||||
{PS_DPAD_UP, NKCODE_DPAD_UP},
|
||||
{PS_DPAD_DOWN, NKCODE_DPAD_DOWN},
|
||||
{PS_DPAD_LEFT, NKCODE_DPAD_LEFT},
|
||||
{PS_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
|
||||
{PS_BTN_SQUARE, NKCODE_BUTTON_4},
|
||||
{PS_BTN_TRIANGLE, NKCODE_BUTTON_3},
|
||||
{PS_BTN_CIRCLE, NKCODE_BUTTON_1},
|
||||
{PS_BTN_CROSS, NKCODE_BUTTON_2},
|
||||
{PS_BTN_PS_BUTTON, NKCODE_HOME},
|
||||
{PS_BTN_SHARE, NKCODE_BUTTON_9},
|
||||
{PS_BTN_OPTIONS, NKCODE_BUTTON_10},
|
||||
{PS_BTN_L1, NKCODE_BUTTON_7},
|
||||
{PS_BTN_R1, NKCODE_BUTTON_8},
|
||||
// {PS_BTN_L2, NKCODE_BUTTON_L2}, // These are done by the analog triggers.
|
||||
// {PS_BTN_R2, NKCODE_BUTTON_R2},
|
||||
{PS_BTN_L3, NKCODE_BUTTON_THUMBL},
|
||||
{PS_BTN_R3, NKCODE_BUTTON_THUMBR},
|
||||
};
|
||||
|
||||
static const ButtonInputMapping g_switchProInputMappings[] = {
|
||||
{SWITCH_PRO_DPAD_UP, NKCODE_DPAD_UP},
|
||||
{SWITCH_PRO_DPAD_DOWN, NKCODE_DPAD_DOWN},
|
||||
{SWITCH_PRO_DPAD_LEFT, NKCODE_DPAD_LEFT},
|
||||
{SWITCH_PRO_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
|
||||
{SWITCH_PRO_BTN_Y, NKCODE_BUTTON_4},
|
||||
{SWITCH_PRO_BTN_X, NKCODE_BUTTON_1},
|
||||
{SWITCH_PRO_BTN_B, NKCODE_BUTTON_2},
|
||||
{SWITCH_PRO_BTN_A, NKCODE_BUTTON_3},
|
||||
{SWITCH_PRO_BTN_PS_BUTTON, NKCODE_HOME},
|
||||
{SWITCH_PRO_BTN_SHARE, NKCODE_BUTTON_9},
|
||||
{SWITCH_PRO_BTN_OPTIONS, NKCODE_BUTTON_10},
|
||||
{SWITCH_PRO_BTN_L1, NKCODE_BUTTON_7},
|
||||
{SWITCH_PRO_BTN_R1, NKCODE_BUTTON_8},
|
||||
{SWITCH_PRO_BTN_L2, NKCODE_BUTTON_L2}, // No analog triggers.
|
||||
{SWITCH_PRO_BTN_R2, NKCODE_BUTTON_R2},
|
||||
{SWITCH_PRO_BTN_L3, NKCODE_BUTTON_THUMBL},
|
||||
{SWITCH_PRO_BTN_R3, NKCODE_BUTTON_THUMBR},
|
||||
};
|
||||
|
||||
enum PSStickAxis : u32 {
|
||||
PS_STICK_LX = 0,
|
||||
PS_STICK_LY = 1,
|
||||
PS_STICK_RX = 2,
|
||||
PS_STICK_RY = 3,
|
||||
};
|
||||
|
||||
struct PSStickMapping {
|
||||
PSStickAxis stickAxis;
|
||||
InputAxis inputAxis;
|
||||
};
|
||||
|
||||
// This is the same mapping as DInput etc.
|
||||
static const PSStickMapping g_psStickMappings[] = {
|
||||
{PS_STICK_LX, JOYSTICK_AXIS_X},
|
||||
{PS_STICK_LY, JOYSTICK_AXIS_Y},
|
||||
{PS_STICK_RX, JOYSTICK_AXIS_Z},
|
||||
{PS_STICK_RY, JOYSTICK_AXIS_RX},
|
||||
};
|
||||
|
||||
enum PSTriggerAxis : u32 {
|
||||
PS_TRIGGER_L2 = 0,
|
||||
PS_TRIGGER_R2 = 1,
|
||||
};
|
||||
|
||||
struct PSTriggerMapping {
|
||||
PSTriggerAxis triggerAxis;
|
||||
InputAxis inputAxis;
|
||||
};
|
||||
|
||||
static const PSTriggerMapping g_psTriggerMappings[] = {
|
||||
{PS_TRIGGER_L2, JOYSTICK_AXIS_LTRIGGER},
|
||||
{PS_TRIGGER_R2, JOYSTICK_AXIS_RTRIGGER},
|
||||
};
|
||||
|
||||
enum class SwitchProSubCmd {
|
||||
SET_INPUT_MODE = 0x03,
|
||||
SET_LOW_POWER_STATE = 0x08,
|
||||
SPI_FLASH_READ = 0x10,
|
||||
SET_LIGHTS = 0x30, // LEDs on controller
|
||||
SET_HOME_LIGHT = 0x38,
|
||||
ENABLE_IMU = 0x40,
|
||||
SET_IMU_SENS = 0x41,
|
||||
ENABLE_VIBRATION = 0x48,
|
||||
};
|
||||
|
||||
constexpr int SwitchPro_INPUT_REPORT_LEN = 362;
|
||||
constexpr int SwitchPro_OUTPUT_REPORT_LEN = 49;
|
||||
constexpr int SwitchPro_RUMBLE_REPORT_LEN = 64;
|
||||
|
||||
static const u8 g_switchProCmdBufHeader[] = {0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
|
||||
|
||||
struct HIDControllerInfo {
|
||||
u16 vendorId;
|
||||
u16 productId;
|
||||
HIDControllerType type;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
constexpr u16 SONY_VID = 0x054C;
|
||||
constexpr u16 NINTENDO_VID = 0x57e;
|
||||
constexpr u16 SWITCH_PRO_PID = 0x2009;
|
||||
constexpr u16 DS4_WIRELESS = 0x0BA0;
|
||||
constexpr u16 PS_CLASSIC = 0x0CDA;
|
||||
|
||||
// We pick a few ones from here to support, let's add more later.
|
||||
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/DS4Devices.cs#L126
|
||||
static const HIDControllerInfo g_psInfos[] = {
|
||||
{SONY_VID, 0x05C4, HIDControllerType::DS4, "DS4 v.1"},
|
||||
{SONY_VID, 0x09CC, HIDControllerType::DS4, "DS4 v.2"},
|
||||
{SONY_VID, 0x0CE6, HIDControllerType::DS5, "DualSense"},
|
||||
{SONY_VID, PS_CLASSIC, HIDControllerType::DS4, "PS Classic"},
|
||||
{NINTENDO_VID, SWITCH_PRO_PID, HIDControllerType::SwitchPro, "Switch Pro"},
|
||||
// {PSSubType::DS4, DS4_WIRELESS},
|
||||
// {PSSubType::DS5, DUALSENSE_WIRELESS},
|
||||
// {PSSubType::DS5, DUALSENSE_EDGE_WIRELESS},
|
||||
};
|
||||
|
||||
static const HIDControllerInfo *GetGamepadInfo(HANDLE handle) {
|
||||
HIDD_ATTRIBUTES attr{sizeof(HIDD_ATTRIBUTES)};
|
||||
if (!HidD_GetAttributes(handle, &attr)) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const auto &info : g_psInfos) {
|
||||
if (attr.VendorID == info.vendorId && attr.ProductID == info.productId) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static bool WriteReport(HANDLE handle, const T &report) {
|
||||
DWORD written;
|
||||
bool result = WriteFile(handle, &report, sizeof(report), &written, NULL);
|
||||
if (!result) {
|
||||
u32 errorCode = GetLastError();
|
||||
|
||||
if (errorCode == ERROR_INVALID_PARAMETER) {
|
||||
if (!HidD_SetOutputReport(handle, (PVOID)&report, sizeof(T))) {
|
||||
errorCode = GetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
WARN_LOG(Log::UI, "Failed initializing: %08x", errorCode);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct DualSenseOutputReport {
|
||||
u8 reportId;
|
||||
u8 flags1;
|
||||
u8 flags2;
|
||||
u8 rumbleRight;
|
||||
u8 rumbleLeft;
|
||||
u8 pad[2];
|
||||
u8 muteLED;
|
||||
u8 micMute; // 10
|
||||
u8 other[32];
|
||||
u8 enableBrightness;
|
||||
u8 fade;
|
||||
u8 brightness;
|
||||
u8 playerLights;
|
||||
u8 lightbarRed;
|
||||
u8 lightbarGreen;
|
||||
u8 lightbarBlue;
|
||||
};
|
||||
static_assert(sizeof(DualSenseOutputReport) == 48);
|
||||
|
||||
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs#L905
|
||||
|
||||
// Sends initialization packet to DualSense
|
||||
static bool InitializeDualSense(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualSenseOutputReport)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DualSenseOutputReport report{};
|
||||
report.reportId = 2;
|
||||
report.flags1 = 0x0C;
|
||||
report.flags2 = 0x15;
|
||||
report.muteLED = 1;
|
||||
report.playerLights = 1;
|
||||
report.enableBrightness = 1;
|
||||
report.brightness = 0; // 0 = high, 1 = medium, 2 = low
|
||||
report.lightbarRed = LED_R;
|
||||
report.lightbarGreen = LED_G;
|
||||
report.lightbarBlue = LED_B;
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
static bool ShutdownDualsense(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualSenseOutputReport)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DualSenseOutputReport report{};
|
||||
report.reportId = 2;
|
||||
report.flags1 = 0x0C;
|
||||
report.flags2 = 0x15;
|
||||
report.muteLED = 1;
|
||||
report.playerLights = 0;
|
||||
report.enableBrightness = 1;
|
||||
report.brightness = 2; // 0 = high, 1 = medium, 2 = low
|
||||
report.lightbarRed = 0;
|
||||
report.lightbarGreen = 0;
|
||||
report.lightbarBlue = 0;
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
static bool InitializeSwitchPro(HANDLE handle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class DS4FeatureBits : u8 {
|
||||
VOL_L = 0x10,
|
||||
VOL_R = 0x20,
|
||||
MIC_VOL = 0x40,
|
||||
SPEAKER_VOL = 0x80,
|
||||
RUMBLE = 0x1,
|
||||
LIGHTBAR = 0x2,
|
||||
FLASH = 0x4,
|
||||
};
|
||||
ENUM_CLASS_BITOPS(DS4FeatureBits);
|
||||
|
||||
struct DualshockOutputReport {
|
||||
u8 reportID;
|
||||
u8 featureBits;
|
||||
u8 two;
|
||||
u8 pad;
|
||||
u8 rumbleRight;
|
||||
u8 rumbleLeft;
|
||||
u8 ledR;
|
||||
u8 ledG;
|
||||
u8 ledB;
|
||||
u8 padding[23];
|
||||
};
|
||||
static_assert(sizeof(DualshockOutputReport) == 32);
|
||||
|
||||
static bool InitializeDualShock(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualshockOutputReport)) {
|
||||
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
DualshockOutputReport report{};
|
||||
report.reportID = 0x05; // Report ID (DS4 output)
|
||||
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
|
||||
report.two = 2;
|
||||
|
||||
// Rumble
|
||||
report.rumbleRight = 0x00; // Right (weak)
|
||||
report.rumbleLeft = 0x00; // Left (strong)
|
||||
|
||||
// Lightbar (RGB)
|
||||
report.ledR = LED_R;
|
||||
report.ledG = LED_G;
|
||||
report.ledB = LED_B;
|
||||
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
static bool ShutdownDualShock(HANDLE handle, int outReportSize) {
|
||||
if (outReportSize != sizeof(DualshockOutputReport)) {
|
||||
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
DualshockOutputReport report{};
|
||||
report.reportID = 0x05; // Report ID (DS4 output)
|
||||
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
|
||||
report.two = 2;
|
||||
|
||||
// Rumble
|
||||
report.rumbleRight = 0x00; // Right (weak)
|
||||
report.rumbleLeft = 0x00; // Left (strong)
|
||||
|
||||
// Lightbar (RGB)
|
||||
report.ledR = 0;
|
||||
report.ledG = 0;
|
||||
report.ledB = 0;
|
||||
|
||||
return WriteReport(handle, report);
|
||||
}
|
||||
|
||||
static HANDLE OpenFirstHIDController(HIDControllerType *subType, int *reportSize, int *outReportSize, const HIDControllerInfo **outInfo) {
|
||||
GUID hidGuid;
|
||||
HidD_GetHidGuid(&hidGuid);
|
||||
|
||||
HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&hidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (deviceInfoSet == INVALID_HANDLE_VALUE)
|
||||
return nullptr;
|
||||
|
||||
SP_DEVICE_INTERFACE_DATA interfaceData;
|
||||
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||||
|
||||
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, nullptr, &hidGuid, i, &interfaceData); ++i) {
|
||||
DWORD requiredSize = 0;
|
||||
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &interfaceData, nullptr, 0, &requiredSize, nullptr);
|
||||
|
||||
std::vector<BYTE> buffer(requiredSize);
|
||||
auto* detailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buffer.data());
|
||||
detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
|
||||
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &interfaceData, detailData, requiredSize, nullptr, nullptr)) {
|
||||
HANDLE handle = CreateFile(detailData->DevicePath, GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
const HIDControllerInfo *info = GetGamepadInfo(handle);
|
||||
*outInfo = info;
|
||||
if (info) {
|
||||
*subType = info->type;
|
||||
INFO_LOG(Log::UI, "Found supported gamepad. PID: %04x", info->productId);
|
||||
HIDP_CAPS caps;
|
||||
PHIDP_PREPARSED_DATA preparsedData;
|
||||
|
||||
HidD_GetPreparsedData(handle, &preparsedData);
|
||||
HidP_GetCaps(preparsedData, &caps);
|
||||
HidD_FreePreparsedData(preparsedData);
|
||||
|
||||
*reportSize = caps.InputReportByteLength;
|
||||
*outReportSize = caps.OutputReportByteLength;
|
||||
|
||||
INFO_LOG(Log::UI, "Initializing gamepad. out report size=%d", *outReportSize);
|
||||
bool result;
|
||||
switch (*subType) {
|
||||
case HIDControllerType::DS5:
|
||||
result = InitializeDualSense(handle, *outReportSize);
|
||||
break;
|
||||
case HIDControllerType::DS4:
|
||||
result = InitializeDualShock(handle, *outReportSize);
|
||||
break;
|
||||
case HIDControllerType::SwitchPro:
|
||||
result = InitializeSwitchPro(handle);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
ERROR_LOG(Log::UI, "Controller initialization failed");
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
|
||||
return handle;
|
||||
}
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HidInputDevice::AddSupportedDevices(std::set<u32> *deviceVIDPIDs) {
|
||||
for (const auto &info : g_psInfos) {
|
||||
const u32 vidpid = MAKELONG(info.vendorId, info.productId);
|
||||
deviceVIDPIDs->insert(vidpid);
|
||||
}
|
||||
}
|
||||
|
||||
static u32 DecodeHatSwitch(u8 dpad) {
|
||||
u32 buttons = 0;
|
||||
if (dpad == 0 || dpad == 1 || dpad == 7) {
|
||||
buttons |= PS_DPAD_UP;
|
||||
}
|
||||
if (dpad == 1 || dpad == 2 || dpad == 3) {
|
||||
buttons |= PS_DPAD_RIGHT;
|
||||
}
|
||||
if (dpad == 3 || dpad == 4 || dpad == 5) {
|
||||
buttons |= PS_DPAD_DOWN;
|
||||
}
|
||||
if (dpad == 5 || dpad == 6 || dpad == 7) {
|
||||
buttons |= PS_DPAD_LEFT;
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
struct DualShockInputReport {
|
||||
u8 lx;
|
||||
u8 ly;
|
||||
u8 rx;
|
||||
u8 ry;
|
||||
u8 buttons[3]; // note, starts at 5 so not aligned
|
||||
u8 l2_analog;
|
||||
u8 r2_analog;
|
||||
u8 pad[2];
|
||||
u8 battery;
|
||||
// Then there's motion and all kinds of stuff.
|
||||
};
|
||||
|
||||
bool ReadDualShockInput(HANDLE handle, HIDControllerState *state) {
|
||||
BYTE inputReport[64]{}; // 64-byte input report for DS4
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
|
||||
u32 error = GetLastError();
|
||||
return false;
|
||||
}
|
||||
DualShockInputReport report{};
|
||||
static_assert(sizeof(report) < sizeof(inputReport));
|
||||
if (bytesRead < 14) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, check the first byte to figure out what we're dealing with here.
|
||||
int offset = 1;
|
||||
int reportId;
|
||||
if (inputReport[0] == 0xA1) {
|
||||
// 2-byte bluetooth frame
|
||||
offset = 2;
|
||||
reportId = inputReport[1];
|
||||
} else {
|
||||
offset = 1;
|
||||
reportId = inputReport[0];
|
||||
}
|
||||
// const bool isBluetooth = (reportId == 0x11 || reportId == 0x31);
|
||||
|
||||
memcpy(&report, inputReport + offset, sizeof(report));
|
||||
|
||||
// Center the sticks.
|
||||
state->stickAxes[PS_STICK_LX] = report.lx - 128;
|
||||
state->stickAxes[PS_STICK_LY] = report.ly - 128;
|
||||
state->stickAxes[PS_STICK_RX] = report.rx - 128;
|
||||
state->stickAxes[PS_STICK_RY] = report.ry - 128;
|
||||
|
||||
// Copy over the triggers.
|
||||
state->triggerAxes[PS_TRIGGER_L2] = report.l2_analog;
|
||||
state->triggerAxes[PS_TRIGGER_R2] = report.r2_analog;
|
||||
|
||||
state->accValid = false;
|
||||
|
||||
u32 buttons{};
|
||||
int frameCounter = report.buttons[2] >> 2;
|
||||
report.buttons[2] &= 3;
|
||||
memcpy(&buttons, &report.buttons[0], 3);
|
||||
|
||||
// Clear out and re-fill the DPAD, it works differently somehow
|
||||
buttons &= ~0xF;
|
||||
buttons |= DecodeHatSwitch(report.buttons[0] & 0xF);
|
||||
|
||||
state->buttons = buttons;
|
||||
return true;
|
||||
}
|
||||
|
||||
// So strange that this is different!
|
||||
struct DualSenseInputReport {
|
||||
u8 firstByte; // must be 1
|
||||
|
||||
u8 lx;
|
||||
u8 ly;
|
||||
u8 rx;
|
||||
u8 ry;
|
||||
|
||||
u8 l2_analog;
|
||||
u8 r2_analog;
|
||||
|
||||
u8 frameCounter; // 7
|
||||
|
||||
u8 buttons[3]; // 8-10
|
||||
u8 pad[5]; // 11,12,13,14,15
|
||||
|
||||
s16 gyroscope[3];
|
||||
s16 accelerometer[3];
|
||||
};
|
||||
|
||||
bool ReadDualSenseInput(HANDLE handle, HIDControllerState *state) {
|
||||
BYTE inputReport[64]{}; // 64-byte input report for DS4
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
|
||||
const u32 error = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
DualSenseInputReport report{};
|
||||
static_assert(sizeof(report) < sizeof(inputReport));
|
||||
if (bytesRead < 14) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, check the first byte to figure out what we're dealing with here.
|
||||
if (inputReport[0] != 1) {
|
||||
// Wrong data
|
||||
return false;
|
||||
}
|
||||
// const bool isBluetooth = (reportId == 0x11 || reportId == 0x31);
|
||||
|
||||
memcpy(&report, inputReport, sizeof(report));
|
||||
|
||||
// Center the sticks.
|
||||
state->stickAxes[PS_STICK_LX] = report.lx - 128;
|
||||
state->stickAxes[PS_STICK_LY] = report.ly - 128;
|
||||
state->stickAxes[PS_STICK_RX] = report.rx - 128;
|
||||
state->stickAxes[PS_STICK_RY] = report.ry - 128;
|
||||
|
||||
// Copy over the triggers.
|
||||
state->triggerAxes[PS_TRIGGER_L2] = report.l2_analog;
|
||||
state->triggerAxes[PS_TRIGGER_R2] = report.r2_analog;
|
||||
|
||||
const float accelScale = (1.0f / 8192.0f) * 9.81f;
|
||||
// We need to remap the axes a bit.
|
||||
state->accValid = true;
|
||||
state->accelerometer[0] = -report.accelerometer[2] * accelScale;
|
||||
state->accelerometer[1] = -report.accelerometer[0] * accelScale;
|
||||
state->accelerometer[2] = report.accelerometer[1] * accelScale;
|
||||
|
||||
u32 buttons{};
|
||||
report.buttons[2] &= 3; // Remove noise
|
||||
memcpy(&buttons, &report.buttons[0], 3);
|
||||
|
||||
// Clear out and re-fill the DPAD, it works differently somehow
|
||||
buttons &= ~0xF;
|
||||
buttons |= DecodeHatSwitch(report.buttons[0] & 0xF);
|
||||
|
||||
state->buttons = buttons;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HidInputDevice::Init() {}
|
||||
void HidInputDevice::Shutdown() {
|
||||
if (controller_) {
|
||||
switch (subType_) {
|
||||
case HIDControllerType::DS4:
|
||||
ShutdownDualShock(controller_, outReportSize_);
|
||||
break;
|
||||
case HIDControllerType::DS5:
|
||||
ShutdownDualsense(controller_, outReportSize_);
|
||||
break;
|
||||
}
|
||||
CloseHandle(controller_);
|
||||
controller_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
struct SwitchProInputReport {
|
||||
u8 reportId;
|
||||
u8 padding;
|
||||
u8 battery;
|
||||
u8 buttons[3];
|
||||
u8 lStick[3]; // 2 12-bit values.
|
||||
u8 rStick[3]; // 2 12-bit values.
|
||||
// Next up is gyro and all sorts of stuff we don't care about right now.
|
||||
};
|
||||
|
||||
static void DecodeSwitchProStick(const u8 *stickData, s8 *outX, s8 *outY) {
|
||||
int x = ((stickData[1] & 0xF) << 8) | (stickData[0]);
|
||||
int y = (stickData[1] >> 4) | (stickData[2] << 4);
|
||||
|
||||
// For some reason the values are not really centered. Let's approximate.
|
||||
// We probably should add some low level calibration?
|
||||
x = (x - 2048) / 12;
|
||||
y = (y - 1950) / 12;
|
||||
|
||||
*outX = (s8)clamp_value(x, -128, 127);
|
||||
*outY = (s8)clamp_value(y, -128, 127);
|
||||
// INFO_LOG(Log::sceCtrl, "Switch Pro input: x=%d, y=%d, cx=%d, cy=%d", x, y, *outX, *outY);
|
||||
}
|
||||
|
||||
static bool ReadSwitchProInput(HANDLE handle, HIDControllerState *state) {
|
||||
BYTE inputReport[SwitchPro_INPUT_REPORT_LEN]{}; // 64-byte input report for Switch Pro
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
|
||||
u32 error = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputReport[0] != 0x30) {
|
||||
// Not a Switch Pro controller input report.
|
||||
return false;
|
||||
}
|
||||
|
||||
SwitchProInputReport report{};
|
||||
memcpy(&report, inputReport, sizeof(report));
|
||||
|
||||
u32 buttons = 0;
|
||||
memcpy(&state->buttons, &report.buttons[0], 3);
|
||||
// INFO_LOG(Log::sceCtrl, "Switch Pro input: buttons=%08x", state->buttons);
|
||||
|
||||
DecodeSwitchProStick(report.lStick, &state->stickAxes[PS_STICK_LX], &state->stickAxes[PS_STICK_LY]);
|
||||
DecodeSwitchProStick(report.rStick, &state->stickAxes[PS_STICK_RX], &state->stickAxes[PS_STICK_RY]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HidInputDevice::ReleaseAllKeys(const ButtonInputMapping *buttonMappings, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
const auto &mapping = buttonMappings[i];
|
||||
KeyInput key;
|
||||
key.deviceId = DEVICE_ID_XINPUT_0 + pad_;
|
||||
key.flags = KeyInputFlags::UP;
|
||||
key.keyCode = mapping.keyCode;
|
||||
NativeKey(key);
|
||||
}
|
||||
|
||||
static const InputAxis allAxes[6] = {
|
||||
JOYSTICK_AXIS_X,
|
||||
JOYSTICK_AXIS_Y,
|
||||
JOYSTICK_AXIS_Z,
|
||||
JOYSTICK_AXIS_RX,
|
||||
JOYSTICK_AXIS_LTRIGGER,
|
||||
JOYSTICK_AXIS_RTRIGGER,
|
||||
};
|
||||
|
||||
for (const auto axisId : allAxes) {
|
||||
AxisInput axis;
|
||||
axis.deviceId = DEVICE_ID_XINPUT_0 + pad_;
|
||||
axis.axisId = axisId;
|
||||
axis.value = 0;
|
||||
NativeAxis(&axis, 1);
|
||||
}
|
||||
}
|
||||
|
||||
InputDeviceID HidInputDevice::DeviceID(int pad) {
|
||||
return DEVICE_ID_PAD_0 + pad;
|
||||
}
|
||||
|
||||
int HidInputDevice::UpdateState() {
|
||||
const InputDeviceID deviceID = DeviceID(pad_);
|
||||
|
||||
if (!controller_) {
|
||||
// Poll for controllers from time to time.
|
||||
if (pollCount_ == 0) {
|
||||
pollCount_ = POLL_FREQ;
|
||||
const HIDControllerInfo *info{};
|
||||
HANDLE newController = OpenFirstHIDController(&subType_, &reportSize_, &outReportSize_, &info);
|
||||
if (newController) {
|
||||
controller_ = newController;
|
||||
if (info) {
|
||||
name_ = info->name;
|
||||
}
|
||||
KeyMap::NotifyPadConnected(deviceID, name_);
|
||||
}
|
||||
} else {
|
||||
pollCount_--;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller_) {
|
||||
HIDControllerState state{};
|
||||
bool result = false;
|
||||
const ButtonInputMapping *buttonMappings = g_psInputMappings;
|
||||
u32 buttonMappingsSize = sizeof(g_psInputMappings) / sizeof(ButtonInputMapping);
|
||||
if (subType_ == HIDControllerType::DS4) {
|
||||
result = ReadDualShockInput(controller_, &state);
|
||||
} else if (subType_ == HIDControllerType::DS5) {
|
||||
result = ReadDualSenseInput(controller_, &state);
|
||||
} else if (subType_ == HIDControllerType::SwitchPro) {
|
||||
result = ReadSwitchProInput(controller_, &state);
|
||||
buttonMappings = g_switchProInputMappings;
|
||||
buttonMappingsSize = sizeof(g_switchProInputMappings) / sizeof(ButtonInputMapping);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
// Process the input and generate input events.
|
||||
const u32 downMask = state.buttons & (~prevState_.buttons);
|
||||
const u32 upMask = (~state.buttons) & prevState_.buttons;
|
||||
|
||||
for (u32 i = 0; i < buttonMappingsSize; i++) {
|
||||
const ButtonInputMapping &mapping = buttonMappings[i];
|
||||
if (downMask & mapping.button) {
|
||||
KeyInput key;
|
||||
key.deviceId = deviceID;
|
||||
key.flags = KeyInputFlags::DOWN;
|
||||
key.keyCode = mapping.keyCode;
|
||||
NativeKey(key);
|
||||
}
|
||||
if (upMask & mapping.button) {
|
||||
KeyInput key;
|
||||
key.deviceId = deviceID;
|
||||
key.flags = KeyInputFlags::UP;
|
||||
key.keyCode = mapping.keyCode;
|
||||
NativeKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &mapping : g_psStickMappings) {
|
||||
if (state.stickAxes[mapping.stickAxis] != prevState_.stickAxes[mapping.stickAxis]) {
|
||||
AxisInput axis;
|
||||
axis.deviceId = deviceID;
|
||||
axis.axisId = mapping.inputAxis;
|
||||
axis.value = (float)state.stickAxes[mapping.stickAxis] * (1.0f / 128.0f);
|
||||
NativeAxis(&axis, 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &mapping : g_psTriggerMappings) {
|
||||
if (state.triggerAxes[mapping.triggerAxis] != prevState_.triggerAxes[mapping.triggerAxis]) {
|
||||
AxisInput axis;
|
||||
axis.deviceId = deviceID;
|
||||
axis.axisId = mapping.inputAxis;
|
||||
axis.value = (float)state.triggerAxes[mapping.triggerAxis] * (1.0f / 255.0f);
|
||||
NativeAxis(&axis, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.accValid) {
|
||||
NativeAccelerometer(state.accelerometer[0], state.accelerometer[1], state.accelerometer[2]);
|
||||
}
|
||||
|
||||
prevState_ = state;
|
||||
return UPDATESTATE_NO_SLEEP; // The ReadFile sleeps for us, effectively.
|
||||
} else {
|
||||
// might have been disconnected. retry later.
|
||||
KeyMap::NotifyPadDisconnected(deviceID);
|
||||
ReleaseAllKeys(buttonMappings, buttonMappingsSize);
|
||||
CloseHandle(controller_);
|
||||
controller_ = NULL;
|
||||
pollCount_ = POLL_FREQ;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
#if !PPSSPP_PLATFORM(UWP)
|
||||
#include "Windows/DinputDevice.h"
|
||||
#include "Windows/HidInputDevice.h"
|
||||
#include "Windows/Hid/HidInputDevice.h"
|
||||
#include "Windows/XinputDevice.h"
|
||||
#endif
|
||||
|
||||
|
||||
+10
-2
@@ -691,7 +691,11 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HidInputDevice.cpp" />
|
||||
<ClCompile Include="HID\DualSense.cpp" />
|
||||
<ClCompile Include="HID\DualShock.cpp" />
|
||||
<ClCompile Include="Hid\HidCommon.cpp" />
|
||||
<ClCompile Include="Hid\HidInputDevice.cpp" />
|
||||
<ClCompile Include="HID\SwitchPro.cpp" />
|
||||
<ClCompile Include="InputDevice.cpp" />
|
||||
<ClCompile Include="MainWindowMenu.cpp" />
|
||||
<ClCompile Include="RawInput.cpp" />
|
||||
@@ -1148,7 +1152,11 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GPU\WindowsGraphicsContext.h" />
|
||||
<ClInclude Include="HidInputDevice.h" />
|
||||
<ClInclude Include="HID\DualSense.h" />
|
||||
<ClInclude Include="HID\DualShock.h" />
|
||||
<ClInclude Include="Hid\HidCommon.h" />
|
||||
<ClInclude Include="Hid\HidInputDevice.h" />
|
||||
<ClInclude Include="HID\SwitchPro.h" />
|
||||
<ClInclude Include="InputDevice.h" />
|
||||
<ClInclude Include="MainWindowMenu.h" />
|
||||
<ClInclude Include="RawInput.h" />
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
<Filter Include="Other Platforms\Mac">
|
||||
<UniqueIdentifier>{e2dd8951-ccb6-4575-996a-69c6832fafbf}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows\Input\Hid">
|
||||
<UniqueIdentifier>{55d277d0-1afa-464d-9bde-672334ad73fe}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Debugger\CtrlDisAsmView.cpp">
|
||||
@@ -271,8 +274,20 @@
|
||||
<ClCompile Include="Debugger\EditSymbolsWindow.cpp">
|
||||
<Filter>Windows\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HidInputDevice.cpp">
|
||||
<Filter>Windows\Input</Filter>
|
||||
<ClCompile Include="HID\DualShock.cpp">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HID\SwitchPro.cpp">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HID\DualSense.cpp">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Hid\HidInputDevice.cpp">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Hid\HidCommon.cpp">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -565,8 +580,20 @@
|
||||
<ClInclude Include="..\ios\ViewControllerMetal.h">
|
||||
<Filter>Other Platforms\iOS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HidInputDevice.h">
|
||||
<Filter>Windows\Input</Filter>
|
||||
<ClInclude Include="HID\DualShock.h">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HID\SwitchPro.h">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HID\DualSense.h">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Hid\HidInputDevice.h">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Hid\HidCommon.h">
|
||||
<Filter>Windows\Input\Hid</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -844,4 +871,4 @@
|
||||
<Filter>Other Platforms\SDL</Filter>
|
||||
</Text>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user