From bbcc205de97a1b3e53257af2a77f5f5d3d59c4f8 Mon Sep 17 00:00:00 2001
From: tmkk <[EMAIL REDACTED]>
Date: Sun, 22 Feb 2026 14:35:33 +0900
Subject: [PATCH] Detect steam virtual gamepads more accurately on macOS
---
src/hidapi/mac/hid.c | 17 ++++++++++++++++-
src/joystick/SDL_gamepad.c | 6 ++++++
src/joystick/darwin/SDL_iokitjoystick.c | 11 +++++++++++
3 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c
index 8b9645ab90225..e58b9a990f9d4 100644
--- a/src/hidapi/mac/hid.c
+++ b/src/hidapi/mac/hid.c
@@ -573,6 +573,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev,
{
unsigned short dev_vid;
unsigned short dev_pid;
+ unsigned short dev_version;
int BUF_LEN = 256;
wchar_t buf[BUF_LEN];
CFTypeRef transport_prop;
@@ -593,6 +594,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev,
dev_vid = get_vendor_id(dev);
dev_pid = get_product_id(dev);
+ dev_version = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));
#ifdef HIDAPI_IGNORE_DEVICE
/* See if there are any devices we should skip in enumeration */
@@ -602,6 +604,19 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev,
}
#endif
+#ifdef HIDAPI_USING_SDL_RUNTIME
+ if (IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDVirtualHIDevice)) == kCFBooleanTrue) {
+ /* Steam virtual gamepads always have kIOHIDVirtualHIDevice property unlike real devices */
+ if (SDL_IsJoystickSteamVirtualGamepad(dev_vid, dev_pid, dev_version)) {
+ const char *allow_steam_virtual_gamepad = SDL_getenv_unsafe("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD");
+ if (!SDL_GetStringBoolean(allow_steam_virtual_gamepad, false)) {
+ free(cur_dev);
+ return NULL;
+ }
+ }
+ }
+#endif
+
cur_dev->usage_page = usage_page;
cur_dev->usage = usage;
@@ -649,7 +664,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev,
cur_dev->product_id = dev_pid;
/* Release Number */
- cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));
+ cur_dev->release_number = dev_version;
/* Interface Number.
* We can only retrieve the interface number for USB HID devices.
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 70dce263def22..f4ba4bf1a70b6 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -3256,6 +3256,11 @@ bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version
}
}
+#ifdef SDL_PLATFORM_MACOS
+ // On macOS do nothing here since we detect Steam virtual gamepads
+ // in IOKit HID backends to ensure accuracy.
+ // See joystick/darwin/SDL_iokitjoystick.c and hidapi/mac/hid.c.
+#else
const char *hint = SDL_getenv_unsafe("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD");
bool allow_steam_virtual_gamepad = SDL_GetStringBoolean(hint, false);
#ifdef SDL_PLATFORM_WIN32
@@ -3271,6 +3276,7 @@ bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version
if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {
return !allow_steam_virtual_gamepad;
}
+#endif
if (SDL_allowed_gamepads.num_included_entries > 0) {
if (SDL_VIDPIDInList(vendor_id, product_id, &SDL_allowed_gamepads)) {
diff --git a/src/joystick/darwin/SDL_iokitjoystick.c b/src/joystick/darwin/SDL_iokitjoystick.c
index 75e7cc5201bfc..7e36391f42012 100644
--- a/src/joystick/darwin/SDL_iokitjoystick.c
+++ b/src/joystick/darwin/SDL_iokitjoystick.c
@@ -28,6 +28,7 @@
#include "../hidapi/SDL_hidapijoystick_c.h"
#include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging
#include "../usb_ids.h"
+#include "../../SDL_hints_c.h"
#include <IOKit/IOKitLib.h>
#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
@@ -515,6 +516,16 @@ static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
return false;
}
+ if (IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVirtualHIDevice)) == kCFBooleanTrue) {
+ // Steam virtual gamepads always have kIOHIDVirtualHIDevice property unlike real devices
+ if (SDL_IsJoystickSteamVirtualGamepad(vendor, product, version)) {
+ const char *allow_steam_virtual_gamepad = SDL_getenv_unsafe("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD");
+ if (!SDL_GetStringBoolean(allow_steam_virtual_gamepad, false)) {
+ return false;
+ }
+ }
+ }
+
// get device name
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) {