summaryrefslogtreecommitdiffstats
path: root/xbmc/games/ports/input
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/games/ports/input')
-rw-r--r--xbmc/games/ports/input/CMakeLists.txt11
-rw-r--r--xbmc/games/ports/input/PhysicalPort.cpp66
-rw-r--r--xbmc/games/ports/input/PhysicalPort.h64
-rw-r--r--xbmc/games/ports/input/PortInput.cpp129
-rw-r--r--xbmc/games/ports/input/PortInput.h77
-rw-r--r--xbmc/games/ports/input/PortManager.cpp347
-rw-r--r--xbmc/games/ports/input/PortManager.h81
7 files changed, 775 insertions, 0 deletions
diff --git a/xbmc/games/ports/input/CMakeLists.txt b/xbmc/games/ports/input/CMakeLists.txt
new file mode 100644
index 0000000..5b2b27f
--- /dev/null
+++ b/xbmc/games/ports/input/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES PhysicalPort.cpp
+ PortInput.cpp
+ PortManager.cpp
+)
+
+set(HEADERS PhysicalPort.h
+ PortInput.h
+ PortManager.h
+)
+
+core_add_library(games_ports_input)
diff --git a/xbmc/games/ports/input/PhysicalPort.cpp b/xbmc/games/ports/input/PhysicalPort.cpp
new file mode 100644
index 0000000..9c53c76
--- /dev/null
+++ b/xbmc/games/ports/input/PhysicalPort.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PhysicalPort.h"
+
+#include "games/controllers/ControllerDefinitions.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <utility>
+
+using namespace KODI;
+using namespace GAME;
+
+CPhysicalPort::CPhysicalPort(std::string portId, std::vector<std::string> accepts)
+ : m_portId(std::move(portId)), m_accepts(std::move(accepts))
+{
+}
+
+void CPhysicalPort::Reset()
+{
+ CPhysicalPort defaultPort;
+ *this = std::move(defaultPort);
+}
+
+bool CPhysicalPort::IsCompatible(const std::string& controllerId) const
+{
+ return std::find(m_accepts.begin(), m_accepts.end(), controllerId) != m_accepts.end();
+}
+
+bool CPhysicalPort::Deserialize(const TiXmlElement* pElement)
+{
+ if (pElement == nullptr)
+ return false;
+
+ Reset();
+
+ m_portId = XMLUtils::GetAttribute(pElement, LAYOUT_XML_ATTR_PORT_ID);
+
+ for (const TiXmlElement* pChild = pElement->FirstChildElement(); pChild != nullptr;
+ pChild = pChild->NextSiblingElement())
+ {
+ if (pChild->ValueStr() == LAYOUT_XML_ELM_ACCEPTS)
+ {
+ std::string controller = XMLUtils::GetAttribute(pChild, LAYOUT_XML_ATTR_CONTROLLER);
+
+ if (!controller.empty())
+ m_accepts.emplace_back(std::move(controller));
+ else
+ CLog::Log(LOGWARNING, "<{}> tag is missing \"{}\" attribute", LAYOUT_XML_ELM_ACCEPTS,
+ LAYOUT_XML_ATTR_CONTROLLER);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Unknown physical topology port tag: <{}>", pChild->ValueStr());
+ }
+ }
+
+ return true;
+}
diff --git a/xbmc/games/ports/input/PhysicalPort.h b/xbmc/games/ports/input/PhysicalPort.h
new file mode 100644
index 0000000..83fe3a5
--- /dev/null
+++ b/xbmc/games/ports/input/PhysicalPort.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+class TiXmlElement;
+
+namespace KODI
+{
+namespace GAME
+{
+
+class CPhysicalPort
+{
+public:
+ CPhysicalPort() = default;
+
+ /*!
+ * \brief Create a controller port
+ *
+ * \param portId The port's ID
+ * \param accepts A list of controller IDs that this port accepts
+ */
+ CPhysicalPort(std::string portId, std::vector<std::string> accepts);
+
+ void Reset();
+
+ /*!
+ * \brief Get the ID of the port
+ *
+ * \return The port's ID, e.g. "1", as a string
+ */
+ const std::string& ID() const { return m_portId; }
+
+ /*!
+ * \brief Get the controllers that can connect to this port
+ *
+ * \return A list of controllers that are physically compatible with this port
+ */
+ const std::vector<std::string>& Accepts() const { return m_accepts; }
+
+ /*!
+ * \brief Check if the controller is compatible with this port
+ *
+ * \return True if the controller is accepted, false otherwise
+ */
+ bool IsCompatible(const std::string& controllerId) const;
+
+ bool Deserialize(const TiXmlElement* pElement);
+
+private:
+ std::string m_portId;
+ std::vector<std::string> m_accepts;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/ports/input/PortInput.cpp b/xbmc/games/ports/input/PortInput.cpp
new file mode 100644
index 0000000..af1652b
--- /dev/null
+++ b/xbmc/games/ports/input/PortInput.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PortInput.h"
+
+#include "games/addons/GameClient.h"
+#include "games/controllers/input/InputSink.h"
+#include "guilib/WindowIDs.h"
+#include "input/joysticks/keymaps/KeymapHandling.h"
+#include "peripherals/devices/Peripheral.h"
+
+using namespace KODI;
+using namespace GAME;
+
+CPortInput::CPortInput(JOYSTICK::IInputHandler* gameInput)
+ : m_gameInput(gameInput), m_inputSink(new CInputSink(gameInput))
+{
+}
+
+CPortInput::~CPortInput() = default;
+
+void CPortInput::RegisterInput(JOYSTICK::IInputProvider* provider)
+{
+ // Give input sink the lowest priority by registering it before the other
+ // input handlers
+ provider->RegisterInputHandler(m_inputSink.get(), false);
+
+ // Register input handler
+ provider->RegisterInputHandler(this, false);
+
+ // Register GUI input
+ m_appInput.reset(new JOYSTICK::CKeymapHandling(provider, false, this));
+}
+
+void CPortInput::UnregisterInput(JOYSTICK::IInputProvider* provider)
+{
+ // Unregister in reverse order
+ if (provider == nullptr && m_appInput)
+ m_appInput->UnregisterInputProvider();
+ m_appInput.reset();
+
+ if (provider != nullptr)
+ {
+ provider->UnregisterInputHandler(this);
+ provider->UnregisterInputHandler(m_inputSink.get());
+ }
+}
+
+std::string CPortInput::ControllerID() const
+{
+ return m_gameInput->ControllerID();
+}
+
+bool CPortInput::AcceptsInput(const std::string& feature) const
+{
+ return m_gameInput->AcceptsInput(feature);
+}
+
+bool CPortInput::OnButtonPress(const std::string& feature, bool bPressed)
+{
+ if (bPressed && !m_gameInput->AcceptsInput(feature))
+ return false;
+
+ return m_gameInput->OnButtonPress(feature, bPressed);
+}
+
+void CPortInput::OnButtonHold(const std::string& feature, unsigned int holdTimeMs)
+{
+ m_gameInput->OnButtonHold(feature, holdTimeMs);
+}
+
+bool CPortInput::OnButtonMotion(const std::string& feature,
+ float magnitude,
+ unsigned int motionTimeMs)
+{
+ if (magnitude > 0.0f && !m_gameInput->AcceptsInput(feature))
+ return false;
+
+ return m_gameInput->OnButtonMotion(feature, magnitude, motionTimeMs);
+}
+
+bool CPortInput::OnAnalogStickMotion(const std::string& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs)
+{
+ if ((x != 0.0f || y != 0.0f) && !m_gameInput->AcceptsInput(feature))
+ return false;
+
+ return m_gameInput->OnAnalogStickMotion(feature, x, y, motionTimeMs);
+}
+
+bool CPortInput::OnAccelerometerMotion(const std::string& feature, float x, float y, float z)
+{
+ if (!m_gameInput->AcceptsInput(feature))
+ return false;
+
+ return m_gameInput->OnAccelerometerMotion(feature, x, y, z);
+}
+
+bool CPortInput::OnWheelMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ if ((position != 0.0f) && !m_gameInput->AcceptsInput(feature))
+ return false;
+
+ return m_gameInput->OnWheelMotion(feature, position, motionTimeMs);
+}
+
+bool CPortInput::OnThrottleMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ if ((position != 0.0f) && !m_gameInput->AcceptsInput(feature))
+ return false;
+
+ return m_gameInput->OnThrottleMotion(feature, position, motionTimeMs);
+}
+
+int CPortInput::GetWindowID() const
+{
+ return WINDOW_FULLSCREEN_GAME;
+}
diff --git a/xbmc/games/ports/input/PortInput.h b/xbmc/games/ports/input/PortInput.h
new file mode 100644
index 0000000..d3c6524
--- /dev/null
+++ b/xbmc/games/ports/input/PortInput.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "input/KeymapEnvironment.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CKeymapHandling;
+class IInputProvider;
+} // namespace JOYSTICK
+
+namespace GAME
+{
+class CPortInput : public JOYSTICK::IInputHandler, public IKeymapEnvironment
+{
+public:
+ CPortInput(JOYSTICK::IInputHandler* gameInput);
+ ~CPortInput() override;
+
+ void RegisterInput(JOYSTICK::IInputProvider* provider);
+ void UnregisterInput(JOYSTICK::IInputProvider* provider);
+
+ JOYSTICK::IInputHandler* InputHandler() { return m_gameInput; }
+
+ // Implementation of IInputHandler
+ std::string ControllerID() const override;
+ bool HasFeature(const std::string& feature) const override { return true; }
+ bool AcceptsInput(const std::string& feature) const override;
+ bool OnButtonPress(const std::string& feature, bool bPressed) override;
+ void OnButtonHold(const std::string& feature, unsigned int holdTimeMs) override;
+ bool OnButtonMotion(const std::string& feature,
+ float magnitude,
+ unsigned int motionTimeMs) override;
+ bool OnAnalogStickMotion(const std::string& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs) override;
+ bool OnAccelerometerMotion(const std::string& feature, float x, float y, float z) override;
+ bool OnWheelMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ bool OnThrottleMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ void OnInputFrame() override {}
+
+ // Implementation of IKeymapEnvironment
+ int GetWindowID() const override;
+ void SetWindowID(int windowId) override {}
+ int GetFallthrough(int windowId) const override { return -1; }
+ bool UseGlobalFallthrough() const override { return false; }
+ bool UseEasterEgg() const override { return false; }
+
+private:
+ // Construction parameters
+ JOYSTICK::IInputHandler* const m_gameInput;
+
+ // Handles input to Kodi
+ std::unique_ptr<JOYSTICK::CKeymapHandling> m_appInput;
+
+ // Prevents input falling through to Kodi when not handled by the game
+ std::unique_ptr<JOYSTICK::IInputHandler> m_inputSink;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/ports/input/PortManager.cpp b/xbmc/games/ports/input/PortManager.cpp
new file mode 100644
index 0000000..a04177a
--- /dev/null
+++ b/xbmc/games/ports/input/PortManager.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PortManager.h"
+
+#include "URL.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/types/ControllerHub.h"
+#include "games/controllers/types/ControllerNode.h"
+#include "games/ports/types/PortNode.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+using namespace KODI;
+using namespace GAME;
+
+namespace
+{
+constexpr const char* PORT_XML_FILE = "ports.xml";
+constexpr const char* XML_ROOT_PORTS = "ports";
+constexpr const char* XML_ELM_PORT = "port";
+constexpr const char* XML_ELM_CONTROLLER = "controller";
+constexpr const char* XML_ATTR_PORT_ID = "id";
+constexpr const char* XML_ATTR_PORT_ADDRESS = "address";
+constexpr const char* XML_ATTR_PORT_CONNECTED = "connected";
+constexpr const char* XML_ATTR_PORT_CONTROLLER = "controller";
+constexpr const char* XML_ATTR_CONTROLLER_ID = "id";
+} // namespace
+
+CPortManager::CPortManager() = default;
+
+CPortManager::~CPortManager() = default;
+
+void CPortManager::Initialize(const std::string& profilePath)
+{
+ m_xmlPath = URIUtils::AddFileToFolder(profilePath, PORT_XML_FILE);
+}
+
+void CPortManager::Deinitialize()
+{
+ // Wait for save tasks
+ for (std::future<void>& task : m_saveFutures)
+ task.wait();
+ m_saveFutures.clear();
+
+ m_controllerTree.Clear();
+ m_xmlPath.clear();
+}
+
+void CPortManager::SetControllerTree(const CControllerTree& controllerTree)
+{
+ m_controllerTree = controllerTree;
+}
+
+void CPortManager::LoadXML()
+{
+ if (!CFileUtils::Exists(m_xmlPath))
+ {
+ CLog::Log(LOGDEBUG, "Can't load port config, file doesn't exist: {}", m_xmlPath);
+ return;
+ }
+
+ CLog::Log(LOGINFO, "Loading port layout: {}", CURL::GetRedacted(m_xmlPath));
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(m_xmlPath))
+ {
+ CLog::Log(LOGDEBUG, "Unable to load file: {} at line {}", xmlDoc.ErrorDesc(),
+ xmlDoc.ErrorRow());
+ return;
+ }
+
+ const TiXmlElement* pRootElement = xmlDoc.RootElement();
+ if (pRootElement == nullptr || pRootElement->NoChildren() ||
+ pRootElement->ValueStr() != XML_ROOT_PORTS)
+ {
+ CLog::Log(LOGERROR, "Can't find root <{}> tag", XML_ROOT_PORTS);
+ return;
+ }
+
+ DeserializePorts(pRootElement, m_controllerTree.GetPorts());
+}
+
+void CPortManager::SaveXMLAsync()
+{
+ PortVec ports = m_controllerTree.GetPorts();
+
+ // Prune any finished save tasks
+ m_saveFutures.erase(std::remove_if(m_saveFutures.begin(), m_saveFutures.end(),
+ [](std::future<void>& task) {
+ return task.wait_for(std::chrono::seconds(0)) ==
+ std::future_status::ready;
+ }),
+ m_saveFutures.end());
+
+ // Save async
+ std::future<void> task = std::async(std::launch::async, [this, ports = std::move(ports)]() {
+ CXBMCTinyXML doc;
+ TiXmlElement node(XML_ROOT_PORTS);
+
+ SerializePorts(node, ports);
+
+ doc.InsertEndChild(node);
+
+ std::lock_guard<std::mutex> lock(m_saveMutex);
+ doc.SaveFile(m_xmlPath);
+ });
+
+ m_saveFutures.emplace_back(std::move(task));
+}
+
+void CPortManager::Clear()
+{
+ m_xmlPath.clear();
+ m_controllerTree.Clear();
+}
+
+void CPortManager::ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId /* = "" */)
+{
+ ConnectController(portAddress, connected, controllerId, m_controllerTree.GetPorts());
+}
+
+bool CPortManager::ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ PortVec& ports)
+{
+ for (CPortNode& port : ports)
+ {
+ if (ConnectController(portAddress, connected, controllerId, port))
+ return true;
+ }
+
+ return false;
+}
+
+bool CPortManager::ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ CPortNode& port)
+{
+ // Base case
+ if (port.GetAddress() == portAddress)
+ {
+ port.SetConnected(connected);
+ if (!controllerId.empty())
+ port.SetActiveController(controllerId);
+ return true;
+ }
+
+ // Check children
+ return ConnectController(portAddress, connected, controllerId, port.GetCompatibleControllers());
+}
+
+bool CPortManager::ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ ControllerNodeVec& controllers)
+{
+ for (CControllerNode& controller : controllers)
+ {
+ if (ConnectController(portAddress, connected, controllerId, controller))
+ return true;
+ }
+
+ return false;
+}
+
+bool CPortManager::ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ CControllerNode& controller)
+{
+ for (CPortNode& childPort : controller.GetHub().GetPorts())
+ {
+ if (ConnectController(portAddress, connected, controllerId, childPort))
+ return true;
+ }
+
+ return false;
+}
+
+void CPortManager::DeserializePorts(const TiXmlElement* pElement, PortVec& ports)
+{
+ for (const TiXmlElement* pPort = pElement->FirstChildElement(); pPort != nullptr;
+ pPort = pPort->NextSiblingElement())
+ {
+ if (pPort->ValueStr() != XML_ELM_PORT)
+ {
+ CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pElement->ValueStr(),
+ pPort->ValueStr());
+ continue;
+ }
+
+ std::string portId = XMLUtils::GetAttribute(pPort, XML_ATTR_PORT_ID);
+
+ auto it = std::find_if(ports.begin(), ports.end(),
+ [&portId](const CPortNode& port) { return port.GetPortID() == portId; });
+ if (it != ports.end())
+ {
+ CPortNode& port = *it;
+
+ DeserializePort(pPort, port);
+ }
+ }
+}
+
+void CPortManager::DeserializePort(const TiXmlElement* pPort, CPortNode& port)
+{
+ // Connected
+ bool connected = (XMLUtils::GetAttribute(pPort, XML_ATTR_PORT_CONNECTED) == "true");
+ port.SetConnected(connected);
+
+ // Controller
+ const std::string activeControllerId = XMLUtils::GetAttribute(pPort, XML_ATTR_PORT_CONTROLLER);
+ if (!port.SetActiveController(activeControllerId))
+ port.SetConnected(false);
+
+ DeserializeControllers(pPort, port.GetCompatibleControllers());
+}
+
+void CPortManager::DeserializeControllers(const TiXmlElement* pPort, ControllerNodeVec& controllers)
+{
+ for (const TiXmlElement* pController = pPort->FirstChildElement(); pController != nullptr;
+ pController = pController->NextSiblingElement())
+ {
+ if (pController->ValueStr() != XML_ELM_CONTROLLER)
+ {
+ CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pPort->ValueStr(),
+ pController->ValueStr());
+ continue;
+ }
+
+ std::string controllerId = XMLUtils::GetAttribute(pController, XML_ATTR_CONTROLLER_ID);
+
+ auto it = std::find_if(controllers.begin(), controllers.end(),
+ [&controllerId](const CControllerNode& controller) {
+ return controller.GetController()->ID() == controllerId;
+ });
+ if (it != controllers.end())
+ {
+ CControllerNode& controller = *it;
+
+ DeserializeController(pController, controller);
+ }
+ }
+}
+
+void CPortManager::DeserializeController(const TiXmlElement* pController,
+ CControllerNode& controller)
+{
+ // Child ports
+ DeserializePorts(pController, controller.GetHub().GetPorts());
+}
+
+void CPortManager::SerializePorts(TiXmlElement& node, const PortVec& ports)
+{
+ for (const CPortNode& port : ports)
+ {
+ TiXmlElement portNode(XML_ELM_PORT);
+
+ SerializePort(portNode, port);
+
+ node.InsertEndChild(portNode);
+ }
+}
+
+void CPortManager::SerializePort(TiXmlElement& portNode, const CPortNode& port)
+{
+ // Port ID
+ portNode.SetAttribute(XML_ATTR_PORT_ID, port.GetPortID());
+
+ // Port address
+ portNode.SetAttribute(XML_ATTR_PORT_ADDRESS, port.GetAddress());
+
+ // Connected state
+ portNode.SetAttribute(XML_ATTR_PORT_CONNECTED, port.IsConnected() ? "true" : "false");
+
+ // Active controller
+ if (port.GetActiveController().GetController())
+ {
+ const std::string controllerId = port.GetActiveController().GetController()->ID();
+ portNode.SetAttribute(XML_ATTR_PORT_CONTROLLER, controllerId);
+ }
+
+ // All compatible controllers
+ SerializeControllers(portNode, port.GetCompatibleControllers());
+}
+
+void CPortManager::SerializeControllers(TiXmlElement& portNode,
+ const ControllerNodeVec& controllers)
+{
+ for (const CControllerNode& controller : controllers)
+ {
+ // Skip controller if it has no state
+ if (!HasState(controller))
+ continue;
+
+ TiXmlElement controllerNode(XML_ELM_CONTROLLER);
+
+ SerializeController(controllerNode, controller);
+
+ portNode.InsertEndChild(controllerNode);
+ }
+}
+
+void CPortManager::SerializeController(TiXmlElement& controllerNode,
+ const CControllerNode& controller)
+{
+ // Controller ID
+ if (controller.GetController())
+ controllerNode.SetAttribute(XML_ATTR_CONTROLLER_ID, controller.GetController()->ID());
+
+ // Ports
+ SerializePorts(controllerNode, controller.GetHub().GetPorts());
+}
+
+bool CPortManager::HasState(const CPortNode& port)
+{
+ // Ports have state (is connected / active controller)
+ return true;
+}
+
+bool CPortManager::HasState(const CControllerNode& controller)
+{
+ // Check controller ports
+ for (const CPortNode& port : controller.GetHub().GetPorts())
+ {
+ if (HasState(port))
+ return true;
+ }
+
+ // Controller itself has no state
+ return false;
+}
diff --git a/xbmc/games/ports/input/PortManager.h b/xbmc/games/ports/input/PortManager.h
new file mode 100644
index 0000000..a02ad35
--- /dev/null
+++ b/xbmc/games/ports/input/PortManager.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "games/controllers/types/ControllerTree.h"
+
+#include <future>
+#include <mutex>
+#include <string>
+
+class TiXmlElement;
+
+namespace KODI
+{
+namespace GAME
+{
+class CPortManager
+{
+public:
+ CPortManager();
+ ~CPortManager();
+
+ void Initialize(const std::string& profilePath);
+ void Deinitialize();
+
+ void SetControllerTree(const CControllerTree& controllerTree);
+ void LoadXML();
+ void SaveXMLAsync();
+ void Clear();
+
+ void ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId = "");
+
+ const CControllerTree& GetControllerTree() const { return m_controllerTree; }
+
+private:
+ static void DeserializePorts(const TiXmlElement* pElement, PortVec& ports);
+ static void DeserializePort(const TiXmlElement* pElement, CPortNode& port);
+ static void DeserializeControllers(const TiXmlElement* pElement, ControllerNodeVec& controllers);
+ static void DeserializeController(const TiXmlElement* pElement, CControllerNode& controller);
+
+ static void SerializePorts(TiXmlElement& node, const PortVec& ports);
+ static void SerializePort(TiXmlElement& portNode, const CPortNode& port);
+ static void SerializeControllers(TiXmlElement& portNode, const ControllerNodeVec& controllers);
+ static void SerializeController(TiXmlElement& controllerNode, const CControllerNode& controller);
+
+ static bool ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ PortVec& ports);
+ static bool ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ CPortNode& port);
+ static bool ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ ControllerNodeVec& controllers);
+ static bool ConnectController(const std::string& portAddress,
+ bool connected,
+ const std::string& controllerId,
+ CControllerNode& controller);
+
+ static bool HasState(const CPortNode& port);
+ static bool HasState(const CControllerNode& controller);
+
+ CControllerTree m_controllerTree;
+ std::string m_xmlPath;
+
+ std::vector<std::future<void>> m_saveFutures;
+ std::mutex m_saveMutex;
+};
+} // namespace GAME
+} // namespace KODI