From 0bde8dc79d796ba731edf8deaadd2585eb474b2a Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 22 May 2021 22:18:00 +0200 Subject: [PATCH] Migrated to the SDL2 GameController API. --- es-app/src/main.cpp | 26 +- es-app/src/views/UIModeController.cpp | 9 - es-app/src/views/ViewController.cpp | 22 - es-core/src/InputConfig.cpp | 102 +-- es-core/src/InputConfig.h | 41 +- es-core/src/InputManager.cpp | 769 ++++++++++-------- es-core/src/InputManager.h | 59 +- es-core/src/Settings.cpp | 2 - .../controllers/es_controller_mappings.cfg | 6 + 9 files changed, 520 insertions(+), 516 deletions(-) create mode 100644 resources/controllers/es_controller_mappings.cfg diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index e2fcd48f6..e420b42be 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -527,9 +527,8 @@ int main(int argc, char* argv[]) } } - // Dont generate joystick events while we're loading. - // (Hopefully fixes "automatically started emulator" bug.) - SDL_JoystickEventState(SDL_DISABLE); + // Don't generate controller events while we're loading. + SDL_GameControllerEventState(SDL_DISABLE); // Preload what we can right away instead of waiting for the user to select it. // This makes for no delays when accessing content, but a longer startup time. @@ -538,23 +537,14 @@ int main(int argc, char* argv[]) if (splashScreen && splashScreenProgress) window.renderLoadingScreen("Done"); - // Choose which GUI to open depending on if an input configuration already exists and - // whether the flag to force the input configuration was passed from the command line. + // Open the input configuration GUI if the flag to force this was passed from the command line. if (!loadSystemsStatus) { - if (!forceInputConfig && Utils::FileSystem::exists(InputManager::getConfigPath()) && - InputManager::getInstance()->getNumConfiguredDevices() > 0) { - ViewController::get()->goToStart(); - } - else if (forceInputConfig) { + if (forceInputConfig) { window.pushGui(new GuiDetectDevice(&window, true, true, [] { - ViewController::get()->goToStart(); })); + ViewController::get()->goToStart(); })); } else { - if (InputManager::getInstance()->getNumJoysticks() > 0) - window.pushGui(new GuiDetectDevice(&window, true, false, [] { - ViewController::get()->goToStart(); })); - else - ViewController::get()->goToStart(); + ViewController::get()->goToStart(); } } @@ -566,8 +556,8 @@ int main(int argc, char* argv[]) LOG(LogInfo) << FileData::getMediaDirectory(); } - // Generate joystick events since we're done loading. - SDL_JoystickEventState(SDL_ENABLE); + // Generate controller events since we're done loading. + SDL_GameControllerEventState(SDL_ENABLE); int lastTime = SDL_GetTicks(); const auto applicationEndTime = std::chrono::system_clock::now(); diff --git a/es-app/src/views/UIModeController.cpp b/es-app/src/views/UIModeController.cpp index 48bee192a..46d1c6a6c 100644 --- a/es-app/src/views/UIModeController.cpp +++ b/es-app/src/views/UIModeController.cpp @@ -161,15 +161,6 @@ bool UIModeController::isValidInput(InputConfig* config, Input input) if ((config->getMappedTo(input).size() == 0) || // Not a mapped input, so ignore it. (!input.value)) // Not a key-down event. return false; - else if (input.type == TYPE_HAT) { - // When the hat goes back to neutral, getMappedTo() will return entries for all - // four directions as the neutral cancels any of them out. So a neutral is - // equivalent to a key-up event and should therefore be ignored. - if (config->getMappedTo(input).size() == 4) - return false; - else - return true; - } else return true; } diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 346efc5be..1f8d736e5 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -198,28 +198,6 @@ void ViewController::noGamesDialog() void ViewController::goToStart() { - // Check if the keyboard config is set as application default, meaning no user - // configuration has been performed. - if (InputManager::getInstance()-> - getInputConfigByDevice(DEVICE_KEYBOARD)->getDefaultConfigFlag()) { - LOG(LogInfo) << "Applying default keyboard mappings..."; - - if (Settings::getInstance()->getBool("ShowDefaultKeyboardWarning")) { - std::string message = "NO KEYBOARD CONFIGURATION COULD BE\n" - "FOUND IN ES_INPUT.CFG, SO APPLYING THE\n" - "DEFAULT KEYBOARD MAPPINGS. IT'S HOWEVER\n" - "RECOMMENDED TO SETUP YOUR OWN KEYBOARD\n" - "CONFIGURATION. TO DO SO, CHOOSE THE ENTRY\n" - "'CONFIGURE INPUT' ON THE MAIN MENU."; - - mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), message.c_str(), - "OK", nullptr, "DON'T SHOW AGAIN", [] { - Settings::getInstance()->setBool("ShowDefaultKeyboardWarning", false); - Settings::getInstance()->saveFile(); - })); - } - } - // If the system view does not exist, then create it. We do this here as it would // otherwise not be done if jumping directly into a specific game system on startup. if (!mSystemListView) diff --git a/es-core/src/InputConfig.cpp b/es-core/src/InputConfig.cpp index 31768fd02..8980ba58e 100644 --- a/es-core/src/InputConfig.cpp +++ b/es-core/src/InputConfig.cpp @@ -12,33 +12,38 @@ #include -// Some utility functions. -std::string inputTypeToString(InputType type) +InputConfig::InputConfig( + int deviceId, + const std::string& deviceName, + const std::string& deviceGUID) + : mDeviceId(deviceId), + mDeviceName(deviceName), + mDeviceGUID(deviceGUID) +{ +} + +std::string InputConfig::inputTypeToString(InputType type) { switch (type) { - case TYPE_AXIS: - return "axis"; - case TYPE_BUTTON: - return "button"; - case TYPE_HAT: - return "hat"; - case TYPE_KEY: - return "key"; - case TYPE_CEC_BUTTON: - return "cec-button"; - default: - return "error"; + case TYPE_AXIS: + return "axis"; + case TYPE_BUTTON: + return "button"; + case TYPE_KEY: + return "key"; + case TYPE_CEC_BUTTON: + return "cec-button"; + default: + return "error"; } } -InputType stringToInputType(const std::string& type) +InputType InputConfig::stringToInputType(const std::string& type) { if (type == "axis") return TYPE_AXIS; if (type == "button") return TYPE_BUTTON; - if (type == "hat") - return TYPE_HAT; if (type == "key") return TYPE_KEY; if (type == "cec-button") @@ -46,25 +51,13 @@ InputType stringToInputType(const std::string& type) return TYPE_COUNT; } -std::string toLower(std::string str) +std::string InputConfig::toLower(std::string str) { for (unsigned int i = 0; i < str.length(); i++) str[i] = static_cast(tolower(str[i])); return str; } -// End of utility functions. - -InputConfig::InputConfig( - int deviceId, - const std::string& deviceName, - const std::string& deviceGUID) - : mDeviceId(deviceId), - mDeviceName(deviceName), - mDeviceGUID(deviceGUID), - mDefaultConfigFlag(false) -{ -} void InputConfig::clear() { @@ -88,25 +81,6 @@ void InputConfig::unmapInput(const std::string& name) mNameMap.erase(it); } -bool InputConfig::getInputByName(const std::string& name, Input* result) -{ - auto it = mNameMap.find(toLower(name)); - if (it != mNameMap.cend()) { - *result = it->second; - return true; - } - return false; -} - -int InputConfig::getInputIDByName(const std::string& name) -{ - auto it = mNameMap.find(toLower(name)); - if (it != mNameMap.cend()) { - return it->second.id; - } - return -1; -} - bool InputConfig::isMappedTo(const std::string& name, Input input) { Input comp; @@ -114,9 +88,6 @@ bool InputConfig::isMappedTo(const std::string& name, Input input) return false; if (comp.configured && comp.type == input.type && comp.id == input.id) { - if (comp.type == TYPE_HAT) - return (input.value == 0 || input.value & comp.value); - if (comp.type == TYPE_AXIS) return input.value == 0 || comp.value == input.value; else @@ -170,12 +141,6 @@ std::vector InputConfig::getMappedTo(Input input) continue; if (chk.device == input.device && chk.type == input.type && chk.id == input.id) { - if (chk.type == TYPE_HAT) { - if (input.value == 0 || input.value & chk.value) - maps.push_back(iterator->first); - continue; - } - if (input.type == TYPE_AXIS) { if (input.value == 0 || chk.value == input.value) maps.push_back(iterator->first); @@ -188,6 +153,25 @@ std::vector InputConfig::getMappedTo(Input input) return maps; } +bool InputConfig::getInputByName(const std::string& name, Input* result) +{ + auto it = mNameMap.find(toLower(name)); + if (it != mNameMap.cend()) { + *result = it->second; + return true; + } + return false; +} + +int InputConfig::getInputIDByName(const std::string& name) +{ + auto it = mNameMap.find(toLower(name)); + if (it != mNameMap.cend()) { + return it->second.id; + } + return -1; +} + void InputConfig::loadFromXML(pugi::xml_node& node) { clear(); @@ -228,7 +212,7 @@ void InputConfig::writeToXML(pugi::xml_node& parent) cfg.append_attribute("deviceName") = "CEC"; } else { - cfg.append_attribute("type") = "joystick"; + cfg.append_attribute("type") = "controller"; cfg.append_attribute("deviceName") = mDeviceName.c_str(); } diff --git a/es-core/src/InputConfig.h b/es-core/src/InputConfig.h index e80c116b5..1ac7a6f6c 100644 --- a/es-core/src/InputConfig.h +++ b/es-core/src/InputConfig.h @@ -22,7 +22,6 @@ enum InputType { TYPE_AXIS, TYPE_BUTTON, - TYPE_HAT, TYPE_KEY, TYPE_CEC_BUTTON, TYPE_COUNT @@ -64,19 +63,6 @@ public: { } - std::string getHatDir(int val) - { - if (val & SDL_HAT_UP) - return "up"; - else if (val & SDL_HAT_DOWN) - return "down"; - else if (val & SDL_HAT_LEFT) - return "left"; - else if (val & SDL_HAT_RIGHT) - return "right"; - return "neutral?"; - } - std::string getCECButtonName(int keycode) { return CECInput::getKeyCodeString(keycode); @@ -86,14 +72,11 @@ public: { std::stringstream stream; switch (type) { - case TYPE_BUTTON: - stream << "Button " << id; - break; case TYPE_AXIS: stream << "Axis " << id << (value > 0 ? "+" : "-"); break; - case TYPE_HAT: - stream << "Hat " << id << " " << getHatDir(value); + case TYPE_BUTTON: + stream << "Button " << id; break; case TYPE_KEY: stream << "Key " << SDL_GetKeyName((SDL_Keycode)id); @@ -115,18 +98,17 @@ class InputConfig public: InputConfig(int deviceId, const std::string& deviceName, const std::string& deviceGUID); + // Utility functions. + std::string inputTypeToString(InputType type); + InputType stringToInputType(const std::string& type); + std::string toLower(std::string str); + void clear(); + bool isConfigured(); + void mapInput(const std::string& name, Input input); void unmapInput(const std::string& name); // Unmap all Inputs mapped to this name. - inline int getDeviceId() const { return mDeviceId; }; - inline const std::string& getDeviceName() { return mDeviceName; } - inline const std::string& getDeviceGUIDString() { return mDeviceGUID; } - - void setDefaultConfigFlag() { mDefaultConfigFlag = true; }; - void unsetDefaultConfigFlag() { mDefaultConfigFlag = false; }; - bool getDefaultConfigFlag() { return mDefaultConfigFlag; }; - // Returns true if Input is mapped to this name, false otherwise. bool isMappedTo(const std::string& name, Input input); bool isMappedLike(const std::string& name, Input input); @@ -142,14 +124,15 @@ public: void loadFromXML(pugi::xml_node& root); void writeToXML(pugi::xml_node& parent); - bool isConfigured(); + inline int getDeviceId() const { return mDeviceId; }; + inline const std::string& getDeviceName() { return mDeviceName; } + inline const std::string& getDeviceGUIDString() { return mDeviceGUID; } private: std::map mNameMap; const int mDeviceId; const std::string mDeviceName; const std::string mDeviceGUID; - bool mDefaultConfigFlag; }; #endif // ES_CORE_INPUT_CONFIG_H diff --git a/es-core/src/InputManager.cpp b/es-core/src/InputManager.cpp index 39ce9f4f8..fca9b5844 100644 --- a/es-core/src/InputManager.cpp +++ b/es-core/src/InputManager.cpp @@ -10,38 +10,22 @@ #include "InputManager.h" +#include "resources/ResourceManager.h" #include "utils/FileSystemUtil.h" -#include "utils/StringUtil.h" #include "CECInput.h" #include "Log.h" #include "Platform.h" #include "Scripting.h" #include "Window.h" -#include -#include #include #include #define KEYBOARD_GUID_STRING "-1" #define CEC_GUID_STRING "-2" -// There are four distinct IDs used for joysticks: -// 1. Device index - this is the "lowest level" identifier, and is just the Nth joystick plugged -// in to the system (like /dev/js#). It can change even if the device is the same, and is -// only used to open joysticks (required to receive SDL events). -// 2. SDL_JoystickID - this is an ID for each joystick that is supposed to remain consistent -// between plugging and unplugging. ES doesn't care if it does, though. -// 3. "Device ID" - this is something I made up and is what InputConfig's getDeviceID() returns. -// This is actually just an SDL_JoystickID (also called instance ID), but -1 means "keyboard" -// instead of "error." -// 4. Joystick GUID - this is some squashed version of joystick vendor, version, and a bunch of -// other device-specific things. It should remain the same across runs of the program/system -// restarts/device reordering and is what I use to identify which joystick to load. - -// Hack for CEC support. int SDL_USER_CECBUTTONDOWN = -1; -int SDL_USER_CECBUTTONUP = -1; +int SDL_USER_CECBUTTONUP = -1; InputManager* InputManager::sInstance = nullptr; @@ -67,80 +51,67 @@ void InputManager::init() if (initialized()) deinit(); - SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, - Settings::getInstance()->getBool("BackgroundJoystickInput") ? "1" : "0"); - SDL_InitSubSystem(SDL_INIT_JOYSTICK); - SDL_JoystickEventState(SDL_ENABLE); + mConfigFileExists = false; + + LOG(LogInfo) << "Setting up InputManager..."; + + SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); + SDL_GameControllerEventState(SDL_ENABLE); + + if (!Utils::FileSystem::exists(getConfigPath())) { + LOG(LogInfo) << "No input configuration file exists"; + } + else { + mConfigFileExists = true; + } + + mKeyboardInputConfig = std::make_unique(DEVICE_KEYBOARD, + "Keyboard", KEYBOARD_GUID_STRING); + + bool customConfig = loadInputConfig(mKeyboardInputConfig.get()); + + if (customConfig) { + LOG(LogInfo) << "Added keyboard with custom configuration"; + } + else { + loadDefaultKBConfig(); + LOG(LogInfo) << "Added keyboard with default configuration"; + } + + // Load optional controller mappings. Normally the supported controllers should be compiled + // into SDL as a header file, but if a user has a very rare controller that is not supported, + // the bundled mapping is incorrect, or the SDL version is a bit older, it makes sense to be + // able to customize this. If a controller GUID is present in the mappings file that is + // already present inside SDL, the custom mapping will overwrite the bundled one. + std::string mappingsFile = Utils::FileSystem::getHomePath() + + "/.emulationstation/" + "es_controller_mappings.cfg"; + + if (!Utils::FileSystem::exists(mappingsFile)) + mappingsFile = ResourceManager::getInstance()-> + getResourcePath(":/controllers/es_controller_mappings.cfg"); + + int controllerMappings = SDL_GameControllerAddMappingsFromFile(mappingsFile.c_str()); + + if (controllerMappings != -1 && controllerMappings != 0) { + LOG(LogInfo) << "Loaded " << controllerMappings << " controller " << + (controllerMappings == 1 ? "mapping" : "mappings"); + } - // First, open all currently present joysticks. int numJoysticks = SDL_NumJoysticks(); + + // Make sure that every joystick is actually supported by the GameController API. + for (int i = 0; i < numJoysticks; i++) + if (!SDL_IsGameController(i)) + numJoysticks--; + for (int i = 0; i < numJoysticks; i++) addJoystickByDeviceIndex(i); - mKeyboardInputConfig = new InputConfig(DEVICE_KEYBOARD, "Keyboard", KEYBOARD_GUID_STRING); - loadInputConfig(mKeyboardInputConfig); - SDL_USER_CECBUTTONDOWN = SDL_RegisterEvents(2); SDL_USER_CECBUTTONUP = SDL_USER_CECBUTTONDOWN + 1; CECInput::init(); - mCECInputConfig = new InputConfig(DEVICE_CEC, "CEC", CEC_GUID_STRING); - loadInputConfig(mCECInputConfig); -} - -void InputManager::addJoystickByDeviceIndex(int id) -{ - assert(id >= 0 && id < SDL_NumJoysticks()); - - // Open joystick & add to our list. - SDL_Joystick* joy = SDL_JoystickOpen(id); - assert(joy); - - // Add it to our list so we can close it again later. - SDL_JoystickID joyId = SDL_JoystickInstanceID(joy); - mJoysticks[joyId] = joy; - - char guid[65]; - SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy), guid, 65); - - // Create the InputConfig. - mInputConfigs[joyId] = new InputConfig(joyId, SDL_JoystickName(joy), guid); - if (!loadInputConfig(mInputConfigs[joyId])) - LOG(LogInfo) << "Added unconfigured joystick " << SDL_JoystickName(joy) << " (GUID: " << - guid << ", instance ID: " << joyId << ", device index: " << id << ")."; - else - LOG(LogInfo) << "Added known joystick " << SDL_JoystickName(joy) << " (instance ID: " << - joyId << ", device index: " << id << ")"; - - // Set up the prevAxisValues. - int numAxes = SDL_JoystickNumAxes(joy); - mPrevAxisValues[joyId] = new int[numAxes]; - // Initialize array to 0. - std::fill(mPrevAxisValues[joyId], mPrevAxisValues[joyId] + numAxes, 0); -} - -void InputManager::removeJoystickByJoystickID(SDL_JoystickID joyId) -{ - assert(joyId != -1); - - // Delete old prevAxisValues. - auto axisIt = mPrevAxisValues.find(joyId); - delete[] axisIt->second; - mPrevAxisValues.erase(axisIt); - - // Delete old InputConfig. - auto it = mInputConfigs.find(joyId); - delete it->second; - mInputConfigs.erase(it); - - // Close the joystick. - auto joyIt = mJoysticks.find(joyId); - if (joyIt != mJoysticks.cend()) { - SDL_JoystickClose(joyIt->second); - mJoysticks.erase(joyIt); - } - else { - LOG(LogError) << "Could not find joystick to close (instance ID: " << joyId << ")"; - } + mCECInputConfig = std::make_unique(DEVICE_CEC, "CEC", CEC_GUID_STRING); + loadInputConfig(mCECInputConfig.get()); } void InputManager::deinit() @@ -148,32 +119,22 @@ void InputManager::deinit() if (!initialized()) return; - for (auto it = mJoysticks.cbegin(); it != mJoysticks.cend(); it++) - SDL_JoystickClose(it->second); - mJoysticks.clear(); + for (auto it = mControllers.cbegin(); it != mControllers.cend(); it++) + SDL_GameControllerClose(it->second); - for (auto it = mInputConfigs.cbegin(); it != mInputConfigs.cend(); it++) - delete it->second; + mControllers.clear(); + mJoysticks.clear(); + mPrevAxisValues.clear(); + mPrevButtonValues.clear(); mInputConfigs.clear(); - for (auto it = mPrevAxisValues.cbegin(); it != mPrevAxisValues.cend(); it++) - delete[] it->second; - mPrevAxisValues.clear(); - - if (mKeyboardInputConfig != nullptr) { - delete mKeyboardInputConfig; - mKeyboardInputConfig = nullptr; - } - - if (mCECInputConfig != nullptr) { - delete mCECInputConfig; - mCECInputConfig = nullptr; - } + mKeyboardInputConfig.reset(); + mCECInputConfig.reset(); CECInput::deinit(); - SDL_JoystickEventState(SDL_DISABLE); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_GameControllerEventState(SDL_DISABLE); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); if (sInstance) { delete sInstance; @@ -181,243 +142,6 @@ void InputManager::deinit() } } -int InputManager::getNumJoysticks() -{ - int numJoysticks = 0; - - // This is a workaround to exclude the keyboard (ID -1) from the total joystick count. - // It's incorrectly added when configuring the keyboard in GuiInputConfig, but I've - // been unable to find a proper fix to not having it added to mJoysticks. - for (auto it = mJoysticks.cbegin(); it != mJoysticks.cend(); it++) { - if ((*it).first >= 0) - numJoysticks += 1; - } - return numJoysticks; -} - -int InputManager::getAxisCountByDevice(SDL_JoystickID id) -{ - return SDL_JoystickNumAxes(mJoysticks[id]); -} - -int InputManager::getButtonCountByDevice(SDL_JoystickID id) -{ - if (id == DEVICE_KEYBOARD) - return 120; // It's a lot, okay. - else if (id == DEVICE_CEC) - #if defined(HAVE_CECLIB) - return CEC::CEC_USER_CONTROL_CODE_MAX; - #else // HAVE_LIBCEF - return 0; - #endif // HAVE_CECLIB - else - return SDL_JoystickNumButtons(mJoysticks[id]); -} - -InputConfig* InputManager::getInputConfigByDevice(int device) -{ - if (device == DEVICE_KEYBOARD) - return mKeyboardInputConfig; - else if (device == DEVICE_CEC) - return mCECInputConfig; - else - return mInputConfigs[device]; -} - -bool InputManager::parseEvent(const SDL_Event& ev, Window* window) -{ - bool causedEvent = false; - int32_t axisValue; - - switch (ev.type) { - case SDL_JOYAXISMOTION: - // This should hopefully prevent a potential crash if the controller was unplugged - // while a game was running. - if (!mInputConfigs[ev.jaxis.which]) - return false; - - axisValue = ev.jaxis.value; - // For the analog trigger buttons, convert the negative<->positive axis values to only - // positive values in order to avoid registering double inputs. This is only a - // temporary solution until ES has been updated to use the SDL GameController API. - if (ev.jaxis.axis == mInputConfigs[ev.jaxis.which]->getInputIDByName("lefttrigger") || - ev.jaxis.axis == mInputConfigs[ev.jaxis.which]->getInputIDByName("righttrigger")) { - axisValue += 32768; - axisValue /= 2; - } - - // Check if the input value switched boundaries. - if ((abs(axisValue) > DEADZONE) != - (abs(mPrevAxisValues[ev.jaxis.which][ev.jaxis.axis]) > DEADZONE)) { - int normValue; - if (abs(axisValue) <= DEADZONE) { - normValue = 0; - } - else { - if (axisValue > 0) - normValue = 1; - else - normValue = -1; - } - - window->input(getInputConfigByDevice(ev.jaxis.which), Input(ev.jaxis.which, - TYPE_AXIS, ev.jaxis.axis, normValue, false)); - causedEvent = true; - } - - mPrevAxisValues[ev.jaxis.which][ev.jaxis.axis] = axisValue; - return causedEvent; - - case SDL_JOYBUTTONDOWN: - - case SDL_JOYBUTTONUP: - if (!mInputConfigs[ev.jaxis.which]) - return false; - - window->input(getInputConfigByDevice(ev.jbutton.which), Input(ev.jbutton.which, - TYPE_BUTTON, ev.jbutton.button, ev.jbutton.state == SDL_PRESSED, false)); - return true; - - case SDL_JOYHATMOTION: - if (!mInputConfigs[ev.jaxis.which]) - return false; - - window->input(getInputConfigByDevice(ev.jhat.which), Input(ev.jhat.which, - TYPE_HAT, ev.jhat.hat, ev.jhat.value, false)); - return true; - - case SDL_KEYDOWN: - if (ev.key.keysym.sym == SDLK_BACKSPACE && SDL_IsTextInputActive()) - window->textInput("\b"); - - if (ev.key.repeat) - return false; - - if (ev.key.keysym.sym == SDLK_F4) { - SDL_Event quit; - quit.type = SDL_QUIT; - SDL_PushEvent(&quit); - return false; - } - - window->input(getInputConfigByDevice(DEVICE_KEYBOARD), Input(DEVICE_KEYBOARD, - TYPE_KEY, ev.key.keysym.sym, 1, false)); - return true; - - case SDL_KEYUP: - window->input(getInputConfigByDevice(DEVICE_KEYBOARD), Input(DEVICE_KEYBOARD, - TYPE_KEY, ev.key.keysym.sym, 0, false)); - return true; - - case SDL_TEXTINPUT: - window->textInput(ev.text.text); - break; - - case SDL_JOYDEVICEADDED: - // ev.jdevice.which is a device index. - addJoystickByDeviceIndex(ev.jdevice.which); - return true; - - case SDL_JOYDEVICEREMOVED: - // ev.jdevice.which is an SDL_JoystickID (instance ID). - removeJoystickByJoystickID(ev.jdevice.which); - return false; - } - - if ((ev.type == static_cast(SDL_USER_CECBUTTONDOWN)) || - (ev.type == static_cast(SDL_USER_CECBUTTONUP))) { - window->input(getInputConfigByDevice(DEVICE_CEC), Input(DEVICE_CEC, - TYPE_CEC_BUTTON, ev.user.code, ev.type == - static_cast(SDL_USER_CECBUTTONDOWN), false)); - return true; - } - - return false; -} - -bool InputManager::loadInputConfig(InputConfig* config) -{ - std::string path = getConfigPath(); - if (!Utils::FileSystem::exists(path)) { - if (config->getDeviceName() == "Keyboard") { - LOG(LogDebug) << "InputManager::loadInputConfig(): Will assign default keyboard " - "mappings as there is no es_input.cfg configuration file"; - loadDefaultKBConfig(); - config->setDefaultConfigFlag(); - } - return false; - } - - pugi::xml_document doc; - #if defined(_WIN64) - pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); - #else - pugi::xml_parse_result res = doc.load_file(path.c_str()); - #endif - - if (!res) { - LOG(LogError) << "Error parsing input config: " << res.description(); - return false; - } - - pugi::xml_node root = doc.child("inputList"); - if (!root) - return false; - - pugi::xml_node configNode = root.find_child_by_attribute("inputConfig", - "deviceGUID", config->getDeviceGUIDString().c_str()); - if (!configNode) - configNode = root.find_child_by_attribute("inputConfig", - "deviceName", config->getDeviceName().c_str()); - if (!configNode) { - if (config->getDeviceName() == "Keyboard") { - LOG(LogDebug) << "InputManager::loadInputConfig(): Assigning default keyboard " - "mappings as there is no keyboard configuration in es_input.cfg."; - loadDefaultKBConfig(); - config->setDefaultConfigFlag(); - return true; - } - else { - return false; - } - } - - config->loadFromXML(configNode); - return true; -} - -// If there is no es_input.cfg file or if the user has not yet configured the keyboard -// mappings, then load these defaults. -void InputManager::loadDefaultKBConfig() -{ - InputConfig* cfg = getInputConfigByDevice(DEVICE_KEYBOARD); - - cfg->clear(); - cfg->mapInput("up", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_UP, 1, true)); - cfg->mapInput("down", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DOWN, 1, true)); - cfg->mapInput("left", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_LEFT, 1, true)); - cfg->mapInput("right", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RIGHT, 1, true)); - - cfg->mapInput("a", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RETURN, 1, true)); - cfg->mapInput("b", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_BACKSPACE, 1, true)); - cfg->mapInput("x", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DELETE, 1, true)); - #if defined(__APPLE__) - cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PRINTSCREEN, 1, true)); - #else - cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_INSERT, 1, true)); - #endif - cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_ESCAPE, 1, true)); - cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true)); - - cfg->mapInput("leftshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEUP, 1, true)); - cfg->mapInput("rightshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEDOWN, 1, true)); - cfg->mapInput("lefttrigger", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_HOME, 1, true)); - cfg->mapInput("righttrigger", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_END, 1, true)); - - cfg->mapInput("leftthumbstickclick", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F2, 1, true)); - cfg->mapInput("rightthumbstickclick", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F3, 1, true)); -} - void InputManager::writeDeviceConfig(InputConfig* config) { assert(initialized()); @@ -425,7 +149,7 @@ void InputManager::writeDeviceConfig(InputConfig* config) std::string path = getConfigPath(); LOG(LogDebug) << "InputManager::writeDeviceConfig(): " - "Saving input configuration file to " << path; + "Saving input configuration file to \"" << path << "\""; pugi::xml_document doc; @@ -438,7 +162,7 @@ void InputManager::writeDeviceConfig(InputConfig* config) pugi::xml_parse_result result = doc.load_file(path.c_str()); #endif if (!result) { - LOG(LogError) << "Error parsing input config: " << result.description(); + LOG(LogError) << "Couldn't parse input configuration file: " << result.description(); } else { // Successfully loaded, delete the old entry if it exists. @@ -486,6 +210,7 @@ void InputManager::writeDeviceConfig(InputConfig* config) // Execute any doOnFinish commands and reload the config for changes. doOnFinish(); + mConfigFileExists = true; loadInputConfig(config); } @@ -504,7 +229,7 @@ void InputManager::doOnFinish() #endif if (!result) { - LOG(LogError) << "Couldn't parse input config: " << result.description(); + LOG(LogError) << "Couldn't parse input configuration file: " << result.description(); } else { pugi::xml_node root = doc.child("inputList"); @@ -546,18 +271,26 @@ std::string InputManager::getTemporaryConfigPath() return path; } -bool InputManager::initialized() const +int InputManager::getNumJoysticks() { - return mKeyboardInputConfig != nullptr; + int numJoysticks = 0; + + // This is a workaround to exclude the keyboard (ID -1) from the total joystick count. + // It's incorrectly added when configuring the keyboard in GuiInputConfig, but I've + // been unable to find a proper fix to not having it added to mJoysticks. + for (auto it = mJoysticks.cbegin(); it != mJoysticks.cend(); it++) { + if ((*it).first >= 0) + numJoysticks += 1; + } + return numJoysticks; } int InputManager::getNumConfiguredDevices() { int num = 0; - for (auto it = mInputConfigs.cbegin(); it != mInputConfigs.cend(); it++) { + for (auto it = mInputConfigs.cbegin(); it != mInputConfigs.cend(); it++) if (it->second->isConfigured()) num++; - } if (mKeyboardInputConfig->isConfigured()) num++; @@ -568,6 +301,25 @@ int InputManager::getNumConfiguredDevices() return num; } +int InputManager::getAxisCountByDevice(SDL_JoystickID id) +{ + return SDL_JoystickNumAxes(mJoysticks[id]); +} + +int InputManager::getButtonCountByDevice(SDL_JoystickID id) +{ + if (id == DEVICE_KEYBOARD) + return -1; + else if (id == DEVICE_CEC) + #if defined(HAVE_CECLIB) + return CEC::CEC_USER_CONTROL_CODE_MAX; + #else + return 0; + #endif + else + return SDL_JoystickNumButtons(mJoysticks[id]); +} + std::string InputManager::getDeviceGUIDString(int deviceId) { if (deviceId == DEVICE_KEYBOARD) @@ -579,10 +331,329 @@ std::string InputManager::getDeviceGUIDString(int deviceId) auto it = mJoysticks.find(deviceId); if (it == mJoysticks.cend()) { LOG(LogError) << "getDeviceGUIDString - deviceId " << deviceId << " not found!"; - return "something went horribly wrong"; + return "Something went horribly wrong"; } char guid[65]; SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(it->second), guid, 65); return std::string(guid); } + +InputConfig* InputManager::getInputConfigByDevice(int device) +{ + if (device == DEVICE_KEYBOARD) + return mKeyboardInputConfig.get(); + else if (device == DEVICE_CEC) + return mCECInputConfig.get(); + else + return mInputConfigs[device].get(); +} + +bool InputManager::parseEvent(const SDL_Event& event, Window* window) +{ + bool causedEvent = false; + int32_t axisValue; + + switch (event.type) { + case SDL_CONTROLLERAXISMOTION: { + // This is needed for a situation which sometimes occur when a game is launched, + // some axis input is generated and then the controller is disconnected before + // leaving the game. In this case, events for the old device index could be received + // when returning from the game. If this happens we simply delete the configuration + // map entry. + if (!mInputConfigs[event.caxis.which]) { + auto it = mInputConfigs.find(event.cdevice.which); + mInputConfigs.erase(it); + return false; + } + + axisValue = event.caxis.value; + + // Check if the input value switched boundaries. + if ((abs(axisValue) > DEADZONE) != (abs(mPrevAxisValues[ + std::make_pair(event.caxis.which, event.caxis.axis)]) > DEADZONE)) { + int normValue; + if (abs(axisValue) <= DEADZONE) { + normValue = 0; + } + else { + if (axisValue > 0) + normValue = 1; + else + normValue = -1; + } + + window->input(getInputConfigByDevice(event.caxis.which), Input(event.caxis.which, + TYPE_AXIS, event.caxis.axis, normValue, false)); + causedEvent = true; + } + + mPrevAxisValues[std::make_pair(event.caxis.which, event.caxis.axis)] = axisValue; + return causedEvent; + } + case SDL_CONTROLLERBUTTONDOWN: { + } + case SDL_CONTROLLERBUTTONUP: { + // The event filtering below is required as some controllers send button presses + // starting with the state 0 when using the D-pad. I consider this invalid behaviour + // and the more popular controllers such as those from Microsoft and Sony do not show + // this strange behavior. + int buttonState = mPrevButtonValues[ + std::make_pair(event.cbutton.which, event.cbutton.button)]; + + if ((buttonState == -1 || buttonState == 0) && event.cbutton.state == 0) + return false; + + mPrevButtonValues[std::make_pair(event.cbutton.which, event.cbutton.button)] = + event.cbutton.state; + + window->input(getInputConfigByDevice(event.cbutton.which), Input(event.cbutton.which, + TYPE_BUTTON, event.cbutton.button, event.cbutton.state == SDL_PRESSED, false)); + + return true; + } + case SDL_KEYDOWN: { + if (event.key.keysym.sym == SDLK_BACKSPACE && SDL_IsTextInputActive()) + window->textInput("\b"); + + if (event.key.repeat) + return false; + + if (event.key.keysym.sym == SDLK_F4) { + SDL_Event quit; + quit.type = SDL_QUIT; + SDL_PushEvent(&quit); + return false; + } + + window->input(getInputConfigByDevice(DEVICE_KEYBOARD), Input(DEVICE_KEYBOARD, + TYPE_KEY, event.key.keysym.sym, 1, false)); + return true; + } + case SDL_KEYUP: { + window->input(getInputConfigByDevice(DEVICE_KEYBOARD), Input(DEVICE_KEYBOARD, + TYPE_KEY, event.key.keysym.sym, 0, false)); + return true; + } + case SDL_TEXTINPUT: { + window->textInput(event.text.text); + break; + } + case SDL_CONTROLLERDEVICEADDED: { + addJoystickByDeviceIndex(event.cdevice.which); + return true; + } + case SDL_CONTROLLERDEVICEREMOVED: { + removeJoystickByJoystickID(event.cdevice.which); + return false; + } + } + + if ((event.type == static_cast(SDL_USER_CECBUTTONDOWN)) || + (event.type == static_cast(SDL_USER_CECBUTTONUP))) { + window->input(getInputConfigByDevice(DEVICE_CEC), Input(DEVICE_CEC, + TYPE_CEC_BUTTON, event.user.code, event.type == + static_cast(SDL_USER_CECBUTTONDOWN), false)); + return true; + } + + return false; +} + +bool InputManager::initialized() const +{ + return mKeyboardInputConfig != nullptr; +} + +bool InputManager::loadInputConfig(InputConfig* config) +{ + if (!mConfigFileExists) + return false; + + std::string path = getConfigPath(); + + pugi::xml_document doc; + #if defined(_WIN64) + pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else + pugi::xml_parse_result res = doc.load_file(path.c_str()); + #endif + + if (!res) { + LOG(LogError) << "Couldn't parse the input configuration file: " << res.description(); + return false; + } + + pugi::xml_node root = doc.child("inputList"); + if (!root) + return false; + + pugi::xml_node configNode = root.find_child_by_attribute("inputConfig", + "deviceGUID", config->getDeviceGUIDString().c_str()); + + // Enabling this will match an entry in es_input.cfg based on the device name if there + // was no GUID match. This is probably not a good idea as many controllers share the same + // name even though the GUID differ and potentially the button configuration could be + // different between them. Keeping the code for now though. +// if (!configNode) +// configNode = root.find_child_by_attribute("inputConfig", +// "deviceName", config->getDeviceName().c_str()); + + // With the move to the SDL GameController API the button layout changed quite a lot, so + // es_input.cfg files generated using the old API will end up with a completely unusable + // controller configuration. These older files had the configuration entry type set to + // "joystick", so it's easy to ignore such entries by only accepting entries with the + // type set to "controller" (which is now applied when saving the es_input.cfg file). + if (configNode && config->getDeviceName() != "Keyboard") + if (!root.find_child_by_attribute("inputConfig", "type", "controller")) + return false; + + if (!configNode) + return false; + + config->loadFromXML(configNode); + return true; +} + +void InputManager::loadDefaultKBConfig() +{ + InputConfig* cfg = getInputConfigByDevice(DEVICE_KEYBOARD); + + if (cfg->isConfigured()) + return; + + cfg->clear(); + cfg->mapInput("up", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_UP, 1, true)); + cfg->mapInput("down", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DOWN, 1, true)); + cfg->mapInput("left", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_LEFT, 1, true)); + cfg->mapInput("right", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RIGHT, 1, true)); + + cfg->mapInput("a", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RETURN, 1, true)); + cfg->mapInput("b", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_BACKSPACE, 1, true)); + cfg->mapInput("x", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DELETE, 1, true)); + #if defined(__APPLE__) + cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PRINTSCREEN, 1, true)); + #else + cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_INSERT, 1, true)); + #endif + cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_ESCAPE, 1, true)); + cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true)); + + cfg->mapInput("leftshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEUP, 1, true)); + cfg->mapInput("rightshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEDOWN, 1, true)); + cfg->mapInput("lefttrigger", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_HOME, 1, true)); + cfg->mapInput("righttrigger", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_END, 1, true)); + + cfg->mapInput("leftthumbstickclick", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F2, 1, true)); + cfg->mapInput("rightthumbstickclick", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F3, 1, true)); +} + +void InputManager::loadDefaultControllerConfig(SDL_JoystickID deviceIndex) +{ + InputConfig* cfg = getInputConfigByDevice(deviceIndex); + + if (cfg->isConfigured()) + return; + + cfg->mapInput("up", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_UP, 1, true)); + cfg->mapInput("down", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_DOWN, 1, true)); + cfg->mapInput("left", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_LEFT, 1, true)); + cfg->mapInput("right", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, 1, true)); + cfg->mapInput("Start", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_START, 1, true)); + cfg->mapInput("Select", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_BACK, 1, true)); + cfg->mapInput("A", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_A, 1, true)); + cfg->mapInput("B", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_B, 1, true)); + cfg->mapInput("X", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_X, 1, true)); + cfg->mapInput("Y", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_Y, 1, true)); + cfg->mapInput("LeftShoulder", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, 1, true)); + cfg->mapInput("RightShoulder", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, 1, true)); + cfg->mapInput("LeftTrigger", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 1, true)); + cfg->mapInput("RightTrigger", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 1, true)); + cfg->mapInput("LeftThumbstickUp", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTY, -1, true)); + cfg->mapInput("LeftThumbstickDown", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTY, 1, true)); + cfg->mapInput("LeftThumbstickLeft", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTX, -1, true)); + cfg->mapInput("LeftThumbstickRight", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTX, 1, true)); + cfg->mapInput("LeftThumbstickClick", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_LEFTSTICK, 1, true)); + cfg->mapInput("RightThumbstickUp", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, -1, true)); + cfg->mapInput("RightThumbstickDown", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, 1, true)); + cfg->mapInput("RightThumbstickLeft", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTX, -1, true)); + cfg->mapInput("RightThumbstickRight", Input(deviceIndex, TYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTX, 1, true)); + cfg->mapInput("RightThumbstickClick", Input(deviceIndex, TYPE_BUTTON, SDL_CONTROLLER_BUTTON_RIGHTSTICK, 1, true)); +} + +void InputManager::addJoystickByDeviceIndex(int deviceIndex) +{ + // Open joystick and add it to our list. + SDL_GameController* controller = SDL_GameControllerOpen(deviceIndex); + SDL_Joystick* joy = SDL_GameControllerGetJoystick(controller); + + // Add it to our list so we can close it again later. + SDL_JoystickID joyID = SDL_JoystickInstanceID(joy); + mJoysticks[joyID] = joy; + mControllers[joyID] = controller; + + char guid[65]; + SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy), guid, 65); + + mInputConfigs[joyID] = std::make_unique( + joyID, SDL_GameControllerName(mControllers[joyID]), guid); + + bool customConfig = loadInputConfig(mInputConfigs[joyID].get()); + + if (customConfig) { + LOG(LogInfo) << "Added controller with custom configuration: \"" << + SDL_GameControllerName(mControllers[joyID]) << "\" (GUID: " << guid << + ", instance ID: " << joyID << ", device index: " << deviceIndex << ")"; + } + else { + loadDefaultControllerConfig(joyID); + LOG(LogInfo) << "Added controller with default configuration: \"" << + SDL_GameControllerName(mControllers[joyID]) << "\" (GUID: " << guid << + ", instance ID: " << joyID << ", device index: " << deviceIndex << ")"; + } + + int numAxes = SDL_JoystickNumAxes(joy); + int numButtons = SDL_JoystickNumButtons(joy); + + for (int axis = 0; axis < numAxes; axis++) + mPrevAxisValues[std::make_pair(joyID, axis)] = 0; + + for (int button = 0; button < numButtons; button++) + mPrevButtonValues[std::make_pair(joyID, button)] = -1; +} + +void InputManager::removeJoystickByJoystickID(SDL_JoystickID deviceIndex) +{ + assert(deviceIndex != -1); + + // Delete mPrevAxisValues for the device. + int axisEntries = mPrevAxisValues.size(); + for (int i = 0; i < axisEntries; i++) { + auto entry = mPrevAxisValues.find(std::make_pair(deviceIndex, i)); + if (entry != mPrevAxisValues.end()) { + mPrevAxisValues.erase(entry); + } + } + + // Delete mPrevButtonValues for the device. + int buttonEntries = mPrevButtonValues.size(); + for (int i = 0; i < buttonEntries; i++) { + auto entry = mPrevButtonValues.find(std::make_pair(deviceIndex, i)); + if (entry != mPrevButtonValues.end()) { + mPrevButtonValues.erase(entry); + } + } + + auto it = mInputConfigs.find(deviceIndex); + mInputConfigs.erase(it); + + // Close the controllers. + auto controllerIt = mControllers.find(deviceIndex); + if (controllerIt != mControllers.cend()) { + SDL_GameControllerClose(controllerIt->second); + mControllers.erase(controllerIt); + } + else { + LOG(LogError) << "Couldn't find controller to close (instance ID: " << deviceIndex << ")"; + } +} diff --git a/es-core/src/InputManager.h b/es-core/src/InputManager.h index ab01ba370..4fd5299ae 100644 --- a/es-core/src/InputManager.h +++ b/es-core/src/InputManager.h @@ -11,6 +11,8 @@ #ifndef ES_CORE_INPUT_MANAGER_H #define ES_CORE_INPUT_MANAGER_H +#include + #if defined(__linux__) || defined(_WIN64) #include #else @@ -18,61 +20,62 @@ #endif #include +#include #include class InputConfig; class Window; union SDL_Event; -// You should only ever instantiate one of these, by the way. class InputManager { public: + InputManager(); virtual ~InputManager(); - static InputManager* getInstance(); - void writeDeviceConfig(InputConfig* config); - void doOnFinish(); - static std::string getConfigPath(); - static std::string getTemporaryConfigPath(); - void init(); void deinit(); + void writeDeviceConfig(InputConfig* config); + void doOnFinish(); + + static std::string getConfigPath(); + static std::string getTemporaryConfigPath(); + int getNumJoysticks(); + int getNumConfiguredDevices(); int getAxisCountByDevice(int deviceId); int getButtonCountByDevice(int deviceId); - int getNumConfiguredDevices(); std::string getDeviceGUIDString(int deviceId); - InputConfig* getInputConfigByDevice(int deviceId); - bool parseEvent(const SDL_Event& ev, Window* window); + bool parseEvent(const SDL_Event& event, Window* window); private: - InputManager(); - - static InputManager* sInstance; - - static const int DEADZONE = 23000; - - void loadDefaultKBConfig(); - - std::map mJoysticks; - std::map mInputConfigs; - InputConfig* mKeyboardInputConfig; - InputConfig* mCECInputConfig; - - std::map mPrevAxisValues; - bool initialized() const; - void addJoystickByDeviceIndex(int id); - void removeJoystickByJoystickID(SDL_JoystickID id); - // Returns true if successfully loaded, false if not (or if it didn't exist). bool loadInputConfig(InputConfig* config); + void loadDefaultKBConfig(); + void loadDefaultControllerConfig(SDL_JoystickID deviceIndex); + + void addJoystickByDeviceIndex(int deviceIndex); + void removeJoystickByJoystickID(SDL_JoystickID deviceIndex); + + static InputManager* sInstance; + static const int DEADZONE = 23000; + bool mConfigFileExists; + + std::map mJoysticks; + std::map mControllers; + std::map> mInputConfigs; + + std::unique_ptr mKeyboardInputConfig; + std::unique_ptr mCECInputConfig; + + std::map, int> mPrevAxisValues; + std::map, int> mPrevButtonValues; }; #endif // ES_CORE_INPUT_MANAGER_H diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 9c19a9ddb..e52be6d26 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -297,7 +297,6 @@ void Settings::setDefaults() // but that are not configurable via the GUI. // - mBoolMap["ShowDefaultKeyboardWarning"] = { true, true }; mStringMap["ROMDirectory"] = { "", "" }; mIntMap["ScraperResizeMaxWidth"] = { 600, 600 }; mIntMap["ScraperResizeMaxHeight"] = { 0, 0 }; @@ -306,7 +305,6 @@ void Settings::setDefaults() // Hardcoded or program-internal settings. // - mBoolMap["BackgroundJoystickInput"] = { false, false }; mBoolMap["DebugGrid"] = { false, false }; mBoolMap["DebugText"] = { false, false }; mBoolMap["DebugImage"] = { false, false }; diff --git a/resources/controllers/es_controller_mappings.cfg b/resources/controllers/es_controller_mappings.cfg new file mode 100644 index 000000000..5d43ffb34 --- /dev/null +++ b/resources/controllers/es_controller_mappings.cfg @@ -0,0 +1,6 @@ +# This file can be used to add mappings for controllers that are not supported by SDL by default. +# Either modify the file at its default location or copy it to ~/.emulationstation/ +# +# Configuration entries should be in the format described at this URL: +# https://github.com/gabomdq/SDL_GameControllerDB +