summaryrefslogtreecommitdiffstats
path: root/xbmc/games/addons/input
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/games/addons/input')
-rw-r--r--xbmc/games/addons/input/CMakeLists.txt23
-rw-r--r--xbmc/games/addons/input/GameClientController.cpp138
-rw-r--r--xbmc/games/addons/input/GameClientController.h71
-rw-r--r--xbmc/games/addons/input/GameClientDevice.cpp73
-rw-r--r--xbmc/games/addons/input/GameClientDevice.h77
-rw-r--r--xbmc/games/addons/input/GameClientHardware.cpp25
-rw-r--r--xbmc/games/addons/input/GameClientHardware.h43
-rw-r--r--xbmc/games/addons/input/GameClientInput.cpp697
-rw-r--r--xbmc/games/addons/input/GameClientInput.h160
-rw-r--r--xbmc/games/addons/input/GameClientJoystick.cpp205
-rw-r--r--xbmc/games/addons/input/GameClientJoystick.h101
-rw-r--r--xbmc/games/addons/input/GameClientKeyboard.cpp101
-rw-r--r--xbmc/games/addons/input/GameClientKeyboard.h76
-rw-r--r--xbmc/games/addons/input/GameClientMouse.cpp106
-rw-r--r--xbmc/games/addons/input/GameClientMouse.h74
-rw-r--r--xbmc/games/addons/input/GameClientPort.cpp72
-rw-r--r--xbmc/games/addons/input/GameClientPort.h103
-rw-r--r--xbmc/games/addons/input/GameClientTopology.cpp110
-rw-r--r--xbmc/games/addons/input/GameClientTopology.h50
19 files changed, 2305 insertions, 0 deletions
diff --git a/xbmc/games/addons/input/CMakeLists.txt b/xbmc/games/addons/input/CMakeLists.txt
new file mode 100644
index 0000000..69cc342
--- /dev/null
+++ b/xbmc/games/addons/input/CMakeLists.txt
@@ -0,0 +1,23 @@
+set(SOURCES GameClientController.cpp
+ GameClientDevice.cpp
+ GameClientHardware.cpp
+ GameClientInput.cpp
+ GameClientJoystick.cpp
+ GameClientKeyboard.cpp
+ GameClientMouse.cpp
+ GameClientPort.cpp
+ GameClientTopology.cpp
+)
+
+set(HEADERS GameClientController.h
+ GameClientDevice.h
+ GameClientHardware.h
+ GameClientInput.h
+ GameClientJoystick.h
+ GameClientKeyboard.h
+ GameClientMouse.h
+ GameClientPort.h
+ GameClientTopology.h
+)
+
+core_add_library(gameinput)
diff --git a/xbmc/games/addons/input/GameClientController.cpp b/xbmc/games/addons/input/GameClientController.cpp
new file mode 100644
index 0000000..435e277
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientController.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * http://kodi.tv
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this Program; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "GameClientController.h"
+
+#include "GameClientInput.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerLayout.h"
+#include "games/controllers/input/PhysicalFeature.h"
+#include "games/controllers/input/PhysicalTopology.h"
+
+#include <algorithm>
+#include <vector>
+
+using namespace KODI;
+using namespace GAME;
+
+CGameClientController::CGameClientController(CGameClientInput& input, ControllerPtr controller)
+ : m_input(input), m_controller(std::move(controller)), m_controllerId(m_controller->ID())
+{
+ // Generate arrays of features
+ for (const CPhysicalFeature& feature : m_controller->Features())
+ {
+ // Skip feature if not supported by the game client
+ if (!m_input.HasFeature(m_controller->ID(), feature.Name()))
+ continue;
+
+ // Add feature to array of the appropriate type
+ switch (feature.Type())
+ {
+ case FEATURE_TYPE::SCALAR:
+ {
+ switch (feature.InputType())
+ {
+ case JOYSTICK::INPUT_TYPE::DIGITAL:
+ m_digitalButtons.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ case JOYSTICK::INPUT_TYPE::ANALOG:
+ m_analogButtons.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case FEATURE_TYPE::ANALOG_STICK:
+ m_analogSticks.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ case FEATURE_TYPE::ACCELEROMETER:
+ m_accelerometers.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ case FEATURE_TYPE::KEY:
+ m_keys.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ case FEATURE_TYPE::RELPOINTER:
+ m_relPointers.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ case FEATURE_TYPE::ABSPOINTER:
+ m_absPointers.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ case FEATURE_TYPE::MOTOR:
+ m_motors.emplace_back(const_cast<char*>(feature.Name().c_str()));
+ break;
+ default:
+ break;
+ }
+ }
+
+ //! @todo Sort vectors
+}
+
+game_controller_layout CGameClientController::TranslateController() const
+{
+ game_controller_layout controllerStruct{};
+
+ controllerStruct.controller_id = const_cast<char*>(m_controllerId.c_str());
+ controllerStruct.provides_input = m_controller->Layout().Topology().ProvidesInput();
+
+ if (!m_digitalButtons.empty())
+ {
+ controllerStruct.digital_buttons = const_cast<char**>(m_digitalButtons.data());
+ controllerStruct.digital_button_count = static_cast<unsigned int>(m_digitalButtons.size());
+ }
+ if (!m_analogButtons.empty())
+ {
+ controllerStruct.analog_buttons = const_cast<char**>(m_analogButtons.data());
+ controllerStruct.analog_button_count = static_cast<unsigned int>(m_analogButtons.size());
+ }
+ if (!m_analogSticks.empty())
+ {
+ controllerStruct.analog_sticks = const_cast<char**>(m_analogSticks.data());
+ controllerStruct.analog_stick_count = static_cast<unsigned int>(m_analogSticks.size());
+ }
+ if (!m_accelerometers.empty())
+ {
+ controllerStruct.accelerometers = const_cast<char**>(m_accelerometers.data());
+ controllerStruct.accelerometer_count = static_cast<unsigned int>(m_accelerometers.size());
+ }
+ if (!m_keys.empty())
+ {
+ controllerStruct.keys = const_cast<char**>(m_keys.data());
+ controllerStruct.key_count = static_cast<unsigned int>(m_keys.size());
+ }
+ if (!m_relPointers.empty())
+ {
+ controllerStruct.rel_pointers = const_cast<char**>(m_relPointers.data());
+ controllerStruct.rel_pointer_count = static_cast<unsigned int>(m_relPointers.size());
+ }
+ if (!m_absPointers.empty())
+ {
+ controllerStruct.abs_pointers = const_cast<char**>(m_absPointers.data());
+ controllerStruct.abs_pointer_count = static_cast<unsigned int>(m_absPointers.size());
+ }
+ if (!m_motors.empty())
+ {
+ controllerStruct.motors = const_cast<char**>(m_motors.data());
+ controllerStruct.motor_count = static_cast<unsigned int>(m_motors.size());
+ }
+
+ return controllerStruct;
+}
diff --git a/xbmc/games/addons/input/GameClientController.h b/xbmc/games/addons/input/GameClientController.h
new file mode 100644
index 0000000..5445868
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientController.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * http://kodi.tv
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this Program; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Game.h"
+#include "games/controllers/ControllerTypes.h"
+
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameClientInput;
+
+/*!
+ * \brief A container for the layout of a controller connected to a game
+ * client input port
+ */
+class CGameClientController
+{
+public:
+ /*!
+ * \brief Construct a controller layout
+ *
+ * \brief controller The controller add-on
+ */
+ CGameClientController(CGameClientInput& input, ControllerPtr controller);
+
+ /*!
+ * \brief Get a controller layout for the Game API
+ */
+ game_controller_layout TranslateController() const;
+
+private:
+ // Construction parameters
+ CGameClientInput& m_input;
+ const ControllerPtr m_controller;
+
+ // Buffer parameters
+ std::string m_controllerId;
+ std::vector<char*> m_digitalButtons;
+ std::vector<char*> m_analogButtons;
+ std::vector<char*> m_analogSticks;
+ std::vector<char*> m_accelerometers;
+ std::vector<char*> m_keys;
+ std::vector<char*> m_relPointers;
+ std::vector<char*> m_absPointers;
+ std::vector<char*> m_motors;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientDevice.cpp b/xbmc/games/addons/input/GameClientDevice.cpp
new file mode 100644
index 0000000..589fb66
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientDevice.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 "GameClientDevice.h"
+
+#include "GameClientPort.h"
+#include "ServiceBroker.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Game.h"
+#include "games/GameServices.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/input/PhysicalTopology.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+using namespace KODI;
+using namespace GAME;
+
+CGameClientDevice::CGameClientDevice(const game_input_device& device)
+ : m_controller(GetController(device.controller_id))
+{
+ if (m_controller && device.available_ports != nullptr)
+ {
+ // Look for matching ports. We enumerate in physical order because logical
+ // order can change per emulator.
+ for (const auto& physicalPort : m_controller->Topology().Ports())
+ {
+ for (unsigned int i = 0; i < device.port_count; i++)
+ {
+ const auto& logicalPort = device.available_ports[i];
+ if (logicalPort.port_id != nullptr && logicalPort.port_id == physicalPort.ID())
+ {
+ // Handle matching ports
+ AddPort(logicalPort, physicalPort);
+ break;
+ }
+ }
+ }
+ }
+}
+
+CGameClientDevice::CGameClientDevice(const ControllerPtr& controller) : m_controller(controller)
+{
+}
+
+CGameClientDevice::~CGameClientDevice() = default;
+
+void CGameClientDevice::AddPort(const game_input_port& logicalPort,
+ const CPhysicalPort& physicalPort)
+{
+ std::unique_ptr<CGameClientPort> port(new CGameClientPort(logicalPort, physicalPort));
+ m_ports.emplace_back(std::move(port));
+}
+
+ControllerPtr CGameClientDevice::GetController(const char* controllerId)
+{
+ ControllerPtr controller;
+
+ if (controllerId != nullptr)
+ {
+ controller = CServiceBroker::GetGameServices().GetController(controllerId);
+ if (!controller)
+ CLog::Log(LOGERROR, "Invalid controller ID: {}", controllerId);
+ }
+
+ return controller;
+}
diff --git a/xbmc/games/addons/input/GameClientDevice.h b/xbmc/games/addons/input/GameClientDevice.h
new file mode 100644
index 0000000..403d76c
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientDevice.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "games/GameTypes.h"
+#include "games/controllers/ControllerTypes.h"
+
+#include <string>
+
+struct game_input_device;
+struct game_input_port;
+
+namespace KODI
+{
+namespace GAME
+{
+class CPhysicalPort;
+
+/*!
+ * \ingroup games
+ * \brief Represents a device connected to a port
+ */
+class CGameClientDevice
+{
+public:
+ /*!
+ * \brief Construct a device
+ *
+ * \param device The device Game API struct
+ */
+ CGameClientDevice(const game_input_device& device);
+
+ /*!
+ * \brief Construct a device from a controller add-on
+ *
+ * \param controller The controller add-on
+ */
+ CGameClientDevice(const ControllerPtr& controller);
+
+ /*!
+ * \brief Destructor
+ */
+ ~CGameClientDevice();
+
+ /*!
+ * \brief The controller profile
+ */
+ const ControllerPtr& Controller() const { return m_controller; }
+
+ /*!
+ * \brief The ports on this device
+ */
+ const GameClientPortVec& Ports() const { return m_ports; }
+
+private:
+ /*!
+ * \brief Add a controller port
+ *
+ * \param logicalPort The logical port Game API struct
+ * \param physicalPort The physical port definition
+ */
+ void AddPort(const game_input_port& logicalPort, const CPhysicalPort& physicalPort);
+
+ // Helper function
+ static ControllerPtr GetController(const char* controllerId);
+
+ ControllerPtr m_controller;
+ GameClientPortVec m_ports;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientHardware.cpp b/xbmc/games/addons/input/GameClientHardware.cpp
new file mode 100644
index 0000000..bacff90
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientHardware.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015-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 "GameClientHardware.h"
+
+#include "games/addons/GameClient.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace GAME;
+
+CGameClientHardware::CGameClientHardware(CGameClient& gameClient) : m_gameClient(gameClient)
+{
+}
+
+void CGameClientHardware::OnResetButton()
+{
+ CLog::Log(LOGDEBUG, "{}: Sending hardware reset", m_gameClient.ID());
+ m_gameClient.Reset();
+}
diff --git a/xbmc/games/addons/input/GameClientHardware.h b/xbmc/games/addons/input/GameClientHardware.h
new file mode 100644
index 0000000..1ffc96b
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientHardware.h
@@ -0,0 +1,43 @@
+/*
+ * 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/hardware/IHardwareInput.h"
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameClient;
+
+/*!
+ * \ingroup games
+ * \brief Handles events for hardware such as reset buttons
+ */
+class CGameClientHardware : public HARDWARE::IHardwareInput
+{
+public:
+ /*!
+ * \brief Constructor
+ *
+ * \param gameClient The game client implementation
+ */
+ explicit CGameClientHardware(CGameClient& gameClient);
+
+ ~CGameClientHardware() override = default;
+
+ // Implementation of IHardwareInput
+ void OnResetButton() override;
+
+private:
+ // Construction parameter
+ CGameClient& m_gameClient;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientInput.cpp b/xbmc/games/addons/input/GameClientInput.cpp
new file mode 100644
index 0000000..5805cbf
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientInput.cpp
@@ -0,0 +1,697 @@
+/*
+ * 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 "GameClientInput.h"
+
+#include "GameClientController.h"
+#include "GameClientHardware.h"
+#include "GameClientJoystick.h"
+#include "GameClientKeyboard.h"
+#include "GameClientMouse.h"
+#include "GameClientPort.h"
+#include "GameClientTopology.h"
+#include "ServiceBroker.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Game.h"
+#include "games/GameServices.h"
+#include "games/addons/GameClient.h"
+#include "games/addons/GameClientCallbacks.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerLayout.h"
+#include "games/controllers/input/PhysicalTopology.h"
+#include "games/controllers/types/ControllerHub.h"
+#include "games/controllers/types/ControllerNode.h"
+#include "games/controllers/types/ControllerTree.h"
+#include "games/ports/input/PortManager.h"
+#include "games/ports/types/PortNode.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "peripherals/EventLockHandle.h"
+#include "peripherals/Peripherals.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace KODI;
+using namespace GAME;
+
+CGameClientInput::CGameClientInput(CGameClient& gameClient,
+ AddonInstance_Game& addonStruct,
+ CCriticalSection& clientAccess)
+ : CGameClientSubsystem(gameClient, addonStruct, clientAccess),
+ m_topology(new CGameClientTopology),
+ m_portManager(std::make_unique<CPortManager>())
+{
+}
+
+CGameClientInput::~CGameClientInput()
+{
+ Deinitialize();
+}
+
+void CGameClientInput::Initialize()
+{
+ LoadTopology();
+
+ // Send controller layouts to game client
+ SetControllerLayouts(m_topology->GetControllerTree().GetControllers());
+
+ // Reset ports to default state (first accepted controller is connected)
+ ActivateControllers(m_topology->GetControllerTree());
+
+ // Initialize the port manager
+ m_portManager->Initialize(m_gameClient.Profile());
+ m_portManager->SetControllerTree(m_topology->GetControllerTree());
+ m_portManager->LoadXML();
+}
+
+void CGameClientInput::Start(IGameInputCallback* input)
+{
+ m_inputCallback = input;
+
+ // Connect/disconnect active controllers
+ for (const CPortNode& port : GetActiveControllerTree().GetPorts())
+ {
+ if (port.IsConnected())
+ {
+ const ControllerPtr& activeController = port.GetActiveController().GetController();
+ if (activeController)
+ ConnectController(port.GetAddress(), activeController);
+ }
+ else
+ DisconnectController(port.GetAddress());
+ }
+
+ // Ensure hardware is open to receive events
+ m_hardware.reset(new CGameClientHardware(m_gameClient));
+
+ // Notify observers of the initial port configuration
+ NotifyObservers(ObservableMessageGamePortsChanged);
+}
+
+void CGameClientInput::Deinitialize()
+{
+ Stop();
+
+ m_topology->Clear();
+ m_controllerLayouts.clear();
+ m_portManager->Clear();
+}
+
+void CGameClientInput::Stop()
+{
+ m_hardware.reset();
+
+ CloseMouse();
+
+ CloseKeyboard();
+
+ PERIPHERALS::EventLockHandlePtr inputHandlingLock;
+ CloseJoysticks(inputHandlingLock);
+
+ // If a port was closed, then this blocks until all peripheral input has
+ // been handled
+ inputHandlingLock.reset();
+
+ m_inputCallback = nullptr;
+}
+
+bool CGameClientInput::HasFeature(const std::string& controllerId,
+ const std::string& featureName) const
+{
+ bool bHasFeature = false;
+
+ try
+ {
+ bHasFeature =
+ m_struct.toAddon->HasFeature(&m_struct, controllerId.c_str(), featureName.c_str());
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "GAME: {}: exception caught in HasFeature()", m_gameClient.ID());
+
+ // Fail gracefully
+ bHasFeature = true;
+ }
+
+ return bHasFeature;
+}
+
+bool CGameClientInput::AcceptsInput() const
+{
+ if (m_inputCallback != nullptr)
+ return m_inputCallback->AcceptsInput();
+
+ return false;
+}
+
+bool CGameClientInput::InputEvent(const game_input_event& event)
+{
+ bool bHandled = false;
+
+ try
+ {
+ bHandled = m_struct.toAddon->InputEvent(&m_struct, &event);
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "GAME: {}: exception caught in InputEvent()", m_gameClient.ID());
+ }
+
+ return bHandled;
+}
+
+void CGameClientInput::LoadTopology()
+{
+ game_input_topology* topologyStruct = nullptr;
+
+ if (m_gameClient.Initialized())
+ {
+ try
+ {
+ topologyStruct = m_struct.toAddon->GetTopology(&m_struct);
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("GetTopology()");
+ }
+ }
+
+ GameClientPortVec hardwarePorts;
+ int playerLimit = -1;
+
+ if (topologyStruct != nullptr)
+ {
+ //! @todo Guard against infinite loops provided by the game client
+
+ game_input_port* ports = topologyStruct->ports;
+ if (ports != nullptr)
+ {
+ for (unsigned int i = 0; i < topologyStruct->port_count; i++)
+ hardwarePorts.emplace_back(new CGameClientPort(ports[i]));
+ }
+
+ playerLimit = topologyStruct->player_limit;
+
+ try
+ {
+ m_struct.toAddon->FreeTopology(&m_struct, topologyStruct);
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("FreeTopology()");
+ }
+ }
+
+ // If no topology is available, create a default one with a single port that
+ // accepts all controllers imported by addon.xml
+ if (hardwarePorts.empty())
+ hardwarePorts.emplace_back(new CGameClientPort(GetControllers(m_gameClient)));
+
+ m_topology.reset(new CGameClientTopology(std::move(hardwarePorts), playerLimit));
+}
+
+void CGameClientInput::ActivateControllers(CControllerHub& hub)
+{
+ for (auto& port : hub.GetPorts())
+ {
+ if (port.GetCompatibleControllers().empty())
+ continue;
+
+ port.SetConnected(true);
+ port.SetActiveController(0);
+ for (auto& controller : port.GetCompatibleControllers())
+ ActivateControllers(controller.GetHub());
+ }
+}
+
+void CGameClientInput::SetControllerLayouts(const ControllerVector& controllers)
+{
+ if (controllers.empty())
+ return;
+
+ for (const auto& controller : controllers)
+ {
+ const std::string controllerId = controller->ID();
+ if (m_controllerLayouts.find(controllerId) == m_controllerLayouts.end())
+ m_controllerLayouts[controllerId].reset(new CGameClientController(*this, controller));
+ }
+
+ std::vector<game_controller_layout> controllerStructs;
+ for (const auto& it : m_controllerLayouts)
+ controllerStructs.emplace_back(it.second->TranslateController());
+
+ try
+ {
+ m_struct.toAddon->SetControllerLayouts(&m_struct, controllerStructs.data(),
+ static_cast<unsigned int>(controllerStructs.size()));
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("SetControllerLayouts()");
+ }
+}
+
+const CControllerTree& CGameClientInput::GetDefaultControllerTree() const
+{
+ return m_topology->GetControllerTree();
+}
+
+const CControllerTree& CGameClientInput::GetActiveControllerTree() const
+{
+ return m_portManager->GetControllerTree();
+}
+
+bool CGameClientInput::SupportsKeyboard() const
+{
+ const CControllerTree& controllers = GetDefaultControllerTree();
+
+ auto it =
+ std::find_if(controllers.GetPorts().begin(), controllers.GetPorts().end(),
+ [](const CPortNode& port) { return port.GetPortType() == PORT_TYPE::KEYBOARD; });
+
+ return it != controllers.GetPorts().end() && !it->GetCompatibleControllers().empty();
+}
+
+bool CGameClientInput::SupportsMouse() const
+{
+ const CControllerTree& controllers = GetDefaultControllerTree();
+
+ auto it =
+ std::find_if(controllers.GetPorts().begin(), controllers.GetPorts().end(),
+ [](const CPortNode& port) { return port.GetPortType() == PORT_TYPE::MOUSE; });
+
+ return it != controllers.GetPorts().end() && !it->GetCompatibleControllers().empty();
+}
+
+int CGameClientInput::GetPlayerLimit() const
+{
+ return m_topology->GetPlayerLimit();
+}
+
+bool CGameClientInput::ConnectController(const std::string& portAddress,
+ const ControllerPtr& controller)
+{
+ // Validate parameters
+ if (portAddress.empty() || !controller)
+ return false;
+
+ const CControllerTree& controllerTree = GetDefaultControllerTree();
+
+ // Validate controller
+ const CPortNode& port = controllerTree.GetPort(portAddress);
+ if (!port.IsControllerAccepted(portAddress, controller->ID()))
+ {
+ CLog::Log(LOGERROR, "Failed to open port: Invalid controller \"{}\" on port \"{}\"",
+ controller->ID(), portAddress);
+ return false;
+ }
+
+ const CPortNode& currentPort = GetActiveControllerTree().GetPort(portAddress);
+
+ // Close current ports if any are open
+ PERIPHERALS::EventLockHandlePtr inputHandlingLock;
+ CloseJoysticks(currentPort, inputHandlingLock);
+ inputHandlingLock.reset();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_clientAccess);
+
+ if (!m_gameClient.Initialized())
+ return false;
+
+ try
+ {
+ if (!m_struct.toAddon->ConnectController(&m_struct, true, portAddress.c_str(),
+ controller->ID().c_str()))
+ {
+ return false;
+ }
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("ConnectController()");
+ return false;
+ }
+ }
+
+ // Update port state
+ m_portManager->ConnectController(portAddress, true, controller->ID());
+ SetChanged();
+
+ // Update agent input
+ if (controller->Layout().Topology().ProvidesInput())
+ OpenJoystick(portAddress, controller);
+
+ bool bSuccess = true;
+
+ // If port is a multitap, we need to activate its children
+ const CPortNode& updatedPort = GetActiveControllerTree().GetPort(portAddress);
+ const PortVec& childPorts = updatedPort.GetActiveController().GetHub().GetPorts();
+ for (const CPortNode& childPort : childPorts)
+ {
+ const ControllerPtr& childController = childPort.GetActiveController().GetController();
+ if (childController)
+ bSuccess &= ConnectController(childPort.GetAddress(), childController);
+ }
+
+ return bSuccess;
+}
+
+bool CGameClientInput::DisconnectController(const std::string& portAddress)
+{
+ PERIPHERALS::EventLockHandlePtr inputHandlingLock;
+
+ // If port is a multitap, we need to deactivate its children
+ const CPortNode& currentPort = GetActiveControllerTree().GetPort(portAddress);
+ CloseJoysticks(currentPort, inputHandlingLock);
+
+ // If a port was closed, then destroying the lock will block until all
+ // peripheral input handling is complete to avoid invalidating the port's
+ // input handler
+ inputHandlingLock.reset();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_clientAccess);
+
+ if (!m_gameClient.Initialized())
+ return false;
+
+ try
+ {
+ if (!m_struct.toAddon->ConnectController(&m_struct, false, portAddress.c_str(), ""))
+ return false;
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("ConnectController()");
+ return false;
+ }
+ }
+
+ // Update port state
+ m_portManager->ConnectController(portAddress, false);
+ SetChanged();
+
+ // Update agent input
+ CloseJoystick(portAddress, inputHandlingLock);
+ inputHandlingLock.reset();
+
+ return true;
+}
+
+void CGameClientInput::SavePorts()
+{
+ // Save port state
+ m_portManager->SaveXMLAsync();
+
+ // Let the observers know that ports have changed
+ NotifyObservers(ObservableMessageGamePortsChanged);
+}
+
+void CGameClientInput::ResetPorts()
+{
+ const CControllerTree& controllerTree = GetDefaultControllerTree();
+ for (const CPortNode& port : controllerTree.GetPorts())
+ ConnectController(port.GetAddress(), port.GetActiveController().GetController());
+}
+
+bool CGameClientInput::HasAgent() const
+{
+ if (!m_joysticks.empty())
+ return true;
+
+ if (m_keyboard)
+ return true;
+
+ if (m_mouse)
+ return true;
+
+ return false;
+}
+
+bool CGameClientInput::OpenKeyboard(const ControllerPtr& controller,
+ const PERIPHERALS::PeripheralPtr& keyboard)
+{
+ using namespace JOYSTICK;
+
+ if (!controller)
+ {
+ CLog::Log(LOGERROR, "Failed to open keyboard, no controller given");
+ return false;
+ }
+
+ if (!keyboard)
+ return false;
+
+ bool bSuccess = false;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_clientAccess);
+
+ if (m_gameClient.Initialized())
+ {
+ try
+ {
+ bSuccess = m_struct.toAddon->EnableKeyboard(&m_struct, true, controller->ID().c_str());
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("EnableKeyboard()");
+ }
+ }
+ }
+
+ if (bSuccess)
+ {
+ m_keyboard =
+ std::make_unique<CGameClientKeyboard>(m_gameClient, controller->ID(), keyboard.get());
+ m_keyboard->SetSource(keyboard);
+ return true;
+ }
+
+ return false;
+}
+
+bool CGameClientInput::IsKeyboardOpen() const
+{
+ return static_cast<bool>(m_keyboard);
+}
+
+void CGameClientInput::CloseKeyboard()
+{
+ if (m_keyboard)
+ {
+ m_keyboard.reset();
+
+ std::unique_lock<CCriticalSection> lock(m_clientAccess);
+
+ if (m_gameClient.Initialized())
+ {
+ try
+ {
+ m_struct.toAddon->EnableKeyboard(&m_struct, false, "");
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("EnableKeyboard()");
+ }
+ }
+ }
+}
+
+bool CGameClientInput::OpenMouse(const ControllerPtr& controller,
+ const PERIPHERALS::PeripheralPtr& mouse)
+{
+ using namespace JOYSTICK;
+
+ if (!controller)
+ {
+ CLog::Log(LOGERROR, "Failed to open mouse, no controller given");
+ return false;
+ }
+
+ if (!mouse)
+ return false;
+
+ bool bSuccess = false;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_clientAccess);
+
+ if (m_gameClient.Initialized())
+ {
+ try
+ {
+ bSuccess = m_struct.toAddon->EnableMouse(&m_struct, true, controller->ID().c_str());
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("EnableMouse()");
+ }
+ }
+ }
+
+ if (bSuccess)
+ {
+ m_mouse = std::make_unique<CGameClientMouse>(m_gameClient, controller->ID(), mouse.get());
+ m_mouse->SetSource(mouse);
+ return true;
+ }
+
+ return false;
+}
+
+bool CGameClientInput::IsMouseOpen() const
+{
+ return static_cast<bool>(m_mouse);
+}
+
+void CGameClientInput::CloseMouse()
+{
+ if (m_mouse)
+ {
+ m_mouse.reset();
+
+ std::unique_lock<CCriticalSection> lock(m_clientAccess);
+
+ if (m_gameClient.Initialized())
+ {
+ try
+ {
+ m_struct.toAddon->EnableMouse(&m_struct, false, "");
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("EnableMouse()");
+ }
+ }
+ }
+}
+
+bool CGameClientInput::OpenJoystick(const std::string& portAddress, const ControllerPtr& controller)
+{
+ using namespace JOYSTICK;
+
+ if (!controller)
+ {
+ CLog::Log(LOGERROR, "Failed to open port \"{}\", no controller given", portAddress);
+ return false;
+ }
+
+ if (m_joysticks.find(portAddress) != m_joysticks.end())
+ {
+ CLog::Log(LOGERROR, "Failed to open port \"{}\", already open", portAddress);
+ return false;
+ }
+
+ m_joysticks[portAddress].reset(new CGameClientJoystick(m_gameClient, portAddress, controller));
+
+ return true;
+}
+
+void CGameClientInput::CloseJoysticks(PERIPHERALS::EventLockHandlePtr& inputHandlingLock)
+{
+ std::vector<std::string> portAddresses;
+ for (const auto& it : m_joysticks)
+ portAddresses.emplace_back(it.first);
+
+ for (const std::string& portAddress : portAddresses)
+ CloseJoystick(portAddress, inputHandlingLock);
+}
+
+void CGameClientInput::CloseJoysticks(const CPortNode& port,
+ PERIPHERALS::EventLockHandlePtr& inputHandlingLock)
+{
+ const PortVec& childPorts = port.GetActiveController().GetHub().GetPorts();
+ for (const CPortNode& childPort : childPorts)
+ CloseJoysticks(childPort, inputHandlingLock);
+
+ CloseJoystick(port.GetAddress(), inputHandlingLock);
+}
+
+void CGameClientInput::CloseJoystick(const std::string& portAddress,
+ PERIPHERALS::EventLockHandlePtr& inputHandlingLock)
+{
+ auto it = m_joysticks.find(portAddress);
+ if (it != m_joysticks.end())
+ {
+ if (!inputHandlingLock)
+ {
+ // An input handler is being destroyed. Disable input until the lock is
+ // released. Note: acquiring the lock blocks until all peripheral input
+ // has been handled.
+ inputHandlingLock = CServiceBroker::GetPeripherals().RegisterEventLock();
+ }
+
+ m_joysticks.erase(it);
+ }
+}
+
+void CGameClientInput::HardwareReset()
+{
+ if (m_hardware)
+ m_hardware->OnResetButton();
+}
+
+bool CGameClientInput::ReceiveInputEvent(const game_input_event& event)
+{
+ bool bHandled = false;
+
+ switch (event.type)
+ {
+ case GAME_INPUT_EVENT_MOTOR:
+ if (event.port_address != nullptr && event.feature_name != nullptr)
+ bHandled = SetRumble(event.port_address, event.feature_name, event.motor.magnitude);
+ break;
+ default:
+ break;
+ }
+
+ return bHandled;
+}
+
+bool CGameClientInput::SetRumble(const std::string& portAddress,
+ const std::string& feature,
+ float magnitude)
+{
+ bool bHandled = false;
+
+ auto it = m_joysticks.find(portAddress);
+ if (it != m_joysticks.end())
+ bHandled = it->second->SetRumble(feature, magnitude);
+
+ return bHandled;
+}
+
+ControllerVector CGameClientInput::GetControllers(const CGameClient& gameClient)
+{
+ using namespace ADDON;
+
+ ControllerVector controllers;
+
+ CGameServices& gameServices = CServiceBroker::GetGameServices();
+
+ const auto& dependencies = gameClient.GetDependencies();
+ for (auto it = dependencies.begin(); it != dependencies.end(); ++it)
+ {
+ ControllerPtr controller = gameServices.GetController(it->id);
+ if (controller)
+ controllers.push_back(controller);
+ }
+
+ if (controllers.empty())
+ {
+ // Use the default controller
+ ControllerPtr controller = gameServices.GetDefaultController();
+ if (controller)
+ controllers.push_back(controller);
+ }
+
+ return controllers;
+}
diff --git a/xbmc/games/addons/input/GameClientInput.h b/xbmc/games/addons/input/GameClientInput.h
new file mode 100644
index 0000000..be7e71c
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientInput.h
@@ -0,0 +1,160 @@
+/*
+ * 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 "games/addons/GameClientSubsystem.h"
+#include "games/controllers/ControllerTypes.h"
+#include "games/controllers/types/ControllerTree.h"
+#include "peripherals/PeripheralTypes.h"
+#include "utils/Observer.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+class CCriticalSection;
+struct game_input_event;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IInputProvider;
+}
+
+namespace GAME
+{
+class CGameClient;
+class CGameClientController;
+class CGameClientHardware;
+class CGameClientJoystick;
+class CGameClientKeyboard;
+class CGameClientMouse;
+class CGameClientTopology;
+class CPortManager;
+class IGameInputCallback;
+
+class CGameClientInput : protected CGameClientSubsystem, public Observable
+{
+public:
+ //! @todo de-duplicate
+ using PortAddress = std::string;
+ using JoystickMap = std::map<PortAddress, std::shared_ptr<CGameClientJoystick>>;
+
+ CGameClientInput(CGameClient& gameClient,
+ AddonInstance_Game& addonStruct,
+ CCriticalSection& clientAccess);
+ ~CGameClientInput() override;
+
+ void Initialize();
+ void Deinitialize();
+
+ void Start(IGameInputCallback* input);
+ void Stop();
+
+ // Input functions
+ bool HasFeature(const std::string& controllerId, const std::string& featureName) const;
+ bool AcceptsInput() const;
+ bool InputEvent(const game_input_event& event);
+
+ // Topology functions
+ const CControllerTree& GetDefaultControllerTree() const;
+ const CControllerTree& GetActiveControllerTree() const;
+ bool SupportsKeyboard() const;
+ bool SupportsMouse() const;
+ int GetPlayerLimit() const;
+ bool ConnectController(const std::string& portAddress, const ControllerPtr& controller);
+ bool DisconnectController(const std::string& portAddress);
+ void SavePorts();
+ void ResetPorts();
+
+ // Joystick functions
+ const JoystickMap& GetJoystickMap() const { return m_joysticks; }
+ void CloseJoysticks(PERIPHERALS::EventLockHandlePtr& inputHandlingLock);
+
+ // Keyboard functions
+ bool OpenKeyboard(const ControllerPtr& controller, const PERIPHERALS::PeripheralPtr& keyboard);
+ bool IsKeyboardOpen() const;
+ void CloseKeyboard();
+
+ // Mouse functions
+ bool OpenMouse(const ControllerPtr& controller, const PERIPHERALS::PeripheralPtr& mouse);
+ bool IsMouseOpen() const;
+ void CloseMouse();
+
+ // Agent functions
+ bool HasAgent() const;
+
+ // Hardware input functions
+ void HardwareReset();
+
+ // Input callbacks
+ bool ReceiveInputEvent(const game_input_event& eventStruct);
+
+private:
+ // Private input helpers
+ void LoadTopology();
+ void SetControllerLayouts(const ControllerVector& controllers);
+ bool OpenJoystick(const std::string& portAddress, const ControllerPtr& controller);
+ void CloseJoysticks(const CPortNode& port, PERIPHERALS::EventLockHandlePtr& inputHandlingLock);
+ void CloseJoystick(const std::string& portAddress,
+ PERIPHERALS::EventLockHandlePtr& inputHandlingLock);
+
+ // Private callback helpers
+ bool SetRumble(const std::string& portAddress, const std::string& feature, float magnitude);
+
+ // Helper functions
+ static ControllerVector GetControllers(const CGameClient& gameClient);
+ static void ActivateControllers(CControllerHub& hub);
+
+ // Input properties
+ IGameInputCallback* m_inputCallback = nullptr;
+ std::unique_ptr<CGameClientTopology> m_topology;
+ using ControllerLayoutMap = std::map<std::string, std::unique_ptr<CGameClientController>>;
+ ControllerLayoutMap m_controllerLayouts;
+
+ /*!
+ * \brief Map of port address to joystick handler
+ *
+ * The port address is a string that identifies the adress of the port.
+ *
+ * The joystick handler connects to joystick input of the game client.
+ *
+ * This property is always populated with the default joystick configuration
+ * (i.e. all ports are connected to the first controller they accept).
+ */
+ JoystickMap m_joysticks;
+
+ // TODO: Guard with a mutex
+ std::unique_ptr<CPortManager> m_portManager;
+
+ /*!
+ * \brief Keyboard handler
+ *
+ * This connects to the keyboard input of the game client.
+ */
+ std::unique_ptr<CGameClientKeyboard> m_keyboard;
+
+ /*!
+ * \brief Mouse handler
+ *
+ * This connects to the mouse input of the game client.
+ */
+ std::unique_ptr<CGameClientMouse> m_mouse;
+
+ /*!
+ * \brief Hardware input handler
+ *
+ * This connects to input from game console hardware belonging to the game
+ * client.
+ */
+ std::unique_ptr<CGameClientHardware> m_hardware;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientJoystick.cpp b/xbmc/games/addons/input/GameClientJoystick.cpp
new file mode 100644
index 0000000..d4f083b
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientJoystick.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2015-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 "GameClientJoystick.h"
+
+#include "GameClientInput.h"
+#include "GameClientTopology.h"
+#include "games/addons/GameClient.h"
+#include "games/controllers/Controller.h"
+#include "games/ports/input/PortInput.h"
+#include "input/joysticks/interfaces/IInputReceiver.h"
+#include "peripherals/devices/Peripheral.h"
+#include "utils/log.h"
+
+#include <assert.h>
+
+using namespace KODI;
+using namespace GAME;
+
+CGameClientJoystick::CGameClientJoystick(CGameClient& gameClient,
+ const std::string& portAddress,
+ const ControllerPtr& controller)
+ : m_gameClient(gameClient),
+ m_portAddress(portAddress),
+ m_controller(controller),
+ m_portInput(new CPortInput(this))
+{
+ assert(m_controller.get() != NULL);
+}
+
+CGameClientJoystick::~CGameClientJoystick() = default;
+
+void CGameClientJoystick::RegisterInput(JOYSTICK::IInputProvider* inputProvider)
+{
+ m_portInput->RegisterInput(inputProvider);
+}
+
+void CGameClientJoystick::UnregisterInput(JOYSTICK::IInputProvider* inputProvider)
+{
+ m_portInput->UnregisterInput(inputProvider);
+}
+
+std::string CGameClientJoystick::ControllerID(void) const
+{
+ return m_controller->ID();
+}
+
+bool CGameClientJoystick::HasFeature(const std::string& feature) const
+{
+ return m_gameClient.Input().HasFeature(m_controller->ID(), feature);
+}
+
+bool CGameClientJoystick::AcceptsInput(const std::string& feature) const
+{
+ return m_gameClient.Input().AcceptsInput();
+}
+
+bool CGameClientJoystick::OnButtonPress(const std::string& feature, bool bPressed)
+{
+ game_input_event event;
+
+ std::string controllerId = m_controller->ID();
+
+ event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON;
+ event.controller_id = controllerId.c_str();
+ event.port_type = GAME_PORT_CONTROLLER;
+ event.port_address = m_portAddress.c_str();
+ event.feature_name = feature.c_str();
+ event.digital_button.pressed = bPressed;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+bool CGameClientJoystick::OnButtonMotion(const std::string& feature,
+ float magnitude,
+ unsigned int motionTimeMs)
+{
+ game_input_event event;
+
+ std::string controllerId = m_controller->ID();
+
+ event.type = GAME_INPUT_EVENT_ANALOG_BUTTON;
+ event.controller_id = controllerId.c_str();
+ event.port_type = GAME_PORT_CONTROLLER;
+ event.port_address = m_portAddress.c_str();
+ event.feature_name = feature.c_str();
+ event.analog_button.magnitude = magnitude;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+bool CGameClientJoystick::OnAnalogStickMotion(const std::string& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs)
+{
+ game_input_event event;
+
+ std::string controllerId = m_controller->ID();
+
+ event.type = GAME_INPUT_EVENT_ANALOG_STICK;
+ event.controller_id = controllerId.c_str();
+ event.port_type = GAME_PORT_CONTROLLER;
+ event.port_address = m_portAddress.c_str();
+ event.feature_name = feature.c_str();
+ event.analog_stick.x = x;
+ event.analog_stick.y = y;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+bool CGameClientJoystick::OnAccelerometerMotion(const std::string& feature,
+ float x,
+ float y,
+ float z)
+{
+ game_input_event event;
+
+ std::string controllerId = m_controller->ID();
+
+ event.type = GAME_INPUT_EVENT_ACCELEROMETER;
+ event.controller_id = controllerId.c_str();
+ event.port_type = GAME_PORT_CONTROLLER;
+ event.port_address = m_portAddress.c_str();
+ event.feature_name = feature.c_str();
+ event.accelerometer.x = x;
+ event.accelerometer.y = y;
+ event.accelerometer.z = z;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+bool CGameClientJoystick::OnWheelMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ game_input_event event;
+
+ std::string controllerId = m_controller->ID();
+
+ event.type = GAME_INPUT_EVENT_AXIS;
+ event.controller_id = controllerId.c_str();
+ event.port_type = GAME_PORT_CONTROLLER;
+ event.port_address = m_portAddress.c_str();
+ event.feature_name = feature.c_str();
+ event.axis.position = position;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+bool CGameClientJoystick::OnThrottleMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ game_input_event event;
+
+ std::string controllerId = m_controller->ID();
+
+ event.type = GAME_INPUT_EVENT_AXIS;
+ event.controller_id = controllerId.c_str();
+ event.port_type = GAME_PORT_CONTROLLER;
+ event.port_address = m_portAddress.c_str();
+ event.feature_name = feature.c_str();
+ event.axis.position = position;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+std::string CGameClientJoystick::GetControllerAddress() const
+{
+ return CGameClientTopology::MakeAddress(m_portAddress, m_controller->ID());
+}
+
+std::string CGameClientJoystick::GetSourceLocation() const
+{
+ if (m_sourcePeripheral)
+ return m_sourcePeripheral->Location();
+
+ return "";
+}
+
+void CGameClientJoystick::SetSource(PERIPHERALS::PeripheralPtr sourcePeripheral)
+{
+ m_sourcePeripheral = std::move(sourcePeripheral);
+}
+
+void CGameClientJoystick::ClearSource()
+{
+ m_sourcePeripheral.reset();
+}
+
+bool CGameClientJoystick::SetRumble(const std::string& feature, float magnitude)
+{
+ bool bHandled = false;
+
+ if (InputReceiver())
+ bHandled = InputReceiver()->SetRumbleState(feature, magnitude);
+
+ return bHandled;
+}
diff --git a/xbmc/games/addons/input/GameClientJoystick.h b/xbmc/games/addons/input/GameClientJoystick.h
new file mode 100644
index 0000000..cfb1709
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientJoystick.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015-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 "games/controllers/ControllerTypes.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "peripherals/PeripheralTypes.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IInputProvider;
+}
+
+namespace GAME
+{
+class CGameClient;
+class CPortInput;
+
+/*!
+ * \ingroup games
+ * \brief Handles game controller events for games.
+ *
+ * Listens to game controller events and forwards them to the games (as game_input_event).
+ */
+class CGameClientJoystick : public JOYSTICK::IInputHandler
+{
+public:
+ /*!
+ * \brief Constructor.
+ * \param addon The game client implementation.
+ * \param port The port this game controller is associated with.
+ * \param controller The game controller which is used (for controller mapping).
+ * \param dllStruct The emulator or game to which the events are sent.
+ */
+ CGameClientJoystick(CGameClient& addon,
+ const std::string& portAddress,
+ const ControllerPtr& controller);
+
+ ~CGameClientJoystick() override;
+
+ void RegisterInput(JOYSTICK::IInputProvider* inputProvider);
+ void UnregisterInput(JOYSTICK::IInputProvider* inputProvider);
+
+ // Implementation of IInputHandler
+ std::string ControllerID() const override;
+ bool HasFeature(const std::string& feature) const override;
+ bool AcceptsInput(const std::string& feature) const override;
+ bool OnButtonPress(const std::string& feature, bool bPressed) override;
+ void OnButtonHold(const std::string& feature, unsigned int holdTimeMs) override {}
+ bool OnButtonMotion(const std::string& feature,
+ float magnitude,
+ unsigned int motionTimeMs) override;
+ bool OnAnalogStickMotion(const std::string& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs) override;
+ bool OnAccelerometerMotion(const std::string& feature, float x, float y, float z) override;
+ bool OnWheelMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ bool OnThrottleMotion(const std::string& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ void OnInputFrame() override {}
+
+ // Input accessors
+ const std::string& GetPortAddress() const { return m_portAddress; }
+ const ControllerPtr& GetController() const { return m_controller; }
+ std::string GetControllerAddress() const;
+ const PERIPHERALS::PeripheralPtr& GetSource() const { return m_sourcePeripheral; }
+ std::string GetSourceLocation() const;
+
+ // Input mutators
+ void SetSource(PERIPHERALS::PeripheralPtr sourcePeripheral);
+ void ClearSource();
+
+ // Input handlers
+ bool SetRumble(const std::string& feature, float magnitude);
+
+private:
+ // Construction parameters
+ CGameClient& m_gameClient;
+ const std::string m_portAddress;
+ const ControllerPtr m_controller;
+
+ // Input parameters
+ std::unique_ptr<CPortInput> m_portInput;
+ PERIPHERALS::PeripheralPtr m_sourcePeripheral;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientKeyboard.cpp b/xbmc/games/addons/input/GameClientKeyboard.cpp
new file mode 100644
index 0000000..e6f48c9
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientKeyboard.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015-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 "GameClientKeyboard.h"
+
+#include "GameClientInput.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Game.h"
+#include "games/addons/GameClient.h"
+#include "games/addons/GameClientTranslator.h"
+#include "input/keyboard/interfaces/IKeyboardInputProvider.h"
+#include "utils/log.h"
+
+#include <utility>
+
+using namespace KODI;
+using namespace GAME;
+
+#define BUTTON_INDEX_MASK 0x01ff
+
+CGameClientKeyboard::CGameClientKeyboard(CGameClient& gameClient,
+ std::string controllerId,
+ KEYBOARD::IKeyboardInputProvider* inputProvider)
+ : m_gameClient(gameClient),
+ m_controllerId(std::move(controllerId)),
+ m_inputProvider(inputProvider)
+{
+ m_inputProvider->RegisterKeyboardHandler(this, false);
+}
+
+CGameClientKeyboard::~CGameClientKeyboard()
+{
+ m_inputProvider->UnregisterKeyboardHandler(this);
+}
+
+std::string CGameClientKeyboard::ControllerID() const
+{
+ return m_controllerId;
+}
+
+bool CGameClientKeyboard::HasKey(const KEYBOARD::KeyName& key) const
+{
+ return m_gameClient.Input().HasFeature(ControllerID(), key);
+}
+
+bool CGameClientKeyboard::OnKeyPress(const KEYBOARD::KeyName& key,
+ KEYBOARD::Modifier mod,
+ uint32_t unicode)
+{
+ // Only allow activated input in fullscreen game
+ if (!m_gameClient.Input().AcceptsInput())
+ {
+ CLog::Log(LOGDEBUG, "GAME: key press ignored, not in fullscreen game");
+ return false;
+ }
+
+ game_input_event event;
+
+ event.type = GAME_INPUT_EVENT_KEY;
+ event.controller_id = m_controllerId.c_str();
+ event.port_type = GAME_PORT_KEYBOARD;
+ event.port_address = ""; // Not used
+ event.feature_name = key.c_str();
+ event.key.pressed = true;
+ event.key.unicode = unicode;
+ event.key.modifiers = CGameClientTranslator::GetModifiers(mod);
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+void CGameClientKeyboard::OnKeyRelease(const KEYBOARD::KeyName& key,
+ KEYBOARD::Modifier mod,
+ uint32_t unicode)
+{
+ game_input_event event;
+
+ event.type = GAME_INPUT_EVENT_KEY;
+ event.controller_id = m_controllerId.c_str();
+ event.port_type = GAME_PORT_KEYBOARD;
+ event.port_address = ""; // Not used
+ event.feature_name = key.c_str();
+ event.key.pressed = false;
+ event.key.unicode = unicode;
+ event.key.modifiers = CGameClientTranslator::GetModifiers(mod);
+
+ m_gameClient.Input().InputEvent(event);
+}
+
+void CGameClientKeyboard::SetSource(PERIPHERALS::PeripheralPtr sourcePeripheral)
+{
+ m_sourcePeripheral = std::move(sourcePeripheral);
+}
+
+void CGameClientKeyboard::ClearSource()
+{
+ m_sourcePeripheral.reset();
+}
diff --git a/xbmc/games/addons/input/GameClientKeyboard.h b/xbmc/games/addons/input/GameClientKeyboard.h
new file mode 100644
index 0000000..a6103f2
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientKeyboard.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015-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/keyboard/interfaces/IKeyboardInputHandler.h"
+#include "peripherals/PeripheralTypes.h"
+
+namespace KODI
+{
+namespace KEYBOARD
+{
+class IKeyboardInputProvider;
+}
+
+namespace GAME
+{
+class CGameClient;
+
+/*!
+ * \ingroup games
+ * \brief Handles keyboard events for games.
+ *
+ * Listens to keyboard events and forwards them to the games (as game_input_event).
+ */
+class CGameClientKeyboard : public KEYBOARD::IKeyboardInputHandler
+{
+public:
+ /*!
+ * \brief Constructor registers for keyboard events at CInputManager.
+ * \param gameClient The game client implementation.
+ * \param controllerId The controller profile used for input
+ * \param dllStruct The emulator or game to which the events are sent.
+ * \param inputProvider The interface providing us with keyboard input.
+ */
+ CGameClientKeyboard(CGameClient& gameClient,
+ std::string controllerId,
+ KEYBOARD::IKeyboardInputProvider* inputProvider);
+
+ /*!
+ * \brief Destructor unregisters from keyboard events from CInputManager.
+ */
+ ~CGameClientKeyboard() override;
+
+ // implementation of IKeyboardInputHandler
+ std::string ControllerID() const override;
+ bool HasKey(const KEYBOARD::KeyName& key) const override;
+ bool OnKeyPress(const KEYBOARD::KeyName& key, KEYBOARD::Modifier mod, uint32_t unicode) override;
+ void OnKeyRelease(const KEYBOARD::KeyName& key,
+ KEYBOARD::Modifier mod,
+ uint32_t unicode) override;
+
+ // Input accessors
+ const std::string& GetControllerID() const { return m_controllerId; }
+ const PERIPHERALS::PeripheralPtr& GetSource() const { return m_sourcePeripheral; }
+
+ // Input mutators
+ void SetSource(PERIPHERALS::PeripheralPtr sourcePeripheral);
+ void ClearSource();
+
+private:
+ // Construction parameters
+ CGameClient& m_gameClient;
+ const std::string m_controllerId;
+ KEYBOARD::IKeyboardInputProvider* const m_inputProvider;
+
+ // Input parameters
+ PERIPHERALS::PeripheralPtr m_sourcePeripheral;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientMouse.cpp b/xbmc/games/addons/input/GameClientMouse.cpp
new file mode 100644
index 0000000..5cc9676
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientMouse.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015-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 "GameClientMouse.h"
+
+#include "GameClientInput.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Game.h"
+#include "games/addons/GameClient.h"
+#include "input/mouse/interfaces/IMouseInputProvider.h"
+
+#include <utility>
+
+using namespace KODI;
+using namespace GAME;
+
+CGameClientMouse::CGameClientMouse(CGameClient& gameClient,
+ std::string controllerId,
+ MOUSE::IMouseInputProvider* inputProvider)
+ : m_gameClient(gameClient),
+ m_controllerId(std::move(controllerId)),
+ m_inputProvider(inputProvider)
+{
+ inputProvider->RegisterMouseHandler(this, false);
+}
+
+CGameClientMouse::~CGameClientMouse()
+{
+ m_inputProvider->UnregisterMouseHandler(this);
+}
+
+std::string CGameClientMouse::ControllerID(void) const
+{
+ return m_controllerId;
+}
+
+bool CGameClientMouse::OnMotion(const std::string& relpointer, int dx, int dy)
+{
+ // Only allow activated input in fullscreen game
+ if (!m_gameClient.Input().AcceptsInput())
+ {
+ return false;
+ }
+
+ const std::string controllerId = ControllerID();
+
+ game_input_event event;
+
+ event.type = GAME_INPUT_EVENT_RELATIVE_POINTER;
+ event.controller_id = m_controllerId.c_str();
+ event.port_type = GAME_PORT_MOUSE;
+ event.port_address = ""; // Not used
+ event.feature_name = relpointer.c_str();
+ event.rel_pointer.x = dx;
+ event.rel_pointer.y = dy;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+bool CGameClientMouse::OnButtonPress(const std::string& button)
+{
+ // Only allow activated input in fullscreen game
+ if (!m_gameClient.Input().AcceptsInput())
+ {
+ return false;
+ }
+
+ game_input_event event;
+
+ event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON;
+ event.controller_id = m_controllerId.c_str();
+ event.port_type = GAME_PORT_MOUSE;
+ event.port_address = ""; // Not used
+ event.feature_name = button.c_str();
+ event.digital_button.pressed = true;
+
+ return m_gameClient.Input().InputEvent(event);
+}
+
+void CGameClientMouse::OnButtonRelease(const std::string& button)
+{
+ game_input_event event;
+
+ event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON;
+ event.controller_id = m_controllerId.c_str();
+ event.port_type = GAME_PORT_MOUSE;
+ event.port_address = ""; // Not used
+ event.feature_name = button.c_str();
+ event.digital_button.pressed = false;
+
+ m_gameClient.Input().InputEvent(event);
+}
+
+void CGameClientMouse::SetSource(PERIPHERALS::PeripheralPtr sourcePeripheral)
+{
+ m_sourcePeripheral = std::move(sourcePeripheral);
+}
+
+void CGameClientMouse::ClearSource()
+{
+ m_sourcePeripheral.reset();
+}
diff --git a/xbmc/games/addons/input/GameClientMouse.h b/xbmc/games/addons/input/GameClientMouse.h
new file mode 100644
index 0000000..4da592f
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientMouse.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015-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/mouse/interfaces/IMouseInputHandler.h"
+#include "peripherals/PeripheralTypes.h"
+
+namespace KODI
+{
+namespace MOUSE
+{
+class IMouseInputProvider;
+}
+
+namespace GAME
+{
+class CGameClient;
+
+/*!
+ * \ingroup games
+ * \brief Handles mouse events for games.
+ *
+ * Listens to mouse events and forwards them to the games (as game_input_event).
+ */
+class CGameClientMouse : public MOUSE::IMouseInputHandler
+{
+public:
+ /*!
+ * \brief Constructor registers for mouse events at CInputManager.
+ * \param gameClient The game client implementation.
+ * \param controllerId The controller profile used for input
+ * \param dllStruct The emulator or game to which the events are sent.
+ * \param inputProvider The interface providing us with mouse input.
+ */
+ CGameClientMouse(CGameClient& gameClient,
+ std::string controllerId,
+ MOUSE::IMouseInputProvider* inputProvider);
+
+ /*!
+ * \brief Destructor unregisters from mouse events from CInputManager.
+ */
+ ~CGameClientMouse() override;
+
+ // implementation of IMouseInputHandler
+ std::string ControllerID() const override;
+ bool OnMotion(const std::string& relpointer, int dx, int dy) override;
+ bool OnButtonPress(const std::string& button) override;
+ void OnButtonRelease(const std::string& button) override;
+
+ // Input accessors
+ const std::string& GetControllerID() const { return m_controllerId; }
+ const PERIPHERALS::PeripheralPtr& GetSource() const { return m_sourcePeripheral; }
+
+ // Input mutators
+ void SetSource(PERIPHERALS::PeripheralPtr sourcePeripheral);
+ void ClearSource();
+
+private:
+ // Construction parameters
+ CGameClient& m_gameClient;
+ const std::string m_controllerId;
+ MOUSE::IMouseInputProvider* const m_inputProvider;
+
+ // Input parameters
+ PERIPHERALS::PeripheralPtr m_sourcePeripheral;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientPort.cpp b/xbmc/games/addons/input/GameClientPort.cpp
new file mode 100644
index 0000000..c582fff
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientPort.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "GameClientPort.h"
+
+#include "GameClientDevice.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Game.h"
+#include "games/addons/GameClientTranslator.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/input/PhysicalTopology.h"
+#include "utils/StringUtils.h"
+
+#include <algorithm>
+
+using namespace KODI;
+using namespace GAME;
+
+CGameClientPort::CGameClientPort(const game_input_port& port)
+ : m_type(CGameClientTranslator::TranslatePortType(port.type)),
+ m_portId(port.port_id ? port.port_id : ""),
+ m_forceConnected(port.force_connected)
+{
+ if (port.accepted_devices != nullptr)
+ {
+ for (unsigned int i = 0; i < port.device_count; i++)
+ {
+ std::unique_ptr<CGameClientDevice> device(new CGameClientDevice(port.accepted_devices[i]));
+
+ if (device->Controller() != CController::EmptyPtr)
+ m_acceptedDevices.emplace_back(std::move(device));
+ }
+ }
+}
+
+CGameClientPort::CGameClientPort(const ControllerVector& controllers)
+ : m_type(PORT_TYPE::CONTROLLER), m_portId(DEFAULT_PORT_ID)
+{
+ for (const auto& controller : controllers)
+ m_acceptedDevices.emplace_back(new CGameClientDevice(controller));
+}
+
+CGameClientPort::CGameClientPort(const game_input_port& logicalPort,
+ const CPhysicalPort& physicalPort)
+ : m_type(PORT_TYPE::CONTROLLER),
+ m_portId(physicalPort.ID()),
+ m_forceConnected(logicalPort.force_connected)
+{
+ if (logicalPort.accepted_devices != nullptr)
+ {
+ for (unsigned int i = 0; i < logicalPort.device_count; i++)
+ {
+ // Ensure device is physically compatible
+ const game_input_device& deviceStruct = logicalPort.accepted_devices[i];
+ std::string controllerId = deviceStruct.controller_id ? deviceStruct.controller_id : "";
+
+ if (physicalPort.IsCompatible(controllerId))
+ {
+ std::unique_ptr<CGameClientDevice> device(new CGameClientDevice(deviceStruct));
+
+ if (device->Controller() != CController::EmptyPtr)
+ m_acceptedDevices.emplace_back(std::move(device));
+ }
+ }
+ }
+}
+
+CGameClientPort::~CGameClientPort() = default;
diff --git a/xbmc/games/addons/input/GameClientPort.h b/xbmc/games/addons/input/GameClientPort.h
new file mode 100644
index 0000000..80fccef
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientPort.h
@@ -0,0 +1,103 @@
+/*
+ * 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 "games/GameTypes.h"
+#include "games/controllers/ControllerTypes.h"
+
+#include <string>
+
+struct game_input_device;
+struct game_input_port;
+
+namespace KODI
+{
+namespace GAME
+{
+class CPhysicalPort;
+
+/*!
+ * \ingroup games
+ * \brief Represents a port that devices can connect to
+ */
+class CGameClientPort
+{
+public:
+ /*!
+ * \brief Construct a hardware port
+ *
+ * \param port The hardware port Game API struct
+ */
+ CGameClientPort(const game_input_port& port);
+
+ /*!
+ * \brief Construct a hardware port that accepts the given controllers
+ *
+ * \param controllers List of accepted controller profiles
+ *
+ * The port is given the ID specified by DEFAULT_PORT_ID.
+ */
+ CGameClientPort(const ControllerVector& controllers);
+
+ /*!
+ * \brief Construct a controller port
+ *
+ * \param logicalPort The logical port Game API struct
+ * \param physicalPort The physical port definition
+ *
+ * The physical port is defined by the controller profile. This definition
+ * specifies which controllers the port is physically compatible with.
+ *
+ * The logical port is defined by the emulator's input topology. This
+ * definition specifies which controllers the emulator's logic can handle.
+ *
+ * Obviously, the controllers specified by the logical port must be a subset
+ * of the controllers supported by the physical port.
+ */
+ CGameClientPort(const game_input_port& logicalPort, const CPhysicalPort& physicalPort);
+
+ /*!
+ * \brief Destructor
+ */
+ ~CGameClientPort();
+
+ /*!
+ * \brief Get the port type
+ *
+ * The port type identifies if this port is for a keyboard, mouse, or
+ * controller.
+ */
+ PORT_TYPE PortType() const { return m_type; }
+
+ /*!
+ * \brief Get the ID of the port
+ *
+ * The ID is used when creating a toplogical address for the port.
+ */
+ const std::string& ID() const { return m_portId; }
+
+ /*!
+ * \brief True if a controller must be connected, preventing the disconnected
+ * option from being shown to the user
+ */
+ bool ForceConnected() const { return m_forceConnected; }
+
+ /*!
+ * \brief Get the list of devices accepted by this port
+ */
+ const GameClientDeviceVec& Devices() const { return m_acceptedDevices; }
+
+private:
+ PORT_TYPE m_type;
+ std::string m_portId;
+ bool m_forceConnected{false};
+ GameClientDeviceVec m_acceptedDevices;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientTopology.cpp b/xbmc/games/addons/input/GameClientTopology.cpp
new file mode 100644
index 0000000..e1e9757
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientTopology.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "GameClientTopology.h"
+
+#include "GameClientDevice.h"
+#include "GameClientPort.h"
+#include "games/controllers/Controller.h"
+
+#include <sstream>
+#include <utility>
+
+using namespace KODI;
+using namespace GAME;
+
+#define CONTROLLER_ADDRESS_SEPARATOR "/"
+
+CGameClientTopology::CGameClientTopology(GameClientPortVec ports, int playerLimit)
+ : m_ports(std::move(ports)), m_playerLimit(playerLimit), m_controllers(GetControllerTree(m_ports))
+{
+}
+
+void CGameClientTopology::Clear()
+{
+ m_ports.clear();
+ m_controllers.Clear();
+}
+
+CControllerTree CGameClientTopology::GetControllerTree(const GameClientPortVec& ports)
+{
+ CControllerTree tree;
+
+ PortVec controllerPorts;
+ for (const GameClientPortPtr& port : ports)
+ {
+ CPortNode portNode = GetPortNode(port, "");
+ controllerPorts.emplace_back(std::move(portNode));
+ }
+
+ tree.SetPorts(std::move(controllerPorts));
+
+ return tree;
+}
+
+CPortNode CGameClientTopology::GetPortNode(const GameClientPortPtr& port,
+ const std::string& controllerAddress)
+{
+ CPortNode portNode;
+
+ std::string portAddress = MakeAddress(controllerAddress, port->ID());
+
+ portNode.SetConnected(false);
+ portNode.SetPortType(port->PortType());
+ portNode.SetPortID(port->ID());
+ portNode.SetAddress(portAddress);
+ portNode.SetForceConnected(port->ForceConnected());
+
+ ControllerNodeVec nodes;
+ for (const GameClientDevicePtr& device : port->Devices())
+ {
+ CControllerNode controllerNode = GetControllerNode(device, portAddress);
+ nodes.emplace_back(std::move(controllerNode));
+ }
+ portNode.SetCompatibleControllers(std::move(nodes));
+
+ return portNode;
+}
+
+CControllerNode CGameClientTopology::GetControllerNode(const GameClientDevicePtr& device,
+ const std::string& portAddress)
+{
+ CControllerNode controllerNode;
+
+ const std::string controllerAddress = MakeAddress(portAddress, device->Controller()->ID());
+
+ controllerNode.SetController(device->Controller());
+ controllerNode.SetPortAddress(portAddress);
+ controllerNode.SetControllerAddress(controllerAddress);
+
+ PortVec ports;
+ for (const GameClientPortPtr& port : device->Ports())
+ {
+ CPortNode portNode = GetPortNode(port, controllerAddress);
+ ports.emplace_back(std::move(portNode));
+ }
+
+ CControllerHub controllerHub;
+ controllerHub.SetPorts(std::move(ports));
+ controllerNode.SetHub(std::move(controllerHub));
+
+ return controllerNode;
+}
+
+std::string CGameClientTopology::MakeAddress(const std::string& baseAddress,
+ const std::string& nodeId)
+{
+ std::ostringstream address;
+
+ if (!baseAddress.empty())
+ address << baseAddress;
+
+ address << CONTROLLER_ADDRESS_SEPARATOR << nodeId;
+
+ return address.str();
+}
diff --git a/xbmc/games/addons/input/GameClientTopology.h b/xbmc/games/addons/input/GameClientTopology.h
new file mode 100644
index 0000000..10b40f1
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientTopology.h
@@ -0,0 +1,50 @@
+/*
+ * 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 "games/GameTypes.h"
+#include "games/controllers/types/ControllerTree.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameClientTopology
+{
+public:
+ CGameClientTopology() = default;
+ CGameClientTopology(GameClientPortVec ports, int playerLimit);
+
+ void Clear();
+
+ int GetPlayerLimit() const { return m_playerLimit; }
+
+ const CControllerTree& GetControllerTree() const { return m_controllers; }
+ CControllerTree& GetControllerTree() { return m_controllers; }
+
+ // Utility function
+ static std::string MakeAddress(const std::string& baseAddress, const std::string& nodeId);
+
+private:
+ static CControllerTree GetControllerTree(const GameClientPortVec& ports);
+ static CPortNode GetPortNode(const GameClientPortPtr& port, const std::string& controllerAddress);
+ static CControllerNode GetControllerNode(const GameClientDevicePtr& device,
+ const std::string& portAddress);
+
+ // Game API parameters
+ GameClientPortVec m_ports;
+ int m_playerLimit = -1;
+
+ // Controller parameters
+ CControllerTree m_controllers;
+};
+} // namespace GAME
+} // namespace KODI