summaryrefslogtreecommitdiffstats
path: root/src/detection/gamepad/gamepad_windows.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/gamepad/gamepad_windows.c')
-rw-r--r--src/detection/gamepad/gamepad_windows.c201
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;
+}