Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controller topology (multitap support) #13499

Merged
merged 3 commits into from Feb 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/treedata/common/games.txt
Expand Up @@ -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
Expand Down
77 changes: 65 additions & 12 deletions xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h
Expand Up @@ -130,17 +130,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
*
Expand All @@ -155,6 +144,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
*
Expand All @@ -175,6 +184,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
*
Expand Down Expand Up @@ -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;
Expand Down
73 changes: 63 additions & 10 deletions xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
22 changes: 0 additions & 22 deletions xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h
Expand Up @@ -187,28 +187,6 @@ class CHelper_libKODI_game

// --- 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
*
Expand Down
4 changes: 2 additions & 2 deletions xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h
Expand Up @@ -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" \
Expand Down
4 changes: 2 additions & 2 deletions xbmc/cores/RetroPlayer/RetroPlayer.cpp
Expand Up @@ -30,13 +30,13 @@
#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"
#include "games/addons/GameClient.h"
#include "games/addons/GameClientTiming.h" //! @todo
#include "games/dialogs/osd/DialogGameVideoSelect.h"
#include "games/ports/PortManager.h"
#include "games/tags/GameInfoTag.h"
#include "games/GameServices.h"
#include "games/GameUtils.h"
Expand Down Expand Up @@ -404,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)
Expand Down
9 changes: 1 addition & 8 deletions xbmc/games/GameServices.cpp
Expand Up @@ -21,7 +21,6 @@
#include "GameServices.h"
#include "controllers/Controller.h"
#include "controllers/ControllerManager.h"
#include "games/ports/PortManager.h"
#include "games/GameSettings.h"
#include "profiles/ProfilesManager.h"

Expand All @@ -36,8 +35,7 @@ CGameServices::CGameServices(CControllerManager &controllerManager,
m_controllerManager(controllerManager),
m_gameRenderManager(renderManager),
m_profileManager(profileManager),
m_gameSettings(new CGameSettings(settings)),
m_portManager(new CPortManager(peripheralManager))
m_gameSettings(new CGameSettings(settings))
{
}

Expand Down Expand Up @@ -72,8 +70,3 @@ std::string CGameServices::GetSavestatesFolder() const
{
return m_profileManager.GetSavestatesFolder();
}

CPortManager& CGameServices::PortManager()
{
return *m_portManager;
}
3 changes: 0 additions & 3 deletions xbmc/games/GameServices.h
Expand Up @@ -43,7 +43,6 @@ namespace GAME
{
class CControllerManager;
class CGameSettings;
class CPortManager;

class CGameServices
{
Expand All @@ -64,7 +63,6 @@ namespace GAME
std::string GetSavestatesFolder() const;

CGameSettings& GameSettings() { return *m_gameSettings; }
CPortManager& PortManager();

RETRO::CGUIGameRenderManager &GameRenderManager() { return m_gameRenderManager; }

Expand All @@ -76,7 +74,6 @@ namespace GAME

// Game services
std::unique_ptr<CGameSettings> m_gameSettings;
std::unique_ptr<CPortManager> m_portManager;
};
}
}
8 changes: 8 additions & 0 deletions xbmc/games/GameTypes.h
Expand Up @@ -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>;

}
}