diff options
Diffstat (limited to 'xbmc/games/ports/input')
-rw-r--r-- | xbmc/games/ports/input/CMakeLists.txt | 11 | ||||
-rw-r--r-- | xbmc/games/ports/input/PhysicalPort.cpp | 66 | ||||
-rw-r--r-- | xbmc/games/ports/input/PhysicalPort.h | 64 | ||||
-rw-r--r-- | xbmc/games/ports/input/PortInput.cpp | 129 | ||||
-rw-r--r-- | xbmc/games/ports/input/PortInput.h | 77 | ||||
-rw-r--r-- | xbmc/games/ports/input/PortManager.cpp | 347 | ||||
-rw-r--r-- | xbmc/games/ports/input/PortManager.h | 81 |
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 |