From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/gamepad/gamepad.h | 11 ++ src/detection/gamepad/gamepad_apple.c | 48 +++++++ src/detection/gamepad/gamepad_bsd.c | 65 ++++++++++ src/detection/gamepad/gamepad_haiku.cpp | 18 +++ src/detection/gamepad/gamepad_linux.c | 97 ++++++++++++++ src/detection/gamepad/gamepad_nosupport.c | 5 + src/detection/gamepad/gamepad_windows.c | 201 ++++++++++++++++++++++++++++++ 7 files changed, 445 insertions(+) create mode 100644 src/detection/gamepad/gamepad.h create mode 100644 src/detection/gamepad/gamepad_apple.c create mode 100644 src/detection/gamepad/gamepad_bsd.c create mode 100644 src/detection/gamepad/gamepad_haiku.cpp create mode 100644 src/detection/gamepad/gamepad_linux.c create mode 100644 src/detection/gamepad/gamepad_nosupport.c create mode 100644 src/detection/gamepad/gamepad_windows.c (limited to 'src/detection/gamepad') diff --git a/src/detection/gamepad/gamepad.h b/src/detection/gamepad/gamepad.h new file mode 100644 index 0000000..f26def9 --- /dev/null +++ b/src/detection/gamepad/gamepad.h @@ -0,0 +1,11 @@ +#pragma once + +#include "fastfetch.h" + +typedef struct FFGamepadDevice { + FFstrbuf serial; + FFstrbuf name; + uint8_t battery; // 0-100% +} FFGamepadDevice; + +const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */); diff --git a/src/detection/gamepad/gamepad_apple.c b/src/detection/gamepad/gamepad_apple.c new file mode 100644 index 0000000..c262c86 --- /dev/null +++ b/src/detection/gamepad/gamepad_apple.c @@ -0,0 +1,48 @@ +#include "gamepad.h" +#include "common/apple/cf_helpers.h" +#include "common/mallocHelper.h" + +#include +#include + +static void enumSet(IOHIDDeviceRef value, FFlist* results) { + FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *results); + ffStrbufInit(&device->serial); + ffStrbufInit(&device->name); + device->battery = 0; + + CFStringRef manufacturer = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDManufacturerKey)); + ffCfStrGetString(manufacturer, &device->name); + + CFStringRef product = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDProductKey)); + if (device->name.length) { + ffCfStrGetString(product, &device->serial); + ffStrbufAppendC(&device->name, ' '); + ffStrbufAppend(&device->name, &device->serial); + } else { + ffCfStrGetString(product, &device->name); + } + + CFStringRef serialNumber = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDSerialNumberKey)); + ffCfStrGetString(serialNumber, &device->serial); +} + +const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { + IOHIDManagerRef FF_CFTYPE_AUTO_RELEASE manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { + return "IOHIDManagerOpen() failed"; + } + + CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching1 = CFDictionaryCreate(kCFAllocatorDefault, (const void**) (CFStringRef[]) { CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) }, (const void**) (CFNumberRef[]) { ffCfCreateInt(kHIDPage_GenericDesktop), ffCfCreateInt(kHIDUsage_GD_Joystick) }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching2 = CFDictionaryCreate(kCFAllocatorDefault, (const void**) (CFStringRef[]) { CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) }, (const void**) (CFNumberRef[]) { ffCfCreateInt(kHIDPage_GenericDesktop), ffCfCreateInt(kHIDUsage_GD_GamePad) }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFArrayRef FF_CFTYPE_AUTO_RELEASE matchings = CFArrayCreate(kCFAllocatorDefault, (const void**) (CFTypeRef[]) { matching1, matching2 }, 2, &kCFTypeArrayCallBacks); + IOHIDManagerSetDeviceMatchingMultiple(manager, matchings); + + CFSetRef FF_CFTYPE_AUTO_RELEASE set = IOHIDManagerCopyDevices(manager); + if (set) { + CFSetApplyFunction(set, (CFSetApplierFunction) &enumSet, devices); + } + IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); + + return NULL; +} diff --git a/src/detection/gamepad/gamepad_bsd.c b/src/detection/gamepad/gamepad_bsd.c new file mode 100644 index 0000000..b67b8fd --- /dev/null +++ b/src/detection/gamepad/gamepad_bsd.c @@ -0,0 +1,65 @@ +#include "gamepad.h" +#include "common/io.h" + +#include +#include +#include + +#if __has_include() + #include // FreeBSD +#else + #include // DragonFly +#endif + +#define MAX_UHID_JOYS 64 + +const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { + char path[16]; + for (int i = 0; i < MAX_UHID_JOYS; i++) { + snprintf(path, ARRAY_SIZE(path), "/dev/uhid%d", i); + FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + if (errno == ENOENT) { + break; // No more devices + } + continue; // Device not found + } + report_desc_t repDesc = hid_get_report_desc(fd); + if (!repDesc) { + continue; + } + + int reportId = hid_get_report_id(fd); + + struct hid_data* hData = hid_start_parse(repDesc, 0, reportId); + if (hData) { + struct hid_item hItem; + while (hid_get_item(hData, &hItem) > 0) { + if (HID_PAGE(hItem.usage) != 1) { + continue; + } + switch (HID_USAGE(hItem.usage)) { + case 1: // Pointer. FreeBSD returns 1 for my Pro Controller for some reason + case 4: // Joystick + case 5: // Gamepad + break; + default: + continue; + } + + struct usb_device_info di; + if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1) { + FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *devices); + ffStrbufInitS(&device->serial, di.udi_serial); + ffStrbufInitF(&device->name, "%s %s", di.udi_vendor, di.udi_product); + device->battery = 0; + } + } + hid_end_parse(hData); + } + + hid_dispose_report_desc(repDesc); + } + + return NULL; +} diff --git a/src/detection/gamepad/gamepad_haiku.cpp b/src/detection/gamepad/gamepad_haiku.cpp new file mode 100644 index 0000000..50b43a0 --- /dev/null +++ b/src/detection/gamepad/gamepad_haiku.cpp @@ -0,0 +1,18 @@ +extern "C" { +#include "gamepad.h" +} +#include + +const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { + BJoystick js; + for (int32 i = 0, n = js.CountDevices(); i < n; ++i) { + char name[B_OS_NAME_LENGTH]; + if (js.GetDeviceName(i, name) == B_OK) { + FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *devices); + ffStrbufInit(&device->serial); + ffStrbufInitS(&device->name, name); + device->battery = 0; + } + } + return NULL; +} diff --git a/src/detection/gamepad/gamepad_linux.c b/src/detection/gamepad/gamepad_linux.c new file mode 100644 index 0000000..6bb4ee2 --- /dev/null +++ b/src/detection/gamepad/gamepad_linux.c @@ -0,0 +1,97 @@ +#include "gamepad.h" +#include "common/io.h" +#include "common/strutil.h" + +static void detectGamepad(FFlist* devices, FFstrbuf* name, FFstrbuf* path) { + uint32_t baseLen = path->length; + FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *devices); + ffStrbufInit(&device->serial); + ffStrbufInitMove(&device->name, name); + device->battery = 0; + + ffStrbufAppendS(path, "uniq"); + if (ffAppendFileBuffer(path->chars, &device->serial)) { + ffStrbufTrimRightSpace(&device->serial); + } + + ffStrbufSubstrBefore(path, baseLen); + ffStrbufAppendS(path, "device/power_supply/"); // /sys/class/input/jsX/device/device/power_supply + + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(path->chars); + if (dirp) { + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.' || (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)) { + continue; + } + ffStrbufAppendS(path, entry->d_name); + ffStrbufAppendS(path, "/capacity"); // /sys/class/input/jsX/device/device/power_supply/XXX/capacity + char capacity[32]; + ssize_t nRead = ffReadFileData(path->chars, ARRAY_SIZE(capacity) - 1, capacity); + if (nRead > 0) // Tested with a PS4 controller + { + capacity[nRead] = '\0'; + device->battery = (uint8_t) strtoul(capacity, NULL, 10); + break; + } + + ffStrbufAppendS(path, "_level"); + nRead = ffReadFileData(path->chars, ARRAY_SIZE(capacity) - 1, capacity); + if (nRead > 0) // Tested with a NS Pro controller + { + // https://github.com/torvalds/linux/blob/52b1853b080a082ec3749c3a9577f6c71b1d4a90/drivers/power/supply/power_supply_sysfs.c#L124 + switch (capacity[0]) { + case 'C': + device->battery = 1; + break; // Critical + case 'L': + device->battery = 25; + break; // Low + case 'N': + device->battery = 50; + break; // Normal + case 'H': + device->battery = 75; + break; // High + case 'F': + device->battery = 100; + break; // Full + } + } + } + } +} + +const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { + FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/input/"); + if (dirp == NULL) { + return "opendir(\"/sys/class/input/\") == NULL"; + } + + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS("/sys/class/input/"); + uint32_t baseLen = path.length; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (!ffStrStartsWith(entry->d_name, "js")) { + continue; + } + if (!ffCharIsDigit(entry->d_name[strlen("js")])) { + continue; + } + + ffStrbufAppendS(&path, entry->d_name); + ffStrbufAppendS(&path, "/device/name"); + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + if (ffAppendFileBuffer(path.chars, &name)) { + ffStrbufTrimRightSpace(&name); + ffStrbufSubstrBefore(&path, path.length - 4); + detectGamepad(devices, &name, &path); + } + + ffStrbufSubstrBefore(&path, baseLen); + } + + return NULL; +} diff --git a/src/detection/gamepad/gamepad_nosupport.c b/src/detection/gamepad/gamepad_nosupport.c new file mode 100644 index 0000000..c0d791e --- /dev/null +++ b/src/detection/gamepad/gamepad_nosupport.c @@ -0,0 +1,5 @@ +#include "gamepad.h" + +const char* ffDetectGamepad(FF_A_UNUSED FFlist* devices /* List of FFGamepadDevice */) { + return "Not supported on this platform"; +} 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 +#include + +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; +} -- cgit v1.2.3