diff options
Diffstat (limited to 'xbmc/games/ports')
-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 | ||||
-rw-r--r-- | xbmc/games/ports/types/CMakeLists.txt | 7 | ||||
-rw-r--r-- | xbmc/games/ports/types/PortNode.cpp | 157 | ||||
-rw-r--r-- | xbmc/games/ports/types/PortNode.h | 130 | ||||
-rw-r--r-- | xbmc/games/ports/windows/CMakeLists.txt | 11 | ||||
-rw-r--r-- | xbmc/games/ports/windows/GUIPortDefines.h | 22 | ||||
-rw-r--r-- | xbmc/games/ports/windows/GUIPortList.cpp | 344 | ||||
-rw-r--r-- | xbmc/games/ports/windows/GUIPortList.h | 79 | ||||
-rw-r--r-- | xbmc/games/ports/windows/GUIPortWindow.cpp | 183 | ||||
-rw-r--r-- | xbmc/games/ports/windows/GUIPortWindow.h | 56 | ||||
-rw-r--r-- | xbmc/games/ports/windows/IPortList.h | 104 |
17 files changed, 1868 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 diff --git a/xbmc/games/ports/types/CMakeLists.txt b/xbmc/games/ports/types/CMakeLists.txt new file mode 100644 index 0000000..735df42 --- /dev/null +++ b/xbmc/games/ports/types/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES PortNode.cpp +) + +set(HEADERS PortNode.h +) + +core_add_library(games_ports_types) diff --git a/xbmc/games/ports/types/PortNode.cpp b/xbmc/games/ports/types/PortNode.cpp new file mode 100644 index 0000000..7caf6eb --- /dev/null +++ b/xbmc/games/ports/types/PortNode.cpp @@ -0,0 +1,157 @@ +/* + * 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 "PortNode.h" + +#include "games/controllers/Controller.h" +#include "games/controllers/types/ControllerHub.h" +#include "games/ports/input/PhysicalPort.h" + +#include <algorithm> +#include <utility> + +using namespace KODI; +using namespace GAME; + +CPortNode::~CPortNode() = default; + +CPortNode& CPortNode::operator=(const CPortNode& rhs) +{ + if (this != &rhs) + { + m_bConnected = rhs.m_bConnected; + m_active = rhs.m_active; + m_portType = rhs.m_portType; + m_portId = rhs.m_portId; + m_address = rhs.m_address; + m_forceConnected = rhs.m_forceConnected; + m_controllers = rhs.m_controllers; + } + + return *this; +} + +CPortNode& CPortNode::operator=(CPortNode&& rhs) noexcept +{ + if (this != &rhs) + { + m_bConnected = rhs.m_bConnected; + m_active = rhs.m_active; + m_portType = rhs.m_portType; + m_portId = std::move(rhs.m_portId); + m_address = std::move(rhs.m_address); + m_forceConnected = rhs.m_forceConnected; + m_controllers = std::move(rhs.m_controllers); + } + + return *this; +} + +const CControllerNode& CPortNode::GetActiveController() const +{ + if (m_bConnected && m_active < m_controllers.size()) + return m_controllers[m_active]; + + static const CControllerNode invalid{}; + return invalid; +} + +CControllerNode& CPortNode::GetActiveController() +{ + if (m_bConnected && m_active < m_controllers.size()) + return m_controllers[m_active]; + + static CControllerNode invalid; + invalid.Clear(); + return invalid; +} + +bool CPortNode::SetActiveController(const std::string& controllerId) +{ + for (size_t i = 0; i < m_controllers.size(); ++i) + { + const ControllerPtr& controller = m_controllers.at(i).GetController(); + if (controller && controller->ID() == controllerId) + { + m_active = i; + return true; + } + } + + return false; +} + +void CPortNode::SetPortID(std::string portId) +{ + m_portId = std::move(portId); +} + +void CPortNode::SetAddress(std::string address) +{ + m_address = std::move(address); +} + +void CPortNode::SetCompatibleControllers(ControllerNodeVec controllers) +{ + m_controllers = std::move(controllers); +} + +bool CPortNode::IsControllerAccepted(const std::string& controllerId) const +{ + // Base case + CPhysicalPort port; + GetPort(port); + if (port.IsCompatible(controllerId)) + return true; + + // Visit nodes + return std::any_of(m_controllers.begin(), m_controllers.end(), + [controllerId](const CControllerNode& node) { + return node.IsControllerAccepted(controllerId); + }); +} + +bool CPortNode::IsControllerAccepted(const std::string& portAddress, + const std::string& controllerId) const +{ + bool bAccepted = false; + + if (m_address == portAddress) + { + // Base case + CPhysicalPort port; + GetPort(port); + if (port.IsCompatible(controllerId)) + bAccepted = true; + } + else + { + // Visit nodes + if (std::any_of(m_controllers.begin(), m_controllers.end(), + [portAddress, controllerId](const CControllerNode& node) { + return node.IsControllerAccepted(portAddress, controllerId); + })) + { + bAccepted = true; + } + } + + return bAccepted; +} + +void CPortNode::GetPort(CPhysicalPort& port) const +{ + std::vector<std::string> accepts; + for (const CControllerNode& node : m_controllers) + { + if (node.GetController()) + accepts.emplace_back(node.GetController()->ID()); + } + + port = CPhysicalPort(m_portId, std::move(accepts)); +} diff --git a/xbmc/games/ports/types/PortNode.h b/xbmc/games/ports/types/PortNode.h new file mode 100644 index 0000000..660a7bc --- /dev/null +++ b/xbmc/games/ports/types/PortNode.h @@ -0,0 +1,130 @@ +/* + * 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 "games/controllers/ControllerTypes.h" +#include "games/controllers/types/ControllerNode.h" + +#include <string> +#include <vector> + +namespace KODI +{ +namespace GAME +{ +class CPhysicalPort; + +/*! + * \brief Collection of nodes that can be connected to this port + */ +class CPortNode +{ +public: + CPortNode() = default; + CPortNode(const CPortNode& other) { *this = other; } + CPortNode(CPortNode&& other) = default; + CPortNode& operator=(const CPortNode& rhs); + CPortNode& operator=(CPortNode&& rhs) noexcept; + ~CPortNode(); + + /*! + * \brief Connection state of the port + * + * \return True if a controller is connected, false otherwise + */ + bool IsConnected() const { return m_bConnected; } + void SetConnected(bool bConnected) { m_bConnected = bConnected; } + + /*! + * \brief The controller that is active on this port + * + * \return The active controller, or invalid if port is disconnected + */ + const CControllerNode& GetActiveController() const; + CControllerNode& GetActiveController(); + void SetActiveController(unsigned int controllerIndex) { m_active = controllerIndex; } + bool SetActiveController(const std::string& controllerId); + + /*! + * \brief The port type + * + * \return The port type, if known + */ + PORT_TYPE GetPortType() const { return m_portType; } + void SetPortType(PORT_TYPE type) { m_portType = type; } + + /*! + * \brief The hardware or controller port ID + * + * \return The port ID of the hardware port or controller port, or empty if + * the port is only identified by its type + */ + const std::string& GetPortID() const { return m_portId; } + void SetPortID(std::string portId); + + /*! + * \brief Address given to the node by the implementation + */ + const std::string& GetAddress() const { return m_address; } + void SetAddress(std::string address); + + /*! + * \brief If true, prevents a disconnection option from being shown for this + * port + */ + bool IsForceConnected() const { return m_forceConnected; } + void SetForceConnected(bool forceConnected) { m_forceConnected = forceConnected; } + + /*! + * \brief Return the controller profiles that are compatible with this port + * + * \return The controller profiles, or empty if this port doesn't support + * any controller profiles + */ + const ControllerNodeVec& GetCompatibleControllers() const { return m_controllers; } + ControllerNodeVec& GetCompatibleControllers() { return m_controllers; } + void SetCompatibleControllers(ControllerNodeVec controllers); + + /*! + * \brief Check to see if a controller is compatible with this tree + * + * \param controllerId The ID of the controller + * + * \return True if the controller is compatible with the tree, false otherwise + */ + bool IsControllerAccepted(const std::string& controllerId) const; + + /*! + * \brief Check to see if a controller is compatible with this tree + * + * \param portAddress The port address + * \param controllerId The ID of the controller + * + * \return True if the controller is compatible with the tree, false otherwise + */ + bool IsControllerAccepted(const std::string& portAddress, const std::string& controllerId) const; + +private: + void GetPort(CPhysicalPort& port) const; + + bool m_bConnected = false; + unsigned int m_active = 0; + PORT_TYPE m_portType = PORT_TYPE::UNKNOWN; + std::string m_portId; + std::string m_address; + bool m_forceConnected{true}; + ControllerNodeVec m_controllers; +}; + +/*! + * \brief Collection of port nodes + */ +using PortVec = std::vector<CPortNode>; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/ports/windows/CMakeLists.txt b/xbmc/games/ports/windows/CMakeLists.txt new file mode 100644 index 0000000..4cf07ea --- /dev/null +++ b/xbmc/games/ports/windows/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES GUIPortList.cpp + GUIPortWindow.cpp +) + +set(HEADERS GUIPortDefines.h + GUIPortList.h + GUIPortWindow.h + IPortList.h +) + +core_add_library(games_ports_windows) diff --git a/xbmc/games/ports/windows/GUIPortDefines.h b/xbmc/games/ports/windows/GUIPortDefines.h new file mode 100644 index 0000000..c9c255b --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortDefines.h @@ -0,0 +1,22 @@ +/* + * 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 + +// Dialog title +#define CONTROL_PORT_DIALOG_LABEL 2 + +// GUI control IDs +#define CONTROL_PORT_LIST 3 + +// GUI button IDs +#define CONTROL_CLOSE_BUTTON 18 +#define CONTROL_RESET_BUTTON 19 + +// Skin XML file +#define PORT_DIALOG_XML "DialogGameControllers.xml" diff --git a/xbmc/games/ports/windows/GUIPortList.cpp b/xbmc/games/ports/windows/GUIPortList.cpp new file mode 100644 index 0000000..a25c94b --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortList.cpp @@ -0,0 +1,344 @@ +/* + * 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 "GUIPortList.h" + +#include "FileItem.h" +#include "GUIPortDefines.h" +#include "GUIPortWindow.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "games/GameServices.h" +#include "games/addons/GameClient.h" +#include "games/addons/input/GameClientInput.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerLayout.h" +#include "games/controllers/types/ControllerHub.h" +#include "games/controllers/types/ControllerTree.h" +#include "games/ports/types/PortNode.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindow.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "view/GUIViewControl.h" +#include "view/ViewState.h" + +using namespace KODI; +using namespace ADDON; +using namespace GAME; + +CGUIPortList::CGUIPortList(CGUIWindow& window) + : m_guiWindow(window), + m_viewControl(std::make_unique<CGUIViewControl>()), + m_vecItems(std::make_unique<CFileItemList>()) +{ +} + +CGUIPortList::~CGUIPortList() +{ + Deinitialize(); +} + +void CGUIPortList::OnWindowLoaded() +{ + m_viewControl->Reset(); + m_viewControl->SetParentWindow(m_guiWindow.GetID()); + m_viewControl->AddView(m_guiWindow.GetControl(CONTROL_PORT_LIST)); +} + +void CGUIPortList::OnWindowUnload() +{ + m_viewControl->Reset(); +} + +bool CGUIPortList::Initialize(GameClientPtr gameClient) +{ + // Validate parameters + if (!gameClient) + return false; + + // Initialize state + m_gameClient = std::move(gameClient); + m_viewControl->SetCurrentView(DEFAULT_VIEW_LIST); + + // Initialize GUI + Refresh(); + + CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CGUIPortList::OnEvent); + + return true; +} + +void CGUIPortList::Deinitialize() +{ + CServiceBroker::GetAddonMgr().Events().Unsubscribe(this); + + // Deinitialize GUI + CleanupItems(); + + // Reset state + m_gameClient.reset(); +} + +bool CGUIPortList::HasControl(int controlId) +{ + return m_viewControl->HasControl(controlId); +} + +int CGUIPortList::GetCurrentControl() +{ + return m_viewControl->GetCurrentControl(); +} + +void CGUIPortList::Refresh() +{ + // Send a synchronous message to clear the view control + m_viewControl->Clear(); + + CleanupItems(); + + if (m_gameClient) + { + unsigned int itemIndex = 0; + for (const CPortNode& port : m_gameClient->Input().GetActiveControllerTree().GetPorts()) + AddItems(port, itemIndex, GetLabel(port)); + + m_viewControl->SetItems(*m_vecItems); + + // Try to restore focus to the previously focused port + if (!m_focusedPort.empty() && m_addressToItem.find(m_focusedPort) != m_addressToItem.end()) + { + const unsigned int itemIndex = m_addressToItem[m_focusedPort]; + m_viewControl->SetSelectedItem(itemIndex); + OnItemFocus(itemIndex); + } + } +} + +void CGUIPortList::FrameMove() +{ + const int itemIndex = m_viewControl->GetSelectedItem(); + if (itemIndex != m_currentItem) + { + m_currentItem = itemIndex; + if (itemIndex >= 0) + OnItemFocus(static_cast<unsigned int>(itemIndex)); + } +} + +void CGUIPortList::SetFocused() +{ + m_viewControl->SetFocused(); +} + +bool CGUIPortList::OnSelect() +{ + const int itemIndex = m_viewControl->GetSelectedItem(); + if (itemIndex >= 0) + { + OnItemSelect(static_cast<unsigned int>(itemIndex)); + return true; + } + + return false; +} + +void CGUIPortList::ResetPorts() +{ + if (m_gameClient) + { + // Update the game client + m_gameClient->Input().ResetPorts(); + m_gameClient->Input().SavePorts(); + + // Refresh the GUI + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); + } +} + +void CGUIPortList::OnEvent(const ADDON::AddonEvent& event) +{ + if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) || // Also called on install + typeid(event) == typeid(ADDON::AddonEvents::Disabled) || // Not called on uninstall + typeid(event) == typeid(ADDON::AddonEvents::ReInstalled) || + typeid(event) == typeid(ADDON::AddonEvents::UnInstalled)) + { + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); + msg.SetStringParam(event.addonId); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); + } +} + +bool CGUIPortList::AddItems(const CPortNode& port, + unsigned int& itemId, + const std::string& itemLabel) +{ + // Validate parameters + if (itemLabel.empty()) + return false; + + // Record the port address so that we can decode item indexes later + m_itemToAddress[itemId] = port.GetAddress(); + m_addressToItem[port.GetAddress()] = itemId; + + if (port.IsConnected()) + { + const CControllerNode& controllerNode = port.GetActiveController(); + const ControllerPtr& controller = controllerNode.GetController(); + + // Create the list item + CFileItemPtr item = std::make_shared<CFileItem>(itemLabel); + item->SetLabel2(controller->Layout().Label()); + item->SetPath(port.GetAddress()); + item->SetArt("icon", controller->Layout().ImagePath()); + m_vecItems->Add(std::move(item)); + ++itemId; + + // Handle items for child ports + const PortVec& ports = controllerNode.GetHub().GetPorts(); + for (const CPortNode& childPort : ports) + { + std::ostringstream childItemLabel; + childItemLabel << " - "; + childItemLabel << controller->Layout().Label(); + childItemLabel << " - "; + childItemLabel << GetLabel(childPort); + + if (!AddItems(childPort, itemId, childItemLabel.str())) + return false; + } + } + else + { + // Create the list item + CFileItemPtr item = std::make_shared<CFileItem>(itemLabel); + item->SetLabel2(g_localizeStrings.Get(13298)); // "Disconnected" + item->SetPath(port.GetAddress()); + item->SetArt("icon", "DefaultAddonNone.png"); + m_vecItems->Add(std::move(item)); + ++itemId; + } + + return true; +} + +void CGUIPortList::CleanupItems() +{ + m_vecItems->Clear(); + m_itemToAddress.clear(); + m_addressToItem.clear(); +} + +void CGUIPortList::OnItemFocus(unsigned int itemIndex) +{ + m_focusedPort = m_itemToAddress[itemIndex]; +} + +void CGUIPortList::OnItemSelect(unsigned int itemIndex) +{ + if (m_gameClient) + { + const auto it = m_itemToAddress.find(itemIndex); + if (it == m_itemToAddress.end()) + return; + + const std::string& portAddress = it->second; + if (portAddress.empty()) + return; + + const CPortNode& port = m_gameClient->Input().GetActiveControllerTree().GetPort(portAddress); + + ControllerVector controllers; + for (const CControllerNode& controllerNode : port.GetCompatibleControllers()) + controllers.emplace_back(controllerNode.GetController()); + + // Get current controller to give initial focus + ControllerPtr controller = port.GetActiveController().GetController(); + + auto callback = [this, &port](const ControllerPtr& controller) { + OnControllerSelected(port, controller); + }; + + const bool showDisconnect = !port.IsForceConnected(); + m_controllerSelectDialog.Initialize(std::move(controllers), std::move(controller), + showDisconnect, callback); + } +} + +void CGUIPortList::OnControllerSelected(const CPortNode& port, const ControllerPtr& controller) +{ + if (m_gameClient) + { + // Translate parameter + const bool bConnected = static_cast<bool>(controller); + + // Update the game client + const bool bSuccess = + bConnected ? m_gameClient->Input().ConnectController(port.GetAddress(), controller) + : m_gameClient->Input().DisconnectController(port.GetAddress()); + + if (bSuccess) + { + m_gameClient->Input().SavePorts(); + } + else + { + // "Failed to change controller" + // "The emulator "%s" had an internal error." + MESSAGING::HELPERS::ShowOKDialogText( + CVariant{35114}, + CVariant{StringUtils::Format(g_localizeStrings.Get(35213), m_gameClient->Name())}); + } + + // Send a GUI message to reload the port list + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); + } +} + +std::string CGUIPortList::GetLabel(const CPortNode& port) +{ + const PORT_TYPE portType = port.GetPortType(); + switch (portType) + { + case PORT_TYPE::KEYBOARD: + { + // "Keyboard" + return g_localizeStrings.Get(35150); + } + case PORT_TYPE::MOUSE: + { + // "Mouse" + return g_localizeStrings.Get(35171); + } + case PORT_TYPE::CONTROLLER: + { + const std::string& portId = port.GetPortID(); + if (portId.empty()) + { + CLog::Log(LOGERROR, "Controller port with address \"{}\" doesn't have a port ID", + port.GetAddress()); + } + else + { + // "Port {0:s}" + const std::string& portString = g_localizeStrings.Get(35112); + return StringUtils::Format(portString, portId); + } + break; + } + default: + break; + } + + return ""; +} diff --git a/xbmc/games/ports/windows/GUIPortList.h b/xbmc/games/ports/windows/GUIPortList.h new file mode 100644 index 0000000..3fed6b7 --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortList.h @@ -0,0 +1,79 @@ +/* + * 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 "IPortList.h" +#include "addons/AddonEvents.h" +#include "games/GameTypes.h" +#include "games/controllers/ControllerTypes.h" +#include "games/controllers/dialogs/ControllerSelect.h" +#include "games/controllers/types/ControllerTree.h" + +#include <map> +#include <memory> +#include <string> + +class CFileItemList; +class CGUIViewControl; +class CGUIWindow; + +namespace KODI +{ +namespace GAME +{ +class CGUIPortList : public IPortList +{ +public: + CGUIPortList(CGUIWindow& window); + ~CGUIPortList() override; + + // Implementation of IPortList + void OnWindowLoaded() override; + void OnWindowUnload() override; + bool Initialize(GameClientPtr gameClient) override; + void Deinitialize() override; + bool HasControl(int controlId) override; + int GetCurrentControl() override; + void Refresh() override; + void FrameMove() override; + void SetFocused() override; + bool OnSelect() override; + void ResetPorts() override; + +private: + // Add-on API + void OnEvent(const ADDON::AddonEvent& event); + + bool AddItems(const CPortNode& port, unsigned int& itemId, const std::string& itemLabel); + void CleanupItems(); + void OnItemFocus(unsigned int itemIndex); + void OnItemSelect(unsigned int itemIndex); + + // Controller selection callback + void OnControllerSelected(const CPortNode& port, const ControllerPtr& controller); + + static std::string GetLabel(const CPortNode& port); + + // Construction parameters + CGUIWindow& m_guiWindow; + + // GUI parameters + CControllerSelect m_controllerSelectDialog; + std::string m_focusedPort; // Address of focused port + int m_currentItem{-1}; // Index of the selected item, or -1 if no item is selected + std::unique_ptr<CGUIViewControl> m_viewControl; + std::unique_ptr<CFileItemList> m_vecItems; + + // Game parameters + GameClientPtr m_gameClient; + std::map<unsigned int, std::string> m_itemToAddress; // item index -> port address + std::map<std::string, unsigned int> m_addressToItem; // port address -> item index +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/ports/windows/GUIPortWindow.cpp b/xbmc/games/ports/windows/GUIPortWindow.cpp new file mode 100644 index 0000000..e95927a --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortWindow.cpp @@ -0,0 +1,183 @@ +/* + * 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 "GUIPortWindow.h" + +#include "GUIPortDefines.h" +#include "GUIPortList.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonType.h" +#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" +#include "cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h" +#include "games/addons/GameClient.h" +#include "guilib/GUIButtonControl.h" +#include "guilib/GUIControl.h" +#include "guilib/GUIMessage.h" +#include "guilib/WindowIDs.h" +#include "input/actions/ActionIDs.h" +#include "utils/StringUtils.h" + +using namespace KODI; +using namespace GAME; + +CGUIPortWindow::CGUIPortWindow() + : CGUIDialog(WINDOW_DIALOG_GAME_PORTS, PORT_DIALOG_XML), + m_portList(std::make_unique<CGUIPortList>(*this)) +{ + // Initialize CGUIWindow + m_loadType = KEEP_IN_MEMORY; +} + +CGUIPortWindow::~CGUIPortWindow() = default; + +bool CGUIPortWindow::OnMessage(CGUIMessage& message) +{ + // Set to true to block the call to the super class + bool bHandled = false; + + switch (message.GetMessage()) + { + case GUI_MSG_SETFOCUS: + { + const int controlId = message.GetControlId(); + if (m_portList->HasControl(controlId) && m_portList->GetCurrentControl() != controlId) + { + FocusPortList(); + bHandled = true; + } + break; + } + case GUI_MSG_CLICKED: + { + const int controlId = message.GetSenderId(); + + if (controlId == CONTROL_CLOSE_BUTTON) + { + CloseDialog(); + bHandled = true; + } + else if (controlId == CONTROL_RESET_BUTTON) + { + ResetPorts(); + bHandled = true; + } + else if (m_portList->HasControl(controlId)) + { + const int actionId = message.GetParam1(); + if (actionId == ACTION_SELECT_ITEM || actionId == ACTION_MOUSE_LEFT_CLICK) + { + OnClickAction(); + bHandled = true; + } + } + break; + } + case GUI_MSG_REFRESH_LIST: + { + UpdatePortList(); + break; + } + default: + break; + } + + if (!bHandled) + bHandled = CGUIDialog::OnMessage(message); + + return bHandled; +} + +void CGUIPortWindow::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + + m_portList->OnWindowLoaded(); +} + +void CGUIPortWindow::OnWindowUnload() +{ + m_portList->OnWindowUnload(); + + CGUIDialog::OnWindowUnload(); +} + +void CGUIPortWindow::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); + + // Get active game add-on + GameClientPtr gameClient; + { + auto gameSettingsHandle = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + if (gameSettingsHandle) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(gameSettingsHandle->GameClientID(), addon, + ADDON::AddonType::GAMEDLL, + ADDON::OnlyEnabled::CHOICE_YES)) + gameClient = std::static_pointer_cast<CGameClient>(addon); + } + } + m_gameClient = std::move(gameClient); + + // Set the heading + // "Port Setup - {game client name}" + SET_CONTROL_LABEL(CONTROL_PORT_DIALOG_LABEL, + StringUtils::Format("$LOCALIZE[35111] - {}", m_gameClient->Name())); + + m_portList->Initialize(m_gameClient); + + UpdatePortList(); + + // Focus the port list + CGUIMessage msgFocus(GUI_MSG_SETFOCUS, GetID(), CONTROL_PORT_LIST); + OnMessage(msgFocus); +} + +void CGUIPortWindow::OnDeinitWindow(int nextWindowID) +{ + m_portList->Deinitialize(); + + m_gameClient.reset(); + + CGUIDialog::OnDeinitWindow(nextWindowID); +} + +void CGUIPortWindow::FrameMove() +{ + CGUIDialog::FrameMove(); + + m_portList->FrameMove(); +} + +void CGUIPortWindow::UpdatePortList() +{ + m_portList->Refresh(); +} + +void CGUIPortWindow::FocusPortList() +{ + m_portList->SetFocused(); +} + +bool CGUIPortWindow::OnClickAction() +{ + return m_portList->OnSelect(); +} + +void CGUIPortWindow::ResetPorts() +{ + m_portList->ResetPorts(); +} + +void CGUIPortWindow::CloseDialog() +{ + Close(); +} diff --git a/xbmc/games/ports/windows/GUIPortWindow.h b/xbmc/games/ports/windows/GUIPortWindow.h new file mode 100644 index 0000000..3b98708 --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortWindow.h @@ -0,0 +1,56 @@ +/* + * 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/GameTypes.h" +#include "guilib/GUIDialog.h" + +#include <memory> + +namespace KODI +{ +namespace GAME +{ +class IPortList; + +class CGUIPortWindow : public CGUIDialog +{ +public: + CGUIPortWindow(); + ~CGUIPortWindow() override; + + // Implementation of CGUIControl via CGUIDialog + bool OnMessage(CGUIMessage& message) override; + +protected: + // Implementation of CGUIWindow via CGUIDialog + void OnWindowLoaded() override; + void OnWindowUnload() override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + void FrameMove() override; + +private: + // Actions for port list + void UpdatePortList(); + void FocusPortList(); + bool OnClickAction(); + + // Actions for the available buttons + void ResetPorts(); + void CloseDialog(); + + // GUI parameters + std::unique_ptr<IPortList> m_portList; + + // Game parameters + GameClientPtr m_gameClient; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/ports/windows/IPortList.h b/xbmc/games/ports/windows/IPortList.h new file mode 100644 index 0000000..e330c6e --- /dev/null +++ b/xbmc/games/ports/windows/IPortList.h @@ -0,0 +1,104 @@ +/* + * 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/GameTypes.h" + +/*! + * \brief Controller port setup window + * + * The port setup window presents a list of ports and their attached + * controllers. + * + * The label2 of each port is the currently-connected controller. The user + * selects from all controllers that the port accepts (as given by the + * game-addon's topology.xml file). + * + * The controller topology is stored as a generic tree. Here we apply game logic + * to simplify controller selection. + */ + +namespace KODI +{ +namespace GAME +{ +/*! + * \brief A list populated by controller ports + */ +class IPortList +{ +public: + virtual ~IPortList() = default; + + /*! + * \brief Callback when the GUI window is loaded + */ + virtual void OnWindowLoaded() = 0; + + /*! + * \brief Callback when the GUI window is unloaded + */ + virtual void OnWindowUnload() = 0; + + /*! + * \brief Initialize resources + * + * \param gameClient The game client providing the ports + * + * \return True if the resource is initialized and can be used, false if the + * resource failed to initialize and must not be used + */ + virtual bool Initialize(GameClientPtr gameClient) = 0; + + /*! + * \brief Deinitialize resources + */ + virtual void Deinitialize() = 0; + + /*! + * \brief Query if a control with the given ID belongs to this list + */ + virtual bool HasControl(int controlId) = 0; + + /*! + * \brief Query the ID of the current control in this list + * + * \return The control ID, or -1 if no control is currently active + */ + virtual int GetCurrentControl() = 0; + + /*! + * \brief Refresh the contents of the list + */ + virtual void Refresh() = 0; + + /*! + * \brief Callback when a frame is rendered by the GUI + */ + virtual void FrameMove() = 0; + + /*! + * \brief The port list has been focused in the GUI + */ + virtual void SetFocused() = 0; + + /*! + * \brief The port list has been selected + * + * \brief True if a control was active, false of all controls were inactive + */ + virtual bool OnSelect() = 0; + + /*! + * \brief Reset the ports to their game add-on's default configuration + */ + virtual void ResetPorts() = 0; +}; +} // namespace GAME +} // namespace KODI |