diff options
Diffstat (limited to 'xbmc/games/addons/input')
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 |