aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett Brown <themagnificentmrb@gmail.com>2017-07-22 13:41:11 -0700
committerGarrett Brown <themagnificentmrb@gmail.com>2018-02-15 15:59:56 -0800
commite597e70696bc2a809c5cd19f7f73bde4beecad30 (patch)
tree83c8d46b821f19335ec1cd1e936b25fab946647e
parent502c46c81a89817d427f766034974a583317ecc4 (diff)
Game API v1.0.36: Controller topology (hub support)
-rw-r--r--cmake/treedata/common/games.txt1
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h77
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h73
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h22
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h4
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayer.cpp3
-rw-r--r--xbmc/games/GameTypes.h8
-rw-r--r--xbmc/games/addons/GameClient.cpp46
-rw-r--r--xbmc/games/addons/GameClient.h8
-rw-r--r--xbmc/games/addons/GameClientTranslator.cpp14
-rw-r--r--xbmc/games/addons/GameClientTranslator.h8
-rw-r--r--xbmc/games/addons/input/CMakeLists.txt10
-rw-r--r--xbmc/games/addons/input/GameClientDevice.cpp80
-rw-r--r--xbmc/games/addons/input/GameClientDevice.h88
-rw-r--r--xbmc/games/addons/input/GameClientHardware.cpp6
-rw-r--r--xbmc/games/addons/input/GameClientHardware.h4
-rw-r--r--xbmc/games/addons/input/GameClientInput.cpp415
-rw-r--r--xbmc/games/addons/input/GameClientInput.h56
-rw-r--r--xbmc/games/addons/input/GameClientJoystick.cpp38
-rw-r--r--xbmc/games/addons/input/GameClientJoystick.h21
-rw-r--r--xbmc/games/addons/input/GameClientKeyboard.cpp6
-rw-r--r--xbmc/games/addons/input/GameClientMouse.cpp9
-rw-r--r--xbmc/games/addons/input/GameClientPort.cpp82
-rw-r--r--xbmc/games/addons/input/GameClientPort.h107
-rw-r--r--xbmc/games/addons/input/GameClientTopology.cpp115
-rw-r--r--xbmc/games/addons/input/GameClientTopology.h49
-rw-r--r--xbmc/games/controllers/CMakeLists.txt2
-rw-r--r--xbmc/games/controllers/Controller.cpp21
-rw-r--r--xbmc/games/controllers/Controller.h19
-rw-r--r--xbmc/games/controllers/ControllerDefinitions.h6
-rw-r--r--xbmc/games/controllers/ControllerLayout.cpp27
-rw-r--r--xbmc/games/controllers/ControllerLayout.h20
-rw-r--r--xbmc/games/controllers/ControllerTopology.cpp119
-rw-r--r--xbmc/games/controllers/ControllerTopology.h116
-rw-r--r--xbmc/games/controllers/ControllerTypes.h11
-rw-r--r--xbmc/games/controllers/types/CMakeLists.txt9
-rw-r--r--xbmc/games/controllers/types/ControllerGrid.cpp255
-rw-r--r--xbmc/games/controllers/types/ControllerGrid.h174
-rw-r--r--xbmc/games/controllers/types/ControllerTree.cpp228
-rw-r--r--xbmc/games/controllers/types/ControllerTree.h224
-rw-r--r--xbmc/input/hardware/IHardwareInput.h7
41 files changed, 2351 insertions, 237 deletions
diff --git a/cmake/treedata/common/games.txt b/cmake/treedata/common/games.txt
index 02e9af6076..7b63f6a538 100644
--- a/cmake/treedata/common/games.txt
+++ b/cmake/treedata/common/games.txt
@@ -6,6 +6,7 @@ xbmc/games/addons/savestates games/addons/savestates
xbmc/games/controllers games/controllers
xbmc/games/controllers/dialogs games/controllers/dialogs
xbmc/games/controllers/guicontrols games/controllers/guicontrols
+xbmc/games/controllers/types games/controllers/types
xbmc/games/controllers/windows games/controllers/windows
xbmc/games/dialogs games/dialogs
xbmc/games/dialogs/osd games/dialogs/osd
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h
index 691dd5ee43..79ca778d8e 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h
@@ -131,17 +131,6 @@ GAME_ERROR HwContextDestroy(void);
// --- Input operations --------------------------------------------------------
/*!
- * \brief Notify the add-on of a status change on an open port
- *
- * Ports can be opened using the OpenPort() callback
- *
- * \param port Non-negative for a joystick port, or GAME_INPUT_PORT value otherwise
- * \param collected True if a controller was connected, false if disconnected
- * \param controller The connected controller
- */
-void UpdatePort(int port, bool connected, const game_controller* controller);
-
-/*!
* \brief Check if input is accepted for a feature on the controller
*
* If only a subset of the controller profile is used, this can return false
@@ -156,6 +145,26 @@ void UpdatePort(int port, bool connected, const game_controller* controller);
bool HasFeature(const char* controller_id, const char* feature_name);
/*!
+ * \brief Get the input topolgy that specifies which controllers can be connected
+ *
+ * \return The input topology, or null to use the default
+ *
+ * If this returns non-null, topology must be freed using FreeTopology().
+ *
+ * If this returns null, the topology will default to a single port that can
+ * accept all controllers imported by addon.xml. The port ID is set to
+ * the DEFAULT_PORT_ID constant.
+ */
+game_input_topology* GetTopology();
+
+/*!
+ * \brief Free the topology's resources
+ *
+ * \param topology The topology returned by GetTopology()
+ */
+void FreeTopology(game_input_topology* topology);
+
+/*!
* \brief Enable/disable keyboard input using the specified controller
*
* \param enable True to enable input, false otherwise
@@ -176,6 +185,48 @@ bool EnableKeyboard(bool enable, const game_controller* controller);
bool EnableMouse(bool enable, const game_controller* controller);
/*!
+ * \brief Connect/disconnect a controller to a port on the virtual game console
+ *
+ * \param connect True to connect a controller, false to disconnect
+ * \param address The address of the port
+ * \param controller The controller info if connecting, or unused if disconnecting
+ *
+ * The address is a string that allows traversal of the controller topology.
+ * It is formed by alternating port IDs and controller IDs separated by "/".
+ *
+ * For example, assume that the topology represented in XML for Snes9x is:
+ *
+ * <logicaltopology>
+ * <port type="controller" id="1">
+ * <accepts controller="game.controller.snes"/>
+ * <accepts controller="game.controller.snes.multitap">
+ * <port type="controller" id="1">
+ * <accepts controller="game.controller.snes"/>
+ * </port>
+ * <port type="controller" id="2">
+ * <accepts controller="game.controller.snes"/>
+ * </port>
+ * ...
+ * </accepts>
+ * </port>
+ * </logicaltopology>
+ *
+ * To connect a multitap to the console's first port, the multitap's controller
+ * info is set using the port address:
+ *
+ * 1
+ *
+ * To connect a SNES controller to the second port of the multitap, the
+ * controller info is next set using the address:
+ *
+ * 1/game.controller.multitap/2
+ *
+ * Any attempts to connect a controller to a port on a disconnected multitap
+ * will return false.
+ */
+bool ConnectController(bool connect, const char* port_address, const game_controller* controller);
+
+/*!
* \brief Notify the add-on of an input event
*
* \param event The input event
@@ -267,10 +318,12 @@ void __declspec(dllexport) get_addon(void* ptr)
pClient->toAddon.Reset = Reset;
pClient->toAddon.HwContextReset = HwContextReset;
pClient->toAddon.HwContextDestroy = HwContextDestroy;
- pClient->toAddon.UpdatePort = UpdatePort;
pClient->toAddon.HasFeature = HasFeature;
+ pClient->toAddon.GetTopology = GetTopology;
+ pClient->toAddon.FreeTopology = FreeTopology;
pClient->toAddon.EnableKeyboard = EnableKeyboard;
pClient->toAddon.EnableMouse = EnableMouse;
+ pClient->toAddon.ConnectController = ConnectController;
pClient->toAddon.InputEvent = InputEvent;
pClient->toAddon.SerializeSize = SerializeSize;
pClient->toAddon.Serialize = Serialize;
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h
index 54196afa01..23fa773148 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h
@@ -57,6 +57,9 @@
#include "input/XBMC_vkeys.h"
#endif
+/*! Port ID used when topology is unknown */
+#define DEFAULT_PORT_ID "1"
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -159,11 +162,6 @@ typedef enum GAME_HW_CONTEXT_TYPE
GAME_HW_CONTEXT_OPENGLES3, // GLES 3.0
} GAME_HW_CONTEXT_TYPE;
-typedef enum GAME_INPUT_PORT
-{
- GAME_INPUT_PORT_JOYSTICK_START = 0, // Non-negative values are for joystick ports
-} GAME_INPUT_PORT;
-
typedef enum GAME_INPUT_EVENT_SOURCE
{
GAME_INPUT_EVENT_DIGITAL_BUTTON,
@@ -275,9 +273,21 @@ typedef enum GAME_ROTATION
GAME_ROTATION_270_CW,
} GAME_ROTATION;
+/*!
+ * \brief Type of port on the virtual game console
+ */
+typedef enum GAME_PORT_TYPE
+{
+ GAME_PORT_UNKNOWN,
+ GAME_PORT_KEYBOARD,
+ GAME_PORT_MOUSE,
+ GAME_PORT_CONTROLLER,
+} GAME_PORT_TYPE;
+
typedef struct game_controller
{
const char* controller_id;
+ bool provides_input; // False for multitaps
unsigned int digital_button_count;
unsigned int analog_button_count;
unsigned int analog_stick_count;
@@ -288,6 +298,48 @@ typedef struct game_controller
unsigned int motor_count;
} ATTRIBUTE_PACKED game_controller;
+struct game_input_port;
+
+/*!
+ * \brief Device that can provide input
+ */
+typedef struct game_input_device
+{
+ const char* controller_id; // ID used in the Kodi controller API
+ const char* port_address;
+ game_input_port* available_ports;
+ unsigned int port_count;
+} ATTRIBUTE_PACKED game_input_device;
+
+/*!
+ * \brief Port that can provide input
+ *
+ * Ports can accept multiple devices and devices can have multiple ports, so
+ * the topology of possible configurations is a tree structure of alternating
+ * port and device nodes.
+ */
+typedef struct game_input_port
+{
+ GAME_PORT_TYPE type;
+ const char* port_id; // Required for GAME_PORT_CONTROLLER type
+ game_input_device* accepted_devices;
+ unsigned int device_count;
+} ATTRIBUTE_PACKED game_input_port;
+
+/*!
+ * \brief The input topology is the possible ways to connect input devices
+ *
+ * This represents the logical topology, which is the possible connections that
+ * the game client's logic can handle. It is strictly a subset of the physical
+ * topology. Loops are not allowed.
+ */
+typedef struct game_input_topology
+{
+ game_input_port *ports; //! The list of ports on the virtual game console
+ unsigned int port_count; //! The number of ports
+ int player_limit; //! A limit on the number of input-providing devices, or -1 for no limit
+} ATTRIBUTE_PACKED game_input_topology;
+
typedef struct game_digital_button_event
{
bool pressed;
@@ -351,8 +403,9 @@ typedef struct game_motor_event
typedef struct game_input_event
{
GAME_INPUT_EVENT_SOURCE type;
- int port;
const char* controller_id;
+ GAME_PORT_TYPE port_type;
+ const char* port_address;
const char* feature_name;
union
{
@@ -479,8 +532,6 @@ typedef struct AddonToKodiFuncTable_Game
uintptr_t (*HwGetCurrentFramebuffer)(void* kodiInstance);
game_proc_address_t (*HwGetProcAddress)(void* kodiInstance, const char* symbol);
void (*RenderFrame)(void* kodiInstance);
- bool (*OpenPort)(void* kodiInstance, unsigned int port);
- void (*ClosePort)(void* kodiInstance, unsigned int port);
bool (*InputEvent)(void* kodiInstance, const game_input_event* event);
} AddonToKodiFuncTable_Game;
@@ -498,10 +549,12 @@ typedef struct KodiToAddonFuncTable_Game
GAME_ERROR (__cdecl* Reset)(void);
GAME_ERROR (__cdecl* HwContextReset)(void);
GAME_ERROR (__cdecl* HwContextDestroy)(void);
- void (__cdecl* UpdatePort)(int, bool, const game_controller*);
- bool (__cdecl* HasFeature)(const char* controller_id, const char* feature_name);
+ bool (__cdecl* HasFeature)(const char*, const char*);
+ game_input_topology* (__cdecl* GetTopology)();
+ void (__cdecl* FreeTopology)(game_input_topology*);
bool (__cdecl* EnableKeyboard)(bool, const game_controller*);
bool (__cdecl* EnableMouse)(bool, const game_controller*);
+ bool (__cdecl* ConnectController)(bool, const char*, const game_controller*);
bool (__cdecl* InputEvent)(const game_input_event*);
size_t (__cdecl* SerializeSize)(void);
GAME_ERROR (__cdecl* Serialize)(uint8_t*, size_t);
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h
index 1ca91a412b..2774afd567 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h
@@ -188,28 +188,6 @@ public:
// --- Input callbacks -------------------------------------------------------
/*!
- * \brief Begin reporting events for the specified joystick port
- *
- * \param port The zero-indexed port number
- *
- * \return true if the port was opened, false otherwise
- */
- bool OpenPort(unsigned int port)
- {
- return m_callbacks->toKodi.OpenPort(m_callbacks->toKodi.kodiInstance, port);
- }
-
- /*!
- * \brief End reporting events for the specified port
- *
- * \param port The port number passed to OpenPort()
- */
- void ClosePort(unsigned int port)
- {
- return m_callbacks->toKodi.ClosePort(m_callbacks->toKodi.kodiInstance, port);
- }
-
- /*!
* \brief Notify the port of an input event
*
* \param event The input event
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h
index 0e3c9e31a4..6ad93541ec 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h
@@ -91,8 +91,8 @@
#define ADDON_INSTANCE_VERSION_AUDIOENCODER_XML_ID "kodi.binary.instance.audioencoder"
#define ADDON_INSTANCE_VERSION_AUDIOENCODER_DEPENDS "addon-instance/AudioEncoder.h"
-#define ADDON_INSTANCE_VERSION_GAME "1.0.35"
-#define ADDON_INSTANCE_VERSION_GAME_MIN "1.0.35"
+#define ADDON_INSTANCE_VERSION_GAME "1.0.36"
+#define ADDON_INSTANCE_VERSION_GAME_MIN "1.0.36"
#define ADDON_INSTANCE_VERSION_GAME_XML_ID "kodi.binary.instance.game"
#define ADDON_INSTANCE_VERSION_GAME_DEPENDS "kodi_game_dll.h" \
"kodi_game_types.h" \
diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.cpp b/xbmc/cores/RetroPlayer/RetroPlayer.cpp
index 99702c4a6e..0cea5e4af9 100644
--- a/xbmc/cores/RetroPlayer/RetroPlayer.cpp
+++ b/xbmc/cores/RetroPlayer/RetroPlayer.cpp
@@ -30,6 +30,7 @@
#include "cores/RetroPlayer/rendering/RPRenderManager.h"
#include "dialogs/GUIDialogYesNo.h"
#include "filesystem/File.h"
+#include "games/addons/input/GameClientInput.h"
#include "games/addons/playback/IGameClientPlayback.h"
#include "games/addons/savestates/Savestate.h"
#include "games/addons/savestates/SavestateUtils.h"
@@ -403,7 +404,7 @@ bool CRetroPlayer::OnAction(const CAction &action)
m_gameClient->GetPlayback()->SetSpeed(0.0);
CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Sending reset command via ACTION_PLAYER_RESET");
- //m_gameServices.PortManager().HardwareReset();
+ m_gameClient->Input().HardwareReset();
// If rewinding or paused, begin playback
if (speed <= 0.0f)
diff --git a/xbmc/games/GameTypes.h b/xbmc/games/GameTypes.h
index 5713643e09..980cca7b8c 100644
--- a/xbmc/games/GameTypes.h
+++ b/xbmc/games/GameTypes.h
@@ -31,5 +31,13 @@ namespace GAME
using GameClientPtr = std::shared_ptr<CGameClient>;
using GameClientVector = std::vector<GameClientPtr>;
+ class CGameClientPort;
+ using GameClientPortPtr = std::unique_ptr<CGameClientPort>;
+ using GameClientPortVec = std::vector<GameClientPortPtr>;
+
+ class CGameClientDevice;
+ using GameClientDevicePtr = std::unique_ptr<CGameClientDevice>;
+ using GameClientDeviceVec = std::vector<GameClientDevicePtr>;
+
}
}
diff --git a/xbmc/games/addons/GameClient.cpp b/xbmc/games/addons/GameClient.cpp
index 144f939f1d..a7bd32a5ff 100644
--- a/xbmc/games/addons/GameClient.cpp
+++ b/xbmc/games/addons/GameClient.cpp
@@ -32,7 +32,6 @@
#include "games/addons/input/GameClientInput.h"
#include "games/addons/playback/GameClientRealtimePlayback.h"
#include "games/addons/playback/GameClientReversiblePlayback.h"
-#include "games/controllers/Controller.h"
#include "games/GameServices.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
@@ -65,8 +64,6 @@ using namespace KODI::MESSAGING;
#define GAME_PROPERTY_EXTENSIONS "extensions"
#define GAME_PROPERTY_SUPPORTS_VFS "supports_vfs"
#define GAME_PROPERTY_SUPPORTS_STANDALONE "supports_standalone"
-#define GAME_PROPERTY_SUPPORTS_KEYBOARD "supports_keyboard"
-#define GAME_PROPERTY_SUPPORTS_MOUSE "supports_mouse"
// --- NormalizeExtension ------------------------------------------------------
@@ -101,8 +98,6 @@ std::unique_ptr<CGameClient> CGameClient::FromExtension(ADDON::CAddonInfo addonI
GAME_PROPERTY_EXTENSIONS,
GAME_PROPERTY_SUPPORTS_VFS,
GAME_PROPERTY_SUPPORTS_STANDALONE,
- GAME_PROPERTY_SUPPORTS_KEYBOARD,
- GAME_PROPERTY_SUPPORTS_MOUSE,
};
for (const auto& property : properties)
@@ -120,8 +115,6 @@ CGameClient::CGameClient(ADDON::CAddonInfo addonInfo) :
m_subsystems(CGameClientSubsystem::CreateSubsystems(*this, m_struct, m_critSection)),
m_bSupportsVFS(false),
m_bSupportsStandalone(false),
- m_bSupportsKeyboard(false),
- m_bSupportsMouse(false),
m_bSupportsAllExtensions(false),
m_bIsPlaying(false),
m_serializeSize(0),
@@ -155,14 +148,6 @@ CGameClient::CGameClient(ADDON::CAddonInfo addonInfo) :
if (it != extraInfo.end())
m_bSupportsStandalone = (it->second == "true");
- it = extraInfo.find(GAME_PROPERTY_SUPPORTS_KEYBOARD);
- if (it != extraInfo.end())
- m_bSupportsKeyboard = (it->second == "true");
-
- it = extraInfo.find(GAME_PROPERTY_SUPPORTS_MOUSE);
- if (it != extraInfo.end())
- m_bSupportsMouse = (it->second == "true");
-
ResetPlayback();
}
@@ -233,12 +218,11 @@ bool CGameClient::Initialize(void)
m_struct.toKodi.HwGetCurrentFramebuffer = cb_hw_get_current_framebuffer;
m_struct.toKodi.HwGetProcAddress = cb_hw_get_proc_address;
m_struct.toKodi.RenderFrame = cb_render_frame;
- m_struct.toKodi.OpenPort = cb_open_port;
- m_struct.toKodi.ClosePort = cb_close_port;
m_struct.toKodi.InputEvent = cb_input_event;
if (Create(ADDON_INSTANCE_GAME, &m_struct, &m_struct.props) == ADDON_STATUS_OK)
{
+ Input().Initialize();
LogAddonProperties();
return true;
}
@@ -248,6 +232,8 @@ bool CGameClient::Initialize(void)
void CGameClient::Unload()
{
+ Input().Deinitialize();
+
Destroy();
}
@@ -346,8 +332,6 @@ bool CGameClient::InitializeGameplay(const std::string& gamePath, IGameAudioCall
m_video = video;
m_input = input;
- Input().Initialize();
-
m_inGameSaves.reset(new CGameClientInGameSaves(this, &m_struct.toAddon));
m_inGameSaves->Load();
@@ -490,7 +474,7 @@ void CGameClient::ResetPlayback()
m_playback.reset(new CGameClientRealtimePlayback);
}
-void CGameClient::Reset(unsigned int port)
+void CGameClient::Reset()
{
ResetPlayback();
@@ -520,8 +504,6 @@ void CGameClient::CloseFile()
catch (...) { LogException("UnloadGame()"); }
}
- Input().Deinitialize();
-
m_bIsPlaying = false;
m_gamePath.clear();
m_serializeSize = 0;
@@ -751,8 +733,6 @@ void CGameClient::LogAddonProperties(void) const
CLog::Log(LOGINFO, "GAME: Valid extensions: %s", StringUtils::Join(m_extensions, " ").c_str());
CLog::Log(LOGINFO, "GAME: Supports VFS: %s", m_bSupportsVFS ? "yes" : "no");
CLog::Log(LOGINFO, "GAME: Supports standalone execution: %s", m_bSupportsStandalone ? "yes" : "no");
- CLog::Log(LOGINFO, "GAME: Supports keyboard: %s", m_bSupportsKeyboard ? "yes" : "no");
- CLog::Log(LOGINFO, "GAME: Supports mouse: %s", m_bSupportsMouse ? "yes" : "no");
CLog::Log(LOGINFO, "GAME: ------------------------------------");
}
@@ -874,24 +854,6 @@ void CGameClient::cb_render_frame(void* kodiInstance)
//! @todo
}
-bool CGameClient::cb_open_port(void* kodiInstance, unsigned int port)
-{
- CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance);
- if (!gameClient)
- return false;
-
- return gameClient->Input().OpenPort(port);
-}
-
-void CGameClient::cb_close_port(void* kodiInstance, unsigned int port)
-{
- CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance);
- if (!gameClient)
- return;
-
- gameClient->Input().ClosePort(port);
-}
-
bool CGameClient::cb_input_event(void* kodiInstance, const game_input_event* event)
{
CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance);
diff --git a/xbmc/games/addons/GameClient.h b/xbmc/games/addons/GameClient.h
index 69e65b0385..ecc422bf65 100644
--- a/xbmc/games/addons/GameClient.h
+++ b/xbmc/games/addons/GameClient.h
@@ -77,8 +77,6 @@ public:
bool SupportsStandalone() const { return m_bSupportsStandalone; }
bool SupportsPath() const;
bool SupportsVFS() const { return m_bSupportsVFS; }
- bool SupportsKeyboard() const { return m_bSupportsKeyboard; }
- bool SupportsMouse() const { return m_bSupportsMouse; }
const std::set<std::string>& GetExtensions() const { return m_extensions; }
bool SupportsAllExtensions() const { return m_bSupportsAllExtensions; }
bool IsExtensionValid(const std::string& strExtension) const;
@@ -88,7 +86,7 @@ public:
void Unload();
bool OpenFile(const CFileItem& file, IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input);
bool OpenStandalone(IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input);
- void Reset(unsigned int port);
+ void Reset();
void CloseFile();
const std::string& GetGamePath() const { return m_gamePath; }
@@ -153,8 +151,6 @@ private:
static uintptr_t cb_hw_get_current_framebuffer(void* kodiInstance);
static game_proc_address_t cb_hw_get_proc_address(void* kodiInstance, const char* sym);
static void cb_render_frame(void* kodiInstance);
- static bool cb_open_port(void* kodiInstance, unsigned int port);
- static void cb_close_port(void* kodiInstance, unsigned int port);
static bool cb_input_event(void* kodiInstance, const game_input_event* event);
//@}
@@ -164,8 +160,6 @@ private:
// Game API xml parameters
bool m_bSupportsVFS;
bool m_bSupportsStandalone;
- bool m_bSupportsKeyboard;
- bool m_bSupportsMouse;
std::set<std::string> m_extensions;
bool m_bSupportsAllExtensions;
//GamePlatforms m_platforms;
diff --git a/xbmc/games/addons/GameClientTranslator.cpp b/xbmc/games/addons/GameClientTranslator.cpp
index 4ed1f976f0..51afd5e65e 100644
--- a/xbmc/games/addons/GameClientTranslator.cpp
+++ b/xbmc/games/addons/GameClientTranslator.cpp
@@ -166,3 +166,17 @@ const char* CGameClientTranslator::TranslateRegion(GAME_REGION region)
}
return "Unknown";
}
+
+PORT_TYPE CGameClientTranslator::TranslatePortType(GAME_PORT_TYPE portType)
+{
+ switch (portType)
+ {
+ case GAME_PORT_KEYBOARD: return PORT_TYPE::KEYBOARD;
+ case GAME_PORT_MOUSE: return PORT_TYPE::MOUSE;
+ case GAME_PORT_CONTROLLER: return PORT_TYPE::CONTROLLER;
+ default:
+ break;
+ }
+
+ return PORT_TYPE::UNKNOWN;
+}
diff --git a/xbmc/games/addons/GameClientTranslator.h b/xbmc/games/addons/GameClientTranslator.h
index f8c95cf201..154089f9cd 100644
--- a/xbmc/games/addons/GameClientTranslator.h
+++ b/xbmc/games/addons/GameClientTranslator.h
@@ -21,6 +21,7 @@
#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h"
#include "cores/AudioEngine/Utils/AEChannelData.h"
+#include "games/controllers/ControllerTypes.h"
#include "input/keyboard/KeyboardTypes.h"
#include "libavcodec/avcodec.h"
@@ -103,6 +104,13 @@ namespace GAME
* \return Translated region.
*/
static const char* TranslateRegion(GAME_REGION region);
+
+ /*!
+ * \brief Translate port type (Game API to Kodi)
+ * \param portType The port type to translate
+ * \return Translated port type
+ */
+ static PORT_TYPE TranslatePortType(GAME_PORT_TYPE portType);
};
}
}
diff --git a/xbmc/games/addons/input/CMakeLists.txt b/xbmc/games/addons/input/CMakeLists.txt
index f0f25a0462..9927ceb90d 100644
--- a/xbmc/games/addons/input/CMakeLists.txt
+++ b/xbmc/games/addons/input/CMakeLists.txt
@@ -1,15 +1,21 @@
-set(SOURCES GameClientHardware.cpp
+set(SOURCES GameClientDevice.cpp
+ GameClientHardware.cpp
GameClientInput.cpp
GameClientJoystick.cpp
GameClientKeyboard.cpp
GameClientMouse.cpp
+ GameClientPort.cpp
+ GameClientTopology.cpp
)
-set(HEADERS GameClientHardware.h
+set(HEADERS 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/GameClientDevice.cpp b/xbmc/games/addons/input/GameClientDevice.cpp
new file mode 100644
index 0000000000..a3ae609bf8
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientDevice.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 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 "GameClientPort.h"
+#include "GameClientDevice.h"
+#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerTopology.h"
+#include "games/controllers/ControllerTranslator.h"
+#include "games/GameServices.h"
+#include "utils/StringUtils.h"
+#include "ServiceBroker.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 CControllerPort &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);
+
+ return controller;
+}
diff --git a/xbmc/games/addons/input/GameClientDevice.h b/xbmc/games/addons/input/GameClientDevice.h
new file mode 100644
index 0000000000..071df06c53
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientDevice.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 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 "games/controllers/ControllerTypes.h"
+#include "games/GameTypes.h"
+
+#include <string>
+
+struct game_input_device;
+struct game_input_port;
+
+namespace KODI
+{
+namespace GAME
+{
+ class CControllerPort;
+
+ /*!
+ * \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 CControllerPort &physicalPort);
+
+ // Helper function
+ static ControllerPtr GetController(const char *controllerId);
+
+ ControllerPtr m_controller;
+ GameClientPortVec m_ports;
+ };
+}
+}
diff --git a/xbmc/games/addons/input/GameClientHardware.cpp b/xbmc/games/addons/input/GameClientHardware.cpp
index 438f110112..abca750046 100644
--- a/xbmc/games/addons/input/GameClientHardware.cpp
+++ b/xbmc/games/addons/input/GameClientHardware.cpp
@@ -30,8 +30,8 @@ CGameClientHardware::CGameClientHardware(CGameClient &gameClient) :
{
}
-void CGameClientHardware::OnResetButton(unsigned int port)
+void CGameClientHardware::OnResetButton()
{
- CLog::Log(LOGDEBUG, "%s: Port %d sending hardware reset", m_gameClient.ID().c_str(), port);
- m_gameClient.Reset(port);
+ CLog::Log(LOGDEBUG, "%s: Sending hardware reset", m_gameClient.ID().c_str());
+ m_gameClient.Reset();
}
diff --git a/xbmc/games/addons/input/GameClientHardware.h b/xbmc/games/addons/input/GameClientHardware.h
index cacd6cce7e..ebab70aac8 100644
--- a/xbmc/games/addons/input/GameClientHardware.h
+++ b/xbmc/games/addons/input/GameClientHardware.h
@@ -41,10 +41,10 @@ namespace GAME
*/
explicit CGameClientHardware(CGameClient &gameClient);
- virtual ~CGameClientHardware() = default;
+ ~CGameClientHardware() override = default;
// Implementation of IHardwareInput
- virtual void OnResetButton(unsigned int port) override;
+ void OnResetButton() override;
private:
// Construction parameter
diff --git a/xbmc/games/addons/input/GameClientInput.cpp b/xbmc/games/addons/input/GameClientInput.cpp
index 87c1852e7a..a0d0bcc60e 100644
--- a/xbmc/games/addons/input/GameClientInput.cpp
+++ b/xbmc/games/addons/input/GameClientInput.cpp
@@ -23,19 +23,23 @@
#include "GameClientJoystick.h"
#include "GameClientKeyboard.h"
#include "GameClientMouse.h"
+#include "GameClientPort.h"
+#include "GameClientTopology.h"
#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h"
#include "games/addons/GameClient.h"
#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerTopology.h"
#include "games/GameServices.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/WindowIDs.h"
#include "input/joysticks/JoystickTypes.h"
#include "peripherals/Peripherals.h"
-#include "peripherals/PeripheralTypes.h" //! @todo
-//#include "threads/SingleLock.h"
+#include "threads/SingleLock.h"
#include "utils/log.h"
#include "ServiceBroker.h"
+#include <algorithm>
+
using namespace KODI;
using namespace GAME;
@@ -53,23 +57,64 @@ CGameClientInput::~CGameClientInput()
void CGameClientInput::Initialize()
{
- if (m_gameClient.SupportsKeyboard())
- OpenKeyboard();
+ LoadTopology();
+
+ // Open keyboard
+ //! @todo Move to player manager
+ if (SupportsKeyboard())
+ {
+ auto it = std::find_if(m_controllers.Ports().begin(), m_controllers.Ports().end(),
+ [](const CControllerPortNode &port)
+ {
+ return port.PortType() == PORT_TYPE::KEYBOARD;
+ });
+
+ OpenKeyboard(it->CompatibleControllers().at(0).Controller());
+ }
+
+ // Open mouse
+ //! @todo Move to player manager
+ if (SupportsMouse())
+ {
+ auto it = std::find_if(m_controllers.Ports().begin(), m_controllers.Ports().end(),
+ [](const CControllerPortNode &port)
+ {
+ return port.PortType() == PORT_TYPE::MOUSE;
+ });
+
+ OpenMouse(it->CompatibleControllers().at(0).Controller());
+ }
- if (m_gameClient.SupportsMouse())
- OpenMouse();
+ // Open joysticks
+ //! @todo Move to player manager
+ for (const auto &port : m_controllers.Ports())
+ {
+ if (port.PortType() == PORT_TYPE::CONTROLLER && !port.CompatibleControllers().empty())
+ {
+ ControllerPtr controller = port.CompatibleControllers().at(0).Controller();
+ OpenJoystick(port.Address(), controller);
+ }
+ }
+
+ // Ensure hardware is open to receive events
+ m_hardware.reset(new CGameClientHardware(m_gameClient));
}
void CGameClientInput::Deinitialize()
{
- while (!m_joysticks.empty())
- ClosePort(m_joysticks.begin()->first);
-
m_hardware.reset();
- CloseKeyboard();
+ std::vector<std::string> ports;
+ for (const auto &it : m_joysticks)
+ ports.emplace_back(it.first);
+
+ for (const std::string &port : ports)
+ CloseJoystick(port);
+ m_portMap.clear();
CloseMouse();
+
+ CloseKeyboard();
}
bool CGameClientInput::AcceptsInput() const
@@ -77,115 +122,81 @@ bool CGameClientInput::AcceptsInput() const
return g_windowManager.GetActiveWindowOrDialog() == WINDOW_FULLSCREEN_GAME;
}
-bool CGameClientInput::OpenPort(unsigned int port)
+void CGameClientInput::LoadTopology()
{
- // Fail if port is already open
- if (m_joysticks.find(port) != m_joysticks.end())
- return false;
-
- // Ensure hardware is open to receive events from the port
- if (!m_hardware)
- m_hardware.reset(new CGameClientHardware(m_gameClient));
+ game_input_topology *topologyStruct = nullptr;
- ControllerVector controllers = GetControllers(m_gameClient);
- if (!controllers.empty())
+ if (m_gameClient.Initialized())
{
- //! @todo Choose controller
- ControllerPtr& controller = controllers[0];
+ try { topologyStruct = m_struct.toAddon.GetTopology(); }
+ catch (...) { m_gameClient.LogException("GetTopology()"); }
+ }
- m_joysticks[port].reset(new CGameClientJoystick(m_gameClient, port, controller, m_struct.toAddon));
+ GameClientPortVec hardwarePorts;
- //! @todo
- //CServiceBroker::GetGameServices().PortManager().OpenPort(m_joysticks[port].get(), m_hardware.get(), &m_gameClient, port, device);
+ if (topologyStruct != nullptr)
+ {
+ //! @todo Guard against infinite loops provided by the game client
- UpdatePort(port, controller);
+ 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]));
+ }
- return true;
+ m_playerLimit = topologyStruct->player_limit;
+
+ try { m_struct.toAddon.FreeTopology(topologyStruct); }
+ catch (...) { m_gameClient.LogException("FreeTopology()"); }
}
- return false;
+ // 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)));
+
+ CGameClientTopology topology(std::move(hardwarePorts));
+ m_controllers = topology.GetControllerTree();
}
-void CGameClientInput::ClosePort(unsigned int port)
+bool CGameClientInput::SupportsKeyboard() const
{
- // Can't close port if it doesn't exist
- if (m_joysticks.find(port) == m_joysticks.end())
- return;
-
- //! @todo
- //CServiceBroker::GetGameServices().PortManager().ClosePort(m_joysticks[port].get());
-
- m_joysticks.erase(port);
+ auto it = std::find_if(m_controllers.Ports().begin(), m_controllers.Ports().end(),
+ [](const CControllerPortNode &port)
+ {
+ return port.PortType() == PORT_TYPE::KEYBOARD;
+ });
- UpdatePort(port, CController::EmptyPtr);
+ return it != m_controllers.Ports().end() && !it->CompatibleControllers().empty();
}
-bool CGameClientInput::ReceiveInputEvent(const game_input_event& event)
+bool CGameClientInput::SupportsMouse() const
{
- bool bHandled = false;
-
- switch (event.type)
- {
- case GAME_INPUT_EVENT_MOTOR:
- if (event.feature_name)
- bHandled = SetRumble(event.port, event.feature_name, event.motor.magnitude);
- break;
- default:
- break;
- }
+ auto it = std::find_if(m_controllers.Ports().begin(), m_controllers.Ports().end(),
+ [](const CControllerPortNode &port)
+ {
+ return port.PortType() == PORT_TYPE::MOUSE;
+ });
- return bHandled;
+ return it != m_controllers.Ports().end() && !it->CompatibleControllers().empty();
}
-void CGameClientInput::UpdatePort(unsigned int port, const ControllerPtr& controller)
+bool CGameClientInput::OpenKeyboard(const ControllerPtr &controller)
{
using namespace JOYSTICK;
- CSingleLock lock(m_clientAccess);
-
- if (m_gameClient.Initialized())
+ if (!controller)
{
- if (controller != CController::EmptyPtr)
- {
- std::string strId = controller->ID();
-
- game_controller controllerStruct;
-
- controllerStruct.controller_id = strId.c_str();
- controllerStruct.digital_button_count = controller->FeatureCount(FEATURE_TYPE::SCALAR, INPUT_TYPE::DIGITAL);
- controllerStruct.analog_button_count = controller->FeatureCount(FEATURE_TYPE::SCALAR, INPUT_TYPE::ANALOG);
- controllerStruct.analog_stick_count = controller->FeatureCount(FEATURE_TYPE::ANALOG_STICK);
- controllerStruct.accelerometer_count = controller->FeatureCount(FEATURE_TYPE::ACCELEROMETER);
- controllerStruct.key_count = controller->FeatureCount(FEATURE_TYPE::KEY);
- controllerStruct.rel_pointer_count = controller->FeatureCount(FEATURE_TYPE::RELPOINTER);
- controllerStruct.abs_pointer_count = controller->FeatureCount(FEATURE_TYPE::ABSPOINTER);
- controllerStruct.motor_count = controller->FeatureCount(FEATURE_TYPE::MOTOR);
-
- try { m_struct.toAddon.UpdatePort(port, true, &controllerStruct); }
- catch (...) { m_gameClient.LogException("UpdatePort()"); }
- }
- else
- {
- try { m_struct.toAddon.UpdatePort(port, false, nullptr); }
- catch (...) { m_gameClient.LogException("UpdatePort()"); }
- }
+ CLog::Log(LOGERROR, "Failed to open keyboard, no controller given");
+ return false;
}
-}
-
-void CGameClientInput::OpenKeyboard()
-{
- using namespace JOYSTICK;
//! @todo Move to player manager
PERIPHERALS::PeripheralVector keyboards;
CServiceBroker::GetPeripherals().GetPeripheralsWithFeature(keyboards, PERIPHERALS::FEATURE_KEYBOARD);
-
if (keyboards.empty())
- return;
-
- CGameServices& gameServices = CServiceBroker::GetGameServices();
-
- ControllerPtr controller = gameServices.GetDefaultKeyboard(); //! @todo
+ return false;
std::string controllerId = controller->ID();
@@ -220,7 +231,12 @@ void CGameClientInput::OpenKeyboard()
}
if (bSuccess)
+ {
m_keyboard.reset(new CGameClientKeyboard(m_gameClient, controllerId, m_struct.toAddon, keyboards.at(0).get()));
+ return true;
+ }
+
+ return false;
}
void CGameClientInput::CloseKeyboard()
@@ -244,20 +260,21 @@ void CGameClientInput::CloseKeyboard()
}
}
-void CGameClientInput::OpenMouse()
+bool CGameClientInput::OpenMouse(const ControllerPtr &controller)
{
using namespace JOYSTICK;
+ if (!controller)
+ {
+ CLog::Log(LOGERROR, "Failed to open mouse, no controller given");
+ return false;
+ }
+
//! @todo Move to player manager
PERIPHERALS::PeripheralVector mice;
CServiceBroker::GetPeripherals().GetPeripheralsWithFeature(mice, PERIPHERALS::FEATURE_MOUSE);
-
if (mice.empty())
- return;
-
- CGameServices& gameServices = CServiceBroker::GetGameServices();
-
- ControllerPtr controller = gameServices.GetDefaultMouse(); //! @todo
+ return false;
std::string controllerId = controller->ID();
@@ -292,7 +309,12 @@ void CGameClientInput::OpenMouse()
}
if (bSuccess)
+ {
m_mouse.reset(new CGameClientMouse(m_gameClient, controllerId, m_struct.toAddon, mice.at(0).get()));
+ return true;
+ }
+
+ return false;
}
void CGameClientInput::CloseMouse()
@@ -316,16 +338,209 @@ void CGameClientInput::CloseMouse()
}
}
-bool CGameClientInput::SetRumble(unsigned int port, const std::string& feature, float magnitude)
+bool CGameClientInput::OpenJoystick(const std::string &portAddress, const ControllerPtr &controller)
+{
+ using namespace JOYSTICK;
+
+ if (!controller)
+ {
+ CLog::Log(LOGERROR, "Failed to open port \"%s\", no controller given", portAddress.c_str());
+ return false;
+ }
+
+ const CControllerPortNode &port = m_controllers.GetPort(portAddress);
+ if (!port.IsControllerAccepted(portAddress, controller->ID()))
+ {
+ CLog::Log(LOGERROR, "Failed to open port: Invalid controller \"%s\" on port \"%s\"",
+ controller->ID().c_str(), portAddress.c_str());
+ return false;
+ }
+
+ std::string strId = controller->ID();
+
+ game_controller controllerStruct{};
+
+ controllerStruct.controller_id = strId.c_str();
+ controllerStruct.provides_input = controller->Topology().ProvidesInput();
+ controllerStruct.digital_button_count = controller->FeatureCount(FEATURE_TYPE::SCALAR, INPUT_TYPE::DIGITAL);
+ controllerStruct.analog_button_count = controller->FeatureCount(FEATURE_TYPE::SCALAR, INPUT_TYPE::ANALOG);
+ controllerStruct.analog_stick_count = controller->FeatureCount(FEATURE_TYPE::ANALOG_STICK);
+ controllerStruct.accelerometer_count = controller->FeatureCount(FEATURE_TYPE::ACCELEROMETER);
+ controllerStruct.key_count = controller->FeatureCount(FEATURE_TYPE::KEY);
+ controllerStruct.rel_pointer_count = controller->FeatureCount(FEATURE_TYPE::RELPOINTER);
+ controllerStruct.abs_pointer_count = controller->FeatureCount(FEATURE_TYPE::ABSPOINTER);
+ controllerStruct.motor_count = controller->FeatureCount(FEATURE_TYPE::MOTOR);
+
+ bool bSuccess = false;
+
+ {
+ CSingleLock lock(m_clientAccess);
+
+ if (m_gameClient.Initialized())
+ {
+ try
+ {
+ bSuccess = m_struct.toAddon.ConnectController(true, portAddress.c_str(), &controllerStruct);
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("ConnectController()");
+ }
+ }
+ }
+
+ if (bSuccess)
+ {
+ m_joysticks[portAddress].reset(new CGameClientJoystick(m_gameClient, portAddress, controller, m_struct.toAddon));
+ ProcessJoysticks();
+ return true;
+ }
+
+ return false;
+}
+
+void CGameClientInput::CloseJoystick(const std::string &portAddress)
+{
+ auto it = m_joysticks.find(portAddress);
+ if (it != m_joysticks.end())
+ {
+ std::unique_ptr<CGameClientJoystick> joystick = std::move(it->second);
+ m_joysticks.erase(it);
+ ProcessJoysticks();
+ }
+
+ {
+ CSingleLock lock(m_clientAccess);
+
+ if (m_gameClient.Initialized())
+ {
+ try
+ {
+ m_struct.toAddon.ConnectController(false, portAddress.c_str(), nullptr);
+ }
+ catch (...)
+ {
+ m_gameClient.LogException("ConnectController()");
+ }
+ }
+ }
+}
+
+void CGameClientInput::HardwareReset()
+{
+ if (m_hardware)
+ m_hardware->OnResetButton();
+}
+
+bool CGameClientInput::ReceiveInputEvent(const game_input_event& event)
{
bool bHandled = false;
- if (m_joysticks.find(port) != m_joysticks.end())
- bHandled = m_joysticks[port]->SetRumble(feature, magnitude);
+ 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;
}
+void CGameClientInput::Notify(const Observable& obs, const ObservableMessage msg)
+{
+ switch (msg)
+ {
+ case ObservableMessagePeripheralsChanged:
+ {
+ ProcessJoysticks();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void CGameClientInput::ProcessJoysticks()
+{
+ PERIPHERALS::PeripheralVector joysticks;
+ CServiceBroker::GetPeripherals().GetPeripheralsWithFeature(joysticks, PERIPHERALS::FEATURE_JOYSTICK);
+
+ // Perform the port mapping
+ PortMap newPortMap = MapJoysticks(joysticks, m_joysticks);
+
+ // Update each joystick
+ for (auto& peripheralJoystick : joysticks)
+ {
+ // Upcast to input interface
+ JOYSTICK::IInputProvider *inputProvider = peripheralJoystick.get();
+
+ auto itConnectedPort = newPortMap.find(inputProvider);
+ auto itDisconnectedPort = m_portMap.find(inputProvider);
+
+ CGameClientJoystick* newJoystick = itConnectedPort != newPortMap.end() ? itConnectedPort->second : nullptr;
+ CGameClientJoystick* oldJoystick = itDisconnectedPort != m_portMap.end() ? itDisconnectedPort->second : nullptr;
+
+ if (oldJoystick != newJoystick)
+ {
+ // Unregister old input handler
+ if (oldJoystick != nullptr)
+ {
+ oldJoystick->UnregisterInput(inputProvider);
+ m_portMap.erase(itDisconnectedPort);
+ }
+
+ // Register new handler
+ if (newJoystick != nullptr)
+ {
+ newJoystick->RegisterInput(inputProvider);
+ m_portMap[inputProvider] = newJoystick;
+ }
+ }
+ }
+}
+
+CGameClientInput::PortMap CGameClientInput::MapJoysticks(const PERIPHERALS::PeripheralVector &peripheralJoysticks,
+ const JoystickMap &gameClientjoysticks) const
+{
+ PortMap result;
+
+ //! @todo Preserve existing joystick ports
+
+ unsigned int i = 0;
+ for (const auto &it : gameClientjoysticks)
+ {
+ if (i >= peripheralJoysticks.size())
+ break;
+
+ // Check topology player limit
+ if (m_playerLimit >= 0 && static_cast<int>(i) >= m_playerLimit)
+ break;
+
+ // Dereference iterators
+ const PERIPHERALS::PeripheralPtr &peripheralJoystick = peripheralJoysticks[i++];
+ const std::unique_ptr<CGameClientJoystick> &gameClientJoystick = it.second;
+
+ // Map input provider to input handler
+ result[peripheralJoystick.get()] = gameClientJoystick.get();
+ }
+
+ return result;
+}
+
ControllerVector CGameClientInput::GetControllers(const CGameClient &gameClient)
{
using namespace ADDON;
diff --git a/xbmc/games/addons/input/GameClientInput.h b/xbmc/games/addons/input/GameClientInput.h
index a1ad38c268..0b3215a05e 100644
--- a/xbmc/games/addons/input/GameClientInput.h
+++ b/xbmc/games/addons/input/GameClientInput.h
@@ -20,7 +20,10 @@
#pragma once
#include "games/addons/GameClientSubsystem.h"
+#include "games/controllers/types/ControllerTree.h"
#include "games/controllers/ControllerTypes.h"
+#include "peripherals/PeripheralTypes.h"
+#include "utils/Observer.h"
#include <map>
#include <memory>
@@ -31,6 +34,11 @@ struct game_input_event;
namespace KODI
{
+namespace JOYSTICK
+{
+ class IInputProvider;
+}
+
namespace GAME
{
class CGameClient;
@@ -39,7 +47,8 @@ namespace GAME
class CGameClientKeyboard;
class CGameClientMouse;
- class CGameClientInput : protected CGameClientSubsystem
+ class CGameClientInput : protected CGameClientSubsystem,
+ public Observer
{
public:
CGameClientInput(CGameClient &gameClient,
@@ -53,30 +62,57 @@ namespace GAME
// Input functions
bool AcceptsInput() const;
+ // Topology functions
+ const CControllerTree &GetControllerTree() const { return m_controllers; }
+ bool SupportsKeyboard() const;
+ bool SupportsMouse() const;
+
+ // Keyboard functions
+ bool OpenKeyboard(const ControllerPtr &controller);
+ void CloseKeyboard();
+
+ // Mouse functions
+ bool OpenMouse(const ControllerPtr &controller);
+ void CloseMouse();
+
+ // Joystick functions
+ bool OpenJoystick(const std::string &portAddress, const ControllerPtr &controller);
+ void CloseJoystick(const std::string &portAddress);
+
+ // Hardware input functions
+ void HardwareReset();
+
// Input callbacks
- bool OpenPort(unsigned int port);
- void ClosePort(unsigned int port);
bool ReceiveInputEvent(const game_input_event& eventStruct);
+ // Implementation of Observer
+ void Notify(const Observable& obs, const ObservableMessage msg) override;
+
private:
+ using PortAddress = std::string;
+ using JoystickMap = std::map<PortAddress, std::unique_ptr<CGameClientJoystick>>;
+ using PortMap = std::map<JOYSTICK::IInputProvider*, CGameClientJoystick*>;
+
// Private input helpers
- void UpdatePort(unsigned int port, const ControllerPtr& controller);
- void OpenKeyboard();
- void CloseKeyboard();
- void OpenMouse();
- void CloseMouse();
+ void LoadTopology();
+ void ProcessJoysticks();
+ PortMap MapJoysticks(const PERIPHERALS::PeripheralVector &peripheralJoysticks,
+ const JoystickMap &gameClientjoysticks) const;
// Private callback helpers
- bool SetRumble(unsigned int port, const std::string& feature, float magnitude);
+ bool SetRumble(const std::string &portAddress, const std::string& feature, float magnitude);
// Helper functions
static ControllerVector GetControllers(const CGameClient &gameClient);
// Input properties
- std::map<int, std::unique_ptr<CGameClientJoystick>> m_joysticks;
+ CControllerTree m_controllers;
+ JoystickMap m_joysticks;
+ PortMap m_portMap;
std::unique_ptr<CGameClientKeyboard> m_keyboard;
std::unique_ptr<CGameClientMouse> m_mouse;
std::unique_ptr<CGameClientHardware> m_hardware;
+ int m_playerLimit = -1; // No limit
};
} // namespace GAME
} // namespace KODI
diff --git a/xbmc/games/addons/input/GameClientJoystick.cpp b/xbmc/games/addons/input/GameClientJoystick.cpp
index 6d4c2002b4..093f5add20 100644
--- a/xbmc/games/addons/input/GameClientJoystick.cpp
+++ b/xbmc/games/addons/input/GameClientJoystick.cpp
@@ -22,6 +22,7 @@
#include "GameClientInput.h"
#include "games/addons/GameClient.h"
#include "games/controllers/Controller.h"
+#include "games/ports/Port.h"
#include "input/joysticks/interfaces/IInputReceiver.h"
#include "utils/log.h"
@@ -31,17 +32,30 @@ using namespace KODI;
using namespace GAME;
CGameClientJoystick::CGameClientJoystick(const CGameClient &gameClient,
- int port,
+ const std::string &portAddress,
const ControllerPtr& controller,
const KodiToAddonFuncTable_Game &dllStruct) :
m_gameClient(gameClient),
- m_port(port),
+ m_portAddress(portAddress),
m_controller(controller),
- m_dllStruct(dllStruct)
+ m_dllStruct(dllStruct),
+ m_port(new CPort(this))
{
assert(m_controller.get() != NULL);
}
+CGameClientJoystick::~CGameClientJoystick() = default;
+
+void CGameClientJoystick::RegisterInput(JOYSTICK::IInputProvider *inputProvider)
+{
+ m_port->RegisterInput(inputProvider);
+}
+
+void CGameClientJoystick::UnregisterInput(JOYSTICK::IInputProvider *inputProvider)
+{
+ m_port->UnregisterInput(inputProvider);
+}
+
std::string CGameClientJoystick::ControllerID(void) const
{
return m_controller->ID();
@@ -75,8 +89,9 @@ bool CGameClientJoystick::OnButtonPress(const std::string& feature, bool bPresse
std::string controllerId = m_controller->ID();
event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON;
- event.port = m_port;
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;
@@ -101,8 +116,9 @@ bool CGameClientJoystick::OnButtonMotion(const std::string& feature, float magni
std::string controllerId = m_controller->ID();
event.type = GAME_INPUT_EVENT_ANALOG_BUTTON;
- event.port = m_port;
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;
@@ -127,8 +143,9 @@ bool CGameClientJoystick::OnAnalogStickMotion(const std::string& feature, float
std::string controllerId = m_controller->ID();
event.type = GAME_INPUT_EVENT_ANALOG_STICK;
- event.port = m_port;
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;
@@ -154,8 +171,9 @@ bool CGameClientJoystick::OnAccelerometerMotion(const std::string& feature, floa
std::string controllerId = m_controller->ID();
event.type = GAME_INPUT_EVENT_ACCELEROMETER;
- event.port = m_port;
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;
@@ -182,8 +200,9 @@ bool CGameClientJoystick::OnWheelMotion(const std::string& feature, float positi
std::string controllerId = m_controller->ID();
event.type = GAME_INPUT_EVENT_AXIS;
- event.port = m_port;
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;
@@ -209,8 +228,9 @@ bool CGameClientJoystick::OnThrottleMotion(const std::string& feature, float pos
std::string controllerId = m_controller->ID();
event.type = GAME_INPUT_EVENT_AXIS;
- event.port = m_port;
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;
diff --git a/xbmc/games/addons/input/GameClientJoystick.h b/xbmc/games/addons/input/GameClientJoystick.h
index c8abe1fb66..e1c0152586 100644
--- a/xbmc/games/addons/input/GameClientJoystick.h
+++ b/xbmc/games/addons/input/GameClientJoystick.h
@@ -22,13 +22,21 @@
#include "games/controllers/ControllerTypes.h"
#include "input/joysticks/interfaces/IInputHandler.h"
+#include <memory>
+
struct KodiToAddonFuncTable_Game;
namespace KODI
{
+namespace JOYSTICK
+{
+ class IInputProvider;
+}
+
namespace GAME
{
class CGameClient;
+ class CPort;
/*!
* \ingroup games
@@ -47,11 +55,14 @@ namespace GAME
* \param dllStruct The emulator or game to which the events are sent.
*/
CGameClientJoystick(const CGameClient &addon,
- int port,
+ const std::string &portAddress,
const ControllerPtr& controller,
const KodiToAddonFuncTable_Game &dllStruct);
- virtual ~CGameClientJoystick() = default;
+ ~CGameClientJoystick() override;
+
+ void RegisterInput(JOYSTICK::IInputProvider *inputProvider);
+ void UnregisterInput(JOYSTICK::IInputProvider *inputProvider);
// Implementation of IInputHandler
virtual std::string ControllerID(void) const override;
@@ -68,10 +79,14 @@ namespace GAME
bool SetRumble(const std::string& feature, float magnitude);
private:
+ // Construction parameters
const CGameClient &m_gameClient;
- const int m_port;
+ const std::string m_portAddress;
const ControllerPtr m_controller;
const KodiToAddonFuncTable_Game &m_dllStruct;
+
+ // Input parameters
+ std::unique_ptr<CPort> m_port;
};
}
}
diff --git a/xbmc/games/addons/input/GameClientKeyboard.cpp b/xbmc/games/addons/input/GameClientKeyboard.cpp
index 51c011e084..c2a91b54aa 100644
--- a/xbmc/games/addons/input/GameClientKeyboard.cpp
+++ b/xbmc/games/addons/input/GameClientKeyboard.cpp
@@ -84,8 +84,9 @@ bool CGameClientKeyboard::OnKeyPress(const KEYBOARD::KeyName &key, KEYBOARD::Mod
game_input_event event;
event.type = GAME_INPUT_EVENT_KEY;
- event.port = 0; //! @todo Remove in port refactor
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;
@@ -108,8 +109,9 @@ void CGameClientKeyboard::OnKeyRelease(const KEYBOARD::KeyName &key, KEYBOARD::M
game_input_event event;
event.type = GAME_INPUT_EVENT_KEY;
- event.port = 0; //! @todo Remove in port refactor
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;
diff --git a/xbmc/games/addons/input/GameClientMouse.cpp b/xbmc/games/addons/input/GameClientMouse.cpp
index 1a37d8995a..40e6e417c0 100644
--- a/xbmc/games/addons/input/GameClientMouse.cpp
+++ b/xbmc/games/addons/input/GameClientMouse.cpp
@@ -68,8 +68,9 @@ bool CGameClientMouse::OnMotion(const std::string& relpointer, int dx, int dy)
game_input_event event;
event.type = GAME_INPUT_EVENT_RELATIVE_POINTER;
- event.port = -1; //! @todo Remove in port refactor
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;
@@ -99,8 +100,9 @@ bool CGameClientMouse::OnButtonPress(const std::string& button)
game_input_event event;
event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON;
- event.port = -1; //! @todo Remove in port refactor
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;
@@ -121,8 +123,9 @@ void CGameClientMouse::OnButtonRelease(const std::string& button)
game_input_event event;
event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON;
- event.port = -1; //! @todo Remove in port refactor
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;
diff --git a/xbmc/games/addons/input/GameClientPort.cpp b/xbmc/games/addons/input/GameClientPort.cpp
new file mode 100644
index 0000000000..dc28d77017
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientPort.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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 "GameClientPort.h"
+#include "GameClientDevice.h"
+#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h"
+#include "games/addons/GameClientTranslator.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerTopology.h"
+#include "games/controllers/ControllerTranslator.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 : "")
+{
+ 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 CControllerPort &physicalPort) :
+ m_type(PORT_TYPE::CONTROLLER),
+ m_portId(physicalPort.ID())
+{
+ 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 0000000000..9f58717eb0
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientPort.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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 "games/controllers/ControllerTypes.h"
+#include "games/GameTypes.h"
+
+#include <string>
+
+struct game_input_device;
+struct game_input_port;
+
+namespace KODI
+{
+namespace GAME
+{
+ class CControllerPort;
+
+ /*!
+ * \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 CControllerPort &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 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;
+ GameClientDeviceVec m_acceptedDevices;
+ };
+}
+}
diff --git a/xbmc/games/addons/input/GameClientTopology.cpp b/xbmc/games/addons/input/GameClientTopology.cpp
new file mode 100644
index 0000000000..65a9b3780b
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientTopology.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 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 "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) :
+ m_ports(std::move(ports))
+{
+}
+
+CControllerTree CGameClientTopology::GetControllerTree() const
+{
+ return GetControllerTree(m_ports);
+}
+
+CControllerTree CGameClientTopology::GetControllerTree(const GameClientPortVec &ports)
+{
+ CControllerTree tree;
+
+ ControllerPortVec controllerPorts;
+ for (const GameClientPortPtr &port : ports)
+ {
+ CControllerPortNode portNode = GetPortNode(port, "");
+ controllerPorts.emplace_back(std::move(portNode));
+ }
+
+ tree.SetPorts(std::move(controllerPorts));
+
+ return tree;
+}
+
+CControllerPortNode CGameClientTopology::GetPortNode(const GameClientPortPtr &port, const std::string &address)
+{
+ CControllerPortNode portNode;
+
+ std::string portAddress = MakeAddress(address, port->ID());
+
+ portNode.SetConnected(false);
+ portNode.SetPortType(port->PortType());
+ portNode.SetPortID(port->ID());
+ portNode.SetAddress(portAddress);
+
+ 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 &address)
+{
+ CControllerNode controllerNode;
+
+ std::string controllerAddress = MakeAddress(address, device->Controller()->ID());
+
+ controllerNode.SetController(device->Controller());
+ controllerNode.SetAddress(controllerAddress);
+
+ ControllerPortVec ports;
+ for (const GameClientPortPtr &port : device->Ports())
+ {
+ CControllerPortNode 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 << CONTROLLER_ADDRESS_SEPARATOR;
+
+ address << 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 0000000000..ad4ef52f7d
--- /dev/null
+++ b/xbmc/games/addons/input/GameClientTopology.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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 "games/controllers/types/ControllerTree.h"
+#include "games/GameTypes.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace GAME
+{
+ class CGameClientTopology
+ {
+ public:
+ CGameClientTopology(GameClientPortVec ports);
+
+ CControllerTree GetControllerTree() const;
+
+ private:
+ static CControllerTree GetControllerTree(const GameClientPortVec &ports);
+ static CControllerPortNode GetPortNode(const GameClientPortPtr &port, const std::string &address);
+ static CControllerNode GetControllerNode(const GameClientDevicePtr &device, const std::string &portAddress);
+
+ // Utility function
+ static std::string MakeAddress(const std::string &baseAddress, const std::string &nodeId);
+
+ GameClientPortVec m_ports;
+ };
+}
+}
diff --git a/xbmc/games/controllers/CMakeLists.txt b/xbmc/games/controllers/CMakeLists.txt
index 899ffb733a..d9c4d1cf97 100644
--- a/xbmc/games/controllers/CMakeLists.txt
+++ b/xbmc/games/controllers/CMakeLists.txt
@@ -2,6 +2,7 @@ set(SOURCES Controller.cpp
ControllerFeature.cpp
ControllerLayout.cpp
ControllerManager.cpp
+ ControllerTopology.cpp
ControllerTranslator.cpp)
set(HEADERS Controller.h
@@ -10,6 +11,7 @@ set(HEADERS Controller.h
ControllerIDs.h
ControllerLayout.h
ControllerManager.h
+ ControllerTopology.h
ControllerTranslator.h
ControllerTypes.h)
diff --git a/xbmc/games/controllers/Controller.cpp b/xbmc/games/controllers/Controller.cpp
index 6b90773812..65aa8bdb16 100644
--- a/xbmc/games/controllers/Controller.cpp
+++ b/xbmc/games/controllers/Controller.cpp
@@ -21,6 +21,7 @@
#include "Controller.h"
#include "ControllerDefinitions.h"
#include "ControllerLayout.h"
+#include "ControllerTopology.h"
#include "utils/log.h"
#include "utils/URIUtils.h"
#include "utils/XBMCTinyXML.h"
@@ -79,6 +80,21 @@ CController::CController(ADDON::CAddonInfo addonInfo) :
CController::~CController() = default;
+const CControllerFeature& CController::GetFeature(const std::string &name) const
+{
+ auto it = std::find_if(m_features.begin(), m_features.end(),
+ [&name](const CControllerFeature &feature)
+ {
+ return name == feature.Name();
+ });
+
+ if (it != m_features.end())
+ return *it;
+
+ static const CControllerFeature invalid{};
+ return invalid;
+}
+
unsigned int CController::FeatureCount(FEATURE_TYPE type /* = FEATURE_TYPE::UNKNOWN */,
JOYSTICK::INPUT_TYPE inputType /* = JOYSTICK::INPUT_TYPE::UNKNOWN */) const
{
@@ -150,3 +166,8 @@ bool CController::LoadLayout(void)
return m_bLoaded;
}
+
+const CControllerTopology& CController::Topology() const
+{
+ return m_layout->Topology();
+}
diff --git a/xbmc/games/controllers/Controller.h b/xbmc/games/controllers/Controller.h
index 2882b2167b..d9c8715827 100644
--- a/xbmc/games/controllers/Controller.h
+++ b/xbmc/games/controllers/Controller.h
@@ -34,6 +34,7 @@ namespace KODI
namespace GAME
{
class CControllerLayout;
+class CControllerTopology;
using JOYSTICK::FEATURE_TYPE;
@@ -56,6 +57,15 @@ public:
const std::vector<CControllerFeature>& Features(void) const { return m_features; }
/*!
+ * \brief Get a feature by its name
+ *
+ * \param name The feature name
+ *
+ * \return The feature, or a feature of type FEATURE_TYPE::UNKNOWN if the name is invalid
+ */
+ const CControllerFeature& GetFeature(const std::string &name) const;
+
+ /*!
* \brief Get the count of controller features matching the specified types
*
* \param type The feature type, or FEATURE_TYPE::UNKNOWN to match all feature types
@@ -103,6 +113,15 @@ public:
*/
const CControllerLayout& Layout(void) const { return *m_layout; }
+ /*!
+ * \brief Get the controller's physical topology
+ *
+ * This defines how controllers physically connect to each other.
+ *
+ * \return The physical topology of the controller
+ */
+ const CControllerTopology& Topology() const;
+
private:
std::unique_ptr<CControllerLayout> m_layout;
std::vector<CControllerFeature> m_features;
diff --git a/xbmc/games/controllers/ControllerDefinitions.h b/xbmc/games/controllers/ControllerDefinitions.h
index bb4dd52ae1..4ed74ccacb 100644
--- a/xbmc/games/controllers/ControllerDefinitions.h
+++ b/xbmc/games/controllers/ControllerDefinitions.h
@@ -31,6 +31,9 @@
#define LAYOUT_XML_ELM_WHEEL "wheel"
#define LAYOUT_XML_ELM_THROTTLE "throttle"
#define LAYOUT_XML_ELM_KEY "key"
+#define LAYOUT_XML_ELM_TOPOLOGY "physicaltopology"
+#define LAYOUT_XML_ELM_PORT "port"
+#define LAYOUT_XML_ELM_ACCEPTS "accepts"
#define LAYOUT_XML_ATTR_LAYOUT_LABEL "label"
#define LAYOUT_XML_ATTR_LAYOUT_ICON "icon"
#define LAYOUT_XML_ATTR_LAYOUT_IMAGE "image"
@@ -40,6 +43,9 @@
#define LAYOUT_XML_ATTR_FEATURE_LABEL "label"
#define LAYOUT_XML_ATTR_INPUT_TYPE "type"
#define LAYOUT_XML_ATTR_KEY_SYMBOL "symbol"
+#define LAYOUT_XML_ATTR_PROVIDES_INPUT "providesinput"
+#define LAYOUT_XML_ATTR_PORT_ID "id"
+#define LAYOUT_XML_ATTR_CONTROLLER "controller"
// Controller definitions
#define FEATURE_CATEGORY_FACE "face"
diff --git a/xbmc/games/controllers/ControllerLayout.cpp b/xbmc/games/controllers/ControllerLayout.cpp
index d88f472552..26a47e87a9 100644
--- a/xbmc/games/controllers/ControllerLayout.cpp
+++ b/xbmc/games/controllers/ControllerLayout.cpp
@@ -21,6 +21,7 @@
#include "ControllerLayout.h"
#include "Controller.h"
#include "ControllerDefinitions.h"
+#include "ControllerTopology.h"
#include "ControllerTranslator.h"
#include "guilib/LocalizeStrings.h"
#include "utils/log.h"
@@ -32,12 +33,29 @@
using namespace KODI;
using namespace GAME;
+CControllerLayout::CControllerLayout() :
+ m_topology(new CControllerTopology)
+{
+}
+
+CControllerLayout::CControllerLayout(const CControllerLayout &other) :
+ m_controller(other.m_controller),
+ m_labelId(other.m_labelId),
+ m_icon(other.m_icon),
+ m_strImage(other.m_strImage),
+ m_topology(new CControllerTopology(*other.m_topology))
+{
+}
+
+CControllerLayout::~CControllerLayout() = default;
+
void CControllerLayout::Reset(void)
{
m_controller = nullptr;
m_labelId = -1;
m_icon.clear();
m_strImage.clear();
+ m_topology->Reset();
}
bool CControllerLayout::IsValid(bool bLog) const
@@ -106,7 +124,6 @@ void CControllerLayout::Deserialize(const TiXmlElement* pElement, const CControl
if (!image.empty())
m_strImage = image;
- // Features
for (const TiXmlElement* pChild = pElement->FirstChildElement(); pChild != nullptr; pChild = pChild->NextSiblingElement())
{
if (pChild->ValueStr() == LAYOUT_XML_ELM_CATEGORY)
@@ -122,6 +139,7 @@ void CControllerLayout::Deserialize(const TiXmlElement* pElement, const CControl
if (!strCategoryLabelId.empty())
std::istringstream(strCategoryLabelId) >> categoryLabelId;
+ // Features
for (const TiXmlElement* pFeature = pChild->FirstChildElement(); pFeature != nullptr; pFeature = pFeature->NextSiblingElement())
{
CControllerFeature feature;
@@ -130,6 +148,13 @@ void CControllerLayout::Deserialize(const TiXmlElement* pElement, const CControl
features.push_back(feature);
}
}
+ else if (pChild->ValueStr() == LAYOUT_XML_ELM_TOPOLOGY)
+ {
+ // Topology
+ CControllerTopology topology;
+ if (topology.Deserialize(pChild))
+ *m_topology = std::move(topology);
+ }
else
{
CLog::Log(LOGDEBUG, "Ignoring <%s> tag", pChild->ValueStr().c_str());
diff --git a/xbmc/games/controllers/ControllerLayout.h b/xbmc/games/controllers/ControllerLayout.h
index 72209273fb..89a31c2d01 100644
--- a/xbmc/games/controllers/ControllerLayout.h
+++ b/xbmc/games/controllers/ControllerLayout.h
@@ -19,6 +19,7 @@
*/
#pragma once
+#include <memory>
#include <string>
#include <vector>
@@ -30,11 +31,14 @@ namespace GAME
{
class CController;
class CControllerFeature;
+class CControllerTopology;
class CControllerLayout
{
public:
- CControllerLayout() = default;
+ CControllerLayout();
+ CControllerLayout(const CControllerLayout &other);
+ ~CControllerLayout();
void Reset(void);
@@ -66,6 +70,17 @@ public:
std::string ImagePath(void) const;
/*!
+ * \brief Get the physical topology of this controller
+ *
+ * The topology of a controller defines its ports and which controllers can
+ * physically be connected to them. Also, the topology defines if the
+ * controller can provide player input, which is false in the case of hubs.
+ *
+ * \return The physical topology of the controller
+ */
+ const CControllerTopology &Topology(void) const { return *m_topology; }
+
+ /*!
* \brief Deserialize the specified XML element
*
* \param pLayoutElement The XML element
@@ -78,7 +93,8 @@ private:
const CController *m_controller = nullptr;
int m_labelId = -1;
std::string m_icon;
- std::string m_strImage;
+ std::string m_strImage;
+ std::unique_ptr<CControllerTopology> m_topology;
};
}
diff --git a/xbmc/games/controllers/ControllerTopology.cpp b/xbmc/games/controllers/ControllerTopology.cpp
new file mode 100644
index 0000000000..175213a0ad
--- /dev/null
+++ b/xbmc/games/controllers/ControllerTopology.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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 "ControllerTopology.h"
+#include "ControllerDefinitions.h"
+#include "games/controllers/Controller.h"
+#include "games/GameServices.h"
+#include "utils/log.h"
+#include "utils/XMLUtils.h"
+#include "ServiceManager.h"
+#include <algorithm>
+
+using namespace KODI;
+using namespace GAME;
+
+// --- CControllerPort ---------------------------------------------------------
+
+CControllerPort::CControllerPort(std::string portId, std::vector<std::string> accepts) :
+ m_portId(std::move(portId)),
+ m_accepts(std::move(accepts))
+{
+}
+
+void CControllerPort::Reset(void)
+{
+ CControllerPort defaultPort;
+ *this = std::move(defaultPort);
+}
+
+bool CControllerPort::IsCompatible(const std::string &controllerId) const
+{
+ return std::find(m_accepts.begin(), m_accepts.end(), controllerId) != m_accepts.end();
+}
+
+bool CControllerPort::Deserialize(const TiXmlElement* pElement)
+{
+ Reset();
+
+ if (pElement == nullptr)
+ return false;
+
+ m_portId = XMLUtils::GetAttribute(pElement, LAYOUT_XML_ATTR_PORT_ID);
+
+ for (const TiXmlElement* pChild = pElement->FirstChildElement(); pChild != nullptr; pChild = pChild->NextSiblingElement())
+ {
+ if (pChild->ValueStr() == LAYOUT_XML_ELM_ACCEPTS)
+ {
+ std::string controller = XMLUtils::GetAttribute(pChild, LAYOUT_XML_ATTR_CONTROLLER);
+
+ if (!controller.empty())
+ m_accepts.emplace_back(std::move(controller));
+ else
+ CLog::Log(LOGWARNING, "<%s> tag is missing \"%s\" attribute", LAYOUT_XML_ELM_ACCEPTS, LAYOUT_XML_ATTR_CONTROLLER);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Unknown physical topology port tag: <%s>", pChild->ValueStr().c_str());
+ }
+ }
+
+ return true;
+}
+
+// --- CControllerTopology -----------------------------------------------------
+
+CControllerTopology::CControllerTopology(bool bProvidesInput, std::vector<CControllerPort> ports) :
+ m_bProvidesInput(bProvidesInput),
+ m_ports(std::move(ports))
+{
+}
+
+void CControllerTopology::Reset()
+{
+ CControllerTopology defaultTopology;
+ *this = std::move(defaultTopology);
+}
+
+bool CControllerTopology::Deserialize(const TiXmlElement* pElement)
+{
+ Reset();
+
+ if (pElement == nullptr)
+ return false;
+
+ m_bProvidesInput = (XMLUtils::GetAttribute(pElement, LAYOUT_XML_ATTR_PROVIDES_INPUT) != "false");
+
+ for (const TiXmlElement* pChild = pElement->FirstChildElement(); pChild != nullptr; pChild = pChild->NextSiblingElement())
+ {
+ if (pChild->ValueStr() == LAYOUT_XML_ELM_PORT)
+ {
+ CControllerPort port;
+ if (port.Deserialize(pChild))
+ m_ports.emplace_back(std::move(port));
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Unknown physical topology tag: <%s>", pChild->ValueStr().c_str());
+ }
+ }
+
+ return true;
+}
diff --git a/xbmc/games/controllers/ControllerTopology.h b/xbmc/games/controllers/ControllerTopology.h
new file mode 100644
index 0000000000..5fa25f8e44
--- /dev/null
+++ b/xbmc/games/controllers/ControllerTopology.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 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 "games/GameTypes.h"
+
+#include <string>
+#include <vector>
+
+class TiXmlElement;
+
+namespace KODI
+{
+namespace GAME
+{
+
+class CControllerPort
+{
+public:
+ CControllerPort() = default;
+
+ /*!
+ * \brief Create a controller port
+ *
+ * \param portId The port's ID
+ * \param accepts A list of controller IDs that this port accepts
+ */
+ CControllerPort(std::string portId, std::vector<std::string> accepts);
+
+ void Reset();
+
+ /*!
+ * \brief Get the ID of the port
+ *
+ * \return The port's ID, e.g. "1", as a string
+ */
+ const std::string &ID() const { return m_portId; }
+
+ /*!
+ * \brief Get the controllers that can connect to this port
+ *
+ * \return A list of controllers that are physically compatible with this port
+ */
+ const std::vector<std::string> &Accepts() const { return m_accepts; }
+
+ /*!
+ * \brief Check if the controller is compatible with this port
+ *
+ * \return True if the controller is accepted, false otherwise
+ */
+ bool IsCompatible(const std::string &controllerId) const;
+
+ bool Deserialize(const TiXmlElement* pElement);
+
+private:
+ std::string m_portId;
+ std::vector<std::string> m_accepts;
+};
+
+/*!
+ * \brief Represents the physical topology of controller add-ons
+ *
+ * The physical topology of a controller defines how many ports it has and
+ * whether it can provide player input (hubs like the Super Multitap don't
+ * provide input).
+ */
+class CControllerTopology
+{
+public:
+ CControllerTopology() = default;
+ CControllerTopology(bool bProvidesInput, std::vector<CControllerPort> ports);
+
+ void Reset();
+
+ /*!
+ * \brief Check if the controller can provide player input
+ *
+ * This allows hubs to specify that they provide no input
+ *
+ * \return True if the controller can provide player input, false otherwise
+ */
+ bool ProvidesInput() const { return m_bProvidesInput; }
+
+ /*!
+ * \brief Get a list of ports provided by this controller
+ *
+ * \return The ports
+ */
+ const std::vector<CControllerPort> &Ports() const { return m_ports; }
+
+ bool Deserialize(const TiXmlElement* pElement);
+
+private:
+ bool m_bProvidesInput = true;
+ std::vector<CControllerPort> m_ports;
+};
+
+}
+}
diff --git a/xbmc/games/controllers/ControllerTypes.h b/xbmc/games/controllers/ControllerTypes.h
index 9785d66250..adf1a4e763 100644
--- a/xbmc/games/controllers/ControllerTypes.h
+++ b/xbmc/games/controllers/ControllerTypes.h
@@ -29,5 +29,16 @@ namespace GAME
class CController;
using ControllerPtr = std::shared_ptr<CController>;
using ControllerVector = std::vector<ControllerPtr>;
+
+ /*!
+ * \brief Type of input provided by a hardware or controller port
+ */
+ enum class PORT_TYPE
+ {
+ UNKNOWN,
+ KEYBOARD,
+ MOUSE,
+ CONTROLLER,
+ };
}
}
diff --git a/xbmc/games/controllers/types/CMakeLists.txt b/xbmc/games/controllers/types/CMakeLists.txt
new file mode 100644
index 0000000000..24dfed9044
--- /dev/null
+++ b/xbmc/games/controllers/types/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES ControllerGrid.cpp
+ ControllerTree.cpp
+)
+
+set(HEADERS ControllerGrid.h
+ ControllerTree.h
+)
+
+core_add_library(games_controller_types)
diff --git a/xbmc/games/controllers/types/ControllerGrid.cpp b/xbmc/games/controllers/types/ControllerGrid.cpp
new file mode 100644
index 0000000000..1963276af9
--- /dev/null
+++ b/xbmc/games/controllers/types/ControllerGrid.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2017 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 "ControllerGrid.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerTranslator.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <utility>
+
+using namespace KODI;
+using namespace GAME;
+
+CControllerGrid::CControllerGrid()
+{
+}
+
+CControllerGrid::CControllerGrid(const CControllerGrid &other) :
+ m_grid(other.m_grid),
+ m_height(other.m_height)
+{
+}
+
+CControllerGrid::~CControllerGrid() = default;
+
+void CControllerGrid::SetControllerTree(const CControllerTree &controllerTree)
+{
+ // Clear the result
+ m_grid.clear();
+
+ m_height = AddPorts(controllerTree.Ports(), m_grid);
+ SetHeight(m_height, m_grid);
+}
+
+ControllerVector CControllerGrid::GetControllers(unsigned int playerIndex) const
+{
+ ControllerVector controllers;
+
+ if (playerIndex < m_grid.size())
+ {
+ for (const auto &controllerVertex : m_grid[playerIndex].vertices)
+ {
+ if (controllerVertex.controller)
+ controllers.emplace_back(controllerVertex.controller);
+ }
+ }
+
+ return controllers;
+}
+
+unsigned int CControllerGrid::AddPorts(const ControllerPortVec &ports, ControllerGrid &grid)
+{
+ unsigned int height = 0;
+
+ auto itKeyboard = std::find_if(ports.begin(), ports.end(),
+ [](const CControllerPortNode &port)
+ {
+ return port.PortType() == PORT_TYPE::KEYBOARD;
+ });
+
+ auto itMouse = std::find_if(ports.begin(), ports.end(),
+ [](const CControllerPortNode &port)
+ {
+ return port.PortType() == PORT_TYPE::MOUSE;
+ });
+
+ auto itController = std::find_if(ports.begin(), ports.end(),
+ [](const CControllerPortNode &port)
+ {
+ return port.PortType() == PORT_TYPE::CONTROLLER;
+ });
+
+ // Keyboard and mouse are not allowed to have ports because they might
+ // overlap with controllers
+ if (itKeyboard != ports.end() && itKeyboard->ActiveController().Hub().HasPorts())
+ {
+ CLog::Log(LOGERROR, "Found keyboard with controller ports, skipping");
+ itKeyboard = ports.end();
+ }
+ if (itMouse != ports.end() && itMouse->ActiveController().Hub().HasPorts())
+ {
+ CLog::Log(LOGERROR, "Found mouse with controller ports, skipping");
+ itMouse = ports.end();
+ }
+
+ if (itController != ports.end())
+ {
+ // Add controller ports
+ bool bFirstPlayer = true;
+ for (const CControllerPortNode &port : ports)
+ {
+ ControllerColumn column;
+
+ if (port.PortType() == PORT_TYPE::CONTROLLER)
+ {
+ // Add controller
+ height = std::max(height, AddController(port, column.vertices.size(), column.vertices, grid));
+
+ if (bFirstPlayer == true)
+ {
+ bFirstPlayer = false;
+
+ // Keyboard and mouse are added below the first controller
+ if (itKeyboard != ports.end())
+ height = std::max(height, AddController(*itKeyboard, column.vertices.size(), column.vertices, grid));
+ if (itMouse != ports.end())
+ height = std::max(height, AddController(*itMouse, column.vertices.size(), column.vertices, grid));
+ }
+ }
+
+ if (!column.vertices.empty())
+ grid.emplace_back(std::move(column));
+ }
+ }
+ else
+ {
+ // No controllers, add keyboard and mouse
+ ControllerColumn column;
+
+ if (itKeyboard != ports.end())
+ height = std::max(height, AddController(*itKeyboard, column.vertices.size(), column.vertices, grid));
+ if (itMouse != ports.end())
+ height = std::max(height, AddController(*itMouse, column.vertices.size(), column.vertices, grid));
+
+ if (!column.vertices.empty())
+ grid.emplace_back(std::move(column));
+ }
+
+ return height;
+}
+
+unsigned int CControllerGrid::AddController(const CControllerPortNode &port, unsigned int height,
+ std::vector<ControllerVertex> &column, ControllerGrid &grid)
+{
+ // Add spacers
+ while (column.size() < height)
+ AddInvisible(column);
+
+ const CControllerNode &activeController = port.ActiveController();
+
+ // Add vertex
+ ControllerVertex vertex;
+ vertex.bVisible = true;
+ vertex.bConnected = port.Connected();
+ vertex.portType = port.PortType();
+ vertex.controller = activeController.Controller();
+ vertex.address = activeController.Address();
+ for (const CControllerNode &node : port.CompatibleControllers())
+ vertex.compatible.emplace_back(node.Controller());
+ column.emplace_back(std::move(vertex));
+
+ height++;
+
+ // Process ports
+ const ControllerPortVec &ports = activeController.Hub().Ports();
+ if (!ports.empty())
+ {
+ switch (GetDirection(activeController))
+ {
+ case GRID_DIRECTION::RIGHT:
+ {
+ height = std::max(height, AddHub(ports, height - 1, false, grid));
+ break;
+ }
+ case GRID_DIRECTION::DOWN:
+ {
+ const unsigned int row = height;
+
+ // Add the first controller to the column
+ const CControllerPortNode &firstController = ports.at(0);
+ height = std::max(height, AddController(firstController, row, column, grid));
+
+ // Add the remaining controllers on the same row
+ height = std::max(height, AddHub(ports, row, true, grid));
+
+ break;
+ }
+ }
+ }
+
+ return height;
+}
+
+unsigned int CControllerGrid::AddHub(const ControllerPortVec &ports, unsigned int height, bool bSkipFirst,
+ ControllerGrid &grid)
+{
+ const unsigned int row = height;
+
+ unsigned int port = 0;
+ for (const auto &controllerPort : ports)
+ {
+ // If controller has no player, it has already added the hub's first controller
+ if (bSkipFirst && port == 0)
+ continue;
+
+ // Add a column for this controller
+ grid.emplace_back();
+ ControllerColumn &column = grid.back();
+
+ height = std::max(height, AddController(controllerPort, row, column.vertices, grid));
+
+ port++;
+ }
+
+ return height;
+}
+
+void CControllerGrid::AddInvisible(std::vector<ControllerVertex> &column)
+{
+ ControllerVertex vertex;
+ vertex.bVisible = false;
+ column.emplace_back(std::move(vertex));
+}
+
+void CControllerGrid::SetHeight(unsigned int height, ControllerGrid &grid)
+{
+ for (auto &column : grid)
+ {
+ while (column.vertices.size() < height)
+ AddInvisible(column.vertices);
+ }
+}
+
+CControllerGrid::GRID_DIRECTION CControllerGrid::GetDirection(const CControllerNode &node)
+{
+ // Hub controllers are added horizontally, one per row.
+ //
+ // If the current controller offers a player spot, the row starts to the
+ // right at the same hight as the controller.
+ //
+ // Otherwise, to row starts below the current controller in the same
+ // column.
+ if (node.ProvidesInput())
+ return GRID_DIRECTION::RIGHT;
+ else
+ return GRID_DIRECTION::DOWN;
+}
diff --git a/xbmc/games/controllers/types/ControllerGrid.h b/xbmc/games/controllers/types/ControllerGrid.h
new file mode 100644
index 0000000000..4dd79301b3
--- /dev/null
+++ b/xbmc/games/controllers/types/ControllerGrid.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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 "ControllerTree.h"
+#include "games/controllers/ControllerTypes.h"
+
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace GAME
+{
+ /*!
+ * \brief Vertex in the grid of controllers
+ */
+ struct ControllerVertex
+ {
+ bool bVisible = true;
+ bool bConnected = false;
+ ControllerPtr controller; // Mandatory if connected
+ PORT_TYPE portType = PORT_TYPE::UNKNOWN; // Optional
+ std::string address; // Optional
+ ControllerVector compatible; // Compatible controllers
+ };
+
+ /*!
+ * \brief Column of controllers in the grid
+ */
+ struct ControllerColumn
+ {
+ std::vector<ControllerVertex> vertices;
+ };
+
+ /*!
+ * \brief Collection of controllers in a grid layout
+ */
+ using ControllerGrid = std::vector<ControllerColumn>;
+
+ /*!
+ * \brief Class to encapsulate grid operations
+ */
+ class CControllerGrid
+ {
+ public:
+ CControllerGrid();
+ CControllerGrid(const CControllerGrid &other);
+ ~CControllerGrid();
+
+ /*!
+ * \brief Create a grid from a controller tree
+ */
+ void SetControllerTree(const CControllerTree &controllerTree);
+
+ /*!
+ * \brief Get the width of the controller grid
+ */
+ unsigned int Width() const { return m_grid.size(); }
+
+ /*!
+ * \brief Get the height (deepest controller) of the controller grid
+ *
+ * The height is cached when the controller grid is created to avoid
+ * iterating the grid
+ */
+ unsigned int Height() const { return m_height; }
+
+ /*!
+ * \brief Access the controller grid
+ */
+ const ControllerGrid &Grid() const { return m_grid; }
+
+ /*!
+ * \brief Get the controllers in use by the specified player
+ *
+ * \param playerIndex The column in the grid to get controllers from
+ */
+ ControllerVector GetControllers(unsigned int playerIndex) const;
+
+ private:
+ /*!
+ * \brief Directions of vertex traversal
+ */
+ enum class GRID_DIRECTION
+ {
+ RIGHT,
+ DOWN,
+ };
+
+ /*!
+ * \brief Add ports to the grid
+ *
+ * \param ports The ports on a console or controller
+ * \param[out] grid The controller grid being created
+ *
+ * \return The height of the grid determined by the maximum column height
+ */
+ static unsigned int AddPorts(const ControllerPortVec &ports, ControllerGrid &grid);
+
+ /*!
+ * \brief Draw a controller to the column at the specified height
+ *
+ * \param port The controller's port node
+ * \param height The hight to draw the controller at
+ * \param column[in/out] The column to draw to
+ * \param grid[in/out] The grid to add additional columns to
+ *
+ * \return The height of the grid
+ */
+ static unsigned int AddController(const CControllerPortNode &port, unsigned int height,
+ std::vector<ControllerVertex> &column, ControllerGrid &grid);
+
+ /*!
+ * \brief Draw a series of controllers to the grid at the specified height
+ *
+ * \param ports The ports of the controllers to draw
+ * \param height The height to start drawing the controllers at
+ * \param bSkipFirst True if the first controller has already been drawn to
+ * a column, false to start drawing at the first controller
+ * \param grid[in/out] The grid to add columns to
+ *
+ * \return The height of the grid
+ */
+ static unsigned int AddHub(const ControllerPortVec &ports, unsigned int height, bool bSkipFirst,
+ ControllerGrid &grid);
+
+ /*!
+ * \brief Draw an invisible vertex to the column
+ *
+ * \param[in/out] column The column in a controller grid
+ */
+ static void AddInvisible(std::vector<ControllerVertex> &column);
+
+ /*!
+ * \brief Fill all columns with invisible vertices until the specified height
+ *
+ * \param height The height to make all columns
+ * \param[in/out] grid The grid to update
+ */
+ static void SetHeight(unsigned int height, ControllerGrid &grid);
+
+ /*!
+ * \brief Get the direction of traversal for the next vertex
+ *
+ * \param node The node in the controller tree being visited
+ *
+ * \return The direction of the next vertex, or GRID_DIRECTION::UNKNOWN if
+ * unknown
+ */
+ static GRID_DIRECTION GetDirection(const CControllerNode &node);
+
+ ControllerGrid m_grid;
+ unsigned int m_height = 0;
+ };
+}
+}
diff --git a/xbmc/games/controllers/types/ControllerTree.cpp b/xbmc/games/controllers/types/ControllerTree.cpp
new file mode 100644
index 0000000000..b3d76005be
--- /dev/null
+++ b/xbmc/games/controllers/types/ControllerTree.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 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 "ControllerTree.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerTopology.h"
+
+#include <utility>
+
+using namespace KODI;
+using namespace GAME;
+
+// --- CControllerNode ---------------------------------------------------------
+
+CControllerNode::CControllerNode() :
+ m_hub(new CControllerHub)
+{
+}
+
+CControllerNode::~CControllerNode() = default;
+
+CControllerNode &CControllerNode::operator=(const CControllerNode &rhs)
+{
+ if (this != &rhs)
+ {
+ m_controller = rhs.m_controller;
+ m_address = rhs.m_address;
+ m_hub.reset(new CControllerHub(*rhs.m_hub));
+ }
+
+ return *this;
+}
+
+void CControllerNode::SetController(ControllerPtr controller)
+{
+ m_controller = std::move(controller);
+}
+
+void CControllerNode::SetAddress(std::string address)
+{
+ m_address = std::move(address);
+}
+
+void CControllerNode::SetHub(CControllerHub hub)
+{
+ m_hub.reset(new CControllerHub(std::move(hub)));
+}
+
+bool CControllerNode::IsControllerAccepted(const std::string &portAddress,
+ const std::string &controllerId) const
+{
+ bool bAccepted = false;
+
+ for (const auto &port : m_hub->Ports())
+ {
+ if (port.IsControllerAccepted(portAddress, controllerId))
+ {
+ bAccepted = true;
+ break;
+ }
+ }
+
+ return bAccepted;
+}
+
+bool CControllerNode::ProvidesInput() const
+{
+ return m_controller && m_controller->Topology().ProvidesInput();
+}
+
+// --- CControllerPortNode -----------------------------------------------------
+
+CControllerPortNode::~CControllerPortNode() = default;
+
+CControllerPortNode &CControllerPortNode::operator=(const CControllerPortNode &rhs)
+{
+ if (this != &rhs)
+ {
+ m_bConnected = rhs.m_bConnected;
+ m_portType = rhs.m_portType;
+ m_portId = rhs.m_portId;
+ m_controllers = rhs.m_controllers;
+ m_address = rhs.m_address;
+ m_active = rhs.m_active;
+ }
+
+ return *this;
+}
+
+const CControllerNode &CControllerPortNode::ActiveController() const
+{
+ if (m_bConnected && m_active < m_controllers.size())
+ return m_controllers[m_active];
+
+ static const CControllerNode invalid{};
+ return invalid;
+}
+
+void CControllerPortNode::SetPortID(std::string portId)
+{
+ m_portId = std::move(portId);
+}
+
+void CControllerPortNode::SetAddress(std::string address)
+{
+ m_address = std::move(address);
+}
+
+void CControllerPortNode::SetCompatibleControllers(ControllerNodeVec controllers)
+{
+ m_controllers = std::move(controllers);
+}
+
+bool CControllerPortNode::IsControllerAccepted(const std::string &portAddress,
+ const std::string &controllerId) const
+{
+ bool bAccepted = false;
+
+ if (m_address == portAddress)
+ {
+ // Base case
+ CControllerPort port;
+ GetControllerPort(port);
+ if (port.IsCompatible(controllerId))
+ bAccepted = true;
+ }
+ else
+ {
+ for (const auto &node : m_controllers)
+ {
+ if (node.IsControllerAccepted(portAddress, controllerId))
+ {
+ bAccepted = true;
+ break;
+ }
+ }
+ }
+
+ return bAccepted;
+}
+
+void CControllerPortNode::GetControllerPort(CControllerPort &port) const
+{
+ std::vector<std::string> accepts;
+ for (const CControllerNode &node : m_controllers)
+ accepts.emplace_back(node.Controller()->ID());
+
+ port = CControllerPort(m_portId, std::move(accepts));
+}
+
+// --- CControllerHub ----------------------------------------------------------
+
+CControllerHub::~CControllerHub() = default;
+
+CControllerHub &CControllerHub::operator=(const CControllerHub &rhs)
+{
+ if (this != &rhs)
+ {
+ m_ports = rhs.m_ports;
+ }
+
+ return *this;
+}
+
+void CControllerHub::SetPorts(ControllerPortVec ports)
+{
+ m_ports = std::move(ports);
+}
+
+bool CControllerHub::IsControllerAccepted(const std::string &portAddress,
+ const std::string &controllerId) const
+{
+ bool bAccepted = false;
+
+ for (const CControllerPortNode &port : m_ports)
+ {
+ if (port.IsControllerAccepted(portAddress, controllerId))
+ {
+ bAccepted = true;
+ break;
+ }
+ }
+
+ return bAccepted;
+}
+
+const CControllerPortNode &CControllerHub::GetPort(const std::string &address) const
+{
+ return GetPort(m_ports, address);
+}
+
+const CControllerPortNode &CControllerHub::GetPort(const ControllerPortVec &ports, const std::string &address)
+{
+ for (const CControllerPortNode &port : ports)
+ {
+ if (port.Address() == address)
+ return port;
+
+ for (const CControllerNode &controller : port.CompatibleControllers())
+ {
+ for (const CControllerPortNode &controllerPort : controller.Hub().Ports())
+ {
+ if (port.Address() == address)
+ return controllerPort;
+ }
+ }
+ }
+
+ static const CControllerPortNode empty{};
+ return empty;
+}
diff --git a/xbmc/games/controllers/types/ControllerTree.h b/xbmc/games/controllers/types/ControllerTree.h
new file mode 100644
index 0000000000..ab6baf0375
--- /dev/null
+++ b/xbmc/games/controllers/types/ControllerTree.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 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 "games/controllers/ControllerTypes.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace GAME
+{
+ class CControllerHub;
+ class CControllerPort;
+
+ /*!
+ * \brief Node in the controller tree
+ *
+ * The node identies the controller profile, and optionally the available
+ * controller ports.
+ */
+ class CControllerNode
+ {
+ public:
+ CControllerNode();
+ CControllerNode(const CControllerNode &other) { *this = other; }
+ CControllerNode(CControllerNode &&other) = default;
+ CControllerNode &operator=(const CControllerNode &rhs);
+ ~CControllerNode();
+
+ /*!
+ * \brief Controller profile of this code
+ *
+ * \return Controller profile, or empty if this node is invalid
+ *
+ * \sa IsValid()
+ */
+ const ControllerPtr &Controller() const { return m_controller; }
+ void SetController(ControllerPtr controller);
+
+ /*!
+ * \brief Address given to the node by the implementation
+ */
+ const std::string &Address() const { return m_address; }
+ void SetAddress(std::string address);
+
+ /*!
+ * \brief Collection of ports on this controller
+ *
+ * \return A hub with controller ports, or an empty hub if this controller
+ * has no available ports
+ */
+ const CControllerHub &Hub() const { return *m_hub; }
+ void SetHub(CControllerHub hub);
+
+ /*!
+ * \brief Check if this node has a valid controller profile
+ */
+ bool IsValid() const { return m_controller.get() != nullptr; }
+
+ /*!
+ * \brief Check to see if a controller is compatible with a controller port
+ *
+ * \param portAddress The port address
+ * \param controllerId The ID of the controller
+ *
+ * \return True if the controller is compatible with a port, false otherwise
+ */
+ bool IsControllerAccepted(const std::string &portAddress,
+ const std::string &controllerId) const;
+
+ /*!
+ * \brief Check if this node provides input
+ */
+ bool ProvidesInput() const;
+
+ private:
+ ControllerPtr m_controller;
+ std::string m_address;
+ std::unique_ptr<CControllerHub> m_hub;
+ };
+
+ using ControllerNodeVec = std::vector<CControllerNode>;
+
+ /*!
+ * \brief Collection of nodes that can be connected to this port
+ */
+ class CControllerPortNode
+ {
+ public:
+ CControllerPortNode() = default;
+ CControllerPortNode(const CControllerPortNode &other) { *this = other; }
+ CControllerPortNode(CControllerPortNode &&other) = default;
+ CControllerPortNode &operator=(const CControllerPortNode &rhs);
+ ~CControllerPortNode();
+
+ /*!
+ * \brief Connection state of the port
+ *
+ * \return True if a controller is connected, false otherwise
+ */
+ bool Connected() const { return m_bConnected; }
+ void SetConnected(bool bConnected) { m_bConnected = bConnected; }
+
+ /*!
+ * \brief The controller that is active on this port
+ *
+ * \return The active controller, or invalid if port is disconnected
+ */
+ const CControllerNode &ActiveController() const;
+ void SetActiveController(unsigned int controllerIndex) { m_active = controllerIndex; }
+
+ /*!
+ * \brief The port type
+ *
+ * \return The port type, if known
+ */
+ PORT_TYPE PortType() const { return m_portType; }
+ void SetPortType(PORT_TYPE type) { m_portType = type; }
+
+ /*!
+ * \brief The hardware or controller port ID
+ *
+ * \return The port ID of the hardware port or controller port, or empty if
+ * the port is only identified by its type
+ */
+ const std::string &PortID() const { return m_portId; }
+ void SetPortID(std::string portId);
+
+ /*!
+ * \brief Address given to the node by the implementation
+ */
+ const std::string &Address() const { return m_address; }
+ void SetAddress(std::string address);
+
+ /*!
+ * \brief Return the controller profiles that are compatible with this port
+ *
+ * \return The controller profiles, or empty if this port doesn't support
+ * any controller profiles
+ */
+ const ControllerNodeVec &CompatibleControllers() const { return m_controllers; }
+ void SetCompatibleControllers(ControllerNodeVec controllers);
+
+ /*!
+ * \brief Check to see if a controller is compatible with this tree
+ *
+ * \param portAddress The port address
+ * \param controllerId The ID of the controller
+ *
+ * \return True if the controller is compatible with the tree, false otherwise
+ */
+ bool IsControllerAccepted(const std::string &portAddress,
+ const std::string &controllerId) const;
+
+ private:
+ void GetControllerPort(CControllerPort &port) const;
+
+ bool m_bConnected = false;
+ unsigned int m_active = 0;
+ PORT_TYPE m_portType = PORT_TYPE::UNKNOWN;
+ std::string m_portId;
+ std::string m_address;
+ ControllerNodeVec m_controllers;
+ };
+
+ /*!
+ * \brief Collection of port nodes
+ */
+ using ControllerPortVec = std::vector<CControllerPortNode>;
+
+ /*!
+ * \brief A branch in the controller tree
+ */
+ class CControllerHub
+ {
+ public:
+ CControllerHub() = default;
+ CControllerHub(const CControllerHub &other) { *this = other; }
+ CControllerHub(CControllerHub &&other) = default;
+ CControllerHub &operator=(const CControllerHub &rhs);
+ ~CControllerHub();
+
+ bool HasPorts() const { return !m_ports.empty(); }
+ const ControllerPortVec &Ports() const { return m_ports; }
+ void SetPorts(ControllerPortVec ports);
+
+ bool IsControllerAccepted(const std::string &portAddress,
+ const std::string &controllerId) const;
+
+ const CControllerPortNode &GetPort(const std::string &address) const;
+
+ private:
+ static const CControllerPortNode &GetPort(const ControllerPortVec &ports, const std::string &address);
+
+ ControllerPortVec m_ports;
+ };
+
+ /*!
+ * \brief Collection of ports on a console
+ */
+ using CControllerTree = CControllerHub;
+}
+}
diff --git a/xbmc/input/hardware/IHardwareInput.h b/xbmc/input/hardware/IHardwareInput.h
index ae9216e21e..f17a243fbe 100644
--- a/xbmc/input/hardware/IHardwareInput.h
+++ b/xbmc/input/hardware/IHardwareInput.h
@@ -19,6 +19,8 @@
*/
#pragma once
+#include <string>
+
namespace KODI
{
namespace HARDWARE
@@ -34,11 +36,8 @@ namespace HARDWARE
/*!
* \brief A hardware reset button has been pressed
- *
- * \param port The port belonging to the user who pressed the reset button,
- * or 0 (the default port) if unknown
*/
- virtual void OnResetButton(unsigned int port) = 0;
+ virtual void OnResetButton() = 0;
};
}
}