Merge pull request #26373 from ksooo/app-fix-multi-resolve-playback
[xbmc.git] / xbmc / games / ports / input / PortManager.cpp
blobfb664b7ebccd27b1642f78d08bcf9af2a4a3a50b
1 /*
2 * Copyright (C) 2021 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "PortManager.h"
11 #include "URL.h"
12 #include "games/controllers/Controller.h"
13 #include "games/controllers/types/ControllerHub.h"
14 #include "games/controllers/types/ControllerNode.h"
15 #include "games/ports/types/PortNode.h"
16 #include "utils/FileUtils.h"
17 #include "utils/URIUtils.h"
18 #include "utils/XBMCTinyXML2.h"
19 #include "utils/XMLUtils.h"
20 #include "utils/log.h"
22 #include <algorithm>
23 #include <cstring>
25 using namespace KODI;
26 using namespace GAME;
28 namespace
30 constexpr const char* PORT_XML_FILE = "ports.xml";
31 constexpr const char* XML_ROOT_PORTS = "ports";
32 constexpr const char* XML_ELM_PORT = "port";
33 constexpr const char* XML_ELM_CONTROLLER = "controller";
34 constexpr const char* XML_ATTR_PORT_ID = "id";
35 constexpr const char* XML_ATTR_PORT_ADDRESS = "address";
36 constexpr const char* XML_ATTR_PORT_CONNECTED = "connected";
37 constexpr const char* XML_ATTR_PORT_CONTROLLER = "controller";
38 constexpr const char* XML_ATTR_CONTROLLER_ID = "id";
39 } // namespace
41 CPortManager::CPortManager() = default;
43 CPortManager::~CPortManager() = default;
45 void CPortManager::Initialize(const std::string& profilePath)
47 m_xmlPath = URIUtils::AddFileToFolder(profilePath, PORT_XML_FILE);
50 void CPortManager::Deinitialize()
52 // Wait for save tasks
53 for (std::future<void>& task : m_saveFutures)
54 task.wait();
55 m_saveFutures.clear();
57 m_controllerTree.Clear();
58 m_xmlPath.clear();
61 void CPortManager::SetControllerTree(const CControllerTree& controllerTree)
63 m_controllerTree = controllerTree;
66 void CPortManager::LoadXML()
68 if (!CFileUtils::Exists(m_xmlPath))
70 CLog::Log(LOGDEBUG, "Can't load port config, file doesn't exist: {}",
71 CURL::GetRedacted(m_xmlPath));
72 return;
75 CLog::Log(LOGINFO, "Loading port layout: {}", CURL::GetRedacted(m_xmlPath));
77 CXBMCTinyXML2 xmlDoc;
78 if (!xmlDoc.LoadFile(m_xmlPath))
80 CLog::Log(LOGDEBUG, "Unable to load file: {} at line {}", xmlDoc.ErrorStr(),
81 xmlDoc.ErrorLineNum());
82 return;
85 const auto* pRootElement = xmlDoc.RootElement();
86 if (pRootElement == nullptr || pRootElement->NoChildren() ||
87 std::strcmp(pRootElement->Value(), XML_ROOT_PORTS) != 0)
89 CLog::Log(LOGERROR, "Can't find root <{}> tag", XML_ROOT_PORTS);
90 return;
93 DeserializePorts(pRootElement, m_controllerTree.GetPorts());
96 void CPortManager::SaveXMLAsync()
98 PortVec ports = m_controllerTree.GetPorts();
100 // Prune any finished save tasks
101 m_saveFutures.erase(std::remove_if(m_saveFutures.begin(), m_saveFutures.end(),
102 [](std::future<void>& task) {
103 return task.wait_for(std::chrono::seconds(0)) ==
104 std::future_status::ready;
106 m_saveFutures.end());
108 // Save async
109 std::future<void> task = std::async(std::launch::async,
110 [this, ports = std::move(ports)]()
112 CXBMCTinyXML2 doc;
113 auto* node = doc.NewElement(XML_ROOT_PORTS);
114 if (node == nullptr)
115 return;
117 SerializePorts(*node, ports);
119 doc.InsertEndChild(node);
121 std::lock_guard<std::mutex> lock(m_saveMutex);
122 doc.SaveFile(m_xmlPath);
125 m_saveFutures.emplace_back(std::move(task));
128 void CPortManager::Clear()
130 m_xmlPath.clear();
131 m_controllerTree.Clear();
134 void CPortManager::ConnectController(const std::string& portAddress,
135 bool connected,
136 const std::string& controllerId /* = "" */)
138 ConnectController(portAddress, connected, controllerId, m_controllerTree.GetPorts());
141 bool CPortManager::ConnectController(const std::string& portAddress,
142 bool connected,
143 const std::string& controllerId,
144 PortVec& ports)
146 for (CPortNode& port : ports)
148 if (ConnectController(portAddress, connected, controllerId, port))
149 return true;
152 return false;
155 bool CPortManager::ConnectController(const std::string& portAddress,
156 bool connected,
157 const std::string& controllerId,
158 CPortNode& port)
160 // Base case
161 if (port.GetAddress() == portAddress)
163 port.SetConnected(connected);
164 if (!controllerId.empty())
165 port.SetActiveController(controllerId);
166 return true;
169 // Check children
170 return ConnectController(portAddress, connected, controllerId, port.GetCompatibleControllers());
173 bool CPortManager::ConnectController(const std::string& portAddress,
174 bool connected,
175 const std::string& controllerId,
176 ControllerNodeVec& controllers)
178 for (CControllerNode& controller : controllers)
180 if (ConnectController(portAddress, connected, controllerId, controller))
181 return true;
184 return false;
187 bool CPortManager::ConnectController(const std::string& portAddress,
188 bool connected,
189 const std::string& controllerId,
190 CControllerNode& controller)
192 for (CPortNode& childPort : controller.GetHub().GetPorts())
194 if (ConnectController(portAddress, connected, controllerId, childPort))
195 return true;
198 return false;
201 void CPortManager::DeserializePorts(const tinyxml2::XMLElement* pElement, PortVec& ports)
203 for (const auto* pPort = pElement->FirstChildElement(); pPort != nullptr;
204 pPort = pPort->NextSiblingElement())
206 if (std::strcmp(pPort->Value(), XML_ELM_PORT) != 0)
208 CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pElement->Value(), pPort->Value());
209 continue;
212 std::string portId = XMLUtils::GetAttribute(pPort, XML_ATTR_PORT_ID);
214 auto it = std::find_if(ports.begin(), ports.end(),
215 [&portId](const CPortNode& port) { return port.GetPortID() == portId; });
216 if (it != ports.end())
218 CPortNode& port = *it;
220 DeserializePort(pPort, port);
225 void CPortManager::DeserializePort(const tinyxml2::XMLElement* pPort, CPortNode& port)
227 // Connected
228 bool connected = (XMLUtils::GetAttribute(pPort, XML_ATTR_PORT_CONNECTED) == "true");
229 port.SetConnected(connected);
231 // Controller
232 const std::string activeControllerId = XMLUtils::GetAttribute(pPort, XML_ATTR_PORT_CONTROLLER);
233 if (!port.SetActiveController(activeControllerId))
234 port.SetConnected(false);
236 DeserializeControllers(pPort, port.GetCompatibleControllers());
239 void CPortManager::DeserializeControllers(const tinyxml2::XMLElement* pPort,
240 ControllerNodeVec& controllers)
242 for (const auto* pController = pPort->FirstChildElement(); pController != nullptr;
243 pController = pController->NextSiblingElement())
245 if (std::strcmp(pController->Value(), XML_ELM_CONTROLLER) != 0)
247 CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pPort->Value(),
248 pController->Value());
249 continue;
252 std::string controllerId = XMLUtils::GetAttribute(pController, XML_ATTR_CONTROLLER_ID);
254 auto it = std::find_if(controllers.begin(), controllers.end(),
255 [&controllerId](const CControllerNode& controller)
256 { return controller.GetController()->ID() == controllerId; });
257 if (it != controllers.end())
259 CControllerNode& controller = *it;
261 DeserializeController(pController, controller);
266 void CPortManager::DeserializeController(const tinyxml2::XMLElement* pController,
267 CControllerNode& controller)
269 // Child ports
270 DeserializePorts(pController, controller.GetHub().GetPorts());
273 void CPortManager::SerializePorts(tinyxml2::XMLElement& node, const PortVec& ports)
275 auto doc = node.GetDocument();
276 for (const CPortNode& port : ports)
278 auto portNode = doc->NewElement(XML_ELM_PORT);
279 if (portNode == nullptr)
280 continue;
282 SerializePort(*portNode, port);
284 node.InsertEndChild(portNode);
288 void CPortManager::SerializePort(tinyxml2::XMLElement& portNode, const CPortNode& port)
290 // Port ID
291 portNode.SetAttribute(XML_ATTR_PORT_ID, port.GetPortID().c_str());
293 // Port address
294 portNode.SetAttribute(XML_ATTR_PORT_ADDRESS, port.GetAddress().c_str());
296 // Connected state
297 portNode.SetAttribute(XML_ATTR_PORT_CONNECTED, port.IsConnected() ? "true" : "false");
299 // Active controller
300 if (port.GetActiveController().GetController())
302 const std::string controllerId = port.GetActiveController().GetController()->ID();
303 portNode.SetAttribute(XML_ATTR_PORT_CONTROLLER, controllerId.c_str());
306 // All compatible controllers
307 SerializeControllers(portNode, port.GetCompatibleControllers());
310 void CPortManager::SerializeControllers(tinyxml2::XMLElement& portNode,
311 const ControllerNodeVec& controllers)
313 for (const CControllerNode& controller : controllers)
315 // Skip controller if it has no state
316 if (!HasState(controller))
317 continue;
319 auto controllerNode = portNode.GetDocument()->NewElement(XML_ELM_CONTROLLER);
320 if (controllerNode == nullptr)
321 continue;
323 SerializeController(*controllerNode, controller);
325 portNode.InsertEndChild(controllerNode);
329 void CPortManager::SerializeController(tinyxml2::XMLElement& controllerNode,
330 const CControllerNode& controller)
332 // Controller ID
333 if (controller.GetController())
334 controllerNode.SetAttribute(XML_ATTR_CONTROLLER_ID, controller.GetController()->ID().c_str());
336 // Ports
337 SerializePorts(controllerNode, controller.GetHub().GetPorts());
340 bool CPortManager::HasState(const CPortNode& port)
342 // Ports have state (is connected / active controller)
343 return true;
346 bool CPortManager::HasState(const CControllerNode& controller)
348 // Check controller ports
349 for (const CPortNode& port : controller.GetHub().GetPorts())
351 if (HasState(port))
352 return true;
355 // Controller itself has no state
356 return false;