add new gamecontroller APIs
authorEdward Rudd <urkle@outoforder.cc>
Tue, 05 Mar 2013 17:59:36 -0500
changeset 6964 c8aa24f05042
parent 6963 4658b1101200
child 6965 ab4bb4e99961
add new gamecontroller APIs - add mappings after init (or even before w/o using the hint) - get string for axis - get string for button - get mapping string for controller or for GUID - new event to notify when a controller is remapped. (e.g. mapping was changed via the AddMapping method)
include/SDL_events.h
include/SDL_gamecontroller.h
src/joystick/SDL_gamecontroller.c
--- a/include/SDL_events.h	Mon Mar 04 20:27:51 2013 -0800
+++ b/include/SDL_events.h	Tue Mar 05 17:59:36 2013 -0500
@@ -100,6 +100,7 @@
 	SDL_CONTROLLERBUTTONUP,            /**< Game controller button released */
 	SDL_CONTROLLERDEVICEADDED,         /**< A new Game controller has been inserted into the system */
 	SDL_CONTROLLERDEVICEREMOVED,       /**< An opened Game controller has been removed */
+	SDL_CONTROLLERDEVICEREMAPPED,      /**< The controller mapping was updated */
 
     /* Touch events */
     SDL_FINGERDOWN      = 0x700,
@@ -358,9 +359,9 @@
  */
 typedef struct SDL_ControllerDeviceEvent
 {
-	Uint32 type;        /**< ::SDL_CONTROLLERDEVICEADDED or ::SDL_CONTROLLERDEVICEREMOVED */
+	Uint32 type;        /**< ::SDL_CONTROLLERDEVICEADDED, ::SDL_CONTROLLERDEVICEREMOVED, or ::SDL_CONTROLLERDEVICEREMAPPED */
 	Uint32 timestamp;
-	Uint32 which;       /**< The joystick device index for the ADDED event, instance id for the REMOVED event */
+	Uint32 which;       /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */
 } SDL_ControllerDeviceEvent;
 
 
@@ -484,7 +485,7 @@
     SDL_JoyHatEvent jhat;           /**< Joystick hat event data */
     SDL_JoyButtonEvent jbutton;     /**< Joystick button event data */
     SDL_JoyDeviceEvent jdevice;     /**< Joystick device change event data */
-	SDL_ControllerAxisEvent caxis;		/**< Game Controller button event data */
+	SDL_ControllerAxisEvent caxis;		/**< Game Controller axis event data */
 	SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
 	SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device event data */
     SDL_QuitEvent quit;             /**< Quit request event data */
--- a/include/SDL_gamecontroller.h	Mon Mar 04 20:27:51 2013 -0800
+++ b/include/SDL_gamecontroller.h	Tue Mar 05 17:59:36 2013 -0500
@@ -90,7 +90,7 @@
  *		}
  *  }
  *
- *  Using the SDL_HINT_GAMECONTROLLERCONFIG hint you can add support for controllers SDL is unaware of or cause an existing controller to have a different binding. The format is:
+ *  Using the SDL_HINT_GAMECONTROLLERCONFIG hint or the SDL_GameControllerAddMapping you can add support for controllers SDL is unaware of or cause an existing controller to have a different binding. The format is:
  *	guid,name,mappings
  *
  *  Where GUID is the string value from SDL_JoystickGetGUIDString(), name is the human readable string for the device and mappings are controller mappings to joystick ones.
@@ -106,6 +106,26 @@
  *
  */
 
+/**
+ *  Add or update an existing mapping configuration
+ *
+ * \return 1 if mapping is added, 0 if updated, -1 on error
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerAddMapping( const char* mappingSring );
+
+/**
+ *  Get a mapping string for a GUID
+ *
+ *  \return the mapping string.  Must be freed with SDL_free.  Returns NULL if no mapping is available
+ */
+extern DECLSPEC char * SDLCALL SDL_GameControllerMappingForGUID( SDL_JoystickGUID guid );
+
+/**
+ *  Get a mapping string for an open GameController
+ *
+ *  \return the mapping string.  Must be freed with SDL_free.  Returns NULL if no mapping is available
+ */
+extern DECLSPEC char * SDLCALL SDL_GameControllerMapping( SDL_GameController * gamecontroller );
 
 /**
  *  Is the joystick on this index supported by the game controller interface?
@@ -187,6 +207,11 @@
 extern DECLSPEC SDL_GameControllerAxis SDLCALL SDL_GameControllerGetAxisFromString(const char *pchString);
 
 /**
+ *  turn this axis enum into a string mapping
+ */
+extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForAxis(SDL_GameControllerAxis axis);
+
+/**
  *  Get the SDL joystick layer binding for this controller button mapping
  */
 extern DECLSPEC SDL_GameControllerButtonBind SDLCALL
@@ -233,6 +258,10 @@
  */
 extern DECLSPEC SDL_GameControllerButton SDLCALL SDL_GameControllerGetButtonFromString(const char *pchString);
 
+/**
+ *  turn this button enum into a string mapping
+ */
+extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForButton(SDL_GameControllerButton button);
 
 /**
  *  Get the SDL joystick layer binding for this controller button mapping
--- a/src/joystick/SDL_gamecontroller.c	Mon Mar 04 20:27:51 2013 -0800
+++ b/src/joystick/SDL_gamecontroller.c	Tue Mar 05 17:59:36 2013 -0500
@@ -45,8 +45,10 @@
 
 #define k_nMaxReverseEntries 20
 
-// We are encoding the "HAT" as 0xhm. where h == hat ID and m == mask
-// MAX 4 hats supported
+/**
+ * We are encoding the "HAT" as 0xhm. where h == hat ID and m == mask
+ * MAX 4 hats supported
+ */
 #define k_nMaxHatEntries 0x3f + 1
 
 /* our in memory mapping db between joystick objects and controller mappings*/
@@ -79,7 +81,7 @@
 {
 	SDL_JoystickGUID guid;
 	char *name;
-	const char *mapping;
+	char *mapping;
 	struct _ControllerMapping_t *next;
 } ControllerMapping_t;
 
@@ -262,14 +264,27 @@
 }
 
 /*
+ * Helper function to scan the mappings database for a controller with the specified GUID
+ */
+ControllerMapping_t *SDL_PrivateGetControllerMappingForGUID(SDL_JoystickGUID *guid)
+{
+	ControllerMapping_t *pSupportedController = s_pSupportedControllers;
+	while ( pSupportedController )
+	{
+		if ( !SDL_memcmp( guid, &pSupportedController->guid, sizeof(*guid) ) )
+		{
+			return pSupportedController;
+		}
+		pSupportedController = pSupportedController->next;
+	}
+        return NULL;
+    }
+
+/*
  * Helper function to determine pre-caclulated offset to certain joystick mappings
  */
 ControllerMapping_t *SDL_PrivateGetControllerMapping(int device_index)
 {
-    if ( (device_index < 0) || (device_index >= SDL_NumJoysticks()) ) {
-        return NULL;
-    }
-
 #ifdef SDL_JOYSTICK_DINPUT
 	if ( SDL_SYS_IsXInputDeviceIndex(device_index) && s_pXInputMapping )
 	{
@@ -279,87 +294,98 @@
 #endif
 	{
 		SDL_JoystickGUID jGUID = SDL_JoystickGetDeviceGUID( device_index );
-		ControllerMapping_t *pSupportedController = s_pSupportedControllers;
-		while ( pSupportedController )
-		{
-			if ( !SDL_memcmp( &jGUID, &pSupportedController->guid, sizeof(jGUID) ) )
-			{
-				return pSupportedController;
-			}
-			pSupportedController = pSupportedController->next;
-		}
+		return SDL_PrivateGetControllerMappingForGUID(&jGUID);
 	}
 
 	return NULL;
 }
 
+static const char* map_StringForControllerAxis[] = {
+    "leftx",
+    "lefty",
+    "rightx",
+    "righty",
+    "lefttrigger",
+    "righttrigger",
+    NULL
+};
+
 /*
  * convert a string to its enum equivalent
  */
 SDL_GameControllerAxis SDL_GameControllerGetAxisFromString( const char *pchString )
 {
+    int entry;
 	if ( !pchString || !pchString[0] )
 		return SDL_CONTROLLER_AXIS_INVALID;
 
-	if ( !SDL_strcasecmp( pchString, "leftx" ) )
-		return SDL_CONTROLLER_AXIS_LEFTX;
-	else if ( !SDL_strcasecmp( pchString, "lefty" ) )
-		return SDL_CONTROLLER_AXIS_LEFTY;
-	else if ( !SDL_strcasecmp( pchString, "rightx" ) )
-		return SDL_CONTROLLER_AXIS_RIGHTX;
-	else if ( !SDL_strcasecmp( pchString, "righty" ) )
-		return SDL_CONTROLLER_AXIS_RIGHTY;
-	else if ( !SDL_strcasecmp( pchString, "lefttrigger" ) )
-		return SDL_CONTROLLER_AXIS_TRIGGERLEFT;
-	else if ( !SDL_strcasecmp( pchString, "righttrigger" ) )
-		return SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
-	else
+    for ( entry = 0; map_StringForControllerAxis[entry]; ++entry)
+    {
+        if ( !SDL_strcasecmp( pchString, map_StringForControllerAxis[entry] ) )
+            return entry;
+    }
 		return SDL_CONTROLLER_AXIS_INVALID;
 }
 
+/*
+ * convert an enum to its string equivalent
+ */
+const char* SDL_GameControllerGetStringForAxis( SDL_GameControllerAxis axis )
+{
+    if (axis > SDL_CONTROLLER_AXIS_INVALID && axis < SDL_CONTROLLER_AXIS_MAX)
+    {
+        return map_StringForControllerAxis[axis];
+    }
+    return NULL;
+}
+
+static const char* map_StringForControllerButton[] = {
+    "a",
+    "b",
+    "x",
+    "y",
+    "back",
+    "guide",
+    "start",
+    "leftstick",
+    "rightstick",
+    "leftshoulder",
+    "rightshoulder",
+    "dpup",
+    "dpdown",
+    "dpleft",
+    "dpright",
+    NULL
+};
 
 /*
  * convert a string to its enum equivalent
  */
 SDL_GameControllerButton SDL_GameControllerGetButtonFromString( const char *pchString )
 {
+    int entry;
 	if ( !pchString || !pchString[0] )
 		return SDL_CONTROLLER_BUTTON_INVALID;
 
-	if ( !SDL_strcasecmp( pchString, "a" ) )
-		return SDL_CONTROLLER_BUTTON_A;
-	else if ( !SDL_strcasecmp( pchString, "b" ) )
-		return SDL_CONTROLLER_BUTTON_B;
-	else if ( !SDL_strcasecmp( pchString, "x" ) )
-		return SDL_CONTROLLER_BUTTON_X;
-	else if ( !SDL_strcasecmp( pchString, "y" ) )
-		return SDL_CONTROLLER_BUTTON_Y;
-	else if ( !SDL_strcasecmp( pchString, "start" ) )
-		return SDL_CONTROLLER_BUTTON_START;
-	else if ( !SDL_strcasecmp( pchString, "guide" ) )
-		return SDL_CONTROLLER_BUTTON_GUIDE;
-	else if ( !SDL_strcasecmp( pchString, "back" ) )
-		return SDL_CONTROLLER_BUTTON_BACK;
-	else if ( !SDL_strcasecmp( pchString, "dpup" ) )
-		return SDL_CONTROLLER_BUTTON_DPAD_UP;
-	else if ( !SDL_strcasecmp( pchString, "dpdown" ) )
-		return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
-	else if ( !SDL_strcasecmp( pchString, "dpleft" ) )
-		return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
-	else if ( !SDL_strcasecmp( pchString, "dpright" ) )
-		return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
-	else if ( !SDL_strcasecmp( pchString, "leftshoulder" ) )
-		return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
-	else if ( !SDL_strcasecmp( pchString, "rightshoulder" ) )
-		return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
-	else if ( !SDL_strcasecmp( pchString, "leftstick" ) )
-		return SDL_CONTROLLER_BUTTON_LEFTSTICK;
-	else if ( !SDL_strcasecmp( pchString, "rightstick" ) )
-		return SDL_CONTROLLER_BUTTON_RIGHTSTICK;
-	else
-		return SDL_CONTROLLER_BUTTON_INVALID;
+    for ( entry = 0; map_StringForControllerButton[entry]; ++entry)
+	{
+		if ( !SDL_strcasecmp( pchString, map_StringForControllerButton[entry] ) )
+		return entry;
+	}
+	return SDL_CONTROLLER_BUTTON_INVALID;
 }
 
+/*
+ * convert an enum to its string equivalent
+ */
+const char* SDL_GameControllerGetStringForButton( SDL_GameControllerButton axis )
+{
+    if (axis > SDL_CONTROLLER_BUTTON_INVALID && axis < SDL_CONTROLLER_BUTTON_MAX)
+    {
+        return map_StringForControllerButton[axis];
+    }
+    return NULL;
+}
 
 /*
  * given a controller button name and a joystick name update our mapping structure with it
@@ -602,7 +628,7 @@
 /*
  * grab the button mapping string from a mapping string
  */
-const char *SDL_PrivateGetControllerMappingFromMappingString( const char *pMapping )
+char *SDL_PrivateGetControllerMappingFromMappingString( const char *pMapping )
 {
 	const char *pFirstComma, *pSecondComma;
 
@@ -614,9 +640,126 @@
     if ( !pSecondComma )
         return NULL;
 
-    return pSecondComma + 1; /* mapping is everything after the 3rd comma, no need to malloc it */
+    return SDL_strdup(pSecondComma + 1); /* mapping is everything after the 3rd comma */
+}
+
+void SDL_PrivateGameControllerRefreshMapping( ControllerMapping_t *pControllerMapping )
+{
+    SDL_GameController *gamecontrollerlist = SDL_gamecontrollers;
+    while ( gamecontrollerlist )
+    {
+        if ( !SDL_memcmp( &gamecontrollerlist->mapping.guid, &pControllerMapping->guid, sizeof(pControllerMapping->guid) ) )
+        {
+            SDL_Event event;
+            event.type = SDL_CONTROLLERDEVICEREMAPPED;
+            event.cdevice.which = gamecontrollerlist->joystick->instance_id;
+            SDL_PushEvent(&event);
+            
+            // Not really threadsafe.  Should this lock access within SDL_GameControllerEventWatcher?
+            SDL_PrivateLoadButtonMapping(&gamecontrollerlist->mapping, pControllerMapping->guid, pControllerMapping->name, pControllerMapping->mapping);
+        }
+        
+        gamecontrollerlist = gamecontrollerlist->next;
+    }
 }
 
+/*
+ * Add or update an entry into the Mappings Database
+ */
+int
+SDL_GameControllerAddMapping( const char *mappingString )
+{
+	char *pchGUID;
+	char *pchName;
+	char *pchMapping;
+    SDL_JoystickGUID jGUID;
+    ControllerMapping_t *pControllerMapping;
+#ifdef SDL_JOYSTICK_DINPUT
+    SDL_bool is_xinput_mapping = SDL_FALSE;
+#endif
+
+	pchGUID = SDL_PrivateGetControllerGUIDFromMappingString( mappingString );
+	if (!pchGUID) return -1;
+#ifdef SDL_JOYSTICK_DINPUT
+    if ( !SDL_strcasecmp( pchGUID, "xinput" ) ) {
+        is_xinput_mapping = SDL_TRUE;
+    }
+#endif
+    SDL_free(pchGUID);
+
+	jGUID = SDL_JoystickGetGUIDFromString(pchGUID);
+
+	pControllerMapping = SDL_PrivateGetControllerMappingForGUID(&jGUID);
+
+	pchName = SDL_PrivateGetControllerNameFromMappingString( mappingString );
+	if (!pchName) return -1;
+
+	pchMapping = SDL_PrivateGetControllerMappingFromMappingString( mappingString );
+	if (!pchMapping) {
+		SDL_free( pchName );
+		return -1;
+	}
+
+	if (pControllerMapping) {
+		// Update existing mapping
+		SDL_free( pControllerMapping->name );
+		pControllerMapping->name = pchName;
+		SDL_free( pControllerMapping->mapping );
+		pControllerMapping->mapping = pchMapping;
+		// refresh open controllers
+		SDL_PrivateGameControllerRefreshMapping( pControllerMapping );
+		return 0;
+	} else {
+		pControllerMapping = SDL_malloc( sizeof(*pControllerMapping) );
+		if (!pControllerMapping) {
+			SDL_OutOfMemory();
+			SDL_free( pchName );
+			SDL_free( pchMapping );
+			return -1;
+		}
+#ifdef SDL_JOYSTICK_DINPUT
+		if ( is_xinput_mapping )
+		{
+			s_pXInputMapping = pControllerMapping;
+		}
+#endif
+		pControllerMapping->guid = jGUID;
+		pControllerMapping->name = pchName;
+		pControllerMapping->mapping = pchMapping;
+		pControllerMapping->next = s_pSupportedControllers;
+		s_pSupportedControllers = pControllerMapping;
+		return 1;
+	}
+}
+
+/*
+ * Get the mapping string for this GUID
+ */
+char *
+SDL_GameControllerMappingForGUID( SDL_JoystickGUID guid )
+{
+	char *pMappingString = NULL;
+	ControllerMapping_t *mapping = SDL_PrivateGetControllerMappingForGUID(&guid);
+	if (mapping) {
+		char pchGUID[33];
+        size_t needed;
+		SDL_JoystickGetGUIDString(guid, pchGUID, sizeof(pchGUID));
+		// allocate enough memory for GUID + ',' + name + ',' + mapping + \0
+		needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1;
+		pMappingString = SDL_malloc( needed );
+		SDL_snprintf( pMappingString, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping );
+	}
+	return pMappingString;
+}
+
+/*
+ * Get the mapping string for this device
+ */
+char *
+SDL_GameControllerMapping( SDL_GameController * gamecontroller )
+{
+	return SDL_GameControllerMappingForGUID( gamecontroller->mapping.guid );
+}
 
 /*
  * Initialize the game controller system, mostly load our DB of controller config mappings
@@ -630,36 +773,7 @@
 	pMappingString = s_ControllerMappings[i];
 	while ( pMappingString )
 	{
-		ControllerMapping_t *pControllerMapping;
-		char *pchGUID;
-		char *pchName;
-		const char *pchMapping;
-		pControllerMapping = SDL_malloc( sizeof(*pControllerMapping) );
-		if ( !pControllerMapping )
-		{
-			SDL_OutOfMemory();
-			return -1;
-		}
-
-		pchGUID = SDL_PrivateGetControllerGUIDFromMappingString( pMappingString );
-		pchName = SDL_PrivateGetControllerNameFromMappingString( pMappingString );
-		pchMapping = SDL_PrivateGetControllerMappingFromMappingString( pMappingString );
-		if ( pchGUID && pchName )
-		{
-#ifdef SDL_JOYSTICK_DINPUT
-			if ( !SDL_strcasecmp( pchGUID, "xinput" ) )
-			{
-				s_pXInputMapping = pControllerMapping;
-			}
-#endif
-			pControllerMapping->guid = SDL_JoystickGetGUIDFromString( pchGUID );
-			pControllerMapping->name = pchName;
-			pControllerMapping->mapping = pchMapping;
-			pControllerMapping->next = s_pSupportedControllers;
-			s_pSupportedControllers = pControllerMapping;
-
-			SDL_free( pchGUID );
-		}
+		SDL_GameControllerAddMapping( pMappingString );
 
 		i++;
 		pMappingString = s_ControllerMappings[i];
@@ -671,54 +785,25 @@
 		if ( hint && hint[0] )
 		{
 			int nchHints = SDL_strlen( hint );
-			char *pUserMappings = SDL_malloc( nchHints + 1 ); /* FIXME: memory leak, but we can't free it in this function because pchMapping below points into this memory */
-			SDL_memcpy( pUserMappings, hint, nchHints + 1 );
+			char *pUserMappings = SDL_malloc( nchHints + 1 );
+			char *pTempMappings = pUserMappings;
+			SDL_memcpy( pUserMappings, hint, nchHints );
 			while ( pUserMappings )
 			{
-				char *pchGUID;
-				char *pchName;
-				const char *pchMapping;
 				char *pchNewLine = NULL;
-				ControllerMapping_t *pControllerMapping;
 
 				pchNewLine = SDL_strchr( pUserMappings, '\n' );
 				if ( pchNewLine )
 					*pchNewLine = '\0';
 
-				pControllerMapping = SDL_malloc( sizeof(*pControllerMapping) );
-				if ( !pControllerMapping )
-				{
-					SDL_OutOfMemory();
-					return -1;
-				}
-
-				pchGUID = SDL_PrivateGetControllerGUIDFromMappingString( pUserMappings );
-				pchName = SDL_PrivateGetControllerNameFromMappingString( pUserMappings );
-				pchMapping = SDL_PrivateGetControllerMappingFromMappingString( pUserMappings );
-
-				if ( pchGUID && pchName )
-				{
-#ifdef SDL_JOYSTICK_DINPUT
-					if ( !SDL_strcasecmp( pchGUID, "xinput" ) )
-					{
-						s_pXInputMapping = pControllerMapping;
-					}
-#endif
-
-					pControllerMapping->guid = SDL_JoystickGetGUIDFromString( pchGUID );
-					pControllerMapping->name = pchName;
-					pControllerMapping->mapping = pchMapping;
-					pControllerMapping->next = s_pSupportedControllers;
-					s_pSupportedControllers = pControllerMapping;
-
-					SDL_free( pchGUID );
-				}
+				SDL_GameControllerAddMapping( pUserMappings );
 
 				if ( pchNewLine )
 					pUserMappings = pchNewLine + 1;
 				else
 					pUserMappings = NULL;
 			}
+			SDL_free(pTempMappings);
 		}
 	}
 
@@ -1133,7 +1218,7 @@
 #else
     const Uint32 event_list[] = {
         SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERBUTTONDOWN, SDL_CONTROLLERBUTTONUP,
-        SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMOVED,
+        SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMOVED, SDL_CONTROLLERDEVICEREMAPPED,
     };
     unsigned int i;