1 /*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "GameClientInput.h"
10
11 #include "GameClientController.h"
12 #include "GameClientHardware.h"
13 #include "GameClientJoystick.h"
14 #include "GameClientKeyboard.h"
15 #include "GameClientMouse.h"
16 #include "GameClientPort.h"
17 #include "GameClientTopology.h"
18 #include "ServiceBroker.h"
19 #include "addons/kodi-dev-kit/include/kodi/addon-instance/Game.h"
20 #include "games/GameServices.h"
21 #include "games/addons/GameClient.h"
22 #include "games/addons/GameClientCallbacks.h"
23 #include "games/controllers/Controller.h"
24 #include "games/controllers/ControllerTopology.h"
25 #include "input/joysticks/JoystickTypes.h"
26 #include "peripherals/EventLockHandle.h"
27 #include "peripherals/Peripherals.h"
28 #include "threads/SingleLock.h"
29 #include "utils/log.h"
30
31 #include <algorithm>
32
33 using namespace KODI;
34 using namespace GAME;
35
CGameClientInput(CGameClient & gameClient,AddonInstance_Game & addonStruct,CCriticalSection & clientAccess)36 CGameClientInput::CGameClientInput(CGameClient& gameClient,
37 AddonInstance_Game& addonStruct,
38 CCriticalSection& clientAccess)
39 : CGameClientSubsystem(gameClient, addonStruct, clientAccess), m_topology(new CGameClientTopology)
40 {
41 }
42
~CGameClientInput()43 CGameClientInput::~CGameClientInput()
44 {
45 Deinitialize();
46 }
47
Initialize()48 void CGameClientInput::Initialize()
49 {
50 LoadTopology();
51
52 ActivateControllers(m_topology->ControllerTree());
53
54 SetControllerLayouts(m_topology->ControllerTree().GetControllers());
55 }
56
Start(IGameInputCallback * input)57 void CGameClientInput::Start(IGameInputCallback* input)
58 {
59 m_inputCallback = input;
60
61 const CControllerTree& controllers = m_topology->ControllerTree();
62
63 // Open keyboard
64 //! @todo Move to player manager
65 if (SupportsKeyboard())
66 {
67 auto it = std::find_if(
68 controllers.Ports().begin(), controllers.Ports().end(),
69 [](const CControllerPortNode& port) { return port.PortType() == PORT_TYPE::KEYBOARD; });
70
71 OpenKeyboard(it->ActiveController().Controller());
72 }
73
74 // Open mouse
75 //! @todo Move to player manager
76 if (SupportsMouse())
77 {
78 auto it = std::find_if(
79 controllers.Ports().begin(), controllers.Ports().end(),
80 [](const CControllerPortNode& port) { return port.PortType() == PORT_TYPE::MOUSE; });
81
82 OpenMouse(it->ActiveController().Controller());
83 }
84
85 // Open joysticks
86 //! @todo Move to player manager
87 for (const auto& port : controllers.Ports())
88 {
89 if (port.PortType() == PORT_TYPE::CONTROLLER && !port.CompatibleControllers().empty())
90 {
91 ControllerPtr controller = port.ActiveController().Controller();
92 OpenJoystick(port.Address(), controller);
93 }
94 }
95
96 // Ensure hardware is open to receive events
97 m_hardware.reset(new CGameClientHardware(m_gameClient));
98
99 if (CServiceBroker::IsServiceManagerUp())
100 CServiceBroker::GetPeripherals().RegisterObserver(this);
101 }
102
Deinitialize()103 void CGameClientInput::Deinitialize()
104 {
105 Stop();
106
107 m_topology->Clear();
108 m_controllerLayouts.clear();
109 }
110
Stop()111 void CGameClientInput::Stop()
112 {
113 if (CServiceBroker::IsServiceManagerUp())
114 CServiceBroker::GetPeripherals().UnregisterObserver(this);
115
116 m_hardware.reset();
117
118 std::vector<std::string> ports;
119 for (const auto& it : m_joysticks)
120 ports.emplace_back(it.first);
121
122 for (const std::string& port : ports)
123 CloseJoystick(port);
124 m_portMap.clear();
125
126 CloseMouse();
127
128 CloseKeyboard();
129
130 m_inputCallback = nullptr;
131 }
132
HasFeature(const std::string & controllerId,const std::string & featureName) const133 bool CGameClientInput::HasFeature(const std::string& controllerId,
134 const std::string& featureName) const
135 {
136 bool bHasFeature = false;
137
138 try
139 {
140 bHasFeature =
141 m_struct.toAddon->HasFeature(&m_struct, controllerId.c_str(), featureName.c_str());
142 }
143 catch (...)
144 {
145 CLog::Log(LOGERROR, "GAME: %s: exception caught in HasFeature()", m_gameClient.ID().c_str());
146
147 // Fail gracefully
148 bHasFeature = true;
149 }
150
151 return bHasFeature;
152 }
153
AcceptsInput() const154 bool CGameClientInput::AcceptsInput() const
155 {
156 if (m_inputCallback != nullptr)
157 return m_inputCallback->AcceptsInput();
158
159 return false;
160 }
161
InputEvent(const game_input_event & event)162 bool CGameClientInput::InputEvent(const game_input_event& event)
163 {
164 bool bHandled = false;
165
166 try
167 {
168 bHandled = m_struct.toAddon->InputEvent(&m_struct, &event);
169 }
170 catch (...)
171 {
172 CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient.ID().c_str());
173 }
174
175 return bHandled;
176 }
177
LoadTopology()178 void CGameClientInput::LoadTopology()
179 {
180 game_input_topology* topologyStruct = nullptr;
181
182 if (m_gameClient.Initialized())
183 {
184 try
185 {
186 topologyStruct = m_struct.toAddon->GetTopology(&m_struct);
187 }
188 catch (...)
189 {
190 m_gameClient.LogException("GetTopology()");
191 }
192 }
193
194 GameClientPortVec hardwarePorts;
195 int playerLimit = -1;
196
197 if (topologyStruct != nullptr)
198 {
199 //! @todo Guard against infinite loops provided by the game client
200
201 game_input_port* ports = topologyStruct->ports;
202 if (ports != nullptr)
203 {
204 for (unsigned int i = 0; i < topologyStruct->port_count; i++)
205 hardwarePorts.emplace_back(new CGameClientPort(ports[i]));
206 }
207
208 playerLimit = topologyStruct->player_limit;
209
210 try
211 {
212 m_struct.toAddon->FreeTopology(&m_struct, topologyStruct);
213 }
214 catch (...)
215 {
216 m_gameClient.LogException("FreeTopology()");
217 }
218 }
219
220 // If no topology is available, create a default one with a single port that
221 // accepts all controllers imported by addon.xml
222 if (hardwarePorts.empty())
223 hardwarePorts.emplace_back(new CGameClientPort(GetControllers(m_gameClient)));
224
225 m_topology.reset(new CGameClientTopology(std::move(hardwarePorts), playerLimit));
226 }
227
ActivateControllers(CControllerHub & hub)228 void CGameClientInput::ActivateControllers(CControllerHub& hub)
229 {
230 for (auto& port : hub.Ports())
231 {
232 port.SetConnected(true);
233 port.SetActiveController(0);
234 ActivateControllers(port.ActiveController().Hub());
235 }
236 }
237
SetControllerLayouts(const ControllerVector & controllers)238 void CGameClientInput::SetControllerLayouts(const ControllerVector& controllers)
239 {
240 if (controllers.empty())
241 return;
242
243 for (const auto& controller : controllers)
244 {
245 const std::string controllerId = controller->ID();
246 if (m_controllerLayouts.find(controllerId) == m_controllerLayouts.end())
247 m_controllerLayouts[controllerId].reset(new CGameClientController(*this, controller));
248 }
249
250 std::vector<game_controller_layout> controllerStructs;
251 for (const auto& it : m_controllerLayouts)
252 controllerStructs.emplace_back(it.second->TranslateController());
253
254 try
255 {
256 m_struct.toAddon->SetControllerLayouts(&m_struct, controllerStructs.data(),
257 static_cast<unsigned int>(controllerStructs.size()));
258 }
259 catch (...)
260 {
261 m_gameClient.LogException("SetControllerLayouts()");
262 }
263 }
264
GetControllerTree() const265 const CControllerTree& CGameClientInput::GetControllerTree() const
266 {
267 return m_topology->ControllerTree();
268 }
269
SupportsKeyboard() const270 bool CGameClientInput::SupportsKeyboard() const
271 {
272 const CControllerTree& controllers = m_topology->ControllerTree();
273
274 auto it = std::find_if(
275 controllers.Ports().begin(), controllers.Ports().end(),
276 [](const CControllerPortNode& port) { return port.PortType() == PORT_TYPE::KEYBOARD; });
277
278 return it != controllers.Ports().end() && !it->CompatibleControllers().empty();
279 }
280
SupportsMouse() const281 bool CGameClientInput::SupportsMouse() const
282 {
283 const CControllerTree& controllers = m_topology->ControllerTree();
284
285 auto it = std::find_if(
286 controllers.Ports().begin(), controllers.Ports().end(),
287 [](const CControllerPortNode& port) { return port.PortType() == PORT_TYPE::MOUSE; });
288
289 return it != controllers.Ports().end() && !it->CompatibleControllers().empty();
290 }
291
HasAgent() const292 bool CGameClientInput::HasAgent() const
293 {
294 //! @todo We check m_portMap instead of m_joysticks because m_joysticks is
295 // always populated with the default joystick configuration (i.e.
296 // all ports are connected to the first controller they accept).
297 // The game has no way of knowing which joysticks are actually being
298 // controlled by agents -- this information is stored in m_portMap,
299 // which is not exposed to the game.
300 if (!m_portMap.empty())
301 return true;
302
303 if (m_keyboard)
304 return true;
305
306 if (m_mouse)
307 return true;
308
309 return false;
310 }
311
OpenKeyboard(const ControllerPtr & controller)312 bool CGameClientInput::OpenKeyboard(const ControllerPtr& controller)
313 {
314 using namespace JOYSTICK;
315
316 if (!controller)
317 {
318 CLog::Log(LOGERROR, "Failed to open keyboard, no controller given");
319 return false;
320 }
321
322 //! @todo Move to player manager
323 PERIPHERALS::PeripheralVector keyboards;
324 CServiceBroker::GetPeripherals().GetPeripheralsWithFeature(keyboards,
325 PERIPHERALS::FEATURE_KEYBOARD);
326 if (keyboards.empty())
327 return false;
328
329 bool bSuccess = false;
330
331 {
332 CSingleLock lock(m_clientAccess);
333
334 if (m_gameClient.Initialized())
335 {
336 try
337 {
338 bSuccess = m_struct.toAddon->EnableKeyboard(&m_struct, true, controller->ID().c_str());
339 }
340 catch (...)
341 {
342 m_gameClient.LogException("EnableKeyboard()");
343 }
344 }
345 }
346
347 if (bSuccess)
348 {
349 m_keyboard.reset(
350 new CGameClientKeyboard(m_gameClient, controller->ID(), keyboards.at(0).get()));
351 return true;
352 }
353
354 return false;
355 }
356
CloseKeyboard()357 void CGameClientInput::CloseKeyboard()
358 {
359 m_keyboard.reset();
360
361 {
362 CSingleLock lock(m_clientAccess);
363
364 if (m_gameClient.Initialized())
365 {
366 try
367 {
368 m_struct.toAddon->EnableKeyboard(&m_struct, false, "");
369 }
370 catch (...)
371 {
372 m_gameClient.LogException("EnableKeyboard()");
373 }
374 }
375 }
376 }
377
OpenMouse(const ControllerPtr & controller)378 bool CGameClientInput::OpenMouse(const ControllerPtr& controller)
379 {
380 using namespace JOYSTICK;
381
382 if (!controller)
383 {
384 CLog::Log(LOGERROR, "Failed to open mouse, no controller given");
385 return false;
386 }
387
388 //! @todo Move to player manager
389 PERIPHERALS::PeripheralVector mice;
390 CServiceBroker::GetPeripherals().GetPeripheralsWithFeature(mice, PERIPHERALS::FEATURE_MOUSE);
391 if (mice.empty())
392 return false;
393
394 bool bSuccess = false;
395
396 {
397 CSingleLock lock(m_clientAccess);
398
399 if (m_gameClient.Initialized())
400 {
401 try
402 {
403 bSuccess = m_struct.toAddon->EnableMouse(&m_struct, true, controller->ID().c_str());
404 }
405 catch (...)
406 {
407 m_gameClient.LogException("EnableMouse()");
408 }
409 }
410 }
411
412 if (bSuccess)
413 {
414 m_mouse.reset(new CGameClientMouse(m_gameClient, controller->ID(), mice.at(0).get()));
415 return true;
416 }
417
418 return false;
419 }
420
CloseMouse()421 void CGameClientInput::CloseMouse()
422 {
423 m_mouse.reset();
424
425 {
426 CSingleLock lock(m_clientAccess);
427
428 if (m_gameClient.Initialized())
429 {
430 try
431 {
432 m_struct.toAddon->EnableMouse(&m_struct, false, "");
433 }
434 catch (...)
435 {
436 m_gameClient.LogException("EnableMouse()");
437 }
438 }
439 }
440 }
441
OpenJoystick(const std::string & portAddress,const ControllerPtr & controller)442 bool CGameClientInput::OpenJoystick(const std::string& portAddress, const ControllerPtr& controller)
443 {
444 using namespace JOYSTICK;
445
446 if (!controller)
447 {
448 CLog::Log(LOGERROR, "Failed to open port \"%s\", no controller given", portAddress.c_str());
449 return false;
450 }
451
452 const CControllerTree& controllerTree = m_topology->ControllerTree();
453
454 const CControllerPortNode& port = controllerTree.GetPort(portAddress);
455 if (!port.IsControllerAccepted(portAddress, controller->ID()))
456 {
457 CLog::Log(LOGERROR, "Failed to open port: Invalid controller \"%s\" on port \"%s\"",
458 controller->ID().c_str(), portAddress.c_str());
459 return false;
460 }
461
462 bool bSuccess = false;
463
464 {
465 CSingleLock lock(m_clientAccess);
466
467 if (m_gameClient.Initialized())
468 {
469 try
470 {
471 bSuccess = m_struct.toAddon->ConnectController(&m_struct, true, portAddress.c_str(),
472 controller->ID().c_str());
473 }
474 catch (...)
475 {
476 m_gameClient.LogException("ConnectController()");
477 }
478 }
479 }
480
481 if (bSuccess)
482 {
483 PERIPHERALS::EventLockHandlePtr lock = CServiceBroker::GetPeripherals().RegisterEventLock();
484
485 m_joysticks[portAddress].reset(new CGameClientJoystick(m_gameClient, portAddress, controller));
486 ProcessJoysticks();
487
488 return true;
489 }
490
491 return false;
492 }
493
CloseJoystick(const std::string & portAddress)494 void CGameClientInput::CloseJoystick(const std::string& portAddress)
495 {
496 auto it = m_joysticks.find(portAddress);
497 if (it != m_joysticks.end())
498 {
499 std::unique_ptr<CGameClientJoystick> joystick = std::move(it->second);
500 m_joysticks.erase(it);
501 {
502 PERIPHERALS::EventLockHandlePtr lock = CServiceBroker::GetPeripherals().RegisterEventLock();
503
504 ProcessJoysticks();
505 joystick.reset();
506 }
507 }
508
509 {
510 CSingleLock lock(m_clientAccess);
511
512 if (m_gameClient.Initialized())
513 {
514 try
515 {
516 m_struct.toAddon->ConnectController(&m_struct, false, portAddress.c_str(), "");
517 }
518 catch (...)
519 {
520 m_gameClient.LogException("ConnectController()");
521 }
522 }
523 }
524 }
525
HardwareReset()526 void CGameClientInput::HardwareReset()
527 {
528 if (m_hardware)
529 m_hardware->OnResetButton();
530 }
531
ReceiveInputEvent(const game_input_event & event)532 bool CGameClientInput::ReceiveInputEvent(const game_input_event& event)
533 {
534 bool bHandled = false;
535
536 switch (event.type)
537 {
538 case GAME_INPUT_EVENT_MOTOR:
539 if (event.port_address != nullptr && event.feature_name != nullptr)
540 bHandled = SetRumble(event.port_address, event.feature_name, event.motor.magnitude);
541 break;
542 default:
543 break;
544 }
545
546 return bHandled;
547 }
548
SetRumble(const std::string & portAddress,const std::string & feature,float magnitude)549 bool CGameClientInput::SetRumble(const std::string& portAddress,
550 const std::string& feature,
551 float magnitude)
552 {
553 bool bHandled = false;
554
555 auto it = m_joysticks.find(portAddress);
556 if (it != m_joysticks.end())
557 bHandled = it->second->SetRumble(feature, magnitude);
558
559 return bHandled;
560 }
561
Notify(const Observable & obs,const ObservableMessage msg)562 void CGameClientInput::Notify(const Observable& obs, const ObservableMessage msg)
563 {
564 switch (msg)
565 {
566 case ObservableMessagePeripheralsChanged:
567 {
568 PERIPHERALS::EventLockHandlePtr lock = CServiceBroker::GetPeripherals().RegisterEventLock();
569
570 ProcessJoysticks();
571
572 break;
573 }
574 default:
575 break;
576 }
577 }
578
ProcessJoysticks()579 void CGameClientInput::ProcessJoysticks()
580 {
581 PERIPHERALS::PeripheralVector joysticks;
582 CServiceBroker::GetPeripherals().GetPeripheralsWithFeature(joysticks,
583 PERIPHERALS::FEATURE_JOYSTICK);
584
585 // Update expired joysticks
586 PortMap portMapCopy = m_portMap;
587 for (auto& it : portMapCopy)
588 {
589 JOYSTICK::IInputProvider* inputProvider = it.first;
590 CGameClientJoystick* gameJoystick = it.second;
591
592 const bool bExpired =
593 std::find_if(joysticks.begin(), joysticks.end(),
594 [inputProvider](const PERIPHERALS::PeripheralPtr& joystick) {
595 return inputProvider ==
596 static_cast<JOYSTICK::IInputProvider*>(joystick.get());
597 }) == joysticks.end();
598
599 if (bExpired)
600 {
601 gameJoystick->UnregisterInput(nullptr);
602 m_portMap.erase(inputProvider);
603 }
604 }
605
606 // Perform the port mapping
607 PortMap newPortMap = MapJoysticks(joysticks, m_joysticks);
608
609 // Update connected joysticks
610 for (auto& peripheralJoystick : joysticks)
611 {
612 // Upcast to input interface
613 JOYSTICK::IInputProvider* inputProvider = peripheralJoystick.get();
614
615 auto itConnectedPort = newPortMap.find(inputProvider);
616 auto itDisconnectedPort = m_portMap.find(inputProvider);
617
618 CGameClientJoystick* newJoystick =
619 itConnectedPort != newPortMap.end() ? itConnectedPort->second : nullptr;
620 CGameClientJoystick* oldJoystick =
621 itDisconnectedPort != m_portMap.end() ? itDisconnectedPort->second : nullptr;
622
623 if (oldJoystick != newJoystick)
624 {
625 // Unregister old input handler
626 if (oldJoystick != nullptr)
627 {
628 oldJoystick->UnregisterInput(inputProvider);
629 m_portMap.erase(itDisconnectedPort);
630 }
631
632 // Register new handler
633 if (newJoystick != nullptr)
634 {
635 newJoystick->RegisterInput(inputProvider);
636 m_portMap[inputProvider] = newJoystick;
637 }
638 }
639 }
640 }
641
MapJoysticks(const PERIPHERALS::PeripheralVector & peripheralJoysticks,const JoystickMap & gameClientjoysticks) const642 CGameClientInput::PortMap CGameClientInput::MapJoysticks(
643 const PERIPHERALS::PeripheralVector& peripheralJoysticks,
644 const JoystickMap& gameClientjoysticks) const
645 {
646 PortMap result;
647
648 //! @todo Preserve existing joystick ports
649
650 // Sort by order of last button press
651 PERIPHERALS::PeripheralVector sortedJoysticks = peripheralJoysticks;
652 std::sort(sortedJoysticks.begin(), sortedJoysticks.end(),
653 [](const PERIPHERALS::PeripheralPtr& lhs, const PERIPHERALS::PeripheralPtr& rhs) {
654 if (lhs->LastActive().IsValid() && !rhs->LastActive().IsValid())
655 return true;
656 if (!lhs->LastActive().IsValid() && rhs->LastActive().IsValid())
657 return false;
658
659 return lhs->LastActive() > rhs->LastActive();
660 });
661
662 unsigned int i = 0;
663 for (const auto& it : gameClientjoysticks)
664 {
665 if (i >= peripheralJoysticks.size())
666 break;
667
668 // Check topology player limit
669 const int playerLimit = m_topology->PlayerLimit();
670 if (playerLimit >= 0 && static_cast<int>(i) >= playerLimit)
671 break;
672
673 // Dereference iterators
674 const PERIPHERALS::PeripheralPtr& peripheralJoystick = sortedJoysticks[i++];
675 const std::unique_ptr<CGameClientJoystick>& gameClientJoystick = it.second;
676
677 // Map input provider to input handler
678 result[peripheralJoystick.get()] = gameClientJoystick.get();
679 }
680
681 return result;
682 }
683
GetControllers(const CGameClient & gameClient)684 ControllerVector CGameClientInput::GetControllers(const CGameClient& gameClient)
685 {
686 using namespace ADDON;
687
688 ControllerVector controllers;
689
690 CGameServices& gameServices = CServiceBroker::GetGameServices();
691
692 const auto& dependencies = gameClient.GetDependencies();
693 for (auto it = dependencies.begin(); it != dependencies.end(); ++it)
694 {
695 ControllerPtr controller = gameServices.GetController(it->id);
696 if (controller)
697 controllers.push_back(controller);
698 }
699
700 if (controllers.empty())
701 {
702 // Use the default controller
703 ControllerPtr controller = gameServices.GetDefaultController();
704 if (controller)
705 controllers.push_back(controller);
706 }
707
708 return controllers;
709 }
710