/* Copyright (c) 2023 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "inputcontroller/GamepadMap.h" #include "stem_core.h" #include #define stemobject_implementation GamepadMap stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_end(); struct GamepadMap_alias { int vendorID; int productID; }; struct GamepadMap_deviceRecord { HashTable * elementMap; HashTable * reverseElementMap; HashTable * blacklist; }; struct GamepadMap_elementHardwareRecord { unsigned int hardwareID; int sign; }; GamepadMap * GamepadMap_create(void) { stemobject_create_implementation(init) } bool GamepadMap_init(GamepadMap * self) { call_super(init, self); self->mapData = HashTable_create(sizeof(struct GamepadMap_deviceRecord)); self->aliases = HashTable_create(sizeof(struct GamepadMap_alias)); return true; } static bool freeDeviceRecord(HashTable * hashTable, HashTable_key key, void * value, void * context) { struct GamepadMap_deviceRecord * deviceRecord = value; HashTable_dispose(deviceRecord->elementMap); HashTable_dispose(deviceRecord->reverseElementMap); if (deviceRecord->blacklist != NULL) { HashTable_dispose(deviceRecord->blacklist); } return true; } void GamepadMap_dispose(GamepadMap * self) { HashTable_foreach(self->mapData, freeDeviceRecord, NULL); HashTable_dispose(self->mapData); HashTable_dispose(self->aliases); call_super_virtual(dispose, self); } static struct GamepadMap_deviceRecord * getDeviceRecord(GamepadMap * self, int vendorID, int productID, bool createIfNotPresent) { HashTable_key key = HashTable_uint32PairKey(vendorID, productID); struct GamepadMap_alias * alias; while ((alias = HashTable_get(self->aliases, key)) != NULL) { key.data.uint32Pair.first = alias->vendorID; key.data.uint32Pair.second = alias->productID; } struct GamepadMap_deviceRecord * deviceRecord = HashTable_get(self->mapData, key); if (deviceRecord == NULL && createIfNotPresent) { struct GamepadMap_deviceRecord newDeviceRecord; newDeviceRecord.elementMap = HashTable_create(sizeof(GamepadElement)); newDeviceRecord.reverseElementMap = HashTable_create(sizeof(struct GamepadMap_elementHardwareRecord)); newDeviceRecord.blacklist = NULL; deviceRecord = HashTable_set(self->mapData, key, &newDeviceRecord); } return deviceRecord; } GamepadMap * GamepadMap_deserialize(compat_type(DeserializationContext *) deserializationContext) { stemobject_deserialize_implementation(loadSerializedData) } #define GAMEPAD_ELEMENT_TYPE_ENUM_VALUES { \ {"button", GAMEPAD_ELEMENT_BUTTON}, \ {"axis", GAMEPAD_ELEMENT_AXIS} \ } bool GamepadMap_loadSerializedData(GamepadMap * self, compat_type(DeserializationContext *) deserializationContext) { DeserializationContext * context = deserializationContext; call_virtual(beginStructure, context, GAMEPAD_MAP_SERIALIZATION_FORMAT_TYPE); const char * formatType = call_virtual(readString, context, "format_type"); if (context->status != SERIALIZATION_ERROR_OK || strcmp(formatType, GAMEPAD_MAP_SERIALIZATION_FORMAT_TYPE)) { return false; } uint16_t formatVersion = call_virtual(readUInt16, context, "format_version"); if (context->status == SERIALIZATION_ERROR_OK && formatVersion > GAMEPAD_MAP_SERIALIZATION_FORMAT_VERSION) { return false; } GamepadMap_init(self); unsigned int deviceCount = call_virtual(beginArray, context, "devices"); Serialization_enumKeyValue gamepadElementTypeEnumValues[] = GAMEPAD_ELEMENT_TYPE_ENUM_VALUES; Serialization_enumKeyValue gamepadElementEnumValues[] = GAMEPAD_ELEMENT_ENUM_VALUES; for (unsigned int deviceIndex = 0; deviceIndex < deviceCount; deviceIndex++) { call_virtual(beginStructure, context, NULL); int vendorID = call_virtual(readInt32, context, "vendor"); int productID = call_virtual(readInt32, context, "product"); unsigned int elementCount = call_virtual(beginArray, context, "elements"); for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++) { call_virtual(beginStructure, context, NULL); GamepadElementType elementType = call_virtual(readEnumeration, context, "type", sizeof_count(gamepadElementTypeEnumValues), gamepadElementTypeEnumValues); unsigned int hardwareID = call_virtual(readUInt32, context, "id"); GamepadElement element = call_virtual(readEnumeration, context, "element", sizeof_count(gamepadElementEnumValues), gamepadElementEnumValues); int sign = call_virtual(readInt8, context, "sign"); call_virtual(endStructure, context); GamepadMap_registerHardwareElement(self, vendorID, productID, elementType, hardwareID, element, sign); } call_virtual(endArray, context); unsigned int blacklistedElementCount = call_virtual(beginArray, context, "blacklist"); for (unsigned int blacklistedElementIndex = 0; blacklistedElementIndex < blacklistedElementCount; blacklistedElementIndex++) { call_virtual(beginStructure, context, NULL); GamepadElementType elementType = call_virtual(readEnumeration, context, "type", sizeof_count(gamepadElementTypeEnumValues), gamepadElementTypeEnumValues); unsigned int hardwareID = call_virtual(readUInt32, context, "id"); call_virtual(endStructure, context); GamepadMap_blacklistHardwareElement(self, vendorID, productID, elementType, hardwareID); } call_virtual(endArray, context); call_virtual(endStructure, context); } call_virtual(endArray, context); unsigned int aliasCount = call_virtual(beginArray, context, "aliases"); for (unsigned int aliasIndex = 0; aliasIndex < aliasCount; aliasIndex++) { call_virtual(beginStructure, context, NULL); int fromVendorID = call_virtual(readInt32, context, "from_vendor"); int fromProductID = call_virtual(readInt32, context, "from_product"); int toVendorID = call_virtual(readInt32, context, "to_vendor"); int toProductID = call_virtual(readInt32, context, "to_product"); call_virtual(endStructure, context); GamepadMap_registerGamepadAlias(self, toVendorID, toProductID, fromVendorID, fromProductID); } call_virtual(endArray, context); call_virtual(endStructure, context); if (context->status != SERIALIZATION_ERROR_OK) { GamepadMap_dispose(self); return false; } return true; } struct serializeContext { GamepadMap * self; SerializationContext * context; }; static bool serializeElement(HashTable * hashTable, HashTable_key key, void * value, void * contextUntyped) { struct serializeContext * contextStruct = contextUntyped; SerializationContext * context = contextStruct->context; struct GamepadMap_elementHardwareRecord * hardwareRecord = value; Serialization_enumKeyValue gamepadElementTypeEnumValues[] = GAMEPAD_ELEMENT_TYPE_ENUM_VALUES; Serialization_enumKeyValue gamepadElementEnumValues[] = GAMEPAD_ELEMENT_ENUM_VALUES; call_virtual(beginStructure, context, NULL); call_virtual(writeEnumeration, context, "type", key.data.uint32Pair.first, sizeof_count(gamepadElementTypeEnumValues), gamepadElementTypeEnumValues); call_virtual(writeUInt32, context, "id", hardwareRecord->hardwareID); call_virtual(writeEnumeration, context, "element", key.data.uint32Pair.second, sizeof_count(gamepadElementEnumValues), gamepadElementEnumValues); call_virtual(writeInt8, context, "sign", hardwareRecord->sign); call_virtual(endStructure, context); return true; } static bool serializeBlacklistItem(HashTable * hashTable, HashTable_key key, void * value, void * contextUntyped) { struct serializeContext * contextStruct = contextUntyped; SerializationContext * context = contextStruct->context; Serialization_enumKeyValue gamepadElementTypeEnumValues[] = GAMEPAD_ELEMENT_TYPE_ENUM_VALUES; call_virtual(beginStructure, context, NULL); call_virtual(writeEnumeration, context, "type", key.data.uint32Pair.first, sizeof_count(gamepadElementTypeEnumValues), gamepadElementTypeEnumValues); call_virtual(writeUInt32, context, "id", key.data.uint32Pair.second); call_virtual(endStructure, context); return true; } static bool serializeDevice(HashTable * hashTable, HashTable_key key, void * value, void * contextUntyped) { struct serializeContext * contextStruct = contextUntyped; SerializationContext * context = contextStruct->context; struct GamepadMap_deviceRecord * deviceRecord = value; call_virtual(beginStructure, context, NULL); call_virtual(writeInt32, context, "vendor", key.data.uint32Pair.first); call_virtual(writeInt32, context, "product", key.data.uint32Pair.second); call_virtual(beginArray, context, "elements"); HashTable_foreach(deviceRecord->reverseElementMap, serializeElement, contextStruct); call_virtual(endArray, context); call_virtual(beginArray, context, "blacklist"); if (deviceRecord->blacklist != NULL) { HashTable_foreach(deviceRecord->blacklist, serializeBlacklistItem, contextStruct); } call_virtual(endArray, context); call_virtual(endStructure, context); return true; } static bool serializeAlias(HashTable * hashTable, HashTable_key key, void * value, void * contextUntyped) { struct serializeContext * contextStruct = contextUntyped; SerializationContext * context = contextStruct->context; struct GamepadMap_alias * alias = value; call_virtual(beginStructure, context, NULL); call_virtual(writeInt32, context, "from_vendor", key.data.uint32Pair.first); call_virtual(writeInt32, context, "from_product", key.data.uint32Pair.second); call_virtual(writeInt32, context, "to_vendor", alias->vendorID); call_virtual(writeInt32, context, "to_product", alias->productID); call_virtual(endStructure, context); return true; } void GamepadMap_serialize(GamepadMap * self, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; call_virtual(beginStructure, context, GAMEPAD_MAP_SERIALIZATION_FORMAT_TYPE); call_virtual(writeString, context, "format_type", GAMEPAD_MAP_SERIALIZATION_FORMAT_TYPE); call_virtual(writeUInt16, context, "format_version", GAMEPAD_MAP_SERIALIZATION_FORMAT_VERSION); call_virtual(beginArray, context, "devices"); struct serializeContext contextStruct = {self, context}; HashTable_foreach(self->mapData, serializeDevice, &contextStruct); call_virtual(endArray, context); call_virtual(beginArray, context, "aliases"); HashTable_foreach(self->aliases, serializeAlias, &contextStruct); call_virtual(endArray, context); call_virtual(endStructure, context); } static bool mergeDeviceRecord(HashTable * hashTable, HashTable_key key, void * value, void * context) { GamepadMap * to = context; struct GamepadMap_deviceRecord * fromDeviceRecord = value; struct GamepadMap_deviceRecord * toDeviceRecord = getDeviceRecord(to, key.data.uint32Pair.first, key.data.uint32Pair.second, true); HashTable_merge(toDeviceRecord->elementMap, fromDeviceRecord->elementMap); HashTable_merge(toDeviceRecord->reverseElementMap, fromDeviceRecord->reverseElementMap); if (fromDeviceRecord->blacklist != NULL) { if (toDeviceRecord->blacklist != NULL) { HashTable_merge(toDeviceRecord->blacklist, fromDeviceRecord->blacklist); } else { toDeviceRecord->blacklist = HashTable_copy(fromDeviceRecord->blacklist); } } return true; } void GamepadMap_mergeValuesFrom(GamepadMap * to, GamepadMap * from) { HashTable_foreach(from->mapData, mergeDeviceRecord, to); HashTable_merge(to->aliases, from->aliases); } void GamepadMap_registerGlobalDefaults(GamepadMap * self) { #define GAMEPAD_DEVICE_BEGIN(vendor_id, product_id) { \ int vendorID = vendor_id, productID = product_id; #define GAMEPAD_DEVICE_END() } #define GAMEPAD_BUTTON(hardware_id, element_id) \ GamepadMap_registerHardwareElement(self, vendorID, productID, GAMEPAD_ELEMENT_BUTTON, hardware_id, element_id, 1); #define GAMEPAD_AXIS(hardware_id, element_id, sign) \ GamepadMap_registerHardwareElement(self, vendorID, productID, GAMEPAD_ELEMENT_AXIS, hardware_id, element_id, sign); #define GAMEPAD_BLACKLIST_BUTTON(hardware_id) \ GamepadMap_blacklistHardwareElement(self, vendorID, productID, GAMEPAD_ELEMENT_BUTTON, hardware_id); #define GAMEPAD_BLACKLIST_AXIS(hardware_id) \ GamepadMap_blacklistHardwareElement(self, vendorID, productID, GAMEPAD_ELEMENT_AXIS, hardware_id); #define GAMEPAD_ALIAS(from_vendor_id, from_product_id) \ GamepadMap_registerGamepadAlias(self, vendorID, productID, from_vendor_id, from_product_id); #include "inputcontroller/Gamepad_mappingList.h" #ifdef USE_EXTENDED_GAMEPAD_MAPPING #include "inputcontroller/Gamepad_mappingList_extended.h" #endif #undef GAMEPAD_ELEMENT #undef GAMEPAD_BLACKLIST_ELEMENT #undef GAMEPAD_ALIAS } void GamepadMap_registerHardwareElement(GamepadMap * self, int vendorID, int productID, GamepadElementType elementType, unsigned int hardwareID, GamepadElement elementID, int sign) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, true); HashTable_set(deviceRecord->elementMap, HashTable_uint32PairKey(elementType, hardwareID), &elementID); struct GamepadMap_elementHardwareRecord hardwareRecord = {hardwareID, sign}; HashTable_set(deviceRecord->reverseElementMap, HashTable_uint32PairKey(elementType, elementID), &hardwareRecord); } void GamepadMap_unregisterHardwareElement(GamepadMap * self, int vendorID, int productID, GamepadElementType elementType, unsigned int hardwareID) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, false); if (deviceRecord != NULL) { GamepadElement * elementIDRecord = HashTable_get(deviceRecord->elementMap, HashTable_uint32PairKey(elementType, hardwareID)); if (elementIDRecord != NULL) { HashTable_delete(deviceRecord->reverseElementMap, HashTable_uint32PairKey(elementType, *elementIDRecord)); HashTable_delete(deviceRecord->elementMap, HashTable_uint32PairKey(elementType, hardwareID)); } } } void GamepadMap_blacklistHardwareElement(GamepadMap * self, int vendorID, int productID, GamepadElementType elementType, unsigned int hardwareID) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, true); if (deviceRecord->blacklist == NULL) { deviceRecord->blacklist = HashTable_create(0); } HashTable_set(deviceRecord->blacklist, HashTable_uint32PairKey(elementType, hardwareID), NULL); } void GamepadMap_registerGamepadAlias(GamepadMap * self, int toVendorID, int toProductID, int fromVendorID, int fromProductID) { struct GamepadMap_alias alias = {toVendorID, toProductID}; HashTable_set(self->aliases, HashTable_uint32PairKey(fromVendorID, fromProductID), &alias); } bool GamepadMap_isDeviceKnown(GamepadMap * self, int vendorID, int productID) { return getDeviceRecord(self, vendorID, productID, false) != NULL; } bool GamepadMap_hasHardwareTypeForElement(GamepadMap * self, int vendorID, int productID, GamepadElementType elementType, GamepadElement elementID) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, false); if (deviceRecord == NULL) { return false; } struct GamepadMap_elementHardwareRecord * hardwareRecord = HashTable_get(deviceRecord->reverseElementMap, HashTable_uint32PairKey(elementType, elementID)); if (hardwareRecord == NULL) { return false; } return true; } unsigned int GamepadMap_getHardwareIDForElement(GamepadMap * self, int vendorID, int productID, GamepadElementType elementType, GamepadElement elementID) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, false); if (deviceRecord == NULL) { return UINT_MAX; } struct GamepadMap_elementHardwareRecord * hardwareRecord = HashTable_get(deviceRecord->reverseElementMap, HashTable_uint32PairKey(elementType, elementID)); if (hardwareRecord == NULL) { return UINT_MAX; } return hardwareRecord->hardwareID; } int GamepadMap_getAxisSignForElement(GamepadMap * self, int vendorID, int productID, GamepadElement elementID) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, false); if (deviceRecord == NULL) { return 0; } struct GamepadMap_elementHardwareRecord * hardwareRecord = HashTable_get(deviceRecord->reverseElementMap, HashTable_uint32PairKey(GAMEPAD_ELEMENT_AXIS, elementID)); if (hardwareRecord == NULL) { return 0; } return hardwareRecord->sign; } GamepadElement GamepadMap_getElementForHardwareID(GamepadMap * self, int vendorID, int productID, GamepadElementType elementType, unsigned int hardwareID) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, false); if (deviceRecord == NULL) { return GAMEPAD_UNKNOWN; } GamepadElement * element = HashTable_get(deviceRecord->elementMap, HashTable_uint32PairKey(elementType, hardwareID)); if (element == NULL) { return GAMEPAD_UNKNOWN; } return *element; } bool GamepadMap_isHardwareElementBlacklisted(GamepadMap * self, int vendorID, int productID, GamepadElementType elementType, unsigned int hardwareID) { struct GamepadMap_deviceRecord * deviceRecord = getDeviceRecord(self, vendorID, productID, false); if (deviceRecord == NULL || deviceRecord->blacklist == NULL) { return false; } return HashTable_get(deviceRecord->blacklist, HashTable_uint32PairKey(elementType, hardwareID)) != NULL; }