From 975c733c0aefb761c63f7698a284e294503768f6 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Tue, 11 Jan 2022 16:31:38 -0800 Subject: Ports: Refactor ports into new folder --- cmake/treedata/common/games.txt | 3 + xbmc/games/addons/input/GameClientInput.cpp | 2 +- xbmc/games/addons/input/GameClientJoystick.cpp | 2 +- xbmc/games/controllers/dialogs/CMakeLists.txt | 8 +- .../controllers/dialogs/ControllerInstaller.cpp | 137 +++++++++ .../controllers/dialogs/ControllerInstaller.h | 28 ++ .../games/controllers/dialogs/ControllerSelect.cpp | 130 ++++++++ xbmc/games/controllers/dialogs/ControllerSelect.h | 44 +++ xbmc/games/controllers/input/CMakeLists.txt | 6 - xbmc/games/controllers/input/PhysicalPort.cpp | 66 ----- xbmc/games/controllers/input/PhysicalPort.h | 64 ---- xbmc/games/controllers/input/PhysicalTopology.h | 2 +- xbmc/games/controllers/input/PortInput.cpp | 129 -------- xbmc/games/controllers/input/PortInput.h | 76 ----- xbmc/games/controllers/input/PortManager.cpp | 326 -------------------- xbmc/games/controllers/input/PortManager.h | 74 ----- xbmc/games/controllers/types/CMakeLists.txt | 2 - xbmc/games/controllers/types/ControllerHub.h | 2 +- xbmc/games/controllers/types/ControllerNode.cpp | 2 +- xbmc/games/controllers/types/PortNode.cpp | 156 ---------- xbmc/games/controllers/types/PortNode.h | 130 -------- xbmc/games/controllers/windows/CMakeLists.txt | 14 +- .../controllers/windows/ControllerInstaller.cpp | 137 --------- .../controllers/windows/ControllerInstaller.h | 28 -- .../games/controllers/windows/ControllerSelect.cpp | 130 -------- xbmc/games/controllers/windows/ControllerSelect.h | 44 --- .../controllers/windows/GUIControllerWindow.cpp | 2 +- xbmc/games/controllers/windows/GUIPortDefines.h | 22 -- xbmc/games/controllers/windows/GUIPortList.cpp | 327 --------------------- xbmc/games/controllers/windows/GUIPortList.h | 82 ------ xbmc/games/controllers/windows/GUIPortWindow.cpp | 182 ------------ xbmc/games/controllers/windows/GUIPortWindow.h | 57 ---- xbmc/games/controllers/windows/IPortList.h | 100 ------- xbmc/games/ports/input/CMakeLists.txt | 11 + xbmc/games/ports/input/PhysicalPort.cpp | 66 +++++ xbmc/games/ports/input/PhysicalPort.h | 64 ++++ xbmc/games/ports/input/PortInput.cpp | 129 ++++++++ xbmc/games/ports/input/PortInput.h | 76 +++++ xbmc/games/ports/input/PortManager.cpp | 326 ++++++++++++++++++++ xbmc/games/ports/input/PortManager.h | 74 +++++ xbmc/games/ports/types/CMakeLists.txt | 7 + xbmc/games/ports/types/PortNode.cpp | 156 ++++++++++ xbmc/games/ports/types/PortNode.h | 130 ++++++++ xbmc/games/ports/windows/CMakeLists.txt | 11 + xbmc/games/ports/windows/GUIPortDefines.h | 22 ++ xbmc/games/ports/windows/GUIPortList.cpp | 327 +++++++++++++++++++++ xbmc/games/ports/windows/GUIPortList.h | 82 ++++++ xbmc/games/ports/windows/GUIPortWindow.cpp | 182 ++++++++++++ xbmc/games/ports/windows/GUIPortWindow.h | 57 ++++ xbmc/games/ports/windows/IPortList.h | 100 +++++++ xbmc/guilib/GUIWindowManager.cpp | 2 +- 51 files changed, 2177 insertions(+), 2159 deletions(-) create mode 100644 xbmc/games/controllers/dialogs/ControllerInstaller.cpp create mode 100644 xbmc/games/controllers/dialogs/ControllerInstaller.h create mode 100644 xbmc/games/controllers/dialogs/ControllerSelect.cpp create mode 100644 xbmc/games/controllers/dialogs/ControllerSelect.h delete mode 100644 xbmc/games/controllers/input/PhysicalPort.cpp delete mode 100644 xbmc/games/controllers/input/PhysicalPort.h delete mode 100644 xbmc/games/controllers/input/PortInput.cpp delete mode 100644 xbmc/games/controllers/input/PortInput.h delete mode 100644 xbmc/games/controllers/input/PortManager.cpp delete mode 100644 xbmc/games/controllers/input/PortManager.h delete mode 100644 xbmc/games/controllers/types/PortNode.cpp delete mode 100644 xbmc/games/controllers/types/PortNode.h delete mode 100644 xbmc/games/controllers/windows/ControllerInstaller.cpp delete mode 100644 xbmc/games/controllers/windows/ControllerInstaller.h delete mode 100644 xbmc/games/controllers/windows/ControllerSelect.cpp delete mode 100644 xbmc/games/controllers/windows/ControllerSelect.h delete mode 100644 xbmc/games/controllers/windows/GUIPortDefines.h delete mode 100644 xbmc/games/controllers/windows/GUIPortList.cpp delete mode 100644 xbmc/games/controllers/windows/GUIPortList.h delete mode 100644 xbmc/games/controllers/windows/GUIPortWindow.cpp delete mode 100644 xbmc/games/controllers/windows/GUIPortWindow.h delete mode 100644 xbmc/games/controllers/windows/IPortList.h create mode 100644 xbmc/games/ports/input/CMakeLists.txt create mode 100644 xbmc/games/ports/input/PhysicalPort.cpp create mode 100644 xbmc/games/ports/input/PhysicalPort.h create mode 100644 xbmc/games/ports/input/PortInput.cpp create mode 100644 xbmc/games/ports/input/PortInput.h create mode 100644 xbmc/games/ports/input/PortManager.cpp create mode 100644 xbmc/games/ports/input/PortManager.h create mode 100644 xbmc/games/ports/types/CMakeLists.txt create mode 100644 xbmc/games/ports/types/PortNode.cpp create mode 100644 xbmc/games/ports/types/PortNode.h create mode 100644 xbmc/games/ports/windows/CMakeLists.txt create mode 100644 xbmc/games/ports/windows/GUIPortDefines.h create mode 100644 xbmc/games/ports/windows/GUIPortList.cpp create mode 100644 xbmc/games/ports/windows/GUIPortList.h create mode 100644 xbmc/games/ports/windows/GUIPortWindow.cpp create mode 100644 xbmc/games/ports/windows/GUIPortWindow.h create mode 100644 xbmc/games/ports/windows/IPortList.h diff --git a/cmake/treedata/common/games.txt b/cmake/treedata/common/games.txt index e72c9de9f9..26d60d4cbf 100644 --- a/cmake/treedata/common/games.txt +++ b/cmake/treedata/common/games.txt @@ -10,5 +10,8 @@ xbmc/games/controllers/types games/controllers/types xbmc/games/controllers/windows games/controllers/windows xbmc/games/dialogs games/dialogs xbmc/games/dialogs/osd games/dialogs/osd +xbmc/games/ports/input games/ports/input +xbmc/games/ports/types games/ports/types +xbmc/games/ports/windows games/ports/windows xbmc/games/tags games/tags xbmc/games/windows games/windows diff --git a/xbmc/games/addons/input/GameClientInput.cpp b/xbmc/games/addons/input/GameClientInput.cpp index 07f9a59c3e..f37026b154 100644 --- a/xbmc/games/addons/input/GameClientInput.cpp +++ b/xbmc/games/addons/input/GameClientInput.cpp @@ -23,7 +23,7 @@ #include "games/controllers/Controller.h" #include "games/controllers/ControllerLayout.h" #include "games/controllers/input/PhysicalTopology.h" -#include "games/controllers/input/PortManager.h" +#include "games/ports/input/PortManager.h" #include "input/joysticks/JoystickTypes.h" #include "peripherals/EventLockHandle.h" #include "peripherals/Peripherals.h" diff --git a/xbmc/games/addons/input/GameClientJoystick.cpp b/xbmc/games/addons/input/GameClientJoystick.cpp index 291dc6755b..30a6016d9e 100644 --- a/xbmc/games/addons/input/GameClientJoystick.cpp +++ b/xbmc/games/addons/input/GameClientJoystick.cpp @@ -11,7 +11,7 @@ #include "GameClientInput.h" #include "games/addons/GameClient.h" #include "games/controllers/Controller.h" -#include "games/controllers/input/PortInput.h" +#include "games/ports/input/PortInput.h" #include "input/joysticks/interfaces/IInputReceiver.h" #include "utils/log.h" diff --git a/xbmc/games/controllers/dialogs/CMakeLists.txt b/xbmc/games/controllers/dialogs/CMakeLists.txt index 2b398e98ed..e40e60eaf3 100644 --- a/xbmc/games/controllers/dialogs/CMakeLists.txt +++ b/xbmc/games/controllers/dialogs/CMakeLists.txt @@ -1,9 +1,13 @@ -set(SOURCES GUIDialogAxisDetection.cpp +set(SOURCES ControllerInstaller.cpp + ControllerSelect.cpp + GUIDialogAxisDetection.cpp GUIDialogButtonCapture.cpp GUIDialogIgnoreInput.cpp ) -set(HEADERS GUIDialogAxisDetection.h +set(HEADERS ControllerInstaller.h + ControllerSelect.h + GUIDialogAxisDetection.h GUIDialogButtonCapture.h GUIDialogIgnoreInput.h ) diff --git a/xbmc/games/controllers/dialogs/ControllerInstaller.cpp b/xbmc/games/controllers/dialogs/ControllerInstaller.cpp new file mode 100644 index 0000000000..26cf854749 --- /dev/null +++ b/xbmc/games/controllers/dialogs/ControllerInstaller.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 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 "ControllerInstaller.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "addons/Addon.h" +#include "addons/AddonInstaller.h" +#include "addons/AddonManager.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogSelect.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +using namespace KODI; +using namespace GAME; + +CControllerInstaller::CControllerInstaller() : CThread("ControllerInstaller") +{ +} + +void CControllerInstaller::Process() +{ + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui == nullptr) + return; + + CGUIWindowManager& windowManager = gui->GetWindowManager(); + + auto pSelectDialog = windowManager.GetWindow(WINDOW_DIALOG_SELECT); + if (pSelectDialog == nullptr) + return; + + auto pProgressDialog = windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); + if (pProgressDialog == nullptr) + return; + + ADDON::VECADDONS installableAddons; + CServiceBroker::GetAddonMgr().GetInstallableAddons(installableAddons, + ADDON::ADDON_GAME_CONTROLLER); + if (installableAddons.empty()) + { + // "Controller profiles" + // "All available controller profiles are installed." + MESSAGING::HELPERS::ShowOKDialogText(CVariant{35050}, CVariant{35062}); + return; + } + + CLog::Log(LOGDEBUG, "Controller installer: Found {} controller add-ons", + installableAddons.size()); + + CFileItemList items; + for (const auto& addon : installableAddons) + { + CFileItemPtr item(new CFileItem(addon->Name())); + item->SetArt("icon", addon->Icon()); + items.Add(std::move(item)); + } + + pSelectDialog->Reset(); + pSelectDialog->SetHeading(39020); // "The following additional add-ons will be installed" + pSelectDialog->SetUseDetails(true); + pSelectDialog->EnableButton(true, 186); // "OK"" + for (const auto& it : items) + pSelectDialog->Add(*it); + pSelectDialog->Open(); + + if (!pSelectDialog->IsButtonPressed()) + { + CLog::Log(LOGDEBUG, "Controller installer: User cancelled installation dialog"); + return; + } + + CLog::Log(LOGDEBUG, "Controller installer: Installing {} controller add-ons", + installableAddons.size()); + + pProgressDialog->SetHeading(CVariant{24086}); // "Installing add-on..." + pProgressDialog->SetLine(0, CVariant{""}); + pProgressDialog->SetLine(1, CVariant{""}); + pProgressDialog->SetLine(2, CVariant{""}); + + pProgressDialog->Open(); + + unsigned int installedCount = 0; + while (installedCount < installableAddons.size()) + { + const auto& addon = installableAddons[installedCount]; + + // Set dialog text + const std::string& progressTemplate = g_localizeStrings.Get(24057); // "Installing {0:s}..." + const std::string progressText = StringUtils::Format(progressTemplate, addon->Name()); + pProgressDialog->SetLine(0, CVariant{progressText}); + + // Set dialog percentage + const unsigned int percentage = + 100 * (installedCount + 1) / static_cast(installableAddons.size()); + pProgressDialog->SetPercentage(percentage); + + if (!ADDON::CAddonInstaller::GetInstance().InstallOrUpdate( + addon->ID(), ADDON::BackgroundJob::CHOICE_NO, ADDON::ModalJob::CHOICE_NO)) + { + CLog::Log(LOGERROR, "Controller installer: Failed to install {}", addon->ID()); + // "Error" + // "Failed to install add-on." + MESSAGING::HELPERS::ShowOKDialogText(257, 35256); + break; + } + + if (pProgressDialog->IsCanceled()) + { + CLog::Log(LOGDEBUG, "Controller installer: User cancelled add-on installation"); + break; + } + + if (windowManager.GetActiveWindowOrDialog() != WINDOW_DIALOG_PROGRESS) + { + CLog::Log(LOGDEBUG, "Controller installer: Progress dialog is hidden, cancelling"); + break; + } + + installedCount++; + } + + CLog::Log(LOGDEBUG, "Controller window: Installed {} controller add-ons", installedCount); + pProgressDialog->Close(); +} diff --git a/xbmc/games/controllers/dialogs/ControllerInstaller.h b/xbmc/games/controllers/dialogs/ControllerInstaller.h new file mode 100644 index 0000000000..9863e2754b --- /dev/null +++ b/xbmc/games/controllers/dialogs/ControllerInstaller.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 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 "threads/Thread.h" + +namespace KODI +{ +namespace GAME +{ +class CControllerInstaller : public CThread +{ +public: + CControllerInstaller(); + ~CControllerInstaller() override = default; + +protected: + // implementation of CThread + void Process() override; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/controllers/dialogs/ControllerSelect.cpp b/xbmc/games/controllers/dialogs/ControllerSelect.cpp new file mode 100644 index 0000000000..badfd3b8c8 --- /dev/null +++ b/xbmc/games/controllers/dialogs/ControllerSelect.cpp @@ -0,0 +1,130 @@ +/* + * 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 "ControllerSelect.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "dialogs/GUIDialogSelect.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerLayout.h" +#include "games/controllers/ControllerTypes.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "utils/Variant.h" +#include "utils/log.h" + +using namespace KODI; +using namespace GAME; + +CControllerSelect::CControllerSelect() : CThread("ControllerSelect") +{ +} + +CControllerSelect::~CControllerSelect() = default; + +void CControllerSelect::Initialize(ControllerVector controllers, + ControllerPtr defaultController, + bool showDisconnect, + const std::function& callback) +{ + // Validate parameters + if (!callback) + return; + + // Initialize state + m_controllers = std::move(controllers); + m_defaultController = std::move(defaultController); + m_showDisconnect = showDisconnect; + m_callback = callback; + + // Create thread + Create(false); +} + +void CControllerSelect::Deinitialize() +{ + // Stop thread + StopThread(true); + + // Reset state + m_controllers.clear(); + m_defaultController.reset(); + m_showDisconnect = true; + m_callback = nullptr; +} + +void CControllerSelect::Process() +{ + // Select first controller by default + unsigned int initialSelected = 0; + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui == nullptr) + return; + + CGUIWindowManager& windowManager = gui->GetWindowManager(); + + auto pSelectDialog = windowManager.GetWindow(WINDOW_DIALOG_SELECT); + if (pSelectDialog == nullptr) + return; + + CLog::Log(LOGDEBUG, "Controller select: Showing dialog for {} controllers", m_controllers.size()); + + CFileItemList items; + for (const ControllerPtr& controller : m_controllers) + { + CFileItemPtr item(new CFileItem(controller->Layout().Label())); + item->SetArt("icon", controller->Layout().ImagePath()); + items.Add(std::move(item)); + + // Check if a specified controller should be selected by default + if (m_defaultController && m_defaultController->ID() == controller->ID()) + initialSelected = items.Size() - 1; + } + + if (m_showDisconnect) + { + // Add a button to disconnect the port + CFileItemPtr item(new CFileItem(g_localizeStrings.Get(13298))); // "Disconnected" + item->SetArt("icon", "DefaultAddonNone.png"); + items.Add(std::move(item)); + + // Check if the disconnect button should be selected by default + if (!m_defaultController) + initialSelected = items.Size() - 1; + } + + pSelectDialog->Reset(); + pSelectDialog->SetHeading(CVariant{35113}); // "Select a Controller" + pSelectDialog->SetUseDetails(true); + pSelectDialog->EnableButton(false, 186); // "OK"" + pSelectDialog->SetButtonFocus(false); + for (const auto& it : items) + pSelectDialog->Add(*it); + pSelectDialog->SetSelected(static_cast(initialSelected)); + pSelectDialog->Open(); + + // If the thread was stopped, exit early + if (m_bStop) + return; + + if (pSelectDialog->IsConfirmed()) + { + ControllerPtr resultController; + + const int selected = pSelectDialog->GetSelectedItem(); + if (0 <= selected && selected < static_cast(m_controllers.size())) + resultController = m_controllers.at(selected); + + // Fire a callback with the result + m_callback(resultController); + } +} diff --git a/xbmc/games/controllers/dialogs/ControllerSelect.h b/xbmc/games/controllers/dialogs/ControllerSelect.h new file mode 100644 index 0000000000..83a1b416ce --- /dev/null +++ b/xbmc/games/controllers/dialogs/ControllerSelect.h @@ -0,0 +1,44 @@ +/* + * 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/ControllerTypes.h" +#include "threads/Thread.h" + +#include + +namespace KODI +{ +namespace GAME +{ +class CControllerSelect : public CThread +{ +public: + CControllerSelect(); + ~CControllerSelect() override; + + void Initialize(ControllerVector controllers, + ControllerPtr defaultController, + bool showDisconnect, + const std::function& callback); + void Deinitialize(); + +protected: + // Implementation of CThread + void Process() override; + +private: + // State parameters + ControllerVector m_controllers; + ControllerPtr m_defaultController; + bool m_showDisconnect = true; + std::function m_callback; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/controllers/input/CMakeLists.txt b/xbmc/games/controllers/input/CMakeLists.txt index 9c48ca5189..511fa3751e 100644 --- a/xbmc/games/controllers/input/CMakeLists.txt +++ b/xbmc/games/controllers/input/CMakeLists.txt @@ -1,17 +1,11 @@ set(SOURCES InputSink.cpp PhysicalFeature.cpp - PhysicalPort.cpp PhysicalTopology.cpp - PortInput.cpp - PortManager.cpp ) set(HEADERS InputSink.h PhysicalFeature.h - PhysicalPort.h PhysicalTopology.h - PortInput.h - PortManager.h ) core_add_library(games_controller_input) diff --git a/xbmc/games/controllers/input/PhysicalPort.cpp b/xbmc/games/controllers/input/PhysicalPort.cpp deleted file mode 100644 index 9c53c76199..0000000000 --- a/xbmc/games/controllers/input/PhysicalPort.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 -#include - -using namespace KODI; -using namespace GAME; - -CPhysicalPort::CPhysicalPort(std::string portId, std::vector 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/controllers/input/PhysicalPort.h b/xbmc/games/controllers/input/PhysicalPort.h deleted file mode 100644 index 83fe3a503c..0000000000 --- a/xbmc/games/controllers/input/PhysicalPort.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 -#include - -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 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& 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 m_accepts; -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/input/PhysicalTopology.h b/xbmc/games/controllers/input/PhysicalTopology.h index bc76bb91b2..c194de82f9 100644 --- a/xbmc/games/controllers/input/PhysicalTopology.h +++ b/xbmc/games/controllers/input/PhysicalTopology.h @@ -8,7 +8,7 @@ #pragma once -#include "PhysicalPort.h" +#include "games/ports/input/PhysicalPort.h" #include diff --git a/xbmc/games/controllers/input/PortInput.cpp b/xbmc/games/controllers/input/PortInput.cpp deleted file mode 100644 index e7837c3f7e..0000000000 --- a/xbmc/games/controllers/input/PortInput.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 "InputSink.h" -#include "games/addons/GameClient.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/controllers/input/PortInput.h b/xbmc/games/controllers/input/PortInput.h deleted file mode 100644 index 4e296a376c..0000000000 --- a/xbmc/games/controllers/input/PortInput.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 - -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; - - // 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 m_appInput; - - // Prevents input falling through to Kodi when not handled by the game - std::unique_ptr m_inputSink; -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/input/PortManager.cpp b/xbmc/games/controllers/input/PortManager.cpp deleted file mode 100644 index 9b60291626..0000000000 --- a/xbmc/games/controllers/input/PortManager.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 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 "filesystem/File.h" -#include "games/controllers/Controller.h" -#include "games/controllers/types/ControllerHub.h" -#include "games/controllers/types/ControllerNode.h" -#include "games/controllers/types/PortNode.h" -#include "utils/URIUtils.h" -#include "utils/XBMCTinyXML.h" -#include "utils/XMLUtils.h" -#include "utils/log.h" - -#include - -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() -{ - m_controllerTree.Clear(); - m_xmlPath.clear(); -} - -void CPortManager::SetControllerTree(const CControllerTree& controllerTree) -{ - m_controllerTree = controllerTree; -} - -void CPortManager::LoadXML() -{ - if (!XFILE::CFile::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::SaveXML() -{ - CXBMCTinyXML doc; - TiXmlElement node(XML_ROOT_PORTS); - - SerializePorts(node, m_controllerTree.GetPorts()); - - doc.InsertEndChild(node); - - doc.SaveFile(m_xmlPath); -} - -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(pPort, 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/controllers/input/PortManager.h b/xbmc/games/controllers/input/PortManager.h deleted file mode 100644 index d5193b377c..0000000000 --- a/xbmc/games/controllers/input/PortManager.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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" - -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 SaveXML(); - 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; -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/types/CMakeLists.txt b/xbmc/games/controllers/types/CMakeLists.txt index fc9d6c89e0..503d254257 100644 --- a/xbmc/games/controllers/types/CMakeLists.txt +++ b/xbmc/games/controllers/types/CMakeLists.txt @@ -1,14 +1,12 @@ set(SOURCES ControllerGrid.cpp ControllerHub.cpp ControllerNode.cpp - PortNode.cpp ) set(HEADERS ControllerGrid.h ControllerHub.h ControllerNode.h ControllerTree.h - PortNode.h ) core_add_library(games_controller_types) diff --git a/xbmc/games/controllers/types/ControllerHub.h b/xbmc/games/controllers/types/ControllerHub.h index d9c3f8fc83..fc48a81a8f 100644 --- a/xbmc/games/controllers/types/ControllerHub.h +++ b/xbmc/games/controllers/types/ControllerHub.h @@ -8,8 +8,8 @@ #pragma once -#include "PortNode.h" #include "games/controllers/ControllerTypes.h" +#include "games/ports/types/PortNode.h" #include diff --git a/xbmc/games/controllers/types/ControllerNode.cpp b/xbmc/games/controllers/types/ControllerNode.cpp index 99a7fa4c7b..0434df7f83 100644 --- a/xbmc/games/controllers/types/ControllerNode.cpp +++ b/xbmc/games/controllers/types/ControllerNode.cpp @@ -9,9 +9,9 @@ #include "ControllerNode.h" #include "ControllerHub.h" -#include "PortNode.h" #include "games/controllers/Controller.h" #include "games/controllers/input/PhysicalTopology.h" +#include "games/ports/types/PortNode.h" #include #include diff --git a/xbmc/games/controllers/types/PortNode.cpp b/xbmc/games/controllers/types/PortNode.cpp deleted file mode 100644 index dd54ff12b1..0000000000 --- a/xbmc/games/controllers/types/PortNode.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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/input/PhysicalPort.h" -#include "games/controllers/types/ControllerHub.h" - -#include -#include - -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_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 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/controllers/types/PortNode.h b/xbmc/games/controllers/types/PortNode.h deleted file mode 100644 index f970e9637b..0000000000 --- a/xbmc/games/controllers/types/PortNode.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 "ControllerNode.h" -#include "games/controllers/ControllerTypes.h" - -#include -#include - -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; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/windows/CMakeLists.txt b/xbmc/games/controllers/windows/CMakeLists.txt index c33aa5bbdc..72c815474c 100644 --- a/xbmc/games/controllers/windows/CMakeLists.txt +++ b/xbmc/games/controllers/windows/CMakeLists.txt @@ -1,25 +1,15 @@ -set(SOURCES ControllerInstaller.cpp - ControllerSelect.cpp - GUIConfigurationWizard.cpp +set(SOURCES GUIConfigurationWizard.cpp GUIControllerList.cpp GUIControllerWindow.cpp GUIFeatureList.cpp - GUIPortList.cpp - GUIPortWindow.cpp ) -set(HEADERS ControllerInstaller.h - ControllerSelect.cpp - GUIConfigurationWizard.h +set(HEADERS GUIConfigurationWizard.h GUIControllerDefines.h GUIControllerList.h GUIControllerWindow.h GUIFeatureList.h - GUIPortDefines.h - GUIPortList.h - GUIPortWindow.h IConfigurationWindow.h - IPortList.h ) core_add_library(games_controller_windows) diff --git a/xbmc/games/controllers/windows/ControllerInstaller.cpp b/xbmc/games/controllers/windows/ControllerInstaller.cpp deleted file mode 100644 index 26cf854749..0000000000 --- a/xbmc/games/controllers/windows/ControllerInstaller.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 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 "ControllerInstaller.h" - -#include "FileItem.h" -#include "ServiceBroker.h" -#include "addons/Addon.h" -#include "addons/AddonInstaller.h" -#include "addons/AddonManager.h" -#include "dialogs/GUIDialogProgress.h" -#include "dialogs/GUIDialogSelect.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIWindowManager.h" -#include "guilib/LocalizeStrings.h" -#include "guilib/WindowIDs.h" -#include "messaging/helpers/DialogOKHelper.h" -#include "utils/StringUtils.h" -#include "utils/log.h" - -using namespace KODI; -using namespace GAME; - -CControllerInstaller::CControllerInstaller() : CThread("ControllerInstaller") -{ -} - -void CControllerInstaller::Process() -{ - CGUIComponent* gui = CServiceBroker::GetGUI(); - if (gui == nullptr) - return; - - CGUIWindowManager& windowManager = gui->GetWindowManager(); - - auto pSelectDialog = windowManager.GetWindow(WINDOW_DIALOG_SELECT); - if (pSelectDialog == nullptr) - return; - - auto pProgressDialog = windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); - if (pProgressDialog == nullptr) - return; - - ADDON::VECADDONS installableAddons; - CServiceBroker::GetAddonMgr().GetInstallableAddons(installableAddons, - ADDON::ADDON_GAME_CONTROLLER); - if (installableAddons.empty()) - { - // "Controller profiles" - // "All available controller profiles are installed." - MESSAGING::HELPERS::ShowOKDialogText(CVariant{35050}, CVariant{35062}); - return; - } - - CLog::Log(LOGDEBUG, "Controller installer: Found {} controller add-ons", - installableAddons.size()); - - CFileItemList items; - for (const auto& addon : installableAddons) - { - CFileItemPtr item(new CFileItem(addon->Name())); - item->SetArt("icon", addon->Icon()); - items.Add(std::move(item)); - } - - pSelectDialog->Reset(); - pSelectDialog->SetHeading(39020); // "The following additional add-ons will be installed" - pSelectDialog->SetUseDetails(true); - pSelectDialog->EnableButton(true, 186); // "OK"" - for (const auto& it : items) - pSelectDialog->Add(*it); - pSelectDialog->Open(); - - if (!pSelectDialog->IsButtonPressed()) - { - CLog::Log(LOGDEBUG, "Controller installer: User cancelled installation dialog"); - return; - } - - CLog::Log(LOGDEBUG, "Controller installer: Installing {} controller add-ons", - installableAddons.size()); - - pProgressDialog->SetHeading(CVariant{24086}); // "Installing add-on..." - pProgressDialog->SetLine(0, CVariant{""}); - pProgressDialog->SetLine(1, CVariant{""}); - pProgressDialog->SetLine(2, CVariant{""}); - - pProgressDialog->Open(); - - unsigned int installedCount = 0; - while (installedCount < installableAddons.size()) - { - const auto& addon = installableAddons[installedCount]; - - // Set dialog text - const std::string& progressTemplate = g_localizeStrings.Get(24057); // "Installing {0:s}..." - const std::string progressText = StringUtils::Format(progressTemplate, addon->Name()); - pProgressDialog->SetLine(0, CVariant{progressText}); - - // Set dialog percentage - const unsigned int percentage = - 100 * (installedCount + 1) / static_cast(installableAddons.size()); - pProgressDialog->SetPercentage(percentage); - - if (!ADDON::CAddonInstaller::GetInstance().InstallOrUpdate( - addon->ID(), ADDON::BackgroundJob::CHOICE_NO, ADDON::ModalJob::CHOICE_NO)) - { - CLog::Log(LOGERROR, "Controller installer: Failed to install {}", addon->ID()); - // "Error" - // "Failed to install add-on." - MESSAGING::HELPERS::ShowOKDialogText(257, 35256); - break; - } - - if (pProgressDialog->IsCanceled()) - { - CLog::Log(LOGDEBUG, "Controller installer: User cancelled add-on installation"); - break; - } - - if (windowManager.GetActiveWindowOrDialog() != WINDOW_DIALOG_PROGRESS) - { - CLog::Log(LOGDEBUG, "Controller installer: Progress dialog is hidden, cancelling"); - break; - } - - installedCount++; - } - - CLog::Log(LOGDEBUG, "Controller window: Installed {} controller add-ons", installedCount); - pProgressDialog->Close(); -} diff --git a/xbmc/games/controllers/windows/ControllerInstaller.h b/xbmc/games/controllers/windows/ControllerInstaller.h deleted file mode 100644 index 9863e2754b..0000000000 --- a/xbmc/games/controllers/windows/ControllerInstaller.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 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 "threads/Thread.h" - -namespace KODI -{ -namespace GAME -{ -class CControllerInstaller : public CThread -{ -public: - CControllerInstaller(); - ~CControllerInstaller() override = default; - -protected: - // implementation of CThread - void Process() override; -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/windows/ControllerSelect.cpp b/xbmc/games/controllers/windows/ControllerSelect.cpp deleted file mode 100644 index badfd3b8c8..0000000000 --- a/xbmc/games/controllers/windows/ControllerSelect.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 "ControllerSelect.h" - -#include "FileItem.h" -#include "ServiceBroker.h" -#include "dialogs/GUIDialogSelect.h" -#include "games/controllers/Controller.h" -#include "games/controllers/ControllerLayout.h" -#include "games/controllers/ControllerTypes.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIWindowManager.h" -#include "guilib/LocalizeStrings.h" -#include "guilib/WindowIDs.h" -#include "utils/Variant.h" -#include "utils/log.h" - -using namespace KODI; -using namespace GAME; - -CControllerSelect::CControllerSelect() : CThread("ControllerSelect") -{ -} - -CControllerSelect::~CControllerSelect() = default; - -void CControllerSelect::Initialize(ControllerVector controllers, - ControllerPtr defaultController, - bool showDisconnect, - const std::function& callback) -{ - // Validate parameters - if (!callback) - return; - - // Initialize state - m_controllers = std::move(controllers); - m_defaultController = std::move(defaultController); - m_showDisconnect = showDisconnect; - m_callback = callback; - - // Create thread - Create(false); -} - -void CControllerSelect::Deinitialize() -{ - // Stop thread - StopThread(true); - - // Reset state - m_controllers.clear(); - m_defaultController.reset(); - m_showDisconnect = true; - m_callback = nullptr; -} - -void CControllerSelect::Process() -{ - // Select first controller by default - unsigned int initialSelected = 0; - - CGUIComponent* gui = CServiceBroker::GetGUI(); - if (gui == nullptr) - return; - - CGUIWindowManager& windowManager = gui->GetWindowManager(); - - auto pSelectDialog = windowManager.GetWindow(WINDOW_DIALOG_SELECT); - if (pSelectDialog == nullptr) - return; - - CLog::Log(LOGDEBUG, "Controller select: Showing dialog for {} controllers", m_controllers.size()); - - CFileItemList items; - for (const ControllerPtr& controller : m_controllers) - { - CFileItemPtr item(new CFileItem(controller->Layout().Label())); - item->SetArt("icon", controller->Layout().ImagePath()); - items.Add(std::move(item)); - - // Check if a specified controller should be selected by default - if (m_defaultController && m_defaultController->ID() == controller->ID()) - initialSelected = items.Size() - 1; - } - - if (m_showDisconnect) - { - // Add a button to disconnect the port - CFileItemPtr item(new CFileItem(g_localizeStrings.Get(13298))); // "Disconnected" - item->SetArt("icon", "DefaultAddonNone.png"); - items.Add(std::move(item)); - - // Check if the disconnect button should be selected by default - if (!m_defaultController) - initialSelected = items.Size() - 1; - } - - pSelectDialog->Reset(); - pSelectDialog->SetHeading(CVariant{35113}); // "Select a Controller" - pSelectDialog->SetUseDetails(true); - pSelectDialog->EnableButton(false, 186); // "OK"" - pSelectDialog->SetButtonFocus(false); - for (const auto& it : items) - pSelectDialog->Add(*it); - pSelectDialog->SetSelected(static_cast(initialSelected)); - pSelectDialog->Open(); - - // If the thread was stopped, exit early - if (m_bStop) - return; - - if (pSelectDialog->IsConfirmed()) - { - ControllerPtr resultController; - - const int selected = pSelectDialog->GetSelectedItem(); - if (0 <= selected && selected < static_cast(m_controllers.size())) - resultController = m_controllers.at(selected); - - // Fire a callback with the result - m_callback(resultController); - } -} diff --git a/xbmc/games/controllers/windows/ControllerSelect.h b/xbmc/games/controllers/windows/ControllerSelect.h deleted file mode 100644 index 83a1b416ce..0000000000 --- a/xbmc/games/controllers/windows/ControllerSelect.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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/ControllerTypes.h" -#include "threads/Thread.h" - -#include - -namespace KODI -{ -namespace GAME -{ -class CControllerSelect : public CThread -{ -public: - CControllerSelect(); - ~CControllerSelect() override; - - void Initialize(ControllerVector controllers, - ControllerPtr defaultController, - bool showDisconnect, - const std::function& callback); - void Deinitialize(); - -protected: - // Implementation of CThread - void Process() override; - -private: - // State parameters - ControllerVector m_controllers; - ControllerPtr m_defaultController; - bool m_showDisconnect = true; - std::function m_callback; -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/windows/GUIControllerWindow.cpp b/xbmc/games/controllers/windows/GUIControllerWindow.cpp index d2570d001e..391ec1a502 100644 --- a/xbmc/games/controllers/windows/GUIControllerWindow.cpp +++ b/xbmc/games/controllers/windows/GUIControllerWindow.cpp @@ -8,7 +8,6 @@ #include "GUIControllerWindow.h" -#include "ControllerInstaller.h" #include "GUIControllerDefines.h" #include "GUIControllerList.h" #include "GUIFeatureList.h" @@ -19,6 +18,7 @@ #include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" #include "cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h" #include "games/addons/GameClient.h" +#include "games/controllers/dialogs/ControllerInstaller.h" #include "games/controllers/dialogs/GUIDialogIgnoreInput.h" #include "guilib/GUIButtonControl.h" #include "guilib/GUIControl.h" diff --git a/xbmc/games/controllers/windows/GUIPortDefines.h b/xbmc/games/controllers/windows/GUIPortDefines.h deleted file mode 100644 index c9c255b3fe..0000000000 --- a/xbmc/games/controllers/windows/GUIPortDefines.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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/controllers/windows/GUIPortList.cpp b/xbmc/games/controllers/windows/GUIPortList.cpp deleted file mode 100644 index b488ea2f07..0000000000 --- a/xbmc/games/controllers/windows/GUIPortList.cpp +++ /dev/null @@ -1,327 +0,0 @@ -/* - * 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 "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/controllers/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()), - m_vecItems(std::make_unique()) -{ -} - -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(); - - 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(itemIndex)); - } -} - -void CGUIPortList::SetFocused() -{ - m_viewControl->SetFocused(); -} - -void CGUIPortList::OnSelect() -{ - const int itemIndex = m_viewControl->GetSelectedItem(); - if (itemIndex >= 0) - OnItemSelect(static_cast(itemIndex)); -} - -void CGUIPortList::ResetPorts() -{ - // Update the game client - m_gameClient->Input().ResetPorts(); - m_gameClient->Input().SavePorts(); - - // Refresh the GUI - using namespace MESSAGING; - CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); - CApplicationMessenger::GetInstance().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)) - { - using namespace MESSAGING; - CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); - msg.SetStringParam(event.id); - CApplicationMessenger::GetInstance().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(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 << 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(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) -{ - 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) -{ - // Translate parameter - const bool bConnected = static_cast(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 - using namespace MESSAGING; - CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); - CApplicationMessenger::GetInstance().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/controllers/windows/GUIPortList.h b/xbmc/games/controllers/windows/GUIPortList.h deleted file mode 100644 index 93a27b4ea8..0000000000 --- a/xbmc/games/controllers/windows/GUIPortList.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 "ControllerSelect.h" -#include "IPortList.h" -#include "addons/AddonEvents.h" -#include "games/GameTypes.h" -#include "games/controllers/ControllerTypes.h" -#include "games/controllers/types/ControllerTree.h" - -#include -#include -#include - -class CFileItemList; -class CGUIViewControl; -class CGUIWindow; - -namespace KODI -{ -namespace GAME -{ -class CGameClientTopology; -class CGUIPortWindow; - -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; - void 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 m_viewControl; - std::unique_ptr m_vecItems; - - // Game parameters - GameClientPtr m_gameClient; - std::map m_itemToAddress; // item index -> port address - std::map m_addressToItem; // port address -> item index -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/windows/GUIPortWindow.cpp b/xbmc/games/controllers/windows/GUIPortWindow.cpp deleted file mode 100644 index 47e7aa347b..0000000000 --- a/xbmc/games/controllers/windows/GUIPortWindow.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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 "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(*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::ADDON_GAMEDLL, - ADDON::OnlyEnabled::CHOICE_YES)) - gameClient = std::static_pointer_cast(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(); -} - -void CGUIPortWindow::OnClickAction() -{ - m_portList->OnSelect(); -} - -void CGUIPortWindow::ResetPorts() -{ - m_portList->ResetPorts(); -} - -void CGUIPortWindow::CloseDialog() -{ - Close(); -} diff --git a/xbmc/games/controllers/windows/GUIPortWindow.h b/xbmc/games/controllers/windows/GUIPortWindow.h deleted file mode 100644 index 1b529dccc9..0000000000 --- a/xbmc/games/controllers/windows/GUIPortWindow.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 - -namespace KODI -{ -namespace GAME -{ -class IControllerList; -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(); - void OnClickAction(); - - // Actions for the available buttons - void ResetPorts(); - void CloseDialog(); - - // GUI parameters - std::unique_ptr m_portList; - - // Game parameters - GameClientPtr m_gameClient; -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/controllers/windows/IPortList.h b/xbmc/games/controllers/windows/IPortList.h deleted file mode 100644 index 0956dcc3fa..0000000000 --- a/xbmc/games/controllers/windows/IPortList.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 - */ - 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 - */ - virtual void OnSelect() = 0; - - /*! - * \brief Reset the ports to their game add-on's default configuration - */ - virtual void ResetPorts() = 0; -}; -} // namespace GAME -} // namespace KODI diff --git a/xbmc/games/ports/input/CMakeLists.txt b/xbmc/games/ports/input/CMakeLists.txt new file mode 100644 index 0000000000..5b2b27f78d --- /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 0000000000..9c53c76199 --- /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 +#include + +using namespace KODI; +using namespace GAME; + +CPhysicalPort::CPhysicalPort(std::string portId, std::vector 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 0000000000..83fe3a503c --- /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 +#include + +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 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& 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 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 0000000000..af1652b048 --- /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 0000000000..4e296a376c --- /dev/null +++ b/xbmc/games/ports/input/PortInput.h @@ -0,0 +1,76 @@ +/* + * 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 + +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; + + // 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 m_appInput; + + // Prevents input falling through to Kodi when not handled by the game + std::unique_ptr 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 0000000000..8de43d02c8 --- /dev/null +++ b/xbmc/games/ports/input/PortManager.cpp @@ -0,0 +1,326 @@ +/* + * 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 "filesystem/File.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/URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include + +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() +{ + m_controllerTree.Clear(); + m_xmlPath.clear(); +} + +void CPortManager::SetControllerTree(const CControllerTree& controllerTree) +{ + m_controllerTree = controllerTree; +} + +void CPortManager::LoadXML() +{ + if (!XFILE::CFile::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::SaveXML() +{ + CXBMCTinyXML doc; + TiXmlElement node(XML_ROOT_PORTS); + + SerializePorts(node, m_controllerTree.GetPorts()); + + doc.InsertEndChild(node); + + doc.SaveFile(m_xmlPath); +} + +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(pPort, 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 0000000000..d5193b377c --- /dev/null +++ b/xbmc/games/ports/input/PortManager.h @@ -0,0 +1,74 @@ +/* + * 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" + +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 SaveXML(); + 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; +}; +} // 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 0000000000..735df429c4 --- /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 0000000000..009e37df10 --- /dev/null +++ b/xbmc/games/ports/types/PortNode.cpp @@ -0,0 +1,156 @@ +/* + * 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 +#include + +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_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 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 0000000000..660a7bcc8a --- /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 +#include + +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; +} // 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 0000000000..4cf07ea029 --- /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 0000000000..c9c255b3fe --- /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 0000000000..0797d619d4 --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortList.cpp @@ -0,0 +1,327 @@ +/* + * 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 "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()), + m_vecItems(std::make_unique()) +{ +} + +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(); + + 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(itemIndex)); + } +} + +void CGUIPortList::SetFocused() +{ + m_viewControl->SetFocused(); +} + +void CGUIPortList::OnSelect() +{ + const int itemIndex = m_viewControl->GetSelectedItem(); + if (itemIndex >= 0) + OnItemSelect(static_cast(itemIndex)); +} + +void CGUIPortList::ResetPorts() +{ + // Update the game client + m_gameClient->Input().ResetPorts(); + m_gameClient->Input().SavePorts(); + + // Refresh the GUI + using namespace MESSAGING; + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); + CApplicationMessenger::GetInstance().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)) + { + using namespace MESSAGING; + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); + msg.SetStringParam(event.id); + CApplicationMessenger::GetInstance().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(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 << 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(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) +{ + 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) +{ + // Translate parameter + const bool bConnected = static_cast(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 + using namespace MESSAGING; + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_PORT_LIST); + CApplicationMessenger::GetInstance().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 0000000000..fc6597ca61 --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortList.h @@ -0,0 +1,82 @@ +/* + * 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 +#include +#include + +class CFileItemList; +class CGUIViewControl; +class CGUIWindow; + +namespace KODI +{ +namespace GAME +{ +class CGameClientTopology; +class CGUIPortWindow; + +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; + void 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 m_viewControl; + std::unique_ptr m_vecItems; + + // Game parameters + GameClientPtr m_gameClient; + std::map m_itemToAddress; // item index -> port address + std::map 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 0000000000..47e7aa347b --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortWindow.cpp @@ -0,0 +1,182 @@ +/* + * 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 "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(*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::ADDON_GAMEDLL, + ADDON::OnlyEnabled::CHOICE_YES)) + gameClient = std::static_pointer_cast(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(); +} + +void CGUIPortWindow::OnClickAction() +{ + 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 0000000000..1b529dccc9 --- /dev/null +++ b/xbmc/games/ports/windows/GUIPortWindow.h @@ -0,0 +1,57 @@ +/* + * 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 + +namespace KODI +{ +namespace GAME +{ +class IControllerList; +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(); + void OnClickAction(); + + // Actions for the available buttons + void ResetPorts(); + void CloseDialog(); + + // GUI parameters + std::unique_ptr 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 0000000000..0956dcc3fa --- /dev/null +++ b/xbmc/games/ports/windows/IPortList.h @@ -0,0 +1,100 @@ +/* + * 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 + */ + 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 + */ + virtual void OnSelect() = 0; + + /*! + * \brief Reset the ports to their game add-on's default configuration + */ + virtual void ResetPorts() = 0; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index b55213a128..9696249a04 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -137,13 +137,13 @@ /* Game related include files */ #include "cores/RetroPlayer/guiwindows/GameWindowFullScreen.h" #include "games/controllers/windows/GUIControllerWindow.h" -#include "games/controllers/windows/GUIPortWindow.h" #include "games/dialogs/osd/DialogGameAdvancedSettings.h" #include "games/dialogs/osd/DialogGameOSD.h" #include "games/dialogs/osd/DialogGameStretchMode.h" #include "games/dialogs/osd/DialogGameVideoFilter.h" #include "games/dialogs/osd/DialogGameVideoRotation.h" #include "games/dialogs/osd/DialogGameVolume.h" +#include "games/ports/windows/GUIPortWindow.h" #include "games/windows/GUIWindowGames.h" using namespace KODI; -- cgit v1.2.3