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; };