From c49fa37c5bd0b29522f5b41885ea576134b42100 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 9 Aug 2017 11:59:29 -0700 Subject: [PATCH] Added SDL hints to filter the set of game controllers reported by SDL --- include/SDL_hints.h | 32 +++- src/joystick/SDL_gamecontroller.c | 203 +++++++++++++++++++--- src/joystick/SDL_joystick.c | 10 +- src/joystick/SDL_joystick_c.h | 11 ++ src/joystick/darwin/SDL_sysjoystick.c | 6 + src/joystick/linux/SDL_sysjoystick.c | 4 + src/joystick/windows/SDL_dinputjoystick.c | 6 + src/joystick/windows/SDL_xinputjoystick.c | 6 + 8 files changed, 242 insertions(+), 36 deletions(-) diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 352029e7e4a7d..b9b1e3ee11bec 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -359,7 +359,6 @@ extern "C" { */ #define SDL_HINT_ACCELEROMETER_AS_JOYSTICK "SDL_ACCELEROMETER_AS_JOYSTICK" - /** * \brief A variable that lets you disable the detection and use of Xinput gamepad devices * @@ -369,7 +368,6 @@ extern "C" { */ #define SDL_HINT_XINPUT_ENABLED "SDL_XINPUT_ENABLED" - /** * \brief A variable that causes SDL to use the old axis and button mapping for XInput devices. * @@ -379,9 +377,8 @@ extern "C" { */ #define SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING "SDL_XINPUT_USE_OLD_JOYSTICK_MAPPING" - /** - * \brief A variable that lets you manually hint extra gamecontroller db entries + * \brief A variable that lets you manually hint extra gamecontroller db entries. * * The variable should be newline delimited rows of gamecontroller config data, see SDL_gamecontroller.h * @@ -390,6 +387,31 @@ extern "C" { */ #define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG" +/** + * \brief A variable containing a list of devices to skip when scanning for game controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES "SDL_GAMECONTROLLER_IGNORE_DEVICES" + +/** + * \brief If set, all devices will be skipped when scanning for game controllers except for the ones listed in this variable. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT" /** * \brief A variable that lets you enable joystick (and gamecontroller) events even when your app is in the background. @@ -404,7 +426,6 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS" - /** * \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it. * This is a debugging aid for developers and not expected to be used by end users. The default is "1" @@ -415,7 +436,6 @@ extern "C" { */ #define SDL_HINT_ALLOW_TOPMOST "SDL_ALLOW_TOPMOST" - /** * \brief A variable that controls the timer resolution, in milliseconds. * diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c index 172f3d0f62268..613e48c47d0ff 100644 --- a/src/joystick/SDL_gamecontroller.c +++ b/src/joystick/SDL_gamecontroller.c @@ -112,6 +112,74 @@ struct _SDL_GameController }; +typedef struct +{ + int num_entries; + int max_entries; + Uint32 *entries; +} SDL_vidpid_list; + +static SDL_vidpid_list SDL_allowed_controllers; +static SDL_vidpid_list SDL_ignored_controllers; + +static void +SDL_LoadVIDPIDListFromHint(const char *hint, SDL_vidpid_list *list) +{ + Uint32 entry; + char *spot; + char *file = NULL; + + list->num_entries = 0; + + if (hint && *hint == '@') { + spot = file = (char *)SDL_LoadFile(hint+1, NULL); + } else { + spot = (char *)hint; + } + + if (!spot) { + return; + } + + while ((spot = SDL_strstr(spot, "0x")) != NULL) { + entry = SDL_strtol(spot, &spot, 0); + entry <<= 16; + spot = SDL_strstr(spot, "0x"); + if (!spot) { + break; + } + entry |= SDL_strtol(spot, &spot, 0); + + if (list->num_entries == list->max_entries) { + int max_entries = list->max_entries + 16; + Uint32 *entries = (Uint32 *)SDL_realloc(list->entries, max_entries*sizeof(*list->entries)); + if (entries == NULL) { + /* Out of memory, go with what we have already */ + break; + } + list->entries = entries; + list->max_entries = max_entries; + } + list->entries[list->num_entries++] = entry; + } + + if (file) { + SDL_free(file); + } +} + +static void +SDL_GameControllerIgnoreDevicesChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_LoadVIDPIDListFromHint(hint, &SDL_ignored_controllers); +} + +static void +SDL_GameControllerIgnoreDevicesExceptChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_LoadVIDPIDListFromHint(hint, &SDL_allowed_controllers); +} + static int SDL_PrivateGameControllerAxis(SDL_GameController * gamecontroller, SDL_GameControllerAxis axis, Sint16 value); static int SDL_PrivateGameControllerButton(SDL_GameController * gamecontroller, SDL_GameControllerButton button, Uint8 state); @@ -799,50 +867,51 @@ SDL_PrivateAddMappingForGUID(SDL_JoystickGUID jGUID, const char *mappingString, /* * Helper function to determine pre-calculated offset to certain joystick mappings */ -static ControllerMapping_t *SDL_PrivateGetControllerMapping(int device_index) +static ControllerMapping_t *SDL_PrivateGetControllerMappingForNameAndGUID(const char *name, SDL_JoystickGUID guid) { - SDL_JoystickGUID jGUID = SDL_JoystickGetDeviceGUID(device_index); ControllerMapping_t *mapping; - (void) s_pEmscriptenMapping; /* pacify ARMCC */ - - mapping = SDL_PrivateGetControllerMappingForGUID(&jGUID); -#if SDL_JOYSTICK_XINPUT - if (!mapping && SDL_SYS_IsXInputGamepad_DeviceIndex(device_index)) { - mapping = s_pXInputMapping; - } -#endif + mapping = SDL_PrivateGetControllerMappingForGUID(&guid); #if defined(SDL_JOYSTICK_EMSCRIPTEN) if (!mapping && s_pEmscriptenMapping) { mapping = s_pEmscriptenMapping; } +#else + (void) s_pEmscriptenMapping; /* pacify ARMCC */ #endif #ifdef __LINUX__ - if (!mapping) { - const char *name = SDL_JoystickNameForIndex(device_index); - if (name) { - if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) { - /* The Linux driver xpad.c maps the wireless dpad to buttons */ - SDL_bool existing; - mapping = SDL_PrivateAddMappingForGUID(jGUID, + if (!mapping && name) { + if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) { + /* The Linux driver xpad.c maps the wireless dpad to buttons */ + SDL_bool existing; + mapping = SDL_PrivateAddMappingForGUID(jGUID, "none,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT); - } + &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT); } } #endif /* __LINUX__ */ - if (!mapping) { - const char *name = SDL_JoystickNameForIndex(device_index); - if (name) { - if (SDL_strstr(name, "Xbox") || SDL_strstr(name, "X-Box")) { - mapping = s_pXInputMapping; - } + if (!mapping && name) { + if (SDL_strstr(name, "Xbox") || SDL_strstr(name, "X-Box")) { + mapping = s_pXInputMapping; } } return mapping; } +static ControllerMapping_t *SDL_PrivateGetControllerMapping(int device_index) +{ + const char *name = SDL_JoystickNameForIndex(device_index); + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(device_index); + ControllerMapping_t *mapping = SDL_PrivateGetControllerMappingForNameAndGUID(name, guid); +#if SDL_JOYSTICK_XINPUT + if (!mapping && SDL_SYS_IsXInputGamepad_DeviceIndex(device_index)) { + mapping = s_pXInputMapping; + } +#endif + return mapping; +} + /* * Add or update an entry into the Mappings Database */ @@ -1092,7 +1161,7 @@ SDL_GameControllerLoadHints() * Initialize the game controller system, mostly load our DB of controller config mappings */ int -SDL_GameControllerInit(void) +SDL_GameControllerInitMappings(void) { int i = 0; const char *pMappingString = NULL; @@ -1107,6 +1176,19 @@ SDL_GameControllerInit(void) /* load in any user supplied config */ SDL_GameControllerLoadHints(); + SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, + SDL_GameControllerIgnoreDevicesChanged, NULL); + SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, + SDL_GameControllerIgnoreDevicesExceptChanged, NULL); + + return (0); +} + +int +SDL_GameControllerInit(void) +{ + int i; + /* watch for joy events and fire controller ones if needed */ SDL_AddEventWatch(SDL_GameControllerEventWatcher, NULL); @@ -1138,6 +1220,19 @@ SDL_GameControllerNameForIndex(int device_index) } +/* + * Return 1 if the joystick with this name and GUID is a supported controller + */ +SDL_bool +SDL_IsGameControllerNameAndGUID(const char *name, SDL_JoystickGUID guid) +{ + ControllerMapping_t *pSupportedController = SDL_PrivateGetControllerMappingForNameAndGUID(name, guid); + if (pSupportedController) { + return SDL_TRUE; + } + return SDL_FALSE; +} + /* * Return 1 if the joystick at this device index is a supported controller */ @@ -1151,6 +1246,41 @@ SDL_IsGameController(int device_index) return SDL_FALSE; } +/* + * Return 1 if the game controller should be ignored by SDL + */ +SDL_bool SDL_ShouldIgnoreGameController(const char *name, SDL_JoystickGUID guid) +{ + int i; + Uint16 vendor; + Uint16 product; + Uint32 vidpid; + + if (SDL_allowed_controllers.num_entries == 0 && + SDL_ignored_controllers.num_entries == 0) { + return SDL_FALSE; + } + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL); + vidpid = MAKE_VIDPID(vendor, product); + + if (SDL_allowed_controllers.num_entries > 0) { + for (i = 0; i < SDL_allowed_controllers.num_entries; ++i) { + if (vidpid == SDL_allowed_controllers.entries[i]) { + return SDL_FALSE; + } + } + return SDL_TRUE; + } else { + for (i = 0; i < SDL_ignored_controllers.num_entries; ++i) { + if (vidpid == SDL_ignored_controllers.entries[i]) { + return SDL_TRUE; + } + } + return SDL_FALSE; + } +} + /* * Open a controller for use - the index passed as an argument refers to * the N'th controller on the system. This index is the value which will @@ -1536,14 +1666,18 @@ SDL_GameControllerClose(SDL_GameController * gamecontroller) void SDL_GameControllerQuit(void) { - ControllerMapping_t *pControllerMap; - SDL_LockJoystickList(); while (SDL_gamecontrollers) { SDL_gamecontrollers->ref_count = 1; SDL_GameControllerClose(SDL_gamecontrollers); } SDL_UnlockJoystickList(); +} + +void +SDL_GameControllerQuitMappings(void) +{ + ControllerMapping_t *pControllerMap; while (s_pSupportedControllers) { pControllerMap = s_pSupportedControllers; @@ -1555,6 +1689,19 @@ SDL_GameControllerQuit(void) SDL_DelEventWatch(SDL_GameControllerEventWatcher, NULL); + SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, + SDL_GameControllerIgnoreDevicesChanged, NULL); + SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, + SDL_GameControllerIgnoreDevicesExceptChanged, NULL); + + if (SDL_allowed_controllers.entries) { + SDL_free(SDL_allowed_controllers.entries); + SDL_zero(SDL_allowed_controllers); + } + if (SDL_ignored_controllers.entries) { + SDL_free(SDL_ignored_controllers.entries); + SDL_zero(SDL_ignored_controllers); + } } /* diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 4bff7e084ce98..d7dbd96ece8a7 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -70,6 +70,8 @@ SDL_JoystickInit(void) { int status; + SDL_GameControllerInitMappings(); + /* Create the joystick list lock */ if (!SDL_joystick_lock) { SDL_joystick_lock = SDL_CreateMutex(); @@ -561,10 +563,15 @@ SDL_JoystickQuit(void) SDL_QuitSubSystem(SDL_INIT_EVENTS); #endif + SDL_DelHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, + SDL_JoystickAllowBackgroundEventsChanged, NULL); + if (SDL_joystick_lock) { SDL_DestroyMutex(SDL_joystick_lock); SDL_joystick_lock = NULL; } + + SDL_GameControllerQuitMappings(); } @@ -930,7 +937,7 @@ SDL_JoystickEventState(int state) #endif /* SDL_EVENTS_DISABLED */ } -static void SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version) +void SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version) { Uint16 *guid16 = (Uint16 *)guid.data; @@ -1273,5 +1280,4 @@ SDL_JoystickPowerLevel SDL_JoystickCurrentPowerLevel(SDL_Joystick * joystick) return joystick->epowerlevel; } - /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index c7bb5fe856a5a..5339b83780941 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -28,6 +28,8 @@ extern int SDL_JoystickInit(void); extern void SDL_JoystickQuit(void); /* Initialization and shutdown functions */ +extern int SDL_GameControllerInitMappings(void); +extern void SDL_GameControllerQuitMappings(void); extern int SDL_GameControllerInit(void); extern void SDL_GameControllerQuit(void); @@ -35,6 +37,15 @@ extern void SDL_GameControllerQuit(void); extern void SDL_LockJoystickList(void); extern void SDL_UnlockJoystickList(void); +/* Function to extract information from an SDL joystick GUID */ +extern void SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version); + +/* Function to return whether a joystick name and GUID is a game controller */ +extern SDL_bool SDL_IsGameControllerNameAndGUID(const char *name, SDL_JoystickGUID guid); + +/* Function to return whether a game controller should be ignored */ +extern SDL_bool SDL_ShouldIgnoreGameController(const char *name, SDL_JoystickGUID guid); + /* Internal event queueing functions */ extern void SDL_PrivateJoystickAdded(int device_index); extern void SDL_PrivateJoystickRemoved(SDL_JoystickID device_instance); diff --git a/src/joystick/darwin/SDL_sysjoystick.c b/src/joystick/darwin/SDL_sysjoystick.c index edf35794c402e..a04a8e3f47948 100644 --- a/src/joystick/darwin/SDL_sysjoystick.c +++ b/src/joystick/darwin/SDL_sysjoystick.c @@ -445,6 +445,12 @@ JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDevic return; /* not a device we care about, probably. */ } + if (SDL_IsGameControllerNameAndGUID(device->product, device->guid) && + SDL_ShouldIgnoreGameController(device->product, device->guid)) { + SDL_free(device); + return; + } + /* Get notified when this device is disconnected. */ IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device); IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index 54126846cb4d9..1f19d1d392ae2 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -233,6 +233,10 @@ IsJoystick(int fd, char *namebuf, const size_t namebuflen, SDL_JoystickGUID *gui SDL_strlcpy((char*)guid16, namebuf, sizeof(guid->data) - 4); } + if (SDL_IsGameControllerNameAndGUID(namebuf, *guid) && + SDL_ShouldIgnoreGameController(namebuf, *guid)) { + return 0; + } return 1; } diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c index 2ef088065ee1b..82fc86de52b1e 100644 --- a/src/joystick/windows/SDL_dinputjoystick.c +++ b/src/joystick/windows/SDL_dinputjoystick.c @@ -428,6 +428,12 @@ EnumJoysticksCallback(const DIDEVICEINSTANCE * pdidInstance, VOID * pContext) SDL_strlcpy((char*)guid16, pNewJoystick->joystickname, sizeof(pNewJoystick->guid.data) - 4); } + if (SDL_IsGameControllerNameAndGUID(pNewJoystick->joystickname, pNewJoystick->guid) && + SDL_ShouldIgnoreGameController(pNewJoystick->joystickname, pNewJoystick->guid)) { + SDL_free(pNewJoystick); + return DIENUM_CONTINUE; + } + SDL_SYS_AddJoystickDevice(pNewJoystick); return DIENUM_CONTINUE; /* get next device, please */ diff --git a/src/joystick/windows/SDL_xinputjoystick.c b/src/joystick/windows/SDL_xinputjoystick.c index 13de4cf830388..d2ae19fa540bd 100644 --- a/src/joystick/windows/SDL_xinputjoystick.c +++ b/src/joystick/windows/SDL_xinputjoystick.c @@ -251,6 +251,12 @@ AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext) } pNewJoystick->SubType = SubType; pNewJoystick->XInputUserId = userid; + + if (SDL_ShouldIgnoreGameController(pNewJoystick->joystickname, pNewJoystick->guid)) { + SDL_free(pNewJoystick); + return; + } + SDL_SYS_AddJoystickDevice(pNewJoystick); }