From ac31332b3ec79bbf0d2fe2da46c649cdb58452df Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Wed, 5 Jul 2023 16:42:34 -0700 Subject: [PATCH] Games: Add Agent Dialog --- .../resources/strings.po | 22 +- .../media/osd/fullscreen/buttons/agent.png | Bin 0 -> 989 bytes .../xml/DialogGameControllers.xml | 7 +- addons/skin.estuary/xml/GameOSD.xml | 14 +- addons/skin.estuary/xml/Includes_Games.xml | 152 ++++++++++ cmake/treedata/common/games.txt | 4 + xbmc/games/GameTypes.h | 4 + xbmc/games/agents/CMakeLists.txt | 6 +- xbmc/games/agents/GameAgent.cpp | 71 +++++ xbmc/games/agents/GameAgent.h | 49 ++++ xbmc/games/agents/GameAgentManager.cpp | 95 +++++++ xbmc/games/agents/GameAgentManager.h | 10 +- xbmc/games/agents/input/CMakeLists.txt | 7 + xbmc/games/agents/input/GameAgentJoystick.cpp | 116 ++++++++ xbmc/games/agents/input/GameAgentJoystick.h | 71 +++++ xbmc/games/agents/windows/CMakeLists.txt | 11 + xbmc/games/agents/windows/GUIAgentDefines.h | 18 ++ xbmc/games/agents/windows/GUIAgentList.cpp | 264 ++++++++++++++++++ xbmc/games/agents/windows/GUIAgentList.h | 77 +++++ xbmc/games/agents/windows/GUIAgentWindow.cpp | 193 +++++++++++++ xbmc/games/agents/windows/GUIAgentWindow.h | 60 ++++ xbmc/games/agents/windows/IAgentList.h | 88 ++++++ .../controllers/guicontrols/CMakeLists.txt | 2 + .../guicontrols/GUIGameController.cpp | 35 ++- .../guicontrols/GUIGameController.h | 4 + .../guicontrols/GUIGameControllerList.cpp | 195 +++++++++++++ .../guicontrols/GUIGameControllerList.h | 71 +++++ .../controllers/listproviders/CMakeLists.txt | 7 + .../GUIGameControllerProvider.cpp | 152 ++++++++++ .../listproviders/GUIGameControllerProvider.h | 73 +++++ .../games/controllers/types/ControllerHub.cpp | 6 + xbmc/games/controllers/types/ControllerHub.h | 7 + .../controllers/types/ControllerNode.cpp | 8 + xbmc/games/controllers/types/ControllerNode.h | 7 + xbmc/games/ports/guicontrols/CMakeLists.txt | 8 + .../ports/guicontrols/GUIActivePortList.cpp | 194 +++++++++++++ .../ports/guicontrols/GUIActivePortList.h | 68 +++++ .../games/ports/guicontrols/IActivePortList.h | 55 ++++ xbmc/games/ports/types/PortNode.cpp | 9 + xbmc/games/ports/types/PortNode.h | 7 + xbmc/games/ports/windows/GUIPortDefines.h | 3 + xbmc/guilib/GUIControl.h | 1 + xbmc/guilib/GUIControlFactory.cpp | 28 ++ xbmc/guilib/GUIListGroup.cpp | 1 + xbmc/guilib/GUIWindowManager.cpp | 3 + xbmc/guilib/WindowIDs.h | 1 + xbmc/input/WindowTranslator.cpp | 4 +- .../peripherals/addons/AddonInputHandling.cpp | 6 +- 48 files changed, 2277 insertions(+), 17 deletions(-) create mode 100644 addons/skin.estuary/media/osd/fullscreen/buttons/agent.png create mode 100644 xbmc/games/agents/GameAgent.cpp create mode 100644 xbmc/games/agents/GameAgent.h create mode 100644 xbmc/games/agents/input/CMakeLists.txt create mode 100644 xbmc/games/agents/input/GameAgentJoystick.cpp create mode 100644 xbmc/games/agents/input/GameAgentJoystick.h create mode 100644 xbmc/games/agents/windows/CMakeLists.txt create mode 100644 xbmc/games/agents/windows/GUIAgentDefines.h create mode 100644 xbmc/games/agents/windows/GUIAgentList.cpp create mode 100644 xbmc/games/agents/windows/GUIAgentList.h create mode 100644 xbmc/games/agents/windows/GUIAgentWindow.cpp create mode 100644 xbmc/games/agents/windows/GUIAgentWindow.h create mode 100644 xbmc/games/agents/windows/IAgentList.h create mode 100644 xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp create mode 100644 xbmc/games/controllers/guicontrols/GUIGameControllerList.h create mode 100644 xbmc/games/controllers/listproviders/CMakeLists.txt create mode 100644 xbmc/games/controllers/listproviders/GUIGameControllerProvider.cpp create mode 100644 xbmc/games/controllers/listproviders/GUIGameControllerProvider.h create mode 100644 xbmc/games/ports/guicontrols/CMakeLists.txt create mode 100644 xbmc/games/ports/guicontrols/GUIActivePortList.cpp create mode 100644 xbmc/games/ports/guicontrols/GUIActivePortList.h create mode 100644 xbmc/games/ports/guicontrols/IActivePortList.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 02979e95d03ae..03987b92a46ab 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -18290,7 +18290,27 @@ msgctxt "#35171" msgid "Mouse" msgstr "" -#empty strings from id 35172 to 35199 +#. Name of window for configuring players while playing a game +#: addons/skin.estuary/xml/GameOSD.xml +#: addons/skin.estuary/xml/Includes_Games.xml +msgctxt "#35172" +msgid "Players" +msgstr "" + +#. Name of window for viewing players while playing a game +#: addons/skin.estuary/xml/GameOSD.xml +#: addons/skin.estuary/xml/Includes_Games.xml +msgctxt "#35173" +msgid "View Players" +msgstr "" + +#. Notification shown when no controllers are connected in the in-game dialog that lets users configure player assignment +#: xbmc/games/agents/windows/GUIAgentList.cpp +msgctxt "#35174" +msgid "No controllers connected" +msgstr "" + +#empty strings from id 35175 to 35199 #. This string is an early meme from the late 1990's that made fun of the poor translation in the 16-bit game Zero Wing from the late 1980's. DO NOT TRANSLATE! #: system/settings/settings.xml diff --git a/addons/skin.estuary/media/osd/fullscreen/buttons/agent.png b/addons/skin.estuary/media/osd/fullscreen/buttons/agent.png new file mode 100644 index 0000000000000000000000000000000000000000..f805b37c5983764ad12eb88c3eb9dc5c6b20cfda GIT binary patch literal 989 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2_SG&H|6fVg?3oVGw3ym^DWND5#L^ z5#-CjP^Ah~+|0o6^FNS&$-q!*z`*b-fq}tl1_Oh5{-pS$ZVU{}$(}BbAr-gY-1hYl zNt8MMF>sN$hKNvF0&C?9r72U}<2Y7wIKB!HNV>#b;+Zn}fQm4`>Ry>9KB3l%4+(OM zoGUpFZEzNt!g+~%qS_SJsUmF4EZ%KzUe~vFx3%%CzVmD=UAdJ<0Z)g-V5nU4t#-kj_ZA9 z`1DbT_Zef(SC$KTatHGK8SXw~oKnH` zQCAspj{VF&*C4@V!b;|k#2ml3z3uR^n_lT*Zo5VI$?ErynCB#hmTax}Yn5eO6#SMUe`WgRAoddrdUpSrX~mOn?Nwv^_*5Lbe-Fb0 z!SoZ6T?{wpENVS?(rA%!SNC0JciH&f&9TDC5)T;#&GdL1Ov(cM*$)RNXQ+F;nDy%F zZlICo&(#~J>`4u-SQMpU=|ar)LS<%&QJD%)lEkhjV1R(ANn#Uh?z?SUQbN@{MK<+!`6u0MXSlEE!> zpo-soqwoSg?ry92+GJx71%`FMB$SXcC!nA*uZG=l2XNq+jm(<+Fwdm^B$Z MUHx3vIVCg!0BQPwh5!Hn literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/xml/DialogGameControllers.xml b/addons/skin.estuary/xml/DialogGameControllers.xml index b0f1d11cb31c5..fff180a92e0d0 100644 --- a/addons/skin.estuary/xml/DialogGameControllers.xml +++ b/addons/skin.estuary/xml/DialogGameControllers.xml @@ -5,11 +5,12 @@ but adding new XML windows breaks old skins, so it has been repurposed for any game-related windows. - 3 + 3 Animation_DialogPopupOpenClose - GameDialogControllers - GameDialogPorts + GameDialogControllers + GameDialogPorts + GameDialogAgents diff --git a/addons/skin.estuary/xml/GameOSD.xml b/addons/skin.estuary/xml/GameOSD.xml index b808abaa8f776..0923d9e1b0bec 100644 --- a/addons/skin.estuary/xml/GameOSD.xml +++ b/addons/skin.estuary/xml/GameOSD.xml @@ -5,7 +5,7 @@ DepthOSD - !Window.IsActive(1101) + !Window.IsActive(GameVideoFilter) + !Window.IsActive(GameStretchMode) + !Window.IsActive(GameControllers) + !Window.IsActive(GameVideoRotation) + !Window.IsActive(InGameSaves) + !Window.IsActive(1101) + !Window.IsActive(GameVideoFilter) + !Window.IsActive(GameStretchMode) + !Window.IsActive(GameControllers) + !Window.IsActive(GameVideoRotation) + !Window.IsActive(InGameSaves) + !Window.IsActive(GameAgents) Visible_Fade System.GetBool(gamesgeneral.showosdhelp) @@ -64,13 +64,13 @@ !System.GetBool(gamesgeneral.showosdhelp) 50% - 480 + 560 50% 700 VisibleChange - + @@ -78,7 +78,7 @@ 80 2101 - 480 + 560 vertical @@ -214,6 +214,12 @@ osd/fullscreen/buttons/saves.png ActivateWindow(InGameSaves) + + Players button + + osd/fullscreen/buttons/agent.png + ActivateWindow(GameAgents) + Reset button diff --git a/addons/skin.estuary/xml/Includes_Games.xml b/addons/skin.estuary/xml/Includes_Games.xml index e3d553afa0345..e7d5e626423d1 100644 --- a/addons/skin.estuary/xml/Includes_Games.xml +++ b/addons/skin.estuary/xml/Includes_Games.xml @@ -325,4 +325,156 @@ + + + 50% + 50% + 1820 + 962 + + + + + + + + Content area + 110 + 40 + 40 + 40 + + Area of the dialog for ports + 96 + + Ports button + -20 + -20 + -20 + -20 + 5 + + font37 + 36 + left + center + buttons/dialogbutton-fo.png + buttons/dialogbutton-nofo.png + ActivateWindow(GamePorts) + + + Active port list. Length should fit 13 listitems (12 controllers and one "controller disconnected" indicator). + 0 + 1248 + horizontal + right + false + + + $INFO[ListItem.Icon] + $INFO[ListItem.FilenameAndPath] + button_focus + + + + + $INFO[ListItem.Icon] + $INFO[ListItem.FilenameAndPath] + button_focus + + + + + + Area of the dialog for players + 136 + 576 + + Player list background + -20 + -20 + -20 + -20 + buttons/dialogbutton-nofo.png + + + Player list + 3 + 6 + 61 + 200 + vertical + + GameDialogAgentList + + + + -20 + -20 + -20 + -20 + buttons/dialogbutton-fo.png + Control.HasFocus(5) + + GameDialogAgentList + + + + + Agent list scroll bar + 136 + 576 + -12 + 12 + vertical + + + Action buttons + -20 + -20 + -20 + 100 + 5 + horizontal + dialogbuttons_itemgap + + + + + + + + + + + + Player name + 20 + 20 + + font37 + text_shadow + left + + + Player's game controller list. Length should fit 13 listitems (12 players and one "input disabled" indicator). + 0 + 1248 + horizontal + right + false + + + $INFO[ListItem.FilenameAndPath] + button_focus + + + + + $INFO[ListItem.FilenameAndPath] + button_focus + + + + diff --git a/cmake/treedata/common/games.txt b/cmake/treedata/common/games.txt index 6f9b8425d99a0..7549da6056dcc 100644 --- a/cmake/treedata/common/games.txt +++ b/cmake/treedata/common/games.txt @@ -4,14 +4,18 @@ xbmc/games/addons/cheevos games/addons/cheevos xbmc/games/addons/input games/addons/input xbmc/games/addons/streams games/addons/streams xbmc/games/agents games/agents +xbmc/games/agents/input games/agents/input +xbmc/games/agents/windows games/agents/windows xbmc/games/controllers games/controllers xbmc/games/controllers/dialogs games/controllers/dialogs xbmc/games/controllers/guicontrols games/controllers/guicontrols xbmc/games/controllers/input games/controllers/input +xbmc/games/controllers/listproviders games/controllers/listproviders xbmc/games/controllers/types games/controllers/types xbmc/games/controllers/windows games/controllers/windows xbmc/games/dialogs games/dialogs xbmc/games/dialogs/osd games/dialogs/osd +xbmc/games/ports/guicontrols games/ports/guicontrols xbmc/games/ports/input games/ports/input xbmc/games/ports/types games/ports/types xbmc/games/ports/windows games/ports/windows diff --git a/xbmc/games/GameTypes.h b/xbmc/games/GameTypes.h index 3be3195b20a66..275894674caa1 100644 --- a/xbmc/games/GameTypes.h +++ b/xbmc/games/GameTypes.h @@ -28,5 +28,9 @@ class CGameClientDevice; using GameClientDevicePtr = std::unique_ptr; using GameClientDeviceVec = std::vector; +class CGameAgent; +using GameAgentPtr = std::shared_ptr; +using GameAgentVec = std::vector; + } // namespace GAME } // namespace KODI diff --git a/xbmc/games/agents/CMakeLists.txt b/xbmc/games/agents/CMakeLists.txt index e66ee2f25ee18..523f55f1df710 100644 --- a/xbmc/games/agents/CMakeLists.txt +++ b/xbmc/games/agents/CMakeLists.txt @@ -1,7 +1,9 @@ -set(SOURCES GameAgentManager.cpp +set(SOURCES GameAgent.cpp + GameAgentManager.cpp ) -set(HEADERS GameAgentManager.h +set(HEADERS GameAgent.h + GameAgentManager.h ) core_add_library(games_agents) diff --git a/xbmc/games/agents/GameAgent.cpp b/xbmc/games/agents/GameAgent.cpp new file mode 100644 index 0000000000000..04ccc79b1bcac --- /dev/null +++ b/xbmc/games/agents/GameAgent.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GameAgent.h" + +#include "games/agents/input/GameAgentJoystick.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerLayout.h" +#include "peripherals/devices/Peripheral.h" + +using namespace KODI; +using namespace GAME; + +CGameAgent::CGameAgent(PERIPHERALS::PeripheralPtr peripheral) + : m_peripheral(std::move(peripheral)), + m_joystick(std::make_unique(m_peripheral)) +{ + Initialize(); +} + +CGameAgent::~CGameAgent() = default; + +void CGameAgent::Initialize() +{ + m_joystick->Initialize(); +} + +void CGameAgent::Deinitialize() +{ + m_joystick->Deinitialize(); +} + +std::string CGameAgent::GetPeripheralName() const +{ + std::string deviceName = m_peripheral->DeviceName(); + + // Handle unknown device name + if (deviceName.empty()) + { + ControllerPtr controller = m_peripheral->ControllerProfile(); + if (controller) + deviceName = controller->Layout().Label(); + } + + return deviceName; +} + +std::string CGameAgent::GetPeripheralLocation() const +{ + return m_peripheral->Location(); +} + +ControllerPtr CGameAgent::GetController() const +{ + return m_peripheral->ControllerProfile(); +} + +CDateTime CGameAgent::LastActive() const +{ + return m_peripheral->LastActive(); +} + +float CGameAgent::GetActivation() const +{ + return m_joystick->GetActivation(); +} diff --git a/xbmc/games/agents/GameAgent.h b/xbmc/games/agents/GameAgent.h new file mode 100644 index 0000000000000..f1683e6d1c4e7 --- /dev/null +++ b/xbmc/games/agents/GameAgent.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#pragma once + +#include "XBDateTime.h" +#include "games/controllers/ControllerTypes.h" +#include "peripherals/PeripheralTypes.h" + +#include +#include + +namespace KODI +{ +namespace GAME +{ +class CGameAgentJoystick; + +class CGameAgent +{ +public: + CGameAgent(PERIPHERALS::PeripheralPtr peripheral); + + ~CGameAgent(); + + void Initialize(); + void Deinitialize(); + + PERIPHERALS::PeripheralPtr GetPeripheral() const { return m_peripheral; } + std::string GetPeripheralName() const; + std::string GetPeripheralLocation() const; + ControllerPtr GetController() const; + CDateTime LastActive() const; + float GetActivation() const; + +private: + // Construction parameters + const PERIPHERALS::PeripheralPtr m_peripheral; + + // Input parameters + std::unique_ptr m_joystick; +}; + +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/agents/GameAgentManager.cpp b/xbmc/games/agents/GameAgentManager.cpp index 7ddb0f61407bb..250a88e1917bb 100644 --- a/xbmc/games/agents/GameAgentManager.cpp +++ b/xbmc/games/agents/GameAgentManager.cpp @@ -8,6 +8,7 @@ #include "GameAgentManager.h" +#include "GameAgent.h" #include "games/addons/GameClient.h" #include "games/addons/input/GameClientInput.h" #include "games/addons/input/GameClientJoystick.h" @@ -105,8 +106,13 @@ void CGameAgentManager::Notify(const Observable& obs, const ObservableMessage ms switch (msg) { case ObservableMessageGamePortsChanged: + { + Refresh(); + break; + } case ObservableMessagePeripheralsChanged: { + SetChanged(true); Refresh(); break; } @@ -133,6 +139,35 @@ bool CGameAgentManager::OnButtonPress(MOUSE::BUTTON_ID button) return false; } +GameAgentVec CGameAgentManager::GetAgents() const +{ + std::lock_guard lock(m_agentMutex); + return m_agents; +} + +const std::string& CGameAgentManager::GetPortAddress(JOYSTICK::IInputProvider* inputProvider) const +{ + auto it = m_portMap.find(inputProvider); + if (it != m_portMap.end()) + return it->second->GetPortAddress(); + + static const std::string empty; + return empty; +} + +std::vector CGameAgentManager::GetInputPorts() const +{ + std::vector inputPorts; + + if (m_gameClient) + { + const CControllerTree& controllerTree = m_gameClient->Input().GetActiveControllerTree(); + controllerTree.GetInputPorts(inputPorts); + } + + return inputPorts; +} + float CGameAgentManager::GetPortActivation(const std::string& portAddress) const { float activation = 0.0f; @@ -143,6 +178,24 @@ float CGameAgentManager::GetPortActivation(const std::string& portAddress) const return activation; } +float CGameAgentManager::GetPeripheralActivation(const std::string& peripheralLocation) const +{ + float activation = 0.0f; + + std::lock_guard lock(m_agentMutex); + + for (const GameAgentPtr& agent : m_agents) + { + if (agent->GetPeripheralLocation() == peripheralLocation) + { + activation = agent->GetActivation(); + break; + } + } + + return activation; +} + void CGameAgentManager::ProcessJoysticks(PERIPHERALS::EventLockHandlePtr& inputHandlingLock) { // Get system joysticks. @@ -156,6 +209,48 @@ void CGameAgentManager::ProcessJoysticks(PERIPHERALS::EventLockHandlePtr& inputH PERIPHERALS::PeripheralVector joysticks; m_peripheralManager.GetPeripheralsWithFeature(joysticks, PERIPHERALS::FEATURE_JOYSTICK); + // Update agents + { + std::lock_guard lock(m_agentMutex); + + // Handle new agents + for (const auto& joystick : joysticks) + { + auto it = + std::find_if(m_agents.begin(), m_agents.end(), [&joystick](const GameAgentPtr& agent) { + return agent->GetPeripheralLocation() == joystick->Location(); + }); + + if (it == m_agents.end()) + m_agents.emplace_back(std::make_shared(joystick)); + } + + // Remove expired agents + std::vector expiredJoysticks; + for (const auto& agent : m_agents) + { + auto it = std::find_if(joysticks.begin(), joysticks.end(), + [&agent](const PERIPHERALS::PeripheralPtr& joystick) { + return agent->GetPeripheralLocation() == joystick->Location(); + }); + + if (it == joysticks.end()) + expiredJoysticks.emplace_back(agent->GetPeripheralLocation()); + } + for (const std::string& expiredJoystick : expiredJoysticks) + { + auto it = std::find_if(m_agents.begin(), m_agents.end(), + [&expiredJoystick](const GameAgentPtr& agent) { + return agent->GetPeripheralLocation() == expiredJoystick; + }); + if (it != m_agents.end()) + { + (*it)->Deinitialize(); + m_agents.erase(it, m_agents.end()); + } + } + } + // Update expired joysticks UpdateExpiredJoysticks(joysticks, inputHandlingLock); diff --git a/xbmc/games/agents/GameAgentManager.h b/xbmc/games/agents/GameAgentManager.h index 621cbe2962d18..119339f12015f 100644 --- a/xbmc/games/agents/GameAgentManager.h +++ b/xbmc/games/agents/GameAgentManager.h @@ -22,7 +22,6 @@ class CInputManager; namespace PERIPHERALS { -class CPeripheral; class CPeripherals; } // namespace PERIPHERALS @@ -35,7 +34,6 @@ class IInputProvider; namespace GAME { -class CGameAgent; class CGameClient; class CGameClientJoystick; @@ -79,7 +77,11 @@ class CGameAgentManager : public Observable, void OnButtonRelease(MOUSE::BUTTON_ID button) override {} // Public interface + GameAgentVec GetAgents() const; + const std::string& GetPortAddress(JOYSTICK::IInputProvider* inputProvider) const; + std::vector GetInputPorts() const; float GetPortActivation(const std::string& address) const; + float GetPeripheralActivation(const std::string& peripheralLocation) const; private: //! @todo De-duplicate these types @@ -127,6 +129,10 @@ class CGameAgentManager : public Observable, GameClientPtr m_gameClient; bool m_bHasKeyboard = false; bool m_bHasMouse = false; + GameAgentVec m_agents; + + // Synchronization parameters + mutable std::mutex m_agentMutex; /*! * \brief Map of input provider to joystick handler diff --git a/xbmc/games/agents/input/CMakeLists.txt b/xbmc/games/agents/input/CMakeLists.txt new file mode 100644 index 0000000000000..495a779cce310 --- /dev/null +++ b/xbmc/games/agents/input/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES GameAgentJoystick.cpp +) + +set(HEADERS GameAgentJoystick.h +) + +core_add_library(games_agent_input) diff --git a/xbmc/games/agents/input/GameAgentJoystick.cpp b/xbmc/games/agents/input/GameAgentJoystick.cpp new file mode 100644 index 0000000000000..785821c929ecc --- /dev/null +++ b/xbmc/games/agents/input/GameAgentJoystick.cpp @@ -0,0 +1,116 @@ +/* +* Copyright (C) 2023 Team Kodi +* This file is part of Kodi - https://kodi.tv +* +* SPDX-License-Identifier: GPL-2.0-or-later +* See LICENSES/README.md for more information. +*/ + +#include "GameAgentJoystick.h" + +#include "games/controllers/Controller.h" +#include "games/controllers/input/ControllerActivity.h" +#include "input/joysticks/interfaces/IInputProvider.h" +#include "peripherals/devices/Peripheral.h" + +using namespace KODI; +using namespace GAME; + +CGameAgentJoystick::CGameAgentJoystick(PERIPHERALS::PeripheralPtr peripheral) + : m_peripheral(std::move(peripheral)), + m_controllerActivity(std::make_unique()) +{ +} + +CGameAgentJoystick::~CGameAgentJoystick() = default; + +void CGameAgentJoystick::Initialize() +{ + // Upcast peripheral to input interface + JOYSTICK::IInputProvider* inputProvider = m_peripheral.get(); + + // Register input handler to silently observe all input + inputProvider->RegisterInputHandler(this, true); +} + +void CGameAgentJoystick::Deinitialize() +{ + // Upcast peripheral to input interface + JOYSTICK::IInputProvider* inputProvider = m_peripheral.get(); + + // Unregister input handler + inputProvider->UnregisterInputHandler(this); +} + +float CGameAgentJoystick::GetActivation() const +{ + return m_controllerActivity->GetActivity(); +} + +std::string CGameAgentJoystick::ControllerID(void) const +{ + ControllerPtr controller = m_peripheral->ControllerProfile(); + if (controller) + return controller->ID(); + + return ""; +} + +bool CGameAgentJoystick::HasFeature(const std::string& feature) const +{ + return true; // Capture input for all features +} + +bool CGameAgentJoystick::AcceptsInput(const std::string& feature) const +{ + return true; // Accept input for all features +} + +bool CGameAgentJoystick::OnButtonPress(const std::string& feature, bool bPressed) +{ + m_controllerActivity->OnButtonPress(bPressed); + return true; +} + +void CGameAgentJoystick::OnButtonHold(const std::string& feature, unsigned int holdTimeMs) +{ + m_controllerActivity->OnButtonPress(true); +} + +bool CGameAgentJoystick::OnButtonMotion(const std::string& feature, + float magnitude, + unsigned int motionTimeMs) +{ + m_controllerActivity->OnButtonMotion(magnitude); + return true; +} + +bool CGameAgentJoystick::OnAnalogStickMotion(const std::string& feature, + float x, + float y, + unsigned int motionTimeMs) +{ + m_controllerActivity->OnAnalogStickMotion(x, y); + return true; +} + +bool CGameAgentJoystick::OnWheelMotion(const std::string& feature, + float position, + unsigned int motionTimeMs) +{ + m_controllerActivity->OnWheelMotion(position); + return true; +} + +bool CGameAgentJoystick::OnThrottleMotion(const std::string& feature, + float position, + unsigned int motionTimeMs) +{ + m_controllerActivity->OnThrottleMotion(position); + return true; +} + +void CGameAgentJoystick::OnInputFrame() +{ + m_controllerActivity->OnInputFrame(); +} diff --git a/xbmc/games/agents/input/GameAgentJoystick.h b/xbmc/games/agents/input/GameAgentJoystick.h new file mode 100644 index 0000000000000..a85710e0a2f03 --- /dev/null +++ b/xbmc/games/agents/input/GameAgentJoystick.h @@ -0,0 +1,71 @@ +/* +* Copyright (C) 2023 Team Kodi +* This file is part of Kodi - https://kodi.tv +* +* SPDX-License-Identifier: GPL-2.0-or-later +* See LICENSES/README.md for more information. +*/ + +#pragma once + +#include "input/joysticks/interfaces/IInputHandler.h" +#include "peripherals/PeripheralTypes.h" + +namespace KODI +{ +namespace GAME +{ +class CControllerActivity; + +/*! + * \ingroup games + * + * \brief Handles game controller events for game agent functionality + */ +class CGameAgentJoystick : public JOYSTICK::IInputHandler +{ +public: + CGameAgentJoystick(PERIPHERALS::PeripheralPtr peripheral); + + ~CGameAgentJoystick() override; + + void Initialize(); + void Deinitialize(); + + // Input parameters + float GetActivation() const; + + // Implementation of IJoystickHandler + std::string ControllerID() const override; + bool HasFeature(const std::string& feature) const override; + bool AcceptsInput(const std::string& feature) const override; + bool OnButtonPress(const std::string& feature, bool bPressed) override; + void OnButtonHold(const std::string& feature, unsigned int holdTimeMs) override; + bool OnButtonMotion(const std::string& feature, + float magnitude, + unsigned int motionTimeMs) override; + bool OnAnalogStickMotion(const std::string& feature, + float x, + float y, + unsigned int motionTimeMs) override; + bool OnAccelerometerMotion(const std::string& feature, float x, float y, float z) override + { + return false; + } + bool OnWheelMotion(const std::string& feature, + float position, + unsigned int motionTimeMs) override; + bool OnThrottleMotion(const std::string& feature, + float position, + unsigned int motionTimeMs) override; + void OnInputFrame() override; + +private: + // Construction parameters + const PERIPHERALS::PeripheralPtr m_peripheral; + + // Input state + std::unique_ptr m_controllerActivity; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/agents/windows/CMakeLists.txt b/xbmc/games/agents/windows/CMakeLists.txt new file mode 100644 index 0000000000000..712a8b6e78d85 --- /dev/null +++ b/xbmc/games/agents/windows/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES GUIAgentList.cpp + GUIAgentWindow.cpp +) + +set(HEADERS GUIAgentDefines.h + GUIAgentList.h + GUIAgentWindow.h + IAgentList.h +) + +core_add_library(games_agents_windows) diff --git a/xbmc/games/agents/windows/GUIAgentDefines.h b/xbmc/games/agents/windows/GUIAgentDefines.h new file mode 100644 index 0000000000000..83bf9464516be --- /dev/null +++ b/xbmc/games/agents/windows/GUIAgentDefines.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#pragma once + +// Skin XML file +#define AGENT_DIALOG_XML "DialogGameControllers.xml" + +// GUI control IDs +#define CONTROL_ACTIVE_PORT_LIST 4 +#define CONTROL_AGENT_LIST 5 + +// GUI button IDs +#define CONTROL_CLOSE_BUTTON 18 diff --git a/xbmc/games/agents/windows/GUIAgentList.cpp b/xbmc/games/agents/windows/GUIAgentList.cpp new file mode 100644 index 0000000000000..853eb52feec05 --- /dev/null +++ b/xbmc/games/agents/windows/GUIAgentList.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIAgentList.h" + +#include "FileItem.h" +#include "GUIAgentDefines.h" +#include "GUIAgentWindow.h" +#include "ServiceBroker.h" +#include "addons/AddonEvents.h" +#include "addons/AddonManager.h" +#include "games/GameServices.h" +#include "games/addons/GameClient.h" +#include "games/addons/input/GameClientInput.h" +#include "games/agents/GameAgent.h" +#include "games/agents/GameAgentManager.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerLayout.h" +#include "games/controllers/ControllerManager.h" +#include "guilib/GUIBaseContainer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "view/GUIViewControl.h" +#include "view/ViewState.h" + +using namespace KODI; +using namespace ADDON; +using namespace GAME; + +CGUIAgentList::CGUIAgentList(CGUIWindow& window) + : m_guiWindow(window), + m_viewControl(std::make_unique()), + m_vecItems(std::make_unique()) +{ +} + +CGUIAgentList::~CGUIAgentList() +{ + Deinitialize(); +} + +void CGUIAgentList::OnWindowLoaded() +{ + m_viewControl->Reset(); + m_viewControl->SetParentWindow(m_guiWindow.GetID()); + m_viewControl->AddView(m_guiWindow.GetControl(CONTROL_AGENT_LIST)); +} + +void CGUIAgentList::OnWindowUnload() +{ + m_viewControl->Reset(); +} + +bool CGUIAgentList::Initialize(GameClientPtr gameClient) +{ + // Validate parameters + if (!gameClient) + return false; + + // Initialize state + m_gameClient = std::move(gameClient); + m_viewControl->SetCurrentView(DEFAULT_VIEW_LIST); + + // Initialize GUI + Refresh(); + + // Register observers + if (m_gameClient) + m_gameClient->Input().RegisterObserver(this); + CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CGUIAgentList::OnEvent); + if (CServiceBroker::IsServiceManagerUp()) + CServiceBroker::GetGameServices().GameAgentManager().RegisterObserver(this); + + return true; +} + +void CGUIAgentList::Deinitialize() +{ + // Unregister observers in reverse order + if (CServiceBroker::IsServiceManagerUp()) + CServiceBroker::GetGameServices().GameAgentManager().UnregisterObserver(this); + CServiceBroker::GetAddonMgr().Events().Unsubscribe(this); + if (m_gameClient) + m_gameClient->Input().UnregisterObserver(this); + + // Deinitialize GUI + CleanupItems(); + + // Reset state + m_gameClient.reset(); +} + +bool CGUIAgentList::HasControl(int controlId) const +{ + return m_viewControl->HasControl(controlId); +} + +int CGUIAgentList::GetCurrentControl() const +{ + return m_viewControl->GetCurrentControl(); +} + +void CGUIAgentList::FrameMove() +{ + CGUIBaseContainer* thumbs = + dynamic_cast(m_guiWindow.GetControl(CONTROL_AGENT_LIST)); + if (thumbs != nullptr) + { + const int selectedItem = thumbs->GetSelectedItem(); + if (0 <= selectedItem && selectedItem < m_vecItems->Size()) + { + const unsigned int focusedItem = static_cast(selectedItem); + if (focusedItem != m_currentItem) + OnItemFocus(focusedItem); + } + } +} + +void CGUIAgentList::Refresh() +{ + // Send a synchronous message to clear the view control + m_viewControl->Clear(); + + CleanupItems(); + + CGameAgentManager& agentManager = CServiceBroker::GetGameServices().GameAgentManager(); + + GameAgentVec agents = agentManager.GetAgents(); + for (const GameAgentPtr& agent : agents) + AddItem(*agent); + + // Add a "No controllers connected" item if no agents are available + if (m_vecItems->IsEmpty()) + { + CFileItemPtr item = + std::make_shared(g_localizeStrings.Get(35174)); // "No controllers connected" + m_vecItems->Add(std::move(item)); + } + + // Update items + m_viewControl->SetItems(*m_vecItems); + + // Try to restore focus to the previously focused agent + for (unsigned int currentItem = 0; static_cast(currentItem) < m_vecItems->Size(); + ++currentItem) + { + CFileItemPtr item = m_vecItems->Get(currentItem); + if (item && item->GetPath() == m_currentAgent) + { + m_viewControl->SetSelectedItem(currentItem); + break; + } + } +} + +void CGUIAgentList::SetFocused() +{ + m_viewControl->SetFocused(); +} + +void CGUIAgentList::OnSelect() +{ + const int itemIndex = m_viewControl->GetSelectedItem(); + if (itemIndex >= 0) + OnItemSelect(static_cast(itemIndex)); +} + +void CGUIAgentList::Notify(const Observable& obs, const ObservableMessage msg) +{ + switch (msg) + { + case ObservableMessageGameAgentsChanged: + { + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_AGENT_LIST); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); + } + break; + default: + break; + } +} + +void CGUIAgentList::OnEvent(const ADDON::AddonEvent& event) +{ + if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) || // Also called on install + typeid(event) == typeid(ADDON::AddonEvents::Disabled) || // Not called on uninstall + typeid(event) == typeid(ADDON::AddonEvents::ReInstalled) || + typeid(event) == typeid(ADDON::AddonEvents::UnInstalled)) + { + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_AGENT_LIST); + msg.SetStringParam(event.addonId); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); + } +} + +void CGUIAgentList::AddItem(const CGameAgent& agent) +{ + // Create the list item from agent properties + const std::string label = agent.GetPeripheralName(); + const ControllerPtr controller = agent.GetController(); + const std::string path = agent.GetPeripheralLocation(); + + CFileItemPtr item = std::make_shared(label); + item->SetPath(path); + if (controller) + { + item->SetProperty("Addon.ID", controller->ID()); + item->SetArt("icon", controller->Layout().ImagePath()); + } + m_vecItems->Add(std::move(item)); +} + +void CGUIAgentList::CleanupItems() +{ + m_vecItems->Clear(); +} + +void CGUIAgentList::OnItemFocus(unsigned int itemIndex) +{ + // Remember the focused item + m_currentItem = itemIndex; + + // Handle the focused agent + CFileItemPtr item = m_vecItems->Get(m_currentItem); + if (item) + OnAgentFocus(item->GetPath()); +} + +void CGUIAgentList::OnAgentFocus(const std::string& focusedAgent) +{ + if (!focusedAgent.empty()) + { + // Remember the focused agent + m_currentAgent = focusedAgent; + } +} + +void CGUIAgentList::OnItemSelect(unsigned int itemIndex) +{ + // Handle the selected agent + CFileItemPtr item = m_vecItems->Get(itemIndex); + if (item) + OnAgentSelect(item->GetPath()); +} + +void CGUIAgentList::OnAgentSelect(const std::string& selectedAgent) +{ + if (!selectedAgent.empty()) + { + // Log the selected agent + CLog::Log(LOGDEBUG, "Selected agent: {}", selectedAgent); + } +} diff --git a/xbmc/games/agents/windows/GUIAgentList.h b/xbmc/games/agents/windows/GUIAgentList.h new file mode 100644 index 0000000000000..e1ac884f467a7 --- /dev/null +++ b/xbmc/games/agents/windows/GUIAgentList.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IAgentList.h" +#include "addons/AddonEvents.h" +#include "games/GameTypes.h" +#include "games/controllers/ControllerTypes.h" +#include "games/controllers/types/ControllerTree.h" +#include "utils/Observer.h" + +#include +#include +#include + +class CFileItemList; +class CGUIViewControl; +class CGUIWindow; + +namespace KODI +{ +namespace GAME +{ +class CGameAgent; + +class CGUIAgentList : public IAgentList, public Observer +{ +public: + CGUIAgentList(CGUIWindow& window); + ~CGUIAgentList() override; + + // Implementation of IAgentList + void OnWindowLoaded() override; + void OnWindowUnload() override; + bool Initialize(GameClientPtr gameClient) override; + void Deinitialize() override; + bool HasControl(int controlId) const override; + int GetCurrentControl() const override; + void FrameMove() override; + void Refresh() override; + void SetFocused() override; + void OnSelect() override; + + // Implementation of Observer + void Notify(const Observable& obs, const ObservableMessage msg) override; + +private: + // Add-on API + void OnEvent(const ADDON::AddonEvent& event); + + void AddItem(const CGameAgent& agent); + void CleanupItems(); + void OnItemFocus(unsigned int itemIndex); + void OnAgentFocus(const std::string& focusedAgent); + void OnItemSelect(unsigned int itemIndex); + void OnAgentSelect(const std::string& selectedAgent); + + // Construction parameters + CGUIWindow& m_guiWindow; + + // GUI parameters + std::unique_ptr m_viewControl; + std::unique_ptr m_vecItems; + unsigned int m_currentItem{0}; + std::string m_currentAgent; + + // Game parameters + GameClientPtr m_gameClient; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/agents/windows/GUIAgentWindow.cpp b/xbmc/games/agents/windows/GUIAgentWindow.cpp new file mode 100644 index 0000000000000..885d5ebf824e8 --- /dev/null +++ b/xbmc/games/agents/windows/GUIAgentWindow.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIAgentWindow.h" + +#include "GUIAgentDefines.h" +#include "GUIAgentList.h" +#include "IAgentList.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonType.h" +#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" +#include "cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h" +#include "games/addons/GameClient.h" +#include "games/ports/guicontrols/GUIActivePortList.h" +#include "guilib/GUIButtonControl.h" +#include "guilib/GUIControl.h" +#include "guilib/GUIMessage.h" +#include "guilib/WindowIDs.h" +#include "input/actions/ActionIDs.h" +#include "utils/StringUtils.h" + +using namespace KODI; +using namespace GAME; + +CGUIAgentWindow::CGUIAgentWindow() + : CGUIDialog(WINDOW_DIALOG_GAME_AGENTS, AGENT_DIALOG_XML), + m_portList(std::make_unique(*this)), + m_agentList(std::make_unique(*this)) +{ + // Initialize CGUIWindow + m_loadType = KEEP_IN_MEMORY; +} + +CGUIAgentWindow::~CGUIAgentWindow() = default; + +bool CGUIAgentWindow::OnMessage(CGUIMessage& message) +{ + // Set to true to block the call to the super class + bool bHandled = false; + + switch (message.GetMessage()) + { + case GUI_MSG_SETFOCUS: + { + const int controlId = message.GetControlId(); + if (m_agentList->HasControl(controlId) && m_agentList->GetCurrentControl() != controlId) + { + FocusAgentList(); + bHandled = true; + } + break; + } + case GUI_MSG_CLICKED: + { + const int controlId = message.GetSenderId(); + + if (controlId == CONTROL_CLOSE_BUTTON) + { + CloseDialog(); + bHandled = true; + } + else if (m_agentList->HasControl(controlId)) + { + const int actionId = message.GetParam1(); + if (actionId == ACTION_SELECT_ITEM || actionId == ACTION_MOUSE_LEFT_CLICK) + { + OnAgentClick(); + bHandled = true; + } + } + break; + } + case GUI_MSG_REFRESH_LIST: + { + const int controlId = message.GetControlId(); + switch (controlId) + { + case CONTROL_ACTIVE_PORT_LIST: + { + UpdateActivePortList(); + bHandled = true; + break; + } + case CONTROL_AGENT_LIST: + { + UpdateAgentList(); + bHandled = true; + break; + } + default: + break; + } + break; + } + + default: + break; + } + + if (!bHandled) + bHandled = CGUIDialog::OnMessage(message); + + return bHandled; +} + +void CGUIAgentWindow::FrameMove() +{ + CGUIDialog::FrameMove(); + + m_agentList->FrameMove(); +} + +void CGUIAgentWindow::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + + m_agentList->OnWindowLoaded(); +} + +void CGUIAgentWindow::OnWindowUnload() +{ + m_agentList->OnWindowUnload(); + + CGUIDialog::OnWindowUnload(); +} + +void CGUIAgentWindow::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); + + // Get active game add-on + GameClientPtr gameClient; + { + auto gameSettingsHandle = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + if (gameSettingsHandle) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(gameSettingsHandle->GameClientID(), addon, + ADDON::AddonType::GAMEDLL, + ADDON::OnlyEnabled::CHOICE_YES)) + gameClient = std::static_pointer_cast(addon); + } + } + m_gameClient = std::move(gameClient); + + // Initialize GUI + m_portList->Initialize(m_gameClient); + m_agentList->Initialize(m_gameClient); +} + +void CGUIAgentWindow::OnDeinitWindow(int nextWindowID) +{ + // Deinitialize GUI + m_portList->Deinitialize(); + m_agentList->Deinitialize(); + + // Deinitialize game properties + m_gameClient.reset(); + + CGUIDialog::OnDeinitWindow(nextWindowID); +} + +void CGUIAgentWindow::CloseDialog() +{ + Close(); +} + +void CGUIAgentWindow::UpdateActivePortList() +{ + m_portList->Refresh(); +} + +void CGUIAgentWindow::UpdateAgentList() +{ + m_agentList->Refresh(); +} + +void CGUIAgentWindow::FocusAgentList() +{ + m_agentList->SetFocused(); +} + +void CGUIAgentWindow::OnAgentClick() +{ + m_agentList->OnSelect(); +} diff --git a/xbmc/games/agents/windows/GUIAgentWindow.h b/xbmc/games/agents/windows/GUIAgentWindow.h new file mode 100644 index 0000000000000..d308f6cba8139 --- /dev/null +++ b/xbmc/games/agents/windows/GUIAgentWindow.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "games/GameTypes.h" +#include "guilib/GUIDialog.h" + +#include + +namespace KODI +{ +namespace GAME +{ +class IActivePortList; +class IAgentList; + +class CGUIAgentWindow : public CGUIDialog +{ +public: + CGUIAgentWindow(); + ~CGUIAgentWindow() override; + + // Implementation of CGUIControl via CGUIDialog + bool OnMessage(CGUIMessage& message) override; + +protected: + // Implementation of CGUIWindow via CGUIDialog + void FrameMove() override; + void OnWindowLoaded() override; + void OnWindowUnload() override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + +private: + // Window actions + void CloseDialog(); + + // Actions for port list + void UpdateActivePortList(); + + // Actions for agent list + void UpdateAgentList(); + void FocusAgentList(); + void OnAgentClick(); + + // GUI parameters + std::unique_ptr m_portList; + std::unique_ptr m_agentList; + + // Game parameters + GameClientPtr m_gameClient; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/agents/windows/IAgentList.h b/xbmc/games/agents/windows/IAgentList.h new file mode 100644 index 0000000000000..1ba1d92041740 --- /dev/null +++ b/xbmc/games/agents/windows/IAgentList.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "games/GameTypes.h" + +/*! + * \brief Game player (aka agent) setup window + */ + +namespace KODI +{ +namespace GAME +{ +/*! + * \brief A list populated by game-playing agents + */ +class IAgentList +{ +public: + virtual ~IAgentList() = default; + + /*! + * \brief Callback when the GUI window is loaded + */ + virtual void OnWindowLoaded() = 0; + + /*! + * \brief Callback when the GUI window is unloaded + */ + virtual void OnWindowUnload() = 0; + + /*! + * \brief Initialize resources + * + * \param gameClient The active game client, an empty pointer if no game + * client is active + * + * \return True if the resource is initialized and can be used, false if the + * resource failed to initialize and must not be used + */ + virtual bool Initialize(GameClientPtr gameClient) = 0; + + /*! + * \brief Deinitialize resources + */ + virtual void Deinitialize() = 0; + + /*! + * \brief Query if a control with the given ID belongs to this list + */ + virtual bool HasControl(int controlId) const = 0; + + /*! + * \brief Query the ID of the current control in this list + */ + virtual int GetCurrentControl() const = 0; + + /*! + * \brief Called once per frame + * + * This allows the list to update its currently focused item. + */ + virtual void FrameMove() = 0; + + /*! + * \brief Refresh the contents of the list + */ + virtual void Refresh() = 0; + + /*! + * \brief The agent list has been focused in the GUI + */ + virtual void SetFocused() = 0; + + /*! + * \brief The agent list has been selected in the GUI + */ + virtual void OnSelect() = 0; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/controllers/guicontrols/CMakeLists.txt b/xbmc/games/controllers/guicontrols/CMakeLists.txt index e6babcc9e51a9..371286395f8ef 100644 --- a/xbmc/games/controllers/guicontrols/CMakeLists.txt +++ b/xbmc/games/controllers/guicontrols/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES GUICardinalFeatureButton.cpp GUIFeatureFactory.cpp GUIFeatureTranslator.cpp GUIGameController.cpp + GUIGameControllerList.cpp GUIScalarFeatureButton.cpp GUISelectKeyButton.cpp GUIThrottleButton.cpp @@ -19,6 +20,7 @@ set(HEADERS GUICardinalFeatureButton.h GUIFeatureFactory.h GUIFeatureTranslator.h GUIGameController.h + GUIGameControllerList.h GUIScalarFeatureButton.h GUISelectKeyButton.h GUIThrottleButton.h diff --git a/xbmc/games/controllers/guicontrols/GUIGameController.cpp b/xbmc/games/controllers/guicontrols/GUIGameController.cpp index 39c49fcdaacff..d145c2f2ec449 100644 --- a/xbmc/games/controllers/guicontrols/GUIGameController.cpp +++ b/xbmc/games/controllers/guicontrols/GUIGameController.cpp @@ -18,6 +18,7 @@ #include "guilib/GUIListItem.h" #include "utils/log.h" +#include #include #include @@ -43,8 +44,10 @@ CGUIGameController::CGUIGameController(const CGUIGameController& from) m_controllerAddressInfo(from.m_controllerAddressInfo), m_controllerDiffuse(from.m_controllerDiffuse), m_portAddressInfo(from.m_portAddressInfo), + m_peripheralLocationInfo(from.m_peripheralLocationInfo), m_currentController(from.m_currentController), - m_portAddress(from.m_portAddress) + m_portAddress(from.m_portAddress), + m_peripheralLocation(from.m_peripheralLocation) { // Initialize CGUIControl ControlType = GUICONTROL_GAMECONTROLLER; @@ -58,10 +61,12 @@ CGUIGameController* CGUIGameController::Clone(void) const void CGUIGameController::DoProcess(unsigned int currentTime, CDirtyRegionList& dirtyregions) { std::string portAddress; + std::string peripheralLocation; { std::lock_guard lock(m_mutex); portAddress = m_portAddress; + peripheralLocation = m_peripheralLocation; } const GAME::CGameAgentManager& agentManager = @@ -73,6 +78,9 @@ void CGUIGameController::DoProcess(unsigned int currentTime, CDirtyRegionList& d if (!portAddress.empty()) activation = agentManager.GetPortActivation(portAddress); + if (!peripheralLocation.empty()) + activation = std::max(agentManager.GetPeripheralActivation(peripheralLocation), activation); + SetActivation(activation); CGUIImage::DoProcess(currentTime, dirtyregions); @@ -86,6 +94,7 @@ void CGUIGameController::UpdateInfo(const CGUIListItem* item /* = nullptr */) { std::string controllerId; std::string portAddress; + std::string peripheralLocation; if (item->HasProperty("Addon.ID")) controllerId = item->GetProperty("Addon.ID").asString(); @@ -94,6 +103,7 @@ void CGUIGameController::UpdateInfo(const CGUIListItem* item /* = nullptr */) controllerId = m_controllerIdInfo.GetItemLabel(item); portAddress = m_portAddressInfo.GetItemLabel(item); + peripheralLocation = m_peripheralLocationInfo.GetItemLabel(item); std::string controllerAddress = m_controllerAddressInfo.GetItemLabel(item); if (!controllerAddress.empty()) @@ -105,6 +115,8 @@ void CGUIGameController::UpdateInfo(const CGUIListItem* item /* = nullptr */) ActivateController(controllerId); if (!portAddress.empty()) m_portAddress = portAddress; + if (!peripheralLocation.empty()) + m_peripheralLocation = peripheralLocation; } } @@ -161,6 +173,21 @@ void CGUIGameController::SetPortAddress(const GUILIB::GUIINFO::CGUIInfoLabel& po } } +void CGUIGameController::SetPeripheralLocation( + const GUILIB::GUIINFO::CGUIInfoLabel& peripheralLocation) +{ + m_peripheralLocationInfo = peripheralLocation; + + // Check if a port address is available without a listitem + static const CFileItem empty; + const std::string strPeripheralLocation = m_peripheralLocationInfo.GetItemLabel(&empty); + if (!strPeripheralLocation.empty()) + { + std::lock_guard lock(m_mutex); + m_peripheralLocation = strPeripheralLocation; + } +} + void CGUIGameController::ActivateController(const std::string& controllerId) { CGameServices& gameServices = CServiceBroker::GetGameServices(); @@ -193,6 +220,12 @@ std::string CGUIGameController::GetPortAddress() return m_portAddress; } +std::string CGUIGameController::GetPeripheralLocation() +{ + std::lock_guard lock(m_mutex); + return m_peripheralLocation; +} + void CGUIGameController::SetActivation(float activation) { // Validate parameters diff --git a/xbmc/games/controllers/guicontrols/GUIGameController.h b/xbmc/games/controllers/guicontrols/GUIGameController.h index 351d97f308878..651c4d05ee404 100644 --- a/xbmc/games/controllers/guicontrols/GUIGameController.h +++ b/xbmc/games/controllers/guicontrols/GUIGameController.h @@ -41,11 +41,13 @@ class CGUIGameController : public CGUIImage void SetControllerAddress(const GUILIB::GUIINFO::CGUIInfoLabel& controllerAddress); void SetControllerDiffuse(const GUILIB::GUIINFO::CGUIInfoColor& color); void SetPortAddress(const GUILIB::GUIINFO::CGUIInfoLabel& portAddress); + void SetPeripheralLocation(const GUILIB::GUIINFO::CGUIInfoLabel& peripheralLocation); // Game functions void ActivateController(const std::string& controllerId); void ActivateController(const ControllerPtr& controller); std::string GetPortAddress(); + std::string GetPeripheralLocation(); private: // GUI functions @@ -56,10 +58,12 @@ class CGUIGameController : public CGUIImage GUILIB::GUIINFO::CGUIInfoLabel m_controllerAddressInfo; GUILIB::GUIINFO::CGUIInfoColor m_controllerDiffuse; GUILIB::GUIINFO::CGUIInfoLabel m_portAddressInfo; + GUILIB::GUIINFO::CGUIInfoLabel m_peripheralLocationInfo; // Game parameters ControllerPtr m_currentController; std::string m_portAddress; + std::string m_peripheralLocation; // Synchronization parameters std::mutex m_mutex; diff --git a/xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp b/xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp new file mode 100644 index 0000000000000..c73791d93cbc5 --- /dev/null +++ b/xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIGameControllerList.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "games/GameServices.h" +#include "games/addons/GameClient.h" +#include "games/addons/input/GameClientInput.h" +#include "games/addons/input/GameClientJoystick.h" +#include "games/addons/input/GameClientTopology.h" +#include "games/agents/GameAgent.h" +#include "games/agents/GameAgentManager.h" +#include "games/controllers/Controller.h" +#include "games/controllers/listproviders/GUIGameControllerProvider.h" +#include "guilib/GUIListItem.h" +#include "guilib/GUIMessage.h" +#include "peripherals/devices/Peripheral.h" +#include "utils/Variant.h" + +#include +#include + +using namespace KODI; +using namespace GAME; + +CGUIGameControllerList::CGUIGameControllerList(int parentID, + int controlID, + float posX, + float posY, + float width, + float height, + ORIENTATION orientation, + uint32_t alignment, + const CScroller& scroller) + : CGUIListContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, 0), + m_alignment(alignment) +{ + // Initialize CGUIControl + ControlType = GUICONTROL_GAMECONTROLLERLIST; +} + +CGUIGameControllerList::CGUIGameControllerList(const CGUIGameControllerList& other) + : CGUIListContainer(other), m_alignment(other.m_alignment) +{ + // Initialize CGUIControl + ControlType = GUICONTROL_GAMECONTROLLERLIST; +} + +CGUIGameControllerList* CGUIGameControllerList::Clone(void) const +{ + return new CGUIGameControllerList(*this); +} + +void CGUIGameControllerList::UpdateInfo(const CGUIListItem* item) +{ + CGUIListContainer::UpdateInfo(item); + + if (item == nullptr) + return; + + CGameAgentManager& agentManager = CServiceBroker::GetGameServices().GameAgentManager(); + + // Update port count + const std::vector inputPorts = agentManager.GetInputPorts(); + m_portCount = inputPorts.size(); + + // Update port index + UpdatePort(item->GetCurrentItem(), inputPorts); + + bool bUpdateListProvider = false; + + // Update CGUIListContainer + if (!m_listProvider) + { + m_listProvider = std::make_unique( + m_portCount, m_portIndex, m_peripheralLocation, m_alignment, GetParentID()); + bUpdateListProvider = true; + } + + // Update controller provider + CGUIGameControllerProvider* controllerProvider = + dynamic_cast(m_listProvider.get()); + if (controllerProvider != nullptr) + { + // Update port count + if (controllerProvider->GetPortCount() != m_portCount) + { + controllerProvider->SetPortCount(m_portCount); + bUpdateListProvider = true; + } + + // Update player count + if (controllerProvider->GetPortIndex() != m_portIndex) + { + controllerProvider->SetPortIndex(m_portIndex); + bUpdateListProvider = true; + } + + // Update current controller + const std::string newControllerId = item->GetProperty("Addon.ID").asString(); + if (!newControllerId.empty()) + { + std::string currentControllerId; + if (controllerProvider->GetControllerProfile()) + currentControllerId = controllerProvider->GetControllerProfile()->ID(); + + if (currentControllerId != newControllerId) + { + CGameServices& gameServices = CServiceBroker::GetGameServices(); + ControllerPtr controller = gameServices.GetController(newControllerId); + controllerProvider->SetControllerProfile(std::move(controller)); + bUpdateListProvider = true; + } + } + + // Update peripheral location + if (controllerProvider->GetPeripheralLocation() != m_peripheralLocation) + { + controllerProvider->SetPeripheralLocation(m_peripheralLocation); + bUpdateListProvider = true; + } + } + + if (bUpdateListProvider) + UpdateListProvider(true); +} + +void CGUIGameControllerList::SetGameClient(GAME::GameClientPtr gameClient) +{ + m_gameClient = std::move(gameClient); +} + +void CGUIGameControllerList::ClearGameClient() +{ + m_gameClient.reset(); +} + +void CGUIGameControllerList::UpdatePort(int itemNumber, const std::vector& inputPorts) +{ + // Item numbers start from 1 + if (itemNumber < 1) + return; + + const unsigned int agentIndex = itemNumber - 1; + + CGameAgentManager& agentManager = CServiceBroker::GetGameServices().GameAgentManager(); + + GameAgentVec agents = agentManager.GetAgents(); + if (agentIndex < static_cast(agents.size())) + { + const GameAgentPtr& agent = agents.at(agentIndex); + UpdatePortIndex(agent->GetPeripheral(), inputPorts); + UpdatePeripheral(agent->GetPeripheral()); + } +} + +void CGUIGameControllerList::UpdatePortIndex(const PERIPHERALS::PeripheralPtr& agentPeripheral, + const std::vector& inputPorts) +{ + CGameAgentManager& agentManager = CServiceBroker::GetGameServices().GameAgentManager(); + + // Upcast peripheral to input provider + JOYSTICK::IInputProvider* const inputProvider = + static_cast(agentPeripheral.get()); + + // See if the input provider has a port address + const std::string& portAddress = agentManager.GetPortAddress(inputProvider); + if (portAddress.empty()) + return; + + m_portIndex = -1; + + // Search ports for input provider's address + for (size_t i = 0; i < inputPorts.size(); ++i) + { + if (inputPorts.at(i) == portAddress) + { + // Found port address, record index + m_portIndex = i; + break; + } + } +} + +void CGUIGameControllerList::UpdatePeripheral(const PERIPHERALS::PeripheralPtr& agentPeripheral) +{ + m_peripheralLocation = agentPeripheral->Location(); +} diff --git a/xbmc/games/controllers/guicontrols/GUIGameControllerList.h b/xbmc/games/controllers/guicontrols/GUIGameControllerList.h new file mode 100644 index 0000000000000..94b27ba7e0e8b --- /dev/null +++ b/xbmc/games/controllers/guicontrols/GUIGameControllerList.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "games/GameTypes.h" +#include "guilib/GUIControl.h" +#include "guilib/GUIListContainer.h" +#include "peripherals/PeripheralTypes.h" + +#include +#include +#include +#include + +class CScroller; +class TiXmlElement; + +namespace KODI +{ +namespace GAME +{ +class CGUIGameControllerList : public CGUIListContainer +{ +public: + CGUIGameControllerList(int parentID, + int controlID, + float posX, + float posY, + float width, + float height, + ORIENTATION orientation, + uint32_t alignment, + const CScroller& scroller); + explicit CGUIGameControllerList(const CGUIGameControllerList& other); + + ~CGUIGameControllerList() override = default; + + // Implementation of CGUIControl via CGUIListContainer + CGUIGameControllerList* Clone() const override; + void UpdateInfo(const CGUIListItem* item) override; + + // Game properties + void SetGameClient(GameClientPtr gameClient); + void ClearGameClient(); + + // GUI properties + uint32_t GetAlignment() const { return m_alignment; } + +private: + void UpdatePort(int itemNumber, const std::vector& inputPorts); + void UpdatePortIndex(const PERIPHERALS::PeripheralPtr& agentPeripheral, + const std::vector& inputPorts); + void UpdatePeripheral(const PERIPHERALS::PeripheralPtr& agentPeripheral); + + // Game properties + GameClientPtr m_gameClient; + unsigned int m_portCount{0}; + int m_portIndex{-1}; // Not connected + std::string m_peripheralLocation; + + // GUI properties + const uint32_t m_alignment; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/controllers/listproviders/CMakeLists.txt b/xbmc/games/controllers/listproviders/CMakeLists.txt new file mode 100644 index 0000000000000..eae9d9a0e0a91 --- /dev/null +++ b/xbmc/games/controllers/listproviders/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES GUIGameControllerProvider.cpp +) + +set(HEADERS GUIGameControllerProvider.h +) + +core_add_library(games_controller_listproviders) diff --git a/xbmc/games/controllers/listproviders/GUIGameControllerProvider.cpp b/xbmc/games/controllers/listproviders/GUIGameControllerProvider.cpp new file mode 100644 index 0000000000000..32b644f3cc67d --- /dev/null +++ b/xbmc/games/controllers/listproviders/GUIGameControllerProvider.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIGameControllerProvider.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "games/GameServices.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerLayout.h" +#include "games/ports/windows/GUIPortDefines.h" +#include "guilib/GUIFont.h" +#include "guilib/GUIListItem.h" +#include "utils/Variant.h" + +using namespace KODI; +using namespace GAME; + +namespace +{ +// Prepend a "disconnected" icon to the list of ports +constexpr unsigned int ITEM_COUNT = MAX_PORT_COUNT + 1; +} // namespace + +CGUIGameControllerProvider::CGUIGameControllerProvider(unsigned int portCount, + int portIndex, + const std::string& peripheralLocation, + uint32_t alignment, + int parentID) + : IListProvider(parentID), + m_portCount(portCount), + m_portIndex(portIndex), + m_peripheralLocation(peripheralLocation), + m_alignment(alignment) +{ + InitializeItems(); +} + +CGUIGameControllerProvider::CGUIGameControllerProvider(const CGUIGameControllerProvider& other) + : IListProvider(other.m_parentID), + m_portCount(other.m_portCount), + m_portIndex(other.m_portIndex), + m_peripheralLocation(other.m_peripheralLocation), + m_alignment(other.m_alignment) +{ + InitializeItems(); +} + +CGUIGameControllerProvider::~CGUIGameControllerProvider() = default; + +std::unique_ptr CGUIGameControllerProvider::Clone() +{ + return std::make_unique(*this); +} + +bool CGUIGameControllerProvider::Update(bool forceRefresh) +{ + bool bDirty = false; + std::swap(bDirty, m_bDirty); + return bDirty; +} + +void CGUIGameControllerProvider::Fetch(std::vector& items) +{ + items = m_items; +} + +void CGUIGameControllerProvider::SetControllerProfile(ControllerPtr controllerProfile) +{ + const std::string oldControllerId = m_controllerProfile ? m_controllerProfile->ID() : ""; + const std::string newControllerId = controllerProfile ? controllerProfile->ID() : ""; + + if (oldControllerId != newControllerId) + { + m_controllerProfile = std::move(controllerProfile); + UpdateItems(); + } +} + +void CGUIGameControllerProvider::SetPortCount(unsigned int portCount) +{ + if (m_portCount != portCount) + { + m_portCount = portCount; + UpdateItems(); + } +} + +void CGUIGameControllerProvider::SetPortIndex(int portIndex) +{ + if (m_portIndex != portIndex) + { + m_portIndex = portIndex; + UpdateItems(); + } +} + +void CGUIGameControllerProvider::SetPeripheralLocation(const std::string& peripheralLocation) +{ + if (m_peripheralLocation != peripheralLocation) + { + m_peripheralLocation = peripheralLocation; + UpdateItems(); + } +} + +void CGUIGameControllerProvider::InitializeItems() +{ + m_items.resize(ITEM_COUNT); + for (unsigned int i = 0; i < ITEM_COUNT; ++i) + m_items[i] = std::make_shared(); +} + +void CGUIGameControllerProvider::UpdateItems() +{ + ControllerPtr controller = m_controllerProfile; + + int portIndex = -1; + for (unsigned int i = 0; i < static_cast(m_items.size()); ++i) + { + CGUIListItemPtr& guiItem = m_items.at(i); + + // Pad list if aligning to the right + if (m_alignment == XBFONT_RIGHT && i + m_portCount < MAX_PORT_COUNT) + { + // Fully reset item state. Simply resetting the individual properties + // is not enough, as other properties may also be set. + guiItem = std::make_shared(); + continue; + } + + CFileItemPtr fileItem = std::make_shared(); + + // Set the item state for the current port index + if (portIndex++ == m_portIndex && controller) + { + fileItem->SetLabel(controller->Layout().Label()); + fileItem->SetPath(m_peripheralLocation); + fileItem->SetProperty("Addon.ID", controller->ID()); + fileItem->SetArt("icon", controller->Layout().ImagePath()); + } + + guiItem = std::move(fileItem); + } + + m_bDirty = true; +} diff --git a/xbmc/games/controllers/listproviders/GUIGameControllerProvider.h b/xbmc/games/controllers/listproviders/GUIGameControllerProvider.h new file mode 100644 index 0000000000000..fdaa5039275fa --- /dev/null +++ b/xbmc/games/controllers/listproviders/GUIGameControllerProvider.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "games/controllers/ControllerTypes.h" +#include "listproviders/IListProvider.h" + +#include +#include +#include +#include + +namespace KODI +{ +namespace GAME +{ +class CGUIGameControllerProvider : public IListProvider +{ +public: + CGUIGameControllerProvider(unsigned int portCount, + int portIndex, + const std::string& peripheralLocation, + uint32_t alignment, + int parentID); + explicit CGUIGameControllerProvider(const CGUIGameControllerProvider& other); + + ~CGUIGameControllerProvider() override; + + // Implementation of IListProvider + std::unique_ptr Clone() override; + bool Update(bool forceRefresh) override; + void Fetch(std::vector& items) override; + bool OnClick(const CGUIListItemPtr& item) override { return false; } + bool OnInfo(const CGUIListItemPtr& item) override { return false; } + bool OnContextMenu(const CGUIListItemPtr& item) override { return false; } + void SetDefaultItem(int item, bool always) override {} + int GetDefaultItem() const override { return -1; } + bool AlwaysFocusDefaultItem() const override { return false; } + + // Game functions + ControllerPtr GetControllerProfile() const { return m_controllerProfile; } + void SetControllerProfile(ControllerPtr controllerProfile); + unsigned int GetPortCount() const { return m_portCount; } + void SetPortCount(unsigned int portCount); + int GetPortIndex() const { return m_portIndex; } + void SetPortIndex(int portIndex); + const std::string& GetPeripheralLocation() const { return m_peripheralLocation; } + void SetPeripheralLocation(const std::string& peripheralLocation); + +private: + // GUI functions + void InitializeItems(); + void UpdateItems(); + + // Game parameters + ControllerPtr m_controllerProfile; + unsigned int m_portCount{0}; + int m_portIndex{-1}; // Not connected + std::string m_peripheralLocation; + + // GUI parameters + const uint32_t m_alignment; + std::vector> m_items; + bool m_bDirty{true}; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/controllers/types/ControllerHub.cpp b/xbmc/games/controllers/types/ControllerHub.cpp index 5fd0ece91ba2c..f3aa1e59b02fc 100644 --- a/xbmc/games/controllers/types/ControllerHub.cpp +++ b/xbmc/games/controllers/types/ControllerHub.cpp @@ -107,3 +107,9 @@ const CPortNode& CControllerHub::GetPortInternal(const PortVec& ports, const std static const CPortNode empty{}; return empty; } + +void CControllerHub::GetInputPorts(std::vector& inputPorts) const +{ + for (const CPortNode& port : m_ports) + port.GetInputPorts(inputPorts); +} diff --git a/xbmc/games/controllers/types/ControllerHub.h b/xbmc/games/controllers/types/ControllerHub.h index fc48a81a8fee6..1ddfd9ade2d5e 100644 --- a/xbmc/games/controllers/types/ControllerHub.h +++ b/xbmc/games/controllers/types/ControllerHub.h @@ -44,6 +44,13 @@ class CControllerHub const CPortNode& GetPort(const std::string& address) const; + /*! + * \brief Get a list of ports that accept player input + * + * \param[out] inputPorts The list of input ports + */ + void GetInputPorts(std::vector& inputPorts) const; + private: static const CPortNode& GetPortInternal(const PortVec& ports, const std::string& address); diff --git a/xbmc/games/controllers/types/ControllerNode.cpp b/xbmc/games/controllers/types/ControllerNode.cpp index 4bfda734080dd..701b2d1ba6b6d 100644 --- a/xbmc/games/controllers/types/ControllerNode.cpp +++ b/xbmc/games/controllers/types/ControllerNode.cpp @@ -134,3 +134,11 @@ bool CControllerNode::ProvidesInput() const { return m_controller && m_controller->Topology().ProvidesInput(); } + +void CControllerNode::GetInputPorts(std::vector& inputPorts) const +{ + if (ProvidesInput()) + inputPorts.emplace_back(m_portAddress); + + m_hub->GetInputPorts(inputPorts); +} diff --git a/xbmc/games/controllers/types/ControllerNode.h b/xbmc/games/controllers/types/ControllerNode.h index d4d86e599393b..ba494cb24d1d7 100644 --- a/xbmc/games/controllers/types/ControllerNode.h +++ b/xbmc/games/controllers/types/ControllerNode.h @@ -101,6 +101,13 @@ class CControllerNode */ bool ProvidesInput() const; + /*! + * \brief Get a list of ports that accept player input + * + * \param[out] inputPorts The list of input ports + */ + void GetInputPorts(std::vector& activePorts) const; + private: ControllerPtr m_controller; diff --git a/xbmc/games/ports/guicontrols/CMakeLists.txt b/xbmc/games/ports/guicontrols/CMakeLists.txt new file mode 100644 index 0000000000000..852b830197665 --- /dev/null +++ b/xbmc/games/ports/guicontrols/CMakeLists.txt @@ -0,0 +1,8 @@ +set(SOURCES GUIActivePortList.cpp +) + +set(HEADERS GUIActivePortList.h + IActivePortList.h +) + +core_add_library(games_ports_guicontrols) diff --git a/xbmc/games/ports/guicontrols/GUIActivePortList.cpp b/xbmc/games/ports/guicontrols/GUIActivePortList.cpp new file mode 100644 index 0000000000000..c5c2e9c667674 --- /dev/null +++ b/xbmc/games/ports/guicontrols/GUIActivePortList.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2021-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIActivePortList.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "addons/AddonEvents.h" +#include "addons/AddonManager.h" +#include "games/addons/GameClient.h" +#include "games/addons/input/GameClientInput.h" +#include "games/agents/windows/GUIAgentDefines.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerLayout.h" +#include "games/controllers/guicontrols/GUIGameControllerList.h" +#include "games/controllers/input/PhysicalTopology.h" +#include "games/controllers/types/ControllerHub.h" +#include "games/controllers/types/ControllerTree.h" +#include "games/ports/windows/GUIPortDefines.h" +#include "guilib/GUIFont.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindow.h" +#include "messaging/ApplicationMessenger.h" + +using namespace KODI; +using namespace ADDON; +using namespace GAME; + +CGUIActivePortList::CGUIActivePortList(CGUIWindow& window) + : m_guiWindow(window), m_vecItems(std::make_unique()) +{ +} + +CGUIActivePortList::~CGUIActivePortList() +{ + Deinitialize(); +} + +bool CGUIActivePortList::Initialize(GameClientPtr gameClient) +{ + // Validate parameters + if (!gameClient) + return false; + + // Initialize state + m_gameClient = std::move(gameClient); + + // Initialize GUI + InitializeGUI(); + + // Register observers + m_gameClient->Input().RegisterObserver(this); + CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CGUIActivePortList::OnEvent); + + return true; +} + +void CGUIActivePortList::Deinitialize() +{ + // Unregister observers in reverse order + CServiceBroker::GetAddonMgr().Events().Unsubscribe(this); + if (m_gameClient) + m_gameClient->Input().UnregisterObserver(this); + + // Deinitialize GUI + DeinitializeGUI(); + + // Reset state + m_gameClient.reset(); +} + +void CGUIActivePortList::Refresh() +{ + CleanupItems(); + + // Add input disabled icon + AddInputDisabled(); + + // Add controllers of active ports + if (m_gameClient) + AddItems(m_gameClient->Input().GetActiveControllerTree().GetPorts()); + + // Add padding if right-aligned + if (m_alignment == XBFONT_RIGHT) + AddPadding(); + + // Update the GUI + CGUIMessage msg(GUI_MSG_LABEL_BIND, m_guiWindow.GetID(), CONTROL_ACTIVE_PORT_LIST, 0, 0, + m_vecItems.get()); + m_guiWindow.OnMessage(msg); +} + +void CGUIActivePortList::Notify(const Observable& obs, const ObservableMessage msg) +{ + switch (msg) + { + case ObservableMessageGamePortsChanged: + { + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_ACTIVE_PORT_LIST); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); + } + break; + default: + break; + } +} + +void CGUIActivePortList::OnEvent(const ADDON::AddonEvent& event) +{ + if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) || // Also called on install + typeid(event) == typeid(ADDON::AddonEvents::Disabled) || // Not called on uninstall + typeid(event) == typeid(ADDON::AddonEvents::ReInstalled) || + typeid(event) == typeid(ADDON::AddonEvents::UnInstalled)) + { + CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_ACTIVE_PORT_LIST); + msg.SetStringParam(event.addonId); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); + } +} + +void CGUIActivePortList::InitializeGUI() +{ + CGUIGameControllerList* activePortList = + dynamic_cast(m_guiWindow.GetControl(CONTROL_ACTIVE_PORT_LIST)); + + if (activePortList != nullptr) + { + m_alignment = activePortList->GetAlignment(); + activePortList->SetGameClient(m_gameClient); + } + + Refresh(); +} + +void CGUIActivePortList::DeinitializeGUI() +{ + CleanupItems(); + + CGUIGameControllerList* activePortList = + dynamic_cast(m_guiWindow.GetControl(CONTROL_ACTIVE_PORT_LIST)); + + if (activePortList != nullptr) + activePortList->ClearGameClient(); +} + +void CGUIActivePortList::AddInputDisabled() +{ + CFileItem item; + item.SetArt("icon", "DefaultAddonNone.png"); + m_vecItems->Add(std::move(item)); +} + +void CGUIActivePortList::AddItems(const PortVec& ports) +{ + for (const CPortNode& port : ports) + { + // Add controller + ControllerPtr controller = port.GetActiveController().GetController(); + const std::string& controllerAddress = port.GetActiveController().GetControllerAddress(); + AddItem(std::move(controller), controllerAddress); + + // Add child ports + AddItems(port.GetActiveController().GetHub().GetPorts()); + } +} + +void CGUIActivePortList::AddItem(ControllerPtr controller, const std::string& controllerAddress) +{ + // Check if a controller is connected that provides input + if (controller && controller->Topology().ProvidesInput()) + { + // Add GUI item + CFileItemPtr item = std::make_shared(controller->Layout().Label()); + item->SetArt("icon", controller->Layout().ImagePath()); + item->SetPath(controllerAddress); + m_vecItems->Add(std::move(item)); + } +} + +void CGUIActivePortList::AddPadding() +{ + while (m_vecItems->Size() < static_cast(MAX_PORT_COUNT + 1)) + m_vecItems->AddFront(std::make_shared(), 0); +} + +void CGUIActivePortList::CleanupItems() +{ + m_vecItems->Clear(); +} diff --git a/xbmc/games/ports/guicontrols/GUIActivePortList.h b/xbmc/games/ports/guicontrols/GUIActivePortList.h new file mode 100644 index 0000000000000..dfefc8d46f823 --- /dev/null +++ b/xbmc/games/ports/guicontrols/GUIActivePortList.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IActivePortList.h" +#include "addons/AddonEvents.h" +#include "games/GameTypes.h" +#include "games/controllers/ControllerTypes.h" +#include "games/ports/types/PortNode.h" +#include "utils/Observer.h" + +#include + +class CFileItemList; +class CGUIWindow; + +namespace KODI +{ +namespace GAME +{ +class CController; +class IActivePortList; + +class CGUIActivePortList : public IActivePortList, public Observer +{ +public: + CGUIActivePortList(CGUIWindow& window); + ~CGUIActivePortList() override; + + // Implementation of IActivePortList + bool Initialize(GameClientPtr gameClient) override; + void Deinitialize() override; + void Refresh() override; + + // Implementation of Observer + void Notify(const Observable& obs, const ObservableMessage msg) override; + +private: + // Add-on API + void OnEvent(const ADDON::AddonEvent& event); + + // GUI helpers + void InitializeGUI(); + void DeinitializeGUI(); + void AddInputDisabled(); + void AddItems(const PortVec& ports); + void AddItem(ControllerPtr controller, const std::string& controllerAddress); + void AddPadding(); + void CleanupItems(); + + // Construction parameters + CGUIWindow& m_guiWindow; + + // GUI parameters + std::unique_ptr m_vecItems; + uint32_t m_alignment{0}; + + // Game parameters + GameClientPtr m_gameClient; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/ports/guicontrols/IActivePortList.h b/xbmc/games/ports/guicontrols/IActivePortList.h new file mode 100644 index 0000000000000..a9405f231258b --- /dev/null +++ b/xbmc/games/ports/guicontrols/IActivePortList.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021-2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "games/GameTypes.h" +#include "games/controllers/ControllerTypes.h" + +namespace KODI +{ +namespace GAME +{ + +/*! + * \brief A list populated by input ports on a game console + * + * Each port is an item in the list. Ports are represented by the controller + * icon of the connected controller. + * + * Ports are only included in the list if the controller profile provides + * input. For example, Multitaps will be skipped. + */ +class IActivePortList +{ +public: + virtual ~IActivePortList() = default; + + /*! + * \brief Initialize resources + * + * \param gameClient The game client providing the ports + * + * \return True if the resource is initialized and can be used, false if + * the resource failed to initialize and must not be used + */ + virtual bool Initialize(GameClientPtr gameClient) = 0; + + /*! + * \brief Deinitialize resources + */ + virtual void Deinitialize() = 0; + + /*! + * \brief Refresh the contents of the list + */ + virtual void Refresh() = 0; +}; + +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/ports/types/PortNode.cpp b/xbmc/games/ports/types/PortNode.cpp index 7caf6eba17e4e..8da98d4bc0c37 100644 --- a/xbmc/games/ports/types/PortNode.cpp +++ b/xbmc/games/ports/types/PortNode.cpp @@ -144,6 +144,15 @@ bool CPortNode::IsControllerAccepted(const std::string& portAddress, return bAccepted; } +void CPortNode::GetInputPorts(std::vector& inputPorts) const +{ + if (IsConnected()) + { + const CControllerNode& controller = GetActiveController(); + controller.GetInputPorts(inputPorts); + } +} + void CPortNode::GetPort(CPhysicalPort& port) const { std::vector accepts; diff --git a/xbmc/games/ports/types/PortNode.h b/xbmc/games/ports/types/PortNode.h index 660a7bcc8a9ad..4c5f7404f558e 100644 --- a/xbmc/games/ports/types/PortNode.h +++ b/xbmc/games/ports/types/PortNode.h @@ -110,6 +110,13 @@ class CPortNode */ bool IsControllerAccepted(const std::string& portAddress, const std::string& controllerId) const; + /*! + * \brief Get a list of ports that accept player input + * + * \param[out] inputPorts The list of input ports + */ + void GetInputPorts(std::vector& inputPorts) const; + private: void GetPort(CPhysicalPort& port) const; diff --git a/xbmc/games/ports/windows/GUIPortDefines.h b/xbmc/games/ports/windows/GUIPortDefines.h index c9c255b3fe944..5d587e931738c 100644 --- a/xbmc/games/ports/windows/GUIPortDefines.h +++ b/xbmc/games/ports/windows/GUIPortDefines.h @@ -20,3 +20,6 @@ // Skin XML file #define PORT_DIALOG_XML "DialogGameControllers.xml" + +// Allow for two Saturn 6 Player Adapters +constexpr unsigned int MAX_PORT_COUNT = 12; diff --git a/xbmc/guilib/GUIControl.h b/xbmc/guilib/GUIControl.h index 49fadaa9eb5fd..daf681726ab94 100644 --- a/xbmc/guilib/GUIControl.h +++ b/xbmc/guilib/GUIControl.h @@ -271,6 +271,7 @@ class CGUIControl GUICONTROL_FADELABEL, GUICONTROL_GAME, GUICONTROL_GAMECONTROLLER, + GUICONTROL_GAMECONTROLLERLIST, GUICONTROL_GROUP, GUICONTROL_GROUPLIST, GUICONTROL_IMAGE, diff --git a/xbmc/guilib/GUIControlFactory.cpp b/xbmc/guilib/GUIControlFactory.cpp index 8b556a297f85e..ca193e663aba8 100644 --- a/xbmc/guilib/GUIControlFactory.cpp +++ b/xbmc/guilib/GUIControlFactory.cpp @@ -48,6 +48,7 @@ #include "addons/Skin.h" #include "cores/RetroPlayer/guicontrols/GUIGameControl.h" #include "games/controllers/guicontrols/GUIGameController.h" +#include "games/controllers/guicontrols/GUIGameControllerList.h" #include "input/Key.h" #include "pvr/guilib/GUIEPGGridContainer.h" #include "utils/CharsetConverter.h" @@ -74,6 +75,7 @@ static const ControlMapping controls[] = { {"fadelabel", CGUIControl::GUICONTROL_FADELABEL}, {"fixedlist", CGUIControl::GUICONTAINER_FIXEDLIST}, {"gamecontroller", CGUIControl::GUICONTROL_GAMECONTROLLER}, + {"gamecontrollerlist", CGUIControl::GUICONTROL_GAMECONTROLLERLIST}, {"gamewindow", CGUIControl::GUICONTROL_GAME}, {"group", CGUIControl::GUICONTROL_GROUP}, {"group", CGUIControl::GUICONTROL_LISTGROUP}, @@ -1580,6 +1582,32 @@ CGUIControl* CGUIControlFactory::Create(int parentID, const CRect &rect, TiXmlEl GetInfoLabel(pControlNode, "portaddress", portAddress, parentID); gcontrol->SetPortAddress(portAddress); + // Set peripheral location + GUIINFO::CGUIInfoLabel peripheralLocation; + GetInfoLabel(pControlNode, "peripherallocation", peripheralLocation, parentID); + gcontrol->SetPeripheralLocation(peripheralLocation); + + break; + } + case CGUIControl::GUICONTROL_GAMECONTROLLERLIST: + { + CScroller scroller; + GetScroller(pControlNode, "scrolltime", scroller); + + control = new GAME::CGUIGameControllerList(parentID, id, posX, posY, width, height, orientation, labelInfo.align, scroller); + + GAME::CGUIGameControllerList* lcontrol = static_cast(control); + + lcontrol->LoadLayout(pControlNode); + lcontrol->LoadListProvider(pControlNode, defaultControl, defaultAlways); + lcontrol->SetType(viewType, viewLabel); + lcontrol->SetPageControl(pageControl); + lcontrol->SetRenderOffset(offset); + lcontrol->SetAutoScrolling(pControlNode); + lcontrol->SetClickActions(clickActions); + lcontrol->SetFocusActions(focusActions); + lcontrol->SetUnFocusActions(unfocusActions); + break; } case CGUIControl::GUICONTROL_COLORBUTTON: diff --git a/xbmc/guilib/GUIListGroup.cpp b/xbmc/guilib/GUIListGroup.cpp index 67645c2624a8a..8883d944e518c 100644 --- a/xbmc/guilib/GUIListGroup.cpp +++ b/xbmc/guilib/GUIListGroup.cpp @@ -21,6 +21,7 @@ const std::set supportedTypes = { CGUIControl::GUICONTROL_BORDEREDIMAGE, CGUIControl::GUICONTROL_GAME, CGUIControl::GUICONTROL_GAMECONTROLLER, + CGUIControl::GUICONTROL_GAMECONTROLLERLIST, CGUIControl::GUICONTROL_IMAGE, CGUIControl::GUICONTROL_LISTGROUP, CGUIControl::GUICONTROL_LISTLABEL, diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index f15489e00e07f..06cfef5d7d9a9 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -144,6 +144,7 @@ /* Game related include files */ #include "cores/RetroPlayer/guiwindows/GameWindowFullScreen.h" +#include "games/agents/windows/GUIAgentWindow.h" #include "games/controllers/windows/GUIControllerWindow.h" #include "games/dialogs/osd/DialogGameAdvancedSettings.h" #include "games/dialogs/osd/DialogGameOSD.h" @@ -318,6 +319,7 @@ void CGUIWindowManager::CreateWindows() Add(new GAME::CDialogGameAdvancedSettings); Add(new GAME::CDialogGameVideoRotation); Add(new GAME::CDialogInGameSaves); + Add(new GAME::CGUIAgentWindow); Add(new RETRO::CGameWindowFullScreen); } @@ -437,6 +439,7 @@ bool CGUIWindowManager::DestroyWindows() DestroyWindow(WINDOW_DIALOG_GAME_ADVANCED_SETTINGS); DestroyWindow(WINDOW_DIALOG_GAME_VIDEO_ROTATION); DestroyWindow(WINDOW_DIALOG_IN_GAME_SAVES); + DestroyWindow(WINDOW_DIALOG_GAME_AGENTS); DestroyWindow(WINDOW_FULLSCREEN_GAME); Remove(WINDOW_SETTINGS_SERVICE); diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h index 02ff8ebaacc75..20921c618572a 100644 --- a/xbmc/guilib/WindowIDs.h +++ b/xbmc/guilib/WindowIDs.h @@ -156,6 +156,7 @@ #define WINDOW_DIALOG_GAME_PORTS 10828 #define WINDOW_DIALOG_IN_GAME_SAVES 10829 #define WINDOW_DIALOG_GAME_SAVES 10830 +#define WINDOW_DIALOG_GAME_AGENTS 10831 //#define WINDOW_VIRTUAL_KEYBOARD 11000 // WINDOW_ID's from 11100 to 11199 reserved for Skins diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp index cceb3f06f2d11..2a30ab6ed3a9e 100644 --- a/xbmc/input/WindowTranslator.cpp +++ b/xbmc/input/WindowTranslator.cpp @@ -169,7 +169,9 @@ const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName {"gameadvancedsettings", WINDOW_DIALOG_GAME_ADVANCED_SETTINGS}, {"gamevideorotation", WINDOW_DIALOG_GAME_VIDEO_ROTATION}, {"ingamesaves", WINDOW_DIALOG_IN_GAME_SAVES}, - {"gamesaves", WINDOW_DIALOG_GAME_SAVES}}; + {"gamesaves", WINDOW_DIALOG_GAME_SAVES}, + {"gameagents", WINDOW_DIALOG_GAME_AGENTS}, +}; namespace { diff --git a/xbmc/peripherals/addons/AddonInputHandling.cpp b/xbmc/peripherals/addons/AddonInputHandling.cpp index 644c33ea53582..7ae7208a646bc 100644 --- a/xbmc/peripherals/addons/AddonInputHandling.cpp +++ b/xbmc/peripherals/addons/AddonInputHandling.cpp @@ -35,7 +35,7 @@ CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, { CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", peripheral->DeviceName()); } - else + else if (!handler->ControllerID().empty()) { m_buttonMap.reset(new CAddonButtonMap(peripheral, addon, handler->ControllerID())); if (m_buttonMap->Load()) @@ -67,7 +67,7 @@ CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, { CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", peripheral->DeviceName()); } - else + else if (!handler->ControllerID().empty()) { m_buttonMap.reset(new CAddonButtonMap(peripheral, addon, handler->ControllerID())); if (m_buttonMap->Load()) @@ -91,7 +91,7 @@ CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, { CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", peripheral->DeviceName()); } - else + else if (!handler->ControllerID().empty()) { m_buttonMap.reset(new CAddonButtonMap(peripheral, addon, handler->ControllerID())); if (m_buttonMap->Load())