From 6b0e9b098952b2b154526901b568ab9a9e0c70ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 28 Jun 2025 16:29:30 +0200 Subject: [PATCH 1/4] Implement initial native DualShock/DualSense support --- Windows/DinputDevice.cpp | 3 + Windows/HidInputDevice.cpp | 273 +++++++++++++++++++++++++++++++++ Windows/HidInputDevice.h | 45 ++++++ Windows/PPSSPP.vcxproj | 14 +- Windows/PPSSPP.vcxproj.filters | 6 + Windows/main.cpp | 3 +- 6 files changed, 337 insertions(+), 7 deletions(-) create mode 100644 Windows/HidInputDevice.cpp create mode 100644 Windows/HidInputDevice.h diff --git a/Windows/DinputDevice.cpp b/Windows/DinputDevice.cpp index f09ede548d..0c3775fb73 100644 --- a/Windows/DinputDevice.cpp +++ b/Windows/DinputDevice.cpp @@ -33,6 +33,8 @@ #include "Common/System/NativeApp.h" #include "Core/KeyMap.h" #include "Windows/DinputDevice.h" +#include "Windows/HidInputDevice.h" + #pragma comment(lib,"dinput8.lib") // static members of DinputDevice @@ -105,6 +107,7 @@ void DinputDevice::getDevices(bool refresh) { if (refresh) { // We don't want duplicate reporting from XInput devices through DInput. ignoreDevices_ = DetectXInputVIDPIDs(); + HidInputDevice::AddSupportedDevices(&ignoreDevices_); getPDI()->EnumDevices(DI8DEVCLASS_GAMECTRL, &DinputDevice::DevicesCallback, NULL, DIEDFL_ATTACHEDONLY); } } diff --git a/Windows/HidInputDevice.cpp b/Windows/HidInputDevice.cpp new file mode 100644 index 0000000000..54c3ecdba6 --- /dev/null +++ b/Windows/HidInputDevice.cpp @@ -0,0 +1,273 @@ + +#include +#include +#include +#include +#include + +#include "Windows/HidInputDevice.h" +#include "Common/TimeUtil.h" +#include "Common/Log.h" +#include "Common/Input/InputState.h" +#include "Common/System/NativeApp.h" + +enum PSButton : u32 { + PS_DPAD_UP = 1, + 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_L2 = (1 << 9), + PS_BTN_R1 = (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), +}; + +constexpr u16 SONY_VID = 0x054C; + +constexpr u16 DS4_PID = 0x05C4; +constexpr u16 DS5_PID = 0x0CE6; +constexpr u16 DS4_2_PID = 0x09CC; +constexpr u16 DS4_WIRELESS = 0x0BA0; +constexpr u16 DUALSENSE_WIRELESS = 0x0CE6; +constexpr u16 DUALSENSE_EDGE_WIRELESS = 0x0DF2; +constexpr u16 PS_CLASSIC = 0x0CDA; + +struct PSInputMapping { + PSButton button; + InputKeyCode keyCode; +}; + +static const PSInputMapping 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_X}, + {PS_BTN_TRIANGLE, NKCODE_BUTTON_Y}, + {PS_BTN_CIRCLE, NKCODE_BUTTON_B}, + {PS_BTN_CROSS, NKCODE_BUTTON_A}, + {PS_BTN_PS_BUTTON, NKCODE_HOME}, + {PS_BTN_SHARE, NKCODE_BUTTON_START}, + {PS_BTN_OPTIONS, NKCODE_BUTTON_SELECT}, + {PS_BTN_L1, NKCODE_BUTTON_L1}, + {PS_BTN_R1, NKCODE_BUTTON_R1}, + {PS_BTN_L2, NKCODE_BUTTON_L2}, + {PS_BTN_R2, NKCODE_BUTTON_R2}, + {PS_BTN_L3, NKCODE_BUTTON_THUMBL}, + {PS_BTN_R3, NKCODE_BUTTON_THUMBR}, +}; + +struct PSControllerInfo { + PSSubType type; + u16 productId; +}; + +static const PSControllerInfo g_psInfos[] = { + {TYPE_DS4, DS4_PID}, + {TYPE_DS5, DS5_PID}, + {TYPE_DS4, DS4_2_PID}, + {TYPE_DS4, DS4_WIRELESS}, + {TYPE_DS5, DUALSENSE_WIRELESS}, + {TYPE_DS5, DUALSENSE_EDGE_WIRELESS}, + {TYPE_DS4, PS_CLASSIC}, +}; + +static bool IsSonyGamepad(HANDLE handle, USHORT *pidOut, PSSubType *subType) { + HIDD_ATTRIBUTES attr{sizeof(HIDD_ATTRIBUTES)}; + if (!HidD_GetAttributes(handle, &attr)) { + return false; + } + for (const auto &info : g_psInfos) { + if (attr.VendorID == SONY_VID && attr.ProductID == info.productId) { + *pidOut = attr.ProductID; + *subType = info.type; + return true; + } + } + return false; +} + +HANDLE OpenFirstDualShockOrSense(PSSubType *subType) { + 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 buffer(requiredSize); + auto* detailData = reinterpret_cast(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, 0, nullptr); + if (handle != INVALID_HANDLE_VALUE) { + USHORT pid; + if (IsSonyGamepad(handle, &pid, subType)) { + INFO_LOG(Log::UI, "Found Sony gamepad. PID: %04x", pid); + SetupDiDestroyDeviceInfoList(deviceInfoSet); + return handle; + } + CloseHandle(handle); + } + } + } + + SetupDiDestroyDeviceInfoList(deviceInfoSet); + return nullptr; +} + +void HidInputDevice::AddSupportedDevices(std::set *deviceVIDPIDs) { + for (const auto &info : g_psInfos) { + const u32 vidpid = MAKELONG(SONY_VID, info.productId); + deviceVIDPIDs->insert(vidpid); + } +} + +struct PSInputReportHeader { + u8 id; // not sure if useful. + 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 HidInputDevice::ReadDS4Input(HANDLE handle, PSControllerState *state) { + BYTE inputReport[64] = {0}; // 64-byte input report for DS4 + DWORD bytesRead = 0; + if (ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) { + PSInputReportHeader hdr{}; + static_assert(sizeof(hdr) < sizeof(inputReport)); + memcpy(&hdr, inputReport, sizeof(hdr)); + + // Center the sticks. + state->lx = hdr.lx - 128; + state->rx = hdr.rx - 128; + state->ly = hdr.ly - 128; + state->ry = hdr.ry - 128; + + u32 buttons{}; + hdr.buttons[2] &= 3; // Remove noise + memcpy(&buttons, &hdr.buttons[0], 3); + + // Clear out and re-fill the DPAD, it works differently somehow + buttons &= ~0xF; + + const u8 dpad = hdr.buttons[0] & 0xF; + 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; + } + + state->buttons = buttons; + return true; + } else { + return false; + } +} + +void HidInputDevice::Init() {} +void HidInputDevice::Shutdown() { + if (controller_) { + CloseHandle(controller_); + controller_ = nullptr; + } +} + +void HidInputDevice::ReleaseAllKeys() { + for (const auto &mapping : g_psInputMappings) { + KeyInput key; + key.deviceId = DEVICE_ID_XINPUT_0 + pad_; + key.flags = KEY_UP; + key.keyCode = mapping.keyCode; + NativeKey(key); + } +} + +InputDeviceID HidInputDevice::DeviceID(int pad) { + return DEVICE_ID_PAD_0 + pad; +} + +int HidInputDevice::UpdateState() { + if (!controller_) { + // Poll for controllers from time to time. + if (pollCount_ == 0) { + pollCount_ = POLL_FREQ; + HANDLE newController = OpenFirstDualShockOrSense(&subType_); + if (newController) { + controller_ = newController; + } + } else { + pollCount_--; + } + } + + if (controller_) { + PSControllerState state{}; + if (ReadDS4Input(controller_, &state)) { + const InputDeviceID deviceID = DeviceID(pad_); + // Process the input and generate input events. + const u32 downMask = state.buttons & (~prevState_.buttons); + const u32 upMask = (~state.buttons) & prevState_.buttons; + + for (const auto &mapping : g_psInputMappings) { + if (downMask & mapping.button) { + KeyInput key; + key.deviceId = deviceID; + key.flags = KEY_DOWN; + key.keyCode = mapping.keyCode; + NativeKey(key); + } + if (upMask & mapping.button) { + KeyInput key; + key.deviceId = deviceID; + key.flags = KEY_UP; + key.keyCode = mapping.keyCode; + NativeKey(key); + } + } + + prevState_ = state; + } else { + // might have been disconnected. retry later. + ReleaseAllKeys(); + CloseHandle(controller_); + controller_ = NULL; + pollCount_ = POLL_FREQ; + } + } + return 0; +} diff --git a/Windows/HidInputDevice.h b/Windows/HidInputDevice.h new file mode 100644 index 0000000000..53fd510945 --- /dev/null +++ b/Windows/HidInputDevice.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Windows/InputDevice.h" +#include + +enum PSSubType { + TYPE_DS4, + TYPE_DS5 +}; + +enum InputDeviceID; + +// Supports a few specific HID input devices, namely DualShock and DualSense. +// More may be added later. Just picks the first one available, for now. +class HidInputDevice : public InputDevice { +public: + void Init(); + int UpdateState(); + void Shutdown(); + + static void AddSupportedDevices(std::set *deviceVIDPIDs); +private: + struct PSControllerState { + // Analog sticks + s8 lx; + s8 ly; + s8 rx; + s8 ry; + // Buttons + u32 buttons; // Bitmask, PSButton enum + }; + bool ReadDS4Input(HANDLE handle, PSControllerState *state); + void ReleaseAllKeys(); + InputDeviceID DeviceID(int pad); + PSControllerState prevState_{}; + + PSSubType subType_{}; + HANDLE controller_; + int pad_ = 0; + int pollCount_ = 0; + + enum { + POLL_FREQ = 69, + }; +}; diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index d39a9ea087..f8bdcdccb3 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -195,7 +195,7 @@ stdcpp17 - avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;setupapi.lib;hid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86/lib true Windows @@ -235,7 +235,7 @@ stdcpp17 - avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;setupapi.lib;hid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86_64/lib true $(OutDir)$(ProjectName).pdb @@ -271,7 +271,7 @@ stdcpp17 - avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;setupapi.lib;hid.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/aarch64/lib true $(OutDir)$(ProjectName).pdb @@ -309,7 +309,7 @@ stdcpp17 - avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;setupapi.lib;hid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86/lib;%(AdditionalLibraryDirectories) $(OutDir)$(TargetName)$(TargetExt) true @@ -358,7 +358,7 @@ stdcpp17 - avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;setupapi.lib;hid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86_64/lib;%(AdditionalLibraryDirectories) true Windows @@ -401,7 +401,7 @@ stdcpp17 - avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;setupapi.lib;hid.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/aarch64/lib;%(AdditionalLibraryDirectories) true Windows @@ -693,6 +693,7 @@ true true + @@ -1149,6 +1150,7 @@ true + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 7618b385c0..4b4b1c5bd4 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -271,6 +271,9 @@ Windows\Debugger + + Windows\Input + @@ -562,6 +565,9 @@ Other Platforms\iOS + + Windows\Input + diff --git a/Windows/main.cpp b/Windows/main.cpp index 674c1fa388..d1a635ab08 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -69,6 +69,7 @@ #include "Windows/resource.h" #include "Windows/DinputDevice.h" #include "Windows/XinputDevice.h" +#include "Windows/HidInputDevice.h" #include "Windows/MainWindow.h" #include "Windows/Debugger/Debugger_Disasm.h" #include "Windows/Debugger/Debugger_MemoryDlg.h" @@ -1132,7 +1133,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin //add first XInput device to respond g_InputManager.AddDevice(new XinputDevice()); g_InputManager.AddDevice(new DInputMetaDevice()); - + g_InputManager.AddDevice(new HidInputDevice()); // Emu thread (and render thread, if any) is always running! // Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan // manages its own render thread. From 2e1fdc4c78c9a8f0182010c804ce376c8bcab7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 28 Jun 2025 18:35:13 +0200 Subject: [PATCH 2/4] Avoid sleeping the input thread if we don't have to. Improves latency slightly. Also fix mappings. --- Windows/HidInputDevice.cpp | 128 +++++++++++++++++++++++++++++++------ Windows/HidInputDevice.h | 11 ++-- Windows/InputDevice.cpp | 11 +++- Windows/InputDevice.h | 2 +- 4 files changed, 123 insertions(+), 29 deletions(-) diff --git a/Windows/HidInputDevice.cpp b/Windows/HidInputDevice.cpp index 54c3ecdba6..4e2defbc5f 100644 --- a/Windows/HidInputDevice.cpp +++ b/Windows/HidInputDevice.cpp @@ -1,3 +1,4 @@ +// This file in particular along with its header is public domain, use it for whatever you want. #include #include @@ -22,8 +23,8 @@ enum PSButton : u32 { PS_BTN_CIRCLE = 128, PS_BTN_L1 = (1 << 8), - PS_BTN_L2 = (1 << 9), - PS_BTN_R1 = (1 << 10), + 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), @@ -53,21 +54,56 @@ static const PSInputMapping g_psInputMappings[] = { {PS_DPAD_DOWN, NKCODE_DPAD_DOWN}, {PS_DPAD_LEFT, NKCODE_DPAD_LEFT}, {PS_DPAD_RIGHT, NKCODE_DPAD_RIGHT}, - {PS_BTN_SQUARE, NKCODE_BUTTON_X}, - {PS_BTN_TRIANGLE, NKCODE_BUTTON_Y}, - {PS_BTN_CIRCLE, NKCODE_BUTTON_B}, - {PS_BTN_CROSS, NKCODE_BUTTON_A}, + {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_START}, - {PS_BTN_OPTIONS, NKCODE_BUTTON_SELECT}, - {PS_BTN_L1, NKCODE_BUTTON_L1}, - {PS_BTN_R1, NKCODE_BUTTON_R1}, - {PS_BTN_L2, NKCODE_BUTTON_L2}, - {PS_BTN_R2, NKCODE_BUTTON_R2}, + {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}, }; +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}, +}; + struct PSControllerInfo { PSSubType type; u16 productId; @@ -98,7 +134,7 @@ static bool IsSonyGamepad(HANDLE handle, USHORT *pidOut, PSSubType *subType) { return false; } -HANDLE OpenFirstDualShockOrSense(PSSubType *subType) { +HANDLE OpenFirstDualShockOrSense(PSSubType *subType, int *reportSize) { GUID hidGuid; HidD_GetHidGuid(&hidGuid); @@ -124,6 +160,15 @@ HANDLE OpenFirstDualShockOrSense(PSSubType *subType) { USHORT pid; if (IsSonyGamepad(handle, &pid, subType)) { INFO_LOG(Log::UI, "Found Sony gamepad. PID: %04x", pid); + HIDP_CAPS caps; + PHIDP_PREPARSED_DATA preparsedData; + + HidD_GetPreparsedData(handle, &preparsedData); + HidP_GetCaps(preparsedData, &caps); + HidD_FreePreparsedData(preparsedData); + + *reportSize = caps.InputReportByteLength; + SetupDiDestroyDeviceInfoList(deviceInfoSet); return handle; } @@ -144,7 +189,6 @@ void HidInputDevice::AddSupportedDevices(std::set *deviceVIDPIDs) { } struct PSInputReportHeader { - u8 id; // not sure if useful. u8 lx; u8 ly; u8 rx; @@ -158,18 +202,39 @@ struct PSInputReportHeader { }; bool HidInputDevice::ReadDS4Input(HANDLE handle, PSControllerState *state) { - BYTE inputReport[64] = {0}; // 64-byte input report for DS4 + BYTE inputReport[64]{}; // 64-byte input report for DS4 DWORD bytesRead = 0; if (ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) { PSInputReportHeader hdr{}; static_assert(sizeof(hdr) < sizeof(inputReport)); - memcpy(&hdr, inputReport, sizeof(hdr)); + 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(&hdr, inputReport + offset, sizeof(hdr)); // Center the sticks. - state->lx = hdr.lx - 128; - state->rx = hdr.rx - 128; - state->ly = hdr.ly - 128; - state->ry = hdr.ry - 128; + state->stickAxes[PS_STICK_LX] = hdr.lx - 128; + state->stickAxes[PS_STICK_LY] = hdr.ly - 128; + state->stickAxes[PS_STICK_RX] = hdr.rx - 128; + state->stickAxes[PS_STICK_RY] = hdr.ry - 128; + + // Copy over the triggers. + state->triggerAxes[PS_TRIGGER_L2] = hdr.l2_analog; + state->triggerAxes[PS_TRIGGER_R2] = hdr.r2_analog; u32 buttons{}; hdr.buttons[2] &= 3; // Remove noise @@ -226,7 +291,7 @@ int HidInputDevice::UpdateState() { // Poll for controllers from time to time. if (pollCount_ == 0) { pollCount_ = POLL_FREQ; - HANDLE newController = OpenFirstDualShockOrSense(&subType_); + HANDLE newController = OpenFirstDualShockOrSense(&subType_, &reportSize_); if (newController) { controller_ = newController; } @@ -260,7 +325,28 @@ int HidInputDevice::UpdateState() { } } + 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); + } + } + prevState_ = state; + return UPDATESTATE_NO_SLEEP; // The ReadFile sleeps for us, effectively. } else { // might have been disconnected. retry later. ReleaseAllKeys(); diff --git a/Windows/HidInputDevice.h b/Windows/HidInputDevice.h index 53fd510945..ad90034c9f 100644 --- a/Windows/HidInputDevice.h +++ b/Windows/HidInputDevice.h @@ -1,3 +1,5 @@ +// This file in particular along with its cpp file is public domain, use it for whatever you want. + #pragma once #include "Windows/InputDevice.h" @@ -22,10 +24,9 @@ public: private: struct PSControllerState { // Analog sticks - s8 lx; - s8 ly; - s8 rx; - s8 ry; + s8 stickAxes[4]; // LX LY RX RY + // Analog triggers + u8 triggerAxes[2]; // Buttons u32 buttons; // Bitmask, PSButton enum }; @@ -38,7 +39,7 @@ private: HANDLE controller_; int pad_ = 0; int pollCount_ = 0; - + int reportSize_ = 0; enum { POLL_FREQ = 69, }; diff --git a/Windows/InputDevice.cpp b/Windows/InputDevice.cpp index 59126714a7..8e9d6b2785 100644 --- a/Windows/InputDevice.cpp +++ b/Windows/InputDevice.cpp @@ -36,17 +36,24 @@ void InputManager::InputThread() { // NOTE: The keyboard and mouse buttons are handled via raw input, not here. // This is mainly for controllers which need to be polled, instead of generating events. + bool noSleep = false; while (runThread_.load(std::memory_order_relaxed)) { if (focused_.load(std::memory_order_relaxed) || !g_Config.bGamepadOnlyFocused) { System_Notify(SystemNotification::POLL_CONTROLLERS); for (const auto &device : devices_) { - if (device->UpdateState() == InputDevice::UPDATESTATE_SKIP_PAD) + int state = device->UpdateState(); + if (state == InputDevice::UPDATESTATE_SKIP_PAD) break; + if (state == InputDevice::UPDATESTATE_NO_SLEEP) { + // Sleep was handled automatically. + noSleep = true; + } } } // Try to update 250 times per second. - Sleep(4); + if (!noSleep) + Sleep(4); } for (auto &device : devices_) { diff --git a/Windows/InputDevice.h b/Windows/InputDevice.h index ff28e65155..d774f2c704 100644 --- a/Windows/InputDevice.h +++ b/Windows/InputDevice.h @@ -31,7 +31,7 @@ public: virtual void Init() {} virtual void Shutdown() {} - enum { UPDATESTATE_SKIP_PAD = 0x1234}; + enum { UPDATESTATE_SKIP_PAD = 0x1234, UPDATESTATE_NO_SLEEP = 0x2345}; virtual int UpdateState() = 0; }; From 0e0fef2a32a528870c11208844b5ea9e308188b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 28 Jun 2025 18:46:01 +0200 Subject: [PATCH 3/4] Add some more defaults --- Core/KeyMapDefaults.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/KeyMapDefaults.cpp b/Core/KeyMapDefaults.cpp index dbe0d5b682..9d4693b955 100644 --- a/Core/KeyMapDefaults.cpp +++ b/Core/KeyMapDefaults.cpp @@ -276,8 +276,11 @@ static const DefMappingStruct defaultPadMap[] = { {VIRTKEY_AXIS_X_MAX, JOYSTICK_AXIS_X, +1}, {VIRTKEY_AXIS_Y_MIN, JOYSTICK_AXIS_Y, +1}, {VIRTKEY_AXIS_Y_MAX, JOYSTICK_AXIS_Y, -1}, + {VIRTKEY_PAUSE , NKCODE_BUTTON_THUMBL}, {VIRTKEY_PAUSE , NKCODE_BUTTON_SELECT }, {VIRTKEY_PAUSE , NKCODE_BUTTON_L2 }, + {VIRTKEY_PAUSE , JOYSTICK_AXIS_LTRIGGER, +1}, + {VIRTKEY_SPEED_ANALOG, JOYSTICK_AXIS_RTRIGGER, +1}, }; static const DefMappingStruct defaultPadMapIOS[] = { From fc09669ba725cd77f76a20f8f3d7bec22279739c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 3 Jul 2025 11:08:48 +0200 Subject: [PATCH 4/4] Add DualSense support. Remove trying to support bluetooth devices (not reliable without more work) --- Common/Math/CrossSIMD.h | 2 + Windows/HidInputDevice.cpp | 275 ++++++++++++++++++++++++++++++------- Windows/HidInputDevice.h | 9 +- 3 files changed, 230 insertions(+), 56 deletions(-) diff --git a/Common/Math/CrossSIMD.h b/Common/Math/CrossSIMD.h index b5050d75d6..b7eff221f9 100644 --- a/Common/Math/CrossSIMD.h +++ b/Common/Math/CrossSIMD.h @@ -1,6 +1,8 @@ // CrossSIMD // // This file will contain cross-instruction-set SIMD instruction wrappers. +// +// This specific file (and a future CrossSIMD.cpp) file is under public domain or MIT, unlike most of the rest of the emulator. #pragma once diff --git a/Windows/HidInputDevice.cpp b/Windows/HidInputDevice.cpp index 4e2defbc5f..c0adabd38e 100644 --- a/Windows/HidInputDevice.cpp +++ b/Windows/HidInputDevice.cpp @@ -10,10 +10,12 @@ #include "Common/TimeUtil.h" #include "Common/Log.h" #include "Common/Input/InputState.h" +#include "Common/Common.h" #include "Common/System/NativeApp.h" +#include "Common/System/OSD.h" enum PSButton : u32 { - PS_DPAD_UP = 1, + 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, @@ -34,16 +36,6 @@ enum PSButton : u32 { PS_BTN_TOUCHPAD = (1 << 17), }; -constexpr u16 SONY_VID = 0x054C; - -constexpr u16 DS4_PID = 0x05C4; -constexpr u16 DS5_PID = 0x0CE6; -constexpr u16 DS4_2_PID = 0x09CC; -constexpr u16 DS4_WIRELESS = 0x0BA0; -constexpr u16 DUALSENSE_WIRELESS = 0x0CE6; -constexpr u16 DUALSENSE_EDGE_WIRELESS = 0x0DF2; -constexpr u16 PS_CLASSIC = 0x0CDA; - struct PSInputMapping { PSButton button; InputKeyCode keyCode; @@ -105,27 +97,36 @@ static const PSTriggerMapping g_psTriggerMappings[] = { }; struct PSControllerInfo { - PSSubType type; + u16 vendorId; u16 productId; + PSSubType type; + const char *name; }; +constexpr u16 SONY_VID = 0x054C; + +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 PSControllerInfo g_psInfos[] = { - {TYPE_DS4, DS4_PID}, - {TYPE_DS5, DS5_PID}, - {TYPE_DS4, DS4_2_PID}, - {TYPE_DS4, DS4_WIRELESS}, - {TYPE_DS5, DUALSENSE_WIRELESS}, - {TYPE_DS5, DUALSENSE_EDGE_WIRELESS}, - {TYPE_DS4, PS_CLASSIC}, + {SONY_VID, 0x05C4, PSSubType::DS4, "DS4 v.1"}, + {SONY_VID, 0x09CC, PSSubType::DS4, "DS4 v.2"}, + {SONY_VID, 0x0CE6, PSSubType::DS5, "DualSense"}, + {SONY_VID, PS_CLASSIC, PSSubType::DS4, "PS Classic"}, + // {PSSubType::DS4, DS4_WIRELESS}, + // {PSSubType::DS5, DUALSENSE_WIRELESS}, + // {PSSubType::DS5, DUALSENSE_EDGE_WIRELESS}, }; -static bool IsSonyGamepad(HANDLE handle, USHORT *pidOut, PSSubType *subType) { +static bool IsSupportedGamepad(HANDLE handle, USHORT *pidOut, PSSubType *subType) { HIDD_ATTRIBUTES attr{sizeof(HIDD_ATTRIBUTES)}; if (!HidD_GetAttributes(handle, &attr)) { return false; } for (const auto &info : g_psInfos) { - if (attr.VendorID == SONY_VID && attr.ProductID == info.productId) { + if (attr.VendorID == info.vendorId && attr.ProductID == info.productId) { *pidOut = attr.ProductID; *subType = info.type; return true; @@ -134,6 +135,88 @@ static bool IsSonyGamepad(HANDLE handle, USHORT *pidOut, PSSubType *subType) { return false; } +struct DualSenseOutputReport{ + uint8_t reportId; + uint8_t flags1; + uint8_t flags2; + uint8_t headphone[4]; + uint8_t muteLED; + uint8_t micMute; +}; + +// 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 != 48) { + return false; + } + + // Output report (0x05) – sets lightbar, enables full report mode + uint8_t reportData[48] = {0}; + + DualSenseOutputReport report; + report.reportId = 2; + report.flags1 = 0x0C; + report.flags2 = 0x15; + report.muteLED = 1; + + memcpy(reportData, &report, sizeof(report)); + + DWORD written; + bool result = WriteFile(handle, reportData, sizeof(reportData), &written, NULL); + if (!result) { + u32 errorCode = GetLastError(); + + if (errorCode == ERROR_INVALID_PARAMETER) { + if (!HidD_SetOutputReport(handle, reportData, outReportSize)) { + errorCode = GetLastError(); + } + } + + WARN_LOG(Log::UI, "Failed initializing: %08x", errorCode); + return false; + } + 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); + +static bool InitializeDualShock(HANDLE handle, int outReportSize) { + if (outReportSize != 32) { + WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize); + return false; + } + + // DS4 USB output report (report ID 0x05) + // Total size: 32 bytes (must match device buffer size) + uint8_t report[32] = {0}; + + report[0] = 0x05; // Report ID (DS4 output) + report[1] = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc. + report[2] = 0x02; + // Rumble + report[4] = 0x00; // Right (weak) + report[5] = 0x00; // Left (strong) + + // Lightbar (RGB) + report[6] = 0x00; // Red + report[7] = 0x10; // Green + report[8] = 0x40; // Blue (dim blue) + + DWORD written; + return WriteFile(handle, report, sizeof(report), &written, NULL); +} + HANDLE OpenFirstDualShockOrSense(PSSubType *subType, int *reportSize) { GUID hidGuid; HidD_GetHidGuid(&hidGuid); @@ -155,11 +238,11 @@ HANDLE OpenFirstDualShockOrSense(PSSubType *subType, int *reportSize) { 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, 0, nullptr); + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle != INVALID_HANDLE_VALUE) { USHORT pid; - if (IsSonyGamepad(handle, &pid, subType)) { - INFO_LOG(Log::UI, "Found Sony gamepad. PID: %04x", pid); + if (IsSupportedGamepad(handle, &pid, subType)) { + INFO_LOG(Log::UI, "Found supported gamepad. PID: %04x", pid); HIDP_CAPS caps; PHIDP_PREPARSED_DATA preparsedData; @@ -168,9 +251,21 @@ HANDLE OpenFirstDualShockOrSense(PSSubType *subType, int *reportSize) { HidD_FreePreparsedData(preparsedData); *reportSize = caps.InputReportByteLength; + int outReportSize = caps.OutputReportByteLength; + + INFO_LOG(Log::UI, "Initializing gamepad. out report size=%d", outReportSize); + bool result; + if (*subType == PSSubType::DS5) { + result = InitializeDualSense(handle, outReportSize); + } else { + result = InitializeDualShock(handle, outReportSize); + } SetupDiDestroyDeviceInfoList(deviceInfoSet); + return handle; + } else { + DEBUG_LOG(Log::UI, "Skipping HID device: pid=%02x", pid); } CloseHandle(handle); } @@ -188,7 +283,24 @@ void HidInputDevice::AddSupportedDevices(std::set *deviceVIDPIDs) { } } -struct PSInputReportHeader { +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; @@ -201,12 +313,12 @@ struct PSInputReportHeader { // Then there's motion and all kinds of stuff. }; -bool HidInputDevice::ReadDS4Input(HANDLE handle, PSControllerState *state) { +bool HidInputDevice::ReadDualShockInput(HANDLE handle, PSControllerState *state) { BYTE inputReport[64]{}; // 64-byte input report for DS4 DWORD bytesRead = 0; if (ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) { - PSInputReportHeader hdr{}; - static_assert(sizeof(hdr) < sizeof(inputReport)); + DualShockInputReport report{}; + static_assert(sizeof(report) < sizeof(inputReport)); if (bytesRead < 14) { return false; } @@ -224,42 +336,94 @@ bool HidInputDevice::ReadDS4Input(HANDLE handle, PSControllerState *state) { } // const bool isBluetooth = (reportId == 0x11 || reportId == 0x31); - memcpy(&hdr, inputReport + offset, sizeof(hdr)); + memcpy(&report, inputReport + offset, sizeof(report)); // Center the sticks. - state->stickAxes[PS_STICK_LX] = hdr.lx - 128; - state->stickAxes[PS_STICK_LY] = hdr.ly - 128; - state->stickAxes[PS_STICK_RX] = hdr.rx - 128; - state->stickAxes[PS_STICK_RY] = hdr.ry - 128; + 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] = hdr.l2_analog; - state->triggerAxes[PS_TRIGGER_R2] = hdr.r2_analog; + state->triggerAxes[PS_TRIGGER_L2] = report.l2_analog; + state->triggerAxes[PS_TRIGGER_R2] = report.r2_analog; u32 buttons{}; - hdr.buttons[2] &= 3; // Remove noise - memcpy(&buttons, &hdr.buttons[0], 3); + 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; - - const u8 dpad = hdr.buttons[0] & 0xF; - 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; - } + buttons |= DecodeHatSwitch(report.buttons[0] & 0xF); state->buttons = buttons; return true; } else { + u32 error = GetLastError(); + return false; + } +} + +// So strange that this is different! +struct DualSenseInputReport { + u8 lx; + u8 ly; + u8 rx; + u8 ry; + + u8 l2_analog; + u8 r2_analog; + + u8 frameCounter; + + u8 buttons[3]; + + // TODO: More stuff (battery, tilt, etc). +}; + +bool HidInputDevice::ReadDualSenseInput(HANDLE handle, PSControllerState *state) { + BYTE inputReport[64]{}; // 64-byte input report for DS4 + DWORD bytesRead = 0; + if (ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) { + 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. + int offset = 1; + if (inputReport[0] != 1) { + // Wrong data + return false; + } + // 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; + + 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; + } else { + u32 error = GetLastError(); return false; } } @@ -302,7 +466,14 @@ int HidInputDevice::UpdateState() { if (controller_) { PSControllerState state{}; - if (ReadDS4Input(controller_, &state)) { + bool result; + if (subType_ == PSSubType::DS4) { + result = ReadDualShockInput(controller_, &state); + } else if (subType_ == PSSubType::DS5) { + result = ReadDualSenseInput(controller_, &state); + } + + if (result) { const InputDeviceID deviceID = DeviceID(pad_); // Process the input and generate input events. const u32 downMask = state.buttons & (~prevState_.buttons); diff --git a/Windows/HidInputDevice.h b/Windows/HidInputDevice.h index ad90034c9f..2c0ac61c43 100644 --- a/Windows/HidInputDevice.h +++ b/Windows/HidInputDevice.h @@ -5,9 +5,9 @@ #include "Windows/InputDevice.h" #include -enum PSSubType { - TYPE_DS4, - TYPE_DS5 +enum class PSSubType { + DS4, + DS5 }; enum InputDeviceID; @@ -30,7 +30,8 @@ private: // Buttons u32 buttons; // Bitmask, PSButton enum }; - bool ReadDS4Input(HANDLE handle, PSControllerState *state); + bool ReadDualShockInput(HANDLE handle, PSControllerState *state); + bool ReadDualSenseInput(HANDLE handle, PSControllerState *state); void ReleaseAllKeys(); InputDeviceID DeviceID(int pad); PSControllerState prevState_{};