Improved input device configuration.

Default keyboard mappings are now applied if the user has not configured the keyboard.
This commit is contained in:
Leon Styhre 2020-07-14 19:16:21 +02:00
parent 57d6dab2cc
commit 1b65eaac2e
14 changed files with 178 additions and 111 deletions

View file

@ -38,6 +38,7 @@ https://google.github.io/styleguide/cppguide.html
* Comments should be proper sentences, starting with a capital letter and ending with a dot * Comments should be proper sentences, starting with a capital letter and ending with a dot
* Use K&R placements of braces, read the Linux Kernel coding style document for clarifications * Use K&R placements of braces, read the Linux Kernel coding style document for clarifications
* Always use spaces between keywords and opening brackets, i.e. `if ()`, `for ()`, `while ()` etc. * Always use spaces between keywords and opening brackets, i.e. `if ()`, `for ()`, `while ()` etc.
* Indentation of switch/case statements is optional, but it's usually easier to read the code with indentations in place
* Use `std::string` instead of `char *` or `char []` unless there is a specific reason requiring the latter * Use `std::string` instead of `char *` or `char []` unless there is a specific reason requiring the latter
* If the arguments (and initializer list) for a function or class exceeds 4 items, arrange them vertically to make the code easier to read * If the arguments (and initializer list) for a function or class exceeds 4 items, arrange them vertically to make the code easier to read
* Always declare one variable per line, never combine multiple declarations of the same type * Always declare one variable per line, never combine multiple declarations of the same type

View file

@ -18,6 +18,7 @@ v1.0.0
* Per-game launch command override, so that different cores or emulators can be used on a per-game basis (saved to gamelist.xml) * Per-game launch command override, so that different cores or emulators can be used on a per-game basis (saved to gamelist.xml)
* Core location can be defined relative to the emulator binary using the %EMUPATH% varible in es_systems.cfg (mostly useful for Windows) * Core location can be defined relative to the emulator binary using the %EMUPATH% varible in es_systems.cfg (mostly useful for Windows)
* Help system updated and expanded to the complete application (previously it was only partially implemented) * Help system updated and expanded to the complete application (previously it was only partially implemented)
* Improved input device configuration, and default keyboard mappings are now applied if the keyboard has not been configured by the user
* GUI-configurable option to sort favorite games on the top of the game lists (favorites marked with stars) * GUI-configurable option to sort favorite games on the top of the game lists (favorites marked with stars)
* Added new component GuiComplexTextEditPopup to handle changes to configuration file entries and similar * Added new component GuiComplexTextEditPopup to handle changes to configuration file entries and similar
* Speed improvements and optimizations, the application now starts faster and feels more responsive * Speed improvements and optimizations, the application now starts faster and feels more responsive

View file

@ -495,6 +495,11 @@ int main(int argc, char* argv[])
ViewController::get()->goToStart(); ViewController::get()->goToStart();
} }
else { else {
// Always reset ShowDefaultKeyboardWarning to true if the es_input.cfg
// file is missing.
Settings::getInstance()->setBool("ShowDefaultKeyboardWarning", true);
Settings::getInstance()->saveFile();
window.pushGui(new GuiDetectDevice(&window, true, [] { window.pushGui(new GuiDetectDevice(&window, true, [] {
ViewController::get()->goToStart(); })); ViewController::get()->goToStart(); }));
} }

View file

@ -146,8 +146,8 @@ void UIModeController::logInput(InputConfig * config, Input input)
mapname += ", "; mapname += ", ";
} }
LOG(LogDebug) << "UIModeController::logInput(" << config->getDeviceName() << LOG(LogDebug) << "UIModeController::logInput(" << config->getDeviceName() << "): " <<
"): " << input.string() << ", isMappedTo=" << mapname << ", value=" << input.value; input.string() << ", isMappedTo=" << mapname << ", value=" << input.value;
} }
bool UIModeController::isValidInput(InputConfig* config, Input input) bool UIModeController::isValidInput(InputConfig* config, Input input)

View file

@ -14,6 +14,7 @@
#include "animations/LaunchAnimation.h" #include "animations/LaunchAnimation.h"
#include "animations/MoveCameraAnimation.h" #include "animations/MoveCameraAnimation.h"
#include "guis/GuiMenu.h" #include "guis/GuiMenu.h"
#include "guis/GuiMsgBox.h"
#include "views/gamelist/DetailedGameListView.h" #include "views/gamelist/DetailedGameListView.h"
#include "views/gamelist/IGameListView.h" #include "views/gamelist/IGameListView.h"
#include "views/gamelist/GridGameListView.h" #include "views/gamelist/GridGameListView.h"
@ -21,11 +22,12 @@
#include "views/SystemView.h" #include "views/SystemView.h"
#include "views/UIModeController.h" #include "views/UIModeController.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "InputManager.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "Sound.h"
#include "SystemData.h" #include "SystemData.h"
#include "Window.h" #include "Window.h"
#include "Sound.h"
ViewController* ViewController::sInstance = nullptr; ViewController* ViewController::sInstance = nullptr;
@ -60,6 +62,26 @@ ViewController::~ViewController()
void ViewController::goToStart() 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()) {
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 a specific system is requested, go directly to its game list. // If a specific system is requested, go directly to its game list.
auto requestedSystem = Settings::getInstance()->getString("StartupSystem"); auto requestedSystem = Settings::getInstance()->getString("StartupSystem");
if ("" != requestedSystem && "retropie" != requestedSystem) { if ("" != requestedSystem && "retropie" != requestedSystem) {

View file

@ -59,7 +59,8 @@ InputConfig::InputConfig(
const std::string& deviceGUID) const std::string& deviceGUID)
: mDeviceId(deviceId), : mDeviceId(deviceId),
mDeviceName(deviceName), mDeviceName(deviceName),
mDeviceGUID(deviceGUID) mDeviceGUID(deviceGUID),
mDefaultConfigFlag(false)
{ {
} }
@ -186,7 +187,7 @@ void InputConfig::loadFromXML(pugi::xml_node& node)
InputType typeEnum = stringToInputType(type); InputType typeEnum = stringToInputType(type);
if (typeEnum == TYPE_COUNT) { if (typeEnum == TYPE_COUNT) {
LOG(LogError) << "InputConfig load error - input of type \"" << type << LOG(LogError) << "Error - InputConfig load error - input of type \"" << type <<
"\" is invalid! Skipping input \"" << name << "\".\n"; "\" is invalid! Skipping input \"" << name << "\".\n";
continue; continue;
} }
@ -195,7 +196,7 @@ void InputConfig::loadFromXML(pugi::xml_node& node)
int value = input.attribute("value").as_int(); int value = input.attribute("value").as_int();
if (value == 0) { if (value == 0) {
LOG(LogWarning) << "WARNING: InputConfig value is 0 for " << LOG(LogWarning) << "Warning - InputConfig value is 0 for " <<
type << " " << id << "!\n"; type << " " << id << "!\n";
} }

View file

@ -128,6 +128,10 @@ public:
inline const std::string& getDeviceName() { return mDeviceName; } inline const std::string& getDeviceName() { return mDeviceName; }
inline const std::string& getDeviceGUIDString() { return mDeviceGUID; } 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. // Returns true if Input is mapped to this name, false otherwise.
bool isMappedTo(const std::string& name, Input input); bool isMappedTo(const std::string& name, Input input);
bool isMappedLike(const std::string& name, Input input); bool isMappedLike(const std::string& name, Input input);
@ -149,6 +153,7 @@ private:
const int mDeviceId; const int mDeviceId;
const std::string mDeviceName; const std::string mDeviceName;
const std::string mDeviceGUID; const std::string mDeviceGUID;
bool mDefaultConfigFlag;
}; };
#endif // ES_CORE_INPUT_CONFIG_H #endif // ES_CORE_INPUT_CONFIG_H

View file

@ -299,8 +299,15 @@ bool InputManager::parseEvent(const SDL_Event& ev, Window* window)
bool InputManager::loadInputConfig(InputConfig* config) bool InputManager::loadInputConfig(InputConfig* config)
{ {
std::string path = getConfigPath(); std::string path = getConfigPath();
if (!Utils::FileSystem::exists(path)) if (!Utils::FileSystem::exists(path)) {
if (config->getDeviceName() == "Keyboard") {
LOG(LogDebug) << "InputManager::loadInputConfig(): Assigning default keyboard "
"mappings as there is no es_input.cfg configuration file.";
loadDefaultKBConfig();
config->setDefaultConfigFlag();
}
return false; return false;
}
pugi::xml_document doc; pugi::xml_document doc;
#ifdef _WIN64 #ifdef _WIN64
@ -323,17 +330,25 @@ bool InputManager::loadInputConfig(InputConfig* config)
if (!configNode) if (!configNode)
configNode = root.find_child_by_attribute("inputConfig", configNode = root.find_child_by_attribute("inputConfig",
"deviceName", config->getDeviceName().c_str()); "deviceName", config->getDeviceName().c_str());
if (!configNode) 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; return false;
}
}
config->loadFromXML(configNode); config->loadFromXML(configNode);
return true; return true;
} }
// Used in an "emergency" where no keyboard config could be loaded from the inputmanager // If there is no es_input.cfg file or if the user has not yet configured the keyboard
// config file. Allows the user to select to reconfigure in menus if this happens without // mappings, then load these defaults.
// having to delete es_input.cfg manually.
// Note: Not currently used.
void InputManager::loadDefaultKBConfig() void InputManager::loadDefaultKBConfig()
{ {
InputConfig* cfg = getInputConfigByDevice(DEVICE_KEYBOARD); InputConfig* cfg = getInputConfigByDevice(DEVICE_KEYBOARD);
@ -363,6 +378,9 @@ void InputManager::writeDeviceConfig(InputConfig* config)
std::string path = getConfigPath(); std::string path = getConfigPath();
LOG(LogDebug) << "InputManager::writeDeviceConfig(): "
"Saving input configuration file to " << path;
pugi::xml_document doc; pugi::xml_document doc;
if (Utils::FileSystem::exists(path)) { if (Utils::FileSystem::exists(path)) {
@ -420,7 +438,7 @@ void InputManager::writeDeviceConfig(InputConfig* config)
Scripting::fireEvent("config-changed"); Scripting::fireEvent("config-changed");
Scripting::fireEvent("controls-changed"); Scripting::fireEvent("controls-changed");
// Execute any doOnFinish commands and re-load the config for changes. // Execute any doOnFinish commands and reload the config for changes.
doOnFinish(); doOnFinish();
loadInputConfig(config); loadInputConfig(config);
} }
@ -440,7 +458,7 @@ void InputManager::doOnFinish()
#endif #endif
if (!result) { if (!result) {
LOG(LogError) << "Error parsing input config: " << result.description(); LOG(LogError) << "Error - Couldn't parse input config: " << result.description();
} }
else { else {
pugi::xml_node root = doc.child("inputList"); pugi::xml_node root = doc.child("inputList");
@ -514,7 +532,7 @@ std::string InputManager::getDeviceGUIDString(int deviceId)
auto it = mJoysticks.find(deviceId); auto it = mJoysticks.find(deviceId);
if (it == mJoysticks.cend()) { if (it == mJoysticks.cend()) {
LOG(LogError) << "getDeviceGUIDString - deviceId " << deviceId << " not found!"; LOG(LogError) << "Error - getDeviceGUIDString - deviceId " << deviceId << " not found!";
return "something went horribly wrong"; return "something went horribly wrong";
} }

View file

@ -25,29 +25,6 @@ union SDL_Event;
// You should only ever instantiate one of these, by the way. // You should only ever instantiate one of these, by the way.
class InputManager class InputManager
{ {
private:
InputManager();
static InputManager* mInstance;
static const int DEADZONE = 23000;
void loadDefaultKBConfig();
std::map<SDL_JoystickID, SDL_Joystick*> mJoysticks;
std::map<SDL_JoystickID, InputConfig*> mInputConfigs;
InputConfig* mKeyboardInputConfig;
InputConfig* mCECInputConfig;
std::map<SDL_JoystickID, int*> 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);
public: public:
virtual ~InputManager(); virtual ~InputManager();
@ -71,6 +48,29 @@ public:
InputConfig* getInputConfigByDevice(int deviceId); InputConfig* getInputConfigByDevice(int deviceId);
bool parseEvent(const SDL_Event& ev, Window* window); bool parseEvent(const SDL_Event& ev, Window* window);
private:
InputManager();
static InputManager* mInstance;
static const int DEADZONE = 23000;
void loadDefaultKBConfig();
std::map<SDL_JoystickID, SDL_Joystick*> mJoysticks;
std::map<SDL_JoystickID, InputConfig*> mInputConfigs;
InputConfig* mKeyboardInputConfig;
InputConfig* mCECInputConfig;
std::map<SDL_JoystickID, int*> 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);
}; };
#endif // ES_CORE_INPUT_MANAGER_H #endif // ES_CORE_INPUT_MANAGER_H

View file

@ -214,9 +214,10 @@ void Settings::setDefaults()
// //
// Settings that can be changed in es_settings.cfg // Settings that can be changed in es_settings.cfg
// but that are not configurable via the GUI (yet). // but that are not configurable via the GUI.
// //
mBoolMap["ShowDefaultKeyboardWarning"] = true;
mStringMap["DefaultSortOrder"] = "filename, ascending"; mStringMap["DefaultSortOrder"] = "filename, ascending";
mStringMap["MediaDirectory"] = ""; mStringMap["MediaDirectory"] = "";
mStringMap["ROMDirectory"] = ""; mStringMap["ROMDirectory"] = "";

View file

@ -13,6 +13,7 @@
#include "InputManager.h" #include "InputManager.h"
#include "Log.h" #include "Log.h"
#include "Scripting.h" #include "Scripting.h"
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
@ -130,7 +131,8 @@ void Window::input(InputConfig* config, Input input)
if (mScreenSaver->isScreenSaverActive() && if (mScreenSaver->isScreenSaverActive() &&
Settings::getInstance()->getBool("ScreenSaverControls") && Settings::getInstance()->getBool("ScreenSaverControls") &&
(Settings::getInstance()->getString("ScreenSaverBehavior") == "random video")) { (Settings::getInstance()->getString("ScreenSaverBehavior") == "random video")) {
if (mScreenSaver->getCurrentGame() != nullptr && (config->isMappedLike("right", input) || if (mScreenSaver->getCurrentGame() != nullptr &&
(config->isMappedLike("right", input) ||
config->isMappedTo("start", input) || config->isMappedTo("select", input))) { config->isMappedTo("start", input) || config->isMappedTo("select", input))) {
if (config->isMappedLike("right", input) || config->isMappedTo("select", input)) { if (config->isMappedLike("right", input) || config->isMappedTo("select", input)) {
if (input.value != 0) { if (input.value != 0) {
@ -140,7 +142,7 @@ void Window::input(InputConfig* config, Input input)
return; return;
} }
else if (config->isMappedTo("start", input) && input.value != 0) { else if (config->isMappedTo("start", input) && input.value != 0) {
// Launch game! // Launch game.
cancelScreenSaver(); cancelScreenSaver();
mScreenSaver->launchGame(); mScreenSaver->launchGame();
// To force handling the wake up process. // To force handling the wake up process.
@ -388,8 +390,7 @@ void Window::setHelpPrompts(const std::vector<HelpPrompt>& prompts, const HelpSt
int i = 0; int i = 0;
int aVal = 0; int aVal = 0;
int bVal = 0; int bVal = 0;
while (map[i] != nullptr) while (map[i] != nullptr) {
{
if (a.first == map[i]) if (a.first == map[i])
aVal = i; aVal = i;
if (b.first == map[i]) if (b.first == map[i])

View file

@ -27,7 +27,8 @@ struct HelpStyle;
class Window class Window
{ {
public: public:
class ScreenSaver { class ScreenSaver
{
public: public:
virtual void startScreenSaver() = 0; virtual void startScreenSaver() = 0;
virtual void stopScreenSaver() = 0; virtual void stopScreenSaver() = 0;
@ -41,7 +42,8 @@ public:
virtual void resetCounts() = 0; virtual void resetCounts() = 0;
}; };
class InfoPopup { class InfoPopup
{
public: public:
virtual void render(const Transform4x4f& parentTrans) = 0; virtual void render(const Transform4x4f& parentTrans) = 0;
virtual void stop() = 0; virtual void stop() = 0;

View file

@ -51,7 +51,7 @@ GuiDetectDevice::GuiDetectDevice(
// Message. // Message.
mMsg1 = std::make_shared<TextComponent>(mWindow, mMsg1 = std::make_shared<TextComponent>(mWindow,
"HOLD A BUTTON ON YOUR DEVICE TO CONFIGURE IT.", "HOLD A BUTTON ON YOUR DEVICE OR KEYBOARD TO CONFIGURE IT.",
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER); Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
mGrid.setEntry(mMsg1, Vector2i(0, 2), false, true); mGrid.setEntry(mMsg1, Vector2i(0, 2), false, true);

View file

@ -200,22 +200,33 @@ GuiInputConfig::GuiInputConfig(
// Buttons. // Buttons.
std::vector< std::shared_ptr<ButtonComponent> > buttons; std::vector< std::shared_ptr<ButtonComponent> > buttons;
std::function<void()> okFunction = [this, okCallback] { std::function<void()> okFunction = [this, okCallback] {
InputManager::getInstance()->writeDeviceConfig(mTargetConfig); // save // If we have just configured the keyboard, then unset the flag to indicate that
// we are using the default keyboard mappings.
if (mTargetConfig->getDeviceId() == DEVICE_KEYBOARD) {
InputManager::getInstance()->
getInputConfigByDevice(DEVICE_KEYBOARD)->unsetDefaultConfigFlag();
}
InputManager::getInstance()->writeDeviceConfig(mTargetConfig); // Save.
if (okCallback) if (okCallback)
okCallback(); okCallback();
delete this; delete this;
}; };
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "OK", "ok", [this, okFunction] {
// Check if the hotkey enable button is set. if not prompt the buttons.push_back(std::make_shared<ButtonComponent>
// user to use select or nothing. (mWindow, "OK", "ok", [this, okFunction] { okFunction(); }));
Input input;
okFunction(); // This code is disabled as there is no intention to provide emulator configuration or
// Temporarily commented out, needs to be properly cleaned up later. // control via ES Desktop Edition. Let's keep the code for reference though.
// buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "OK", "ok", [this, okFunction] {
// // Check if the hotkey enable button is set. if not prompt the
// // user to use select or nothing.
// Input input;
// okFunction();
// if (!mTargetConfig->getInputByName("HotKeyEnable", &input)) { // if (!mTargetConfig->getInputByName("HotKeyEnable", &input)) {
// mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), // mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
// "YOU DIDN'T CHOOSE A HOTKEY ENABLE BUTTON. THIS IS REQUIRED FOR EXITING GAMES " // "YOU DIDN'T CHOOSE A HOTKEY ENABLE BUTTON. THIS IS REQUIRED FOR EXITING "
// "WITH A CONTROLLER. DO YOU WANT TO USE THE SELECT BUTTON DEFAULT ? PLEASE ANSWER " // "GAMES WITH A CONTROLLER. DO YOU WANT TO USE THE SELECT BUTTON DEFAULT ? "
// "YES TO USE SELECT OR NO TO NOT SET A HOTKEY ENABLE BUTTON.", // "PLEASE ANSWER YES TO USE SELECT OR NO TO NOT SET A HOTKEY ENABLE BUTTON.",
// "YES", [this, okFunction] { // "YES", [this, okFunction] {
// Input input; // Input input;
// mTargetConfig->getInputByName("Select", &input); // mTargetConfig->getInputByName("Select", &input);
@ -223,18 +234,17 @@ GuiInputConfig::GuiInputConfig(
// okFunction(); // okFunction();
// }, // },
// "NO", [this, okFunction] { // "NO", [this, okFunction] {
// // for a disabled hotkey enable button, set to a key with id 0, // // For a disabled hotkey enable button, set to a key with id 0,
// // so the input configuration script can be backwards compatible. // // so the input configuration script can be backwards compatible.
// mTargetConfig->mapInput("HotKeyEnable", Input(DEVICE_KEYBOARD, // mTargetConfig->mapInput("HotKeyEnable", Input(DEVICE_KEYBOARD,
// TYPE_KEY, 0, 1, true)); // TYPE_KEY, 0, 1, true));
// okFunction(); // okFunction();
// } // }));
// ));
// } // }
// else { // else {
// okFunction(); // okFunction();
// } // }
})); // }));
mButtonGrid = makeButtonGrid(mWindow, buttons); mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false); mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false);
@ -354,8 +364,8 @@ bool GuiInputConfig::assign(Input input, int inputId)
input.configured = true; input.configured = true;
mTargetConfig->mapInput(GUI_INPUT_CONFIG_LIST[inputId].name, input); mTargetConfig->mapInput(GUI_INPUT_CONFIG_LIST[inputId].name, input);
LOG(LogInfo) << " Mapping [" << input.string() << "] -> " << LOG(LogInfo) << "Mapping [" << input.string() << "] to [" <<
GUI_INPUT_CONFIG_LIST[inputId].name; GUI_INPUT_CONFIG_LIST[inputId].name << "]";
return true; return true;
} }