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
* 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.
* 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
* 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

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)
* 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)
* 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)
* 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

View file

@ -495,6 +495,11 @@ int main(int argc, char* argv[])
ViewController::get()->goToStart();
}
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, [] {
ViewController::get()->goToStart(); }));
}

View file

@ -12,9 +12,9 @@
#include "Log.h"
#include "Window.h"
UIModeController * UIModeController::sInstance = nullptr;
UIModeController* UIModeController::sInstance = nullptr;
UIModeController * UIModeController::getInstance()
UIModeController* UIModeController::getInstance()
{
if (sInstance == nullptr)
sInstance = new UIModeController();
@ -39,7 +39,7 @@ void UIModeController::monitorUIMode()
}
}
bool UIModeController::listen(InputConfig * config, Input input)
bool UIModeController::listen(InputConfig* config, Input input)
{
// Reads the current input to listen for the passkey sequence to unlock
// the UI mode. The progress is saved in mPassKeyCounter.
@ -59,7 +59,7 @@ bool UIModeController::listen(InputConfig * config, Input input)
return false;
}
bool UIModeController::inputIsMatch(InputConfig * config, Input input)
bool UIModeController::inputIsMatch(InputConfig* config, Input input)
{
for (auto valstring : mInputVals) {
if (config->isMappedLike(valstring, input) &&
@ -136,7 +136,7 @@ std::string UIModeController::getFormattedPassKeyStr()
return out;
}
void UIModeController::logInput(InputConfig * config, Input input)
void UIModeController::logInput(InputConfig* config, Input input)
{
std::string mapname = "";
std::vector<std::string> maps = config->getMappedTo(input);
@ -146,11 +146,11 @@ void UIModeController::logInput(InputConfig * config, Input input)
mapname += ", ";
}
LOG(LogDebug) << "UIModeController::logInput(" << config->getDeviceName() <<
"): " << input.string() << ", isMappedTo=" << mapname << ", value=" << input.value;
LOG(LogDebug) << "UIModeController::logInput(" << config->getDeviceName() << "): " <<
input.string() << ", isMappedTo=" << mapname << ", value=" << input.value;
}
bool UIModeController::isValidInput(InputConfig * config, Input input)
bool UIModeController::isValidInput(InputConfig* config, Input input)
{
if ((config->getMappedTo(input).size() == 0) || // Not a mapped input, so ignore..
(input.type == TYPE_HAT) || // Ignore all hat inputs.

View file

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

View file

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

View file

@ -128,6 +128,10 @@ public:
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);
@ -149,6 +153,7 @@ private:
const int mDeviceId;
const std::string mDeviceName;
const std::string mDeviceGUID;
bool mDefaultConfigFlag;
};
#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)
{
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;
}
pugi::xml_document doc;
#ifdef _WIN64
@ -323,17 +330,25 @@ bool InputManager::loadInputConfig(InputConfig* config)
if (!configNode)
configNode = root.find_child_by_attribute("inputConfig",
"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;
}
}
config->loadFromXML(configNode);
return true;
}
// Used in an "emergency" where no keyboard config could be loaded from the inputmanager
// config file. Allows the user to select to reconfigure in menus if this happens without
// having to delete es_input.cfg manually.
// Note: Not currently used.
// 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);
@ -363,6 +378,9 @@ void InputManager::writeDeviceConfig(InputConfig* config)
std::string path = getConfigPath();
LOG(LogDebug) << "InputManager::writeDeviceConfig(): "
"Saving input configuration file to " << path;
pugi::xml_document doc;
if (Utils::FileSystem::exists(path)) {
@ -420,7 +438,7 @@ void InputManager::writeDeviceConfig(InputConfig* config)
Scripting::fireEvent("config-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();
loadInputConfig(config);
}
@ -440,7 +458,7 @@ void InputManager::doOnFinish()
#endif
if (!result) {
LOG(LogError) << "Error parsing input config: " << result.description();
LOG(LogError) << "Error - Couldn't parse input config: " << result.description();
}
else {
pugi::xml_node root = doc.child("inputList");
@ -514,7 +532,7 @@ std::string InputManager::getDeviceGUIDString(int deviceId)
auto it = mJoysticks.find(deviceId);
if (it == mJoysticks.cend()) {
LOG(LogError) << "getDeviceGUIDString - deviceId " << deviceId << " not found!";
LOG(LogError) << "Error - getDeviceGUIDString - deviceId " << deviceId << " not found!";
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.
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:
virtual ~InputManager();
@ -71,6 +48,29 @@ public:
InputConfig* getInputConfigByDevice(int deviceId);
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

View file

@ -214,9 +214,10 @@ void Settings::setDefaults()
//
// 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["MediaDirectory"] = "";
mStringMap["ROMDirectory"] = "";

View file

@ -13,6 +13,7 @@
#include "InputManager.h"
#include "Log.h"
#include "Scripting.h"
#include <algorithm>
#include <iomanip>
@ -130,7 +131,8 @@ void Window::input(InputConfig* config, Input input)
if (mScreenSaver->isScreenSaverActive() &&
Settings::getInstance()->getBool("ScreenSaverControls") &&
(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))) {
if (config->isMappedLike("right", input) || config->isMappedTo("select", input)) {
if (input.value != 0) {
@ -140,7 +142,7 @@ void Window::input(InputConfig* config, Input input)
return;
}
else if (config->isMappedTo("start", input) && input.value != 0) {
// Launch game!
// Launch game.
cancelScreenSaver();
mScreenSaver->launchGame();
// 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 aVal = 0;
int bVal = 0;
while (map[i] != nullptr)
{
while (map[i] != nullptr) {
if (a.first == map[i])
aVal = i;
if (b.first == map[i])

View file

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

View file

@ -51,7 +51,7 @@ GuiDetectDevice::GuiDetectDevice(
// Message.
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);
mGrid.setEntry(mMsg1, Vector2i(0, 2), false, true);

View file

@ -200,22 +200,33 @@ GuiInputConfig::GuiInputConfig(
// Buttons.
std::vector< std::shared_ptr<ButtonComponent> > buttons;
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)
okCallback();
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
// user to use select or nothing.
Input input;
okFunction();
// Temporarily commented out, needs to be properly cleaned up later.
buttons.push_back(std::make_shared<ButtonComponent>
(mWindow, "OK", "ok", [this, okFunction] { okFunction(); }));
// This code is disabled as there is no intention to provide emulator configuration or
// 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)) {
// mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
// "YOU DIDN'T CHOOSE A HOTKEY ENABLE BUTTON. THIS IS REQUIRED FOR EXITING GAMES "
// "WITH A CONTROLLER. DO YOU WANT TO USE THE SELECT BUTTON DEFAULT ? PLEASE ANSWER "
// "YES TO USE SELECT OR NO TO NOT SET A HOTKEY ENABLE BUTTON.",
// "YOU DIDN'T CHOOSE A HOTKEY ENABLE BUTTON. THIS IS REQUIRED FOR EXITING "
// "GAMES WITH A CONTROLLER. DO YOU WANT TO USE THE SELECT BUTTON DEFAULT ? "
// "PLEASE ANSWER YES TO USE SELECT OR NO TO NOT SET A HOTKEY ENABLE BUTTON.",
// "YES", [this, okFunction] {
// Input input;
// mTargetConfig->getInputByName("Select", &input);
@ -223,18 +234,17 @@ GuiInputConfig::GuiInputConfig(
// 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.
// mTargetConfig->mapInput("HotKeyEnable", Input(DEVICE_KEYBOARD,
// TYPE_KEY, 0, 1, true));
// okFunction();
// }
// ));
// }));
// }
// else {
// okFunction();
// }
}));
// }));
mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false);
@ -354,8 +364,8 @@ bool GuiInputConfig::assign(Input input, int inputId)
input.configured = true;
mTargetConfig->mapInput(GUI_INPUT_CONFIG_LIST[inputId].name, input);
LOG(LogInfo) << " Mapping [" << input.string() << "] -> " <<
GUI_INPUT_CONFIG_LIST[inputId].name;
LOG(LogInfo) << "Mapping [" << input.string() << "] to [" <<
GUI_INPUT_CONFIG_LIST[inputId].name << "]";
return true;
}