diff --git a/include/input_mappings.hpp b/include/input_mappings.hpp index 177f1d51a..eac84d39b 100644 --- a/include/input_mappings.hpp +++ b/include/input_mappings.hpp @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include +#include #include #include "helpers.hpp" @@ -15,8 +19,91 @@ struct InputMappings { } void setMapping(Scancode scancode, u32 key) { container[scancode] = key; } + + void serialize(const std::filesystem::path& path, const std::string& frontend) const { + toml::basic_value data; + + std::error_code error; + if (std::filesystem::exists(path, error)) { + try { + data = toml::parse(path); + } catch (const std::exception& ex) { + Helpers::warn("Exception trying to parse mappings file. Exception: %s\n", ex.what()); + return; + } + } else { + if (error) { + Helpers::warn("Filesystem error accessing %s (error: %s)\n", path.string().c_str(), error.message().c_str()); + } + printf("Saving new mappings file %s\n", path.string().c_str()); + } + + data["Metadata"]["Name"] = name.empty() ? "Unnamed Mappings" : name; + data["Metadata"]["Device"] = device.empty() ? "Unknown Device" : device; + data["Metadata"]["Frontend"] = frontend; + + data["Mappings"] = toml::table{}; + + for (const auto& [scancode, key] : container) { + if (data["Mappings"].contains(HID::Keys::keyToName(key)) == false) { + data["Mappings"][HID::Keys::keyToName(key)] = toml::array{}; + } + + data["Mappings"][HID::Keys::keyToName(key)].push_back(scancodeToName(scancode)); + } + + std::ofstream file(path, std::ios::out); + file << data; + } + + static std::optional deserialize(const std::filesystem::path& path, const std::string& wantFrontend) { + toml::basic_value data; + + std::error_code error; + if (!std::filesystem::exists(path, error)) { + if (error) { + Helpers::warn("Filesystem error accessing %s (error: %s)\n", path.string().c_str(), error.message().c_str()); + } + + return std::nullopt; + } + + InputMappings mappings; + + try { + data = toml::parse(path); + + const auto metadata = toml::find(data, "Metadata"); + mappings.name = toml::find_or(metadata, "Name", "Unnamed Mappings"); + mappings.device = toml::find_or(metadata, "Device", "Unknown Device"); + + std::string haveFrontend = toml::find_or(metadata, "Frontend", "Unknown Frontend"); + if (haveFrontend != wantFrontend) { + Helpers::warn("Mappings file %s was created for frontend %s, but we are using frontend %s\n", path.string().c_str(), haveFrontend.c_str(), wantFrontend.c_str()); + return std::nullopt; + } + } catch (const std::exception& ex) { + Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what()); + + return std::nullopt; + } + + const auto& mappingsTable = toml::find_or(data, "Mappings", toml::table{}); + for (const auto& [key, scancodes] : mappingsTable) { + for (const auto& scancode : scancodes.as_array()) { + mappings.setMapping(nameToScancode(scancode.as_string()), HID::Keys::nameToKey(key)); + } + } + + return mappings; + } + static InputMappings defaultKeyboardMappings(); + static std::string scancodeToName(Scancode scancode); + static Scancode nameToScancode(const std::string& name); private: Container container; + std::string name; + std::string device; }; diff --git a/include/services/hid.hpp b/include/services/hid.hpp index d9018a4fd..aea171891 100644 --- a/include/services/hid.hpp +++ b/include/services/hid.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "helpers.hpp" #include "kernel_types.hpp" @@ -32,7 +33,10 @@ namespace HID::Keys { CirclePadUp = 1 << 30, // Y >= 41 CirclePadDown = 1u << 31 // Y <= -41 }; -} + + const char* keyToName(u32 key); + u32 nameToKey(const std::string& name); +} // namespace HID::Keys // Circular dependency because we need HID to spawn events class Kernel; diff --git a/src/core/services/hid.cpp b/src/core/services/hid.cpp index ef6cbb414..64d1e57d3 100644 --- a/src/core/services/hid.cpp +++ b/src/core/services/hid.cpp @@ -1,7 +1,63 @@ #include "services/hid.hpp" + +#include + #include "ipc.hpp" #include "kernel.hpp" -#include + +namespace HID::Keys { + const char* keyToName(u32 key) { + static std::map keyMap = { + {A, "A"}, + {B, "B"}, + {Select, "Select"}, + {Start, "Start"}, + {Right, "Right"}, + {Left, "Left"}, + {Up, "Up"}, + {Down, "Down"}, + {R, "R"}, + {L, "L"}, + {X, "X"}, + {Y, "Y"}, + {CirclePadRight, "CirclePad Right"}, + {CirclePadLeft, "CirclePad Left"}, + {CirclePadUp, "CirclePad Up"}, + {CirclePadDown, "CirclePad Down"}, + }; + + auto it = keyMap.find(key); + return it != keyMap.end() ? it->second : "Unknown key"; + } + + u32 nameToKey(const std::string& name) { + struct CaseInsensitiveComparator { + bool operator()(const std::string& a, const std::string& b) const noexcept { return ::strcasecmp(a.c_str(), b.c_str()) < 0; } + }; + + static std::map keyMap = { + {"A", A}, + {"B", B}, + {"Select", Select}, + {"Start", Start}, + {"Right", Right}, + {"Left", Left}, + {"Up", Up}, + {"Down", Down}, + {"R", R}, + {"L", L}, + {"X", X}, + {"Y", Y}, + {"CirclePad Right", CirclePadRight}, + {"CirclePad Left", CirclePadLeft}, + {"CirclePad Up", CirclePadUp}, + {"CirclePad Down", CirclePadDown}, + }; + + auto it = keyMap.find(name); + return it != keyMap.end() ? it->second : HID::Keys::Null; + } +} // namespace HID::Keys namespace HIDCommands { enum : u32 { diff --git a/src/panda_qt/mappings.cpp b/src/panda_qt/mappings.cpp index 22741a739..766cd857b 100644 --- a/src/panda_qt/mappings.cpp +++ b/src/panda_qt/mappings.cpp @@ -1,6 +1,7 @@ -#include "input_mappings.hpp" - #include +#include + +#include "input_mappings.hpp" InputMappings InputMappings::defaultKeyboardMappings() { InputMappings mappings; @@ -22,4 +23,8 @@ InputMappings InputMappings::defaultKeyboardMappings() { mappings.setMapping(Qt::Key_A, HID::Keys::CirclePadLeft); return mappings; -} \ No newline at end of file +} + +std::string InputMappings::scancodeToName(Scancode scancode) { return QKeySequence(scancode).toString().toStdString(); } + +InputMappings::Scancode InputMappings::nameToScancode(const std::string& name) { return QKeySequence(QString::fromStdString(name))[0].key(); } \ No newline at end of file diff --git a/src/panda_sdl/mappings.cpp b/src/panda_sdl/mappings.cpp index 0c09b8527..f087c81af 100644 --- a/src/panda_sdl/mappings.cpp +++ b/src/panda_sdl/mappings.cpp @@ -1,7 +1,9 @@ -#include "input_mappings.hpp" - #include +#include + +#include "input_mappings.hpp" + InputMappings InputMappings::defaultKeyboardMappings() { InputMappings mappings; mappings.setMapping(SDLK_l, HID::Keys::A); @@ -22,4 +24,8 @@ InputMappings InputMappings::defaultKeyboardMappings() { mappings.setMapping(SDLK_a, HID::Keys::CirclePadLeft); return mappings; -} \ No newline at end of file +} + +std::string InputMappings::scancodeToName(Scancode scancode) { return SDL_GetKeyName(scancode); } + +InputMappings::Scancode InputMappings::nameToScancode(const std::string& name) { return SDL_GetKeyFromName(name.c_str()); } \ No newline at end of file