From 5f3c5fe307b62bdc6427927d19bdbaf716838262 Mon Sep 17 00:00:00 2001 From: offtkp Date: Wed, 1 May 2024 19:47:34 +0300 Subject: [PATCH] Serialization/deserialization of mappings --- include/input_mappings.hpp | 92 ++++++++++++++++++++++++++++++++++++++ include/services/hid.hpp | 6 ++- src/core/services/hid.cpp | 57 ++++++++++++++++++++++- src/panda_qt/mappings.cpp | 11 +++-- src/panda_sdl/mappings.cpp | 12 +++-- 5 files changed, 170 insertions(+), 8 deletions(-) diff --git a/include/input_mappings.hpp b/include/input_mappings.hpp index 177f1d51..4448d6a3 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,96 @@ 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"); + + bool equal = std::equal(haveFrontend.begin(), haveFrontend.end(), wantFrontend.begin(), wantFrontend.end(), [](char a, char b) { + return std::tolower(a) == std::tolower(b); + }); + + if (equal) { + 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 d9018a4f..0a7c2795 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(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 ef6cbb41..47fb3eb0 100644 --- a/src/core/services/hid.cpp +++ b/src/core/services/hid.cpp @@ -1,7 +1,62 @@ #include "services/hid.hpp" + +#include +#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(std::string name) { + static std::unordered_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}, + }; + + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + 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 22741a73..766cd857 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 0c09b852..f087c81a 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