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/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[] = { 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..c0adabd38e --- /dev/null +++ b/Windows/HidInputDevice.cpp @@ -0,0 +1,530 @@ +// This file in particular along with its header is public domain, use it for whatever you want. + +#include +#include +#include +#include +#include + +#include "Windows/HidInputDevice.h" +#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, // 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), +}; + +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_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}, +}; + +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 { + 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[] = { + {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 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 == info.vendorId && attr.ProductID == info.productId) { + *pidOut = attr.ProductID; + *subType = info.type; + return true; + } + } + 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); + + 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, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle != INVALID_HANDLE_VALUE) { + USHORT pid; + if (IsSupportedGamepad(handle, &pid, subType)) { + INFO_LOG(Log::UI, "Found supported 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; + 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); + } + } + } + + 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); + } +} + +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 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)) { + 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; + + 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; + } 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; + } +} + +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_, &reportSize_); + if (newController) { + controller_ = newController; + } + } else { + pollCount_--; + } + } + + if (controller_) { + PSControllerState 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); + 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); + } + } + + 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(); + 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..2c0ac61c43 --- /dev/null +++ b/Windows/HidInputDevice.h @@ -0,0 +1,47 @@ +// This file in particular along with its cpp file is public domain, use it for whatever you want. + +#pragma once + +#include "Windows/InputDevice.h" +#include + +enum class PSSubType { + DS4, + 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 stickAxes[4]; // LX LY RX RY + // Analog triggers + u8 triggerAxes[2]; + // Buttons + u32 buttons; // Bitmask, PSButton enum + }; + bool ReadDualShockInput(HANDLE handle, PSControllerState *state); + bool ReadDualSenseInput(HANDLE handle, PSControllerState *state); + void ReleaseAllKeys(); + InputDeviceID DeviceID(int pad); + PSControllerState prevState_{}; + + PSSubType subType_{}; + 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; }; 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.