diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/gamepad/gamepad_windows.c | |
Add the files
Diffstat (limited to 'src/detection/gamepad/gamepad_windows.c')
| -rw-r--r-- | src/detection/gamepad/gamepad_windows.c | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/detection/gamepad/gamepad_windows.c b/src/detection/gamepad/gamepad_windows.c new file mode 100644 index 0000000..9cbb561 --- /dev/null +++ b/src/detection/gamepad/gamepad_windows.c @@ -0,0 +1,201 @@ +#include "gamepad.h" +#include "common/io.h" +#include "common/mallocHelper.h" +#include "common/windows/unicode.h" + +#include <windows.h> +#include <hidsdi.h> + +static const char* detectKnownDeviceName(uint32_t vendorId, uint32_t productId) { + switch (vendorId) { + // Nintendo + case 0x057E: { + switch (productId) { + case 0x2006: + return "Nintendo Switch Joycon L"; + case 0x2007: + return "Nintendo Switch Joycon R"; + case 0x2009: + return "Nintendo Switch Pro Controller"; + case 0x200E: + return "Nintendo Switch Charging Grip"; + case 0x2017: + return "Nintendo Switch SNES Controller"; + + default: + return NULL; + } + } + + // Sony + case 0x054C: { + switch (productId) { + case 0x0268: + return "Sony DualShock 3 / Six Axis"; + + case 0x05C4: + return "Sony DualShock 4 Gen1"; + case 0x09CC: + return "Sony DualShock 4 Gen2"; + case 0x0BA0: + return "Sony DualShock 4 USB receiver"; + + case 0x0CE6: + return "Sony DualSense"; + case 0x0DF2: + return "Sony DualSense Edge"; + + default: + return NULL; + } + } + + // Logitech + case 0x046D: { + switch (productId) { + case 0xC216: + return "Logitech F310, DirectInput"; + case 0xC218: + return "Logitech F510, DirectInput"; + case 0xC219: + return "Logitech F710, DirectInput"; + case 0xC21D: + return "Logitech F310"; + case 0xC21E: + return "Logitech F510"; + case 0xC21F: + return "Logitech F710"; + + default: + return NULL; + } + } + + case 0x045E: // Microsoft Xbox compatible controllers should be handled by Windows without problems + default: + return NULL; + } +} + +const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { + UINT nDevices = 0; + if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST))) { + return "GetRawInputDeviceList(NULL) failed"; + } + if (nDevices == 0) { + return "No HID devices found"; + } + RAWINPUTDEVICELIST* FF_AUTO_FREE pRawInputDeviceList = (RAWINPUTDEVICELIST*) malloc(sizeof(RAWINPUTDEVICELIST) * nDevices); + if ((nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT) -1) { + return "GetRawInputDeviceList(pRawInputDeviceList) failed"; + } + + for (UINT i = 0; i < nDevices; ++i) { + if (pRawInputDeviceList[i].dwType != RIM_TYPEHID) { + continue; + } + + HANDLE hDevice = pRawInputDeviceList[i].hDevice; + + RID_DEVICE_INFO rdi; + UINT rdiSize = sizeof(rdi); + if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == (UINT) -1) { + continue; + } + + if (rdi.hid.usUsagePage != 1 || (rdi.hid.usUsage != 4 /*Joystick*/ && rdi.hid.usUsage != 5 /*Gamepad*/)) { + continue; + } + + WCHAR devName[MAX_PATH] = L""; + UINT nameSize = MAX_PATH; + if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICENAME, devName, &nameSize) == (UINT) -1) { + continue; + } + + FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *devices); + ffStrbufInit(&device->serial); + ffStrbufInit(&device->name); + device->battery = 0; + + const char* knownGamepad = detectKnownDeviceName(rdi.hid.dwVendorId, rdi.hid.dwProductId); + if (knownGamepad) { + ffStrbufSetS(&device->name, knownGamepad); + } + HANDLE FF_AUTO_CLOSE_FD hHidFile = CreateFileW(devName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (hHidFile == INVALID_HANDLE_VALUE) { + if (!knownGamepad) { + ffStrbufSetF(&device->name, "Unknown gamepad %04X-%04X", (unsigned) rdi.hid.dwVendorId, (unsigned) rdi.hid.dwProductId); + } + continue; + } + + if (!knownGamepad) { + wchar_t displayName[126]; + if (HidD_GetProductString(hHidFile, displayName, sizeof(displayName) /*in bytes*/)) { + wchar_t manufacturer[126]; + if (HidD_GetManufacturerString(hHidFile, manufacturer, sizeof(manufacturer) /*in bytes*/)) { + ffStrbufSetWS(&device->name, manufacturer); + FF_STRBUF_AUTO_DESTROY displayNameStr = ffStrbufCreateWS(displayName); + ffStrbufAppendC(&device->name, ' '); + ffStrbufAppend(&device->name, &displayNameStr); + } else { + ffStrbufSetWS(&device->name, displayName); + } + } + } + + wchar_t serialNumber[127] = L""; + if (HidD_GetSerialNumberString(hHidFile, serialNumber, sizeof(serialNumber) /*in bytes*/)) { + ffStrbufSetWS(&device->serial, serialNumber); + } + + PHIDP_PREPARSED_DATA preparsedData = NULL; + if (HidD_GetPreparsedData(hHidFile, &preparsedData)) { + HIDP_CAPS caps; + NTSTATUS capsResult = HidP_GetCaps(preparsedData, &caps); + HidD_FreePreparsedData(preparsedData); + if (!NT_SUCCESS(capsResult)) { + continue; + } + + if ( + (rdi.hid.dwVendorId == 0x054C && (rdi.hid.dwProductId == 0x05C4 || // PS4 Gen1 + rdi.hid.dwProductId == 0x09CC // PS4 Gen2 + )) || + (rdi.hid.dwVendorId == 0x057E && (rdi.hid.dwProductId == 0x2009 // NS Pro + ))) { + // Controller must be connected by other programs + FF_AUTO_FREE uint8_t* reportBuffer = malloc(caps.InputReportByteLength); + OVERLAPPED overlapped = {}; + DWORD nBytes; + if (ReadFile(hHidFile, reportBuffer, caps.InputReportByteLength, &nBytes, &overlapped) || + GetOverlappedResultEx(hHidFile, &overlapped, &nBytes, FF_IO_TERM_RESP_WAIT_MS, TRUE)) { + if (rdi.hid.dwVendorId == 0x054C) { + if (nBytes > 31) { + uint8_t batteryInfo = reportBuffer[caps.InputReportByteLength == 64 /*USB?*/ ? 30 : 32]; + device->battery = (uint8_t) ((batteryInfo & 0x0f) * 100 / (batteryInfo & 0x10 /*charging?*/ ? 11 /*BATTERY_MAX_USB*/ : 8 /*BATTERY_MAX*/)); + if (device->battery > 100) { + device->battery = 100; + } + } + } else { + if (nBytes > 3 && reportBuffer[0] == 0x30) { + uint8_t batteryInfo = reportBuffer[2]; + device->battery = (uint8_t) (((batteryInfo & 0xE0) >> 4) * 100 / 8); + if (device->battery == 0) { + device->battery = 1; + } else if (device->battery > 100) { + device->battery = 100; + } + } + } + } else { + CancelIo(hHidFile); + } + } + } + } + + return NULL; +} |