diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/peripherals | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/peripherals')
63 files changed, 12120 insertions, 0 deletions
diff --git a/xbmc/peripherals/CMakeLists.txt b/xbmc/peripherals/CMakeLists.txt new file mode 100644 index 0000000..9de62b9 --- /dev/null +++ b/xbmc/peripherals/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCES EventLockHandle.cpp + EventPollHandle.cpp + EventScanner.cpp + Peripherals.cpp) + +set(HEADERS EventLockHandle.h + EventPollHandle.h + EventScanner.h + IEventScannerCallback.h + Peripherals.h + PeripheralTypes.h) + +core_add_library(peripherals) diff --git a/xbmc/peripherals/EventLockHandle.cpp b/xbmc/peripherals/EventLockHandle.cpp new file mode 100644 index 0000000..b6d2a6d --- /dev/null +++ b/xbmc/peripherals/EventLockHandle.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 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 "EventLockHandle.h" + +using namespace PERIPHERALS; + +CEventLockHandle::CEventLockHandle(IEventLockCallback& callback) : m_callback(callback) +{ +} + +CEventLockHandle::~CEventLockHandle(void) +{ + m_callback.ReleaseLock(*this); +} diff --git a/xbmc/peripherals/EventLockHandle.h b/xbmc/peripherals/EventLockHandle.h new file mode 100644 index 0000000..7012ea8 --- /dev/null +++ b/xbmc/peripherals/EventLockHandle.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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 + +namespace PERIPHERALS +{ +class CEventLockHandle; + +/*! + * \brief Callback implemented by event scanner + */ +class IEventLockCallback +{ +public: + virtual ~IEventLockCallback(void) = default; + + virtual void ReleaseLock(CEventLockHandle& handle) = 0; +}; + +/*! + * \brief Handle returned by the event scanner to disable event processing + * + * When held, this disables event processing. + */ +class CEventLockHandle +{ +public: + /*! + * \brief Create an event lock handle + */ + CEventLockHandle(IEventLockCallback& callback); + + /*! + * \brief Handle is automatically released when this class is destructed + */ + ~CEventLockHandle(void); + +private: + // Construction parameters + IEventLockCallback& m_callback; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/EventPollHandle.cpp b/xbmc/peripherals/EventPollHandle.cpp new file mode 100644 index 0000000..39a2e1a --- /dev/null +++ b/xbmc/peripherals/EventPollHandle.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016-2018 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 "EventPollHandle.h" + +using namespace PERIPHERALS; + +CEventPollHandle::CEventPollHandle(IEventPollCallback& callback) : m_callback(callback) +{ +} + +CEventPollHandle::~CEventPollHandle(void) +{ + m_callback.Release(*this); +} + +void CEventPollHandle::Activate() +{ + m_callback.Activate(*this); +} + +void CEventPollHandle::Deactivate() +{ + m_callback.Deactivate(*this); +} + +void CEventPollHandle::HandleEvents(bool bWait) +{ + m_callback.HandleEvents(bWait); +} diff --git a/xbmc/peripherals/EventPollHandle.h b/xbmc/peripherals/EventPollHandle.h new file mode 100644 index 0000000..5d99e1e --- /dev/null +++ b/xbmc/peripherals/EventPollHandle.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016-2018 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 + +namespace PERIPHERALS +{ +class CEventPollHandle; + +/*! + * \brief Callback implemented by event scanner + */ +class IEventPollCallback +{ +public: + virtual ~IEventPollCallback(void) = default; + + virtual void Activate(CEventPollHandle& handle) = 0; + virtual void Deactivate(CEventPollHandle& handle) = 0; + virtual void HandleEvents(bool bWait) = 0; + virtual void Release(CEventPollHandle& handle) = 0; +}; + +/*! + * \brief Handle returned by the event scanner to control scan timing + * + * When held, this allows one to control the timing of when events are + * handled. + */ +class CEventPollHandle +{ +public: + /*! + * \brief Create an active polling handle + */ + CEventPollHandle(IEventPollCallback& callback); + + /*! + * \brief Handle is automatically released when this class is destructed + */ + ~CEventPollHandle(); + + /*! + * \brief Activate handle + */ + void Activate(); + + /*! + * \brief Deactivate handle + */ + void Deactivate(); + + /*! + * \brief Trigger a scan for events + * + * \param bWait If true, this blocks until all events are handled + */ + void HandleEvents(bool bWait); + +private: + IEventPollCallback& m_callback; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/EventScanner.cpp b/xbmc/peripherals/EventScanner.cpp new file mode 100644 index 0000000..0462a17 --- /dev/null +++ b/xbmc/peripherals/EventScanner.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016-2018 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 "EventScanner.h" + +#include "IEventScannerCallback.h" +#include "utils/log.h" + +#include <algorithm> +#include <mutex> + +using namespace PERIPHERALS; + +// Default event scan rate when no polling handles are held +#define DEFAULT_SCAN_RATE_HZ 60 + +// Timeout when a polling handle is held but doesn't trigger scan. This reduces +// input latency when the game is running at < 1/4 speed. +#define WATCHDOG_TIMEOUT_MS 80 + +CEventScanner::CEventScanner(IEventScannerCallback& callback) + : CThread("PeripEventScan"), m_callback(callback) +{ +} + +void CEventScanner::Start() +{ + Create(); +} + +void CEventScanner::Stop() +{ + StopThread(false); + m_scanEvent.Set(); + StopThread(true); +} + +EventPollHandlePtr CEventScanner::RegisterPollHandle() +{ + EventPollHandlePtr handle(new CEventPollHandle(*this)); + + { + std::unique_lock<CCriticalSection> lock(m_handleMutex); + m_activeHandles.insert(handle.get()); + } + + CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle registered"); + + return handle; +} + +void CEventScanner::Activate(CEventPollHandle& handle) +{ + { + std::unique_lock<CCriticalSection> lock(m_handleMutex); + m_activeHandles.insert(&handle); + } + + CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle activated"); +} + +void CEventScanner::Deactivate(CEventPollHandle& handle) +{ + { + std::unique_lock<CCriticalSection> lock(m_handleMutex); + m_activeHandles.erase(&handle); + } + + CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle deactivated"); +} + +void CEventScanner::HandleEvents(bool bWait) +{ + if (bWait) + { + std::unique_lock<CCriticalSection> lock(m_pollMutex); + + m_scanFinishedEvent.Reset(); + m_scanEvent.Set(); + m_scanFinishedEvent.Wait(); + } + else + { + m_scanEvent.Set(); + } +} + +void CEventScanner::Release(CEventPollHandle& handle) +{ + { + std::unique_lock<CCriticalSection> lock(m_handleMutex); + m_activeHandles.erase(&handle); + } + + CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle released"); +} + +EventLockHandlePtr CEventScanner::RegisterLock() +{ + EventLockHandlePtr handle(new CEventLockHandle(*this)); + + { + std::unique_lock<CCriticalSection> lock(m_lockMutex); + m_activeLocks.insert(handle.get()); + } + + CLog::Log(LOGDEBUG, "PERIPHERALS: Event lock handle registered"); + + return handle; +} + +void CEventScanner::ReleaseLock(CEventLockHandle& handle) +{ + { + std::unique_lock<CCriticalSection> lock(m_lockMutex); + m_activeLocks.erase(&handle); + } + + CLog::Log(LOGDEBUG, "PERIPHERALS: Event lock handle released"); +} + +void CEventScanner::Process() +{ + auto nextScan = std::chrono::steady_clock::now(); + + while (!m_bStop) + { + { + std::unique_lock<CCriticalSection> lock(m_lockMutex); + if (m_activeLocks.empty()) + m_callback.ProcessEvents(); + } + + m_scanFinishedEvent.Set(); + + auto now = std::chrono::steady_clock::now(); + auto scanIntervalMs = GetScanIntervalMs(); + + // Handle wrap-around + if (now < nextScan) + nextScan = now; + + while (nextScan <= now) + nextScan += scanIntervalMs; + + auto waitTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(nextScan - now); + + if (!m_bStop && waitTimeMs.count() > 0) + m_scanEvent.Wait(waitTimeMs); + } +} + +std::chrono::milliseconds CEventScanner::GetScanIntervalMs() const +{ + bool bHasActiveHandle; + + { + std::unique_lock<CCriticalSection> lock(m_handleMutex); + bHasActiveHandle = !m_activeHandles.empty(); + } + + if (!bHasActiveHandle) + { + // this truncates to 16 (from 16.666) should it round up to 17 using std::nearbyint or should we use nanoseconds? + return std::chrono::milliseconds(static_cast<uint32_t>(1000.0 / DEFAULT_SCAN_RATE_HZ)); + } + else + return std::chrono::milliseconds(WATCHDOG_TIMEOUT_MS); +} diff --git a/xbmc/peripherals/EventScanner.h b/xbmc/peripherals/EventScanner.h new file mode 100644 index 0000000..0092278 --- /dev/null +++ b/xbmc/peripherals/EventScanner.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016-2018 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 "EventLockHandle.h" +#include "EventPollHandle.h" +#include "PeripheralTypes.h" +#include "threads/CriticalSection.h" +#include "threads/Event.h" +#include "threads/Thread.h" + +#include <chrono> +#include <set> + +namespace PERIPHERALS +{ +class IEventScannerCallback; + +/*! + * \brief Class to scan for peripheral events + * + * By default, a rate of 60 Hz is used. A client can obtain control over when + * input is handled by registering for a polling handle. + */ +class CEventScanner : public IEventPollCallback, public IEventLockCallback, protected CThread +{ +public: + explicit CEventScanner(IEventScannerCallback& callback); + + ~CEventScanner() override = default; + + void Start(); + void Stop(); + + EventPollHandlePtr RegisterPollHandle(); + + /*! + * \brief Acquire a lock that prevents event processing while held + */ + EventLockHandlePtr RegisterLock(); + + // implementation of IEventPollCallback + void Activate(CEventPollHandle& handle) override; + void Deactivate(CEventPollHandle& handle) override; + void HandleEvents(bool bWait) override; + void Release(CEventPollHandle& handle) override; + + // implementation of IEventLockCallback + void ReleaseLock(CEventLockHandle& handle) override; + +protected: + // implementation of CThread + void Process() override; + +private: + std::chrono::milliseconds GetScanIntervalMs() const; + + // Construction parameters + IEventScannerCallback& m_callback; + + // Event parameters + std::set<void*> m_activeHandles; + std::set<void*> m_activeLocks; + CEvent m_scanEvent; + CEvent m_scanFinishedEvent; + mutable CCriticalSection m_handleMutex; + CCriticalSection m_lockMutex; + CCriticalSection m_pollMutex; // Prevent two poll handles from polling at once +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/IEventScannerCallback.h b/xbmc/peripherals/IEventScannerCallback.h new file mode 100644 index 0000000..d017273 --- /dev/null +++ b/xbmc/peripherals/IEventScannerCallback.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 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 + +namespace PERIPHERALS +{ +class IEventScannerCallback +{ +public: + virtual ~IEventScannerCallback(void) = default; + + virtual void ProcessEvents(void) = 0; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/PeripheralTypes.h b/xbmc/peripherals/PeripheralTypes.h new file mode 100644 index 0000000..425ac0f --- /dev/null +++ b/xbmc/peripherals/PeripheralTypes.h @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2005-2018 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 <algorithm> +#include <map> +#include <memory> +#include <stdio.h> +#include <string> +#include <vector> +#ifdef TARGET_WINDOWS +#include "PlatformDefs.h" +#endif +#include "utils/StringUtils.h" + +class CSetting; + +namespace PERIPHERALS +{ +enum PeripheralBusType +{ + PERIPHERAL_BUS_UNKNOWN = 0, + PERIPHERAL_BUS_USB, + PERIPHERAL_BUS_PCI, + PERIPHERAL_BUS_CEC, + PERIPHERAL_BUS_ADDON, +#ifdef TARGET_ANDROID + PERIPHERAL_BUS_ANDROID, +#endif +#if defined(TARGET_DARWIN) + PERIPHERAL_BUS_GCCONTROLLER, +#endif + PERIPHERAL_BUS_APPLICATION, +}; + +enum PeripheralFeature +{ + FEATURE_UNKNOWN = 0, + FEATURE_HID, + FEATURE_NIC, + FEATURE_DISK, + FEATURE_NYXBOARD, + FEATURE_CEC, + FEATURE_BLUETOOTH, + FEATURE_TUNER, + FEATURE_IMON, + FEATURE_JOYSTICK, + FEATURE_RUMBLE, + FEATURE_POWER_OFF, + FEATURE_KEYBOARD, + FEATURE_MOUSE, +}; + +enum PeripheralType +{ + PERIPHERAL_UNKNOWN = 0, + PERIPHERAL_HID, + PERIPHERAL_NIC, + PERIPHERAL_DISK, + PERIPHERAL_NYXBOARD, + PERIPHERAL_CEC, + PERIPHERAL_BLUETOOTH, + PERIPHERAL_TUNER, + PERIPHERAL_IMON, + PERIPHERAL_JOYSTICK, + PERIPHERAL_KEYBOARD, + PERIPHERAL_MOUSE, +}; + +class CPeripheral; +using PeripheralPtr = std::shared_ptr<CPeripheral>; +using PeripheralVector = std::vector<PeripheralPtr>; + +class CPeripheralAddon; +using PeripheralAddonPtr = std::shared_ptr<CPeripheralAddon>; +using PeripheralAddonVector = std::vector<PeripheralAddonPtr>; + +class CEventPollHandle; +using EventPollHandlePtr = std::unique_ptr<CEventPollHandle>; + +class CEventLockHandle; +using EventLockHandlePtr = std::unique_ptr<CEventLockHandle>; + +struct PeripheralID +{ + int m_iVendorId; + int m_iProductId; +}; + +struct PeripheralDeviceSetting +{ + std::shared_ptr<CSetting> m_setting; + int m_order; +}; + +struct PeripheralDeviceMapping +{ + std::vector<PeripheralID> m_PeripheralID; + PeripheralBusType m_busType; + PeripheralType m_class; + std::string m_strDeviceName; + PeripheralType m_mappedTo; + std::map<std::string, PeripheralDeviceSetting> m_settings; +}; + +class PeripheralTypeTranslator +{ +public: + static const char* TypeToString(const PeripheralType type) + { + switch (type) + { + case PERIPHERAL_BLUETOOTH: + return "bluetooth"; + case PERIPHERAL_CEC: + return "cec"; + case PERIPHERAL_DISK: + return "disk"; + case PERIPHERAL_HID: + return "hid"; + case PERIPHERAL_NIC: + return "nic"; + case PERIPHERAL_NYXBOARD: + return "nyxboard"; + case PERIPHERAL_TUNER: + return "tuner"; + case PERIPHERAL_IMON: + return "imon"; + case PERIPHERAL_JOYSTICK: + return "joystick"; + case PERIPHERAL_KEYBOARD: + return "keyboard"; + case PERIPHERAL_MOUSE: + return "mouse"; + default: + return "unknown"; + } + }; + + static PeripheralType GetTypeFromString(const std::string& strType) + { + std::string strTypeLowerCase(strType); + StringUtils::ToLower(strTypeLowerCase); + + if (strTypeLowerCase == "bluetooth") + return PERIPHERAL_BLUETOOTH; + else if (strTypeLowerCase == "cec") + return PERIPHERAL_CEC; + else if (strTypeLowerCase == "disk") + return PERIPHERAL_DISK; + else if (strTypeLowerCase == "hid") + return PERIPHERAL_HID; + else if (strTypeLowerCase == "nic") + return PERIPHERAL_NIC; + else if (strTypeLowerCase == "nyxboard") + return PERIPHERAL_NYXBOARD; + else if (strTypeLowerCase == "tuner") + return PERIPHERAL_TUNER; + else if (strTypeLowerCase == "imon") + return PERIPHERAL_IMON; + else if (strTypeLowerCase == "joystick") + return PERIPHERAL_JOYSTICK; + else if (strTypeLowerCase == "keyboard") + return PERIPHERAL_KEYBOARD; + else if (strTypeLowerCase == "mouse") + return PERIPHERAL_MOUSE; + + return PERIPHERAL_UNKNOWN; + }; + + static const char* BusTypeToString(const PeripheralBusType type) + { + switch (type) + { + case PERIPHERAL_BUS_USB: + return "usb"; + case PERIPHERAL_BUS_PCI: + return "pci"; + case PERIPHERAL_BUS_CEC: + return "cec"; + case PERIPHERAL_BUS_ADDON: + return "addon"; +#ifdef TARGET_ANDROID + case PERIPHERAL_BUS_ANDROID: + return "android"; +#endif +#if defined(TARGET_DARWIN) + case PERIPHERAL_BUS_GCCONTROLLER: + return "darwin_gccontroller"; +#endif + case PERIPHERAL_BUS_APPLICATION: + return "application"; + default: + return "unknown"; + } + }; + + static PeripheralBusType GetBusTypeFromString(const std::string& strType) + { + std::string strTypeLowerCase(strType); + StringUtils::ToLower(strTypeLowerCase); + + if (strTypeLowerCase == "usb") + return PERIPHERAL_BUS_USB; + else if (strTypeLowerCase == "pci") + return PERIPHERAL_BUS_PCI; + else if (strTypeLowerCase == "cec") + return PERIPHERAL_BUS_CEC; + else if (strTypeLowerCase == "addon") + return PERIPHERAL_BUS_ADDON; +#ifdef TARGET_ANDROID + else if (strTypeLowerCase == "android") + return PERIPHERAL_BUS_ANDROID; +#endif +#if defined(TARGET_DARWIN) + else if (strTypeLowerCase == "darwin_gccontroller") + return PERIPHERAL_BUS_GCCONTROLLER; +#endif + else if (strTypeLowerCase == "application") + return PERIPHERAL_BUS_APPLICATION; + + return PERIPHERAL_BUS_UNKNOWN; + }; + + static const char* FeatureToString(const PeripheralFeature type) + { + switch (type) + { + case FEATURE_HID: + return "HID"; + case FEATURE_NIC: + return "NIC"; + case FEATURE_DISK: + return "disk"; + case FEATURE_NYXBOARD: + return "nyxboard"; + case FEATURE_CEC: + return "CEC"; + case FEATURE_BLUETOOTH: + return "bluetooth"; + case FEATURE_TUNER: + return "tuner"; + case FEATURE_IMON: + return "imon"; + case FEATURE_JOYSTICK: + return "joystick"; + case FEATURE_RUMBLE: + return "rumble"; + case FEATURE_POWER_OFF: + return "poweroff"; + case FEATURE_KEYBOARD: + return "keyboard"; + case FEATURE_MOUSE: + return "mouse"; + case FEATURE_UNKNOWN: + default: + return "unknown"; + } + }; + + static PeripheralFeature GetFeatureTypeFromString(const std::string& strType) + { + std::string strTypeLowerCase(strType); + StringUtils::ToLower(strTypeLowerCase); + + if (strTypeLowerCase == "hid") + return FEATURE_HID; + else if (strTypeLowerCase == "cec") + return FEATURE_CEC; + else if (strTypeLowerCase == "disk") + return FEATURE_DISK; + else if (strTypeLowerCase == "nyxboard") + return FEATURE_NYXBOARD; + else if (strTypeLowerCase == "bluetooth") + return FEATURE_BLUETOOTH; + else if (strTypeLowerCase == "tuner") + return FEATURE_TUNER; + else if (strTypeLowerCase == "imon") + return FEATURE_IMON; + else if (strTypeLowerCase == "joystick") + return FEATURE_JOYSTICK; + else if (strTypeLowerCase == "rumble") + return FEATURE_RUMBLE; + else if (strTypeLowerCase == "poweroff") + return FEATURE_POWER_OFF; + else if (strTypeLowerCase == "keyboard") + return FEATURE_KEYBOARD; + else if (strTypeLowerCase == "mouse") + return FEATURE_MOUSE; + + return FEATURE_UNKNOWN; + }; + + static int HexStringToInt(const char* strHex) + { + int iVal; + sscanf(strHex, "%x", &iVal); + return iVal; + }; + + static void FormatHexString(int iVal, std::string& strHexString) + { + if (iVal < 0) + iVal = 0; + if (iVal > 65536) + iVal = 65536; + + strHexString = StringUtils::Format("{:04X}", iVal); + }; +}; + +class PeripheralScanResult +{ +public: + explicit PeripheralScanResult(const PeripheralBusType busType) + : m_busType(busType), m_mappedBusType(busType) + { + } + + PeripheralScanResult(void) = default; + + bool operator==(const PeripheralScanResult& right) const + { + return m_iVendorId == right.m_iVendorId && m_iProductId == right.m_iProductId && + m_type == right.m_type && m_busType == right.m_busType && + StringUtils::EqualsNoCase(m_strLocation, right.m_strLocation); + } + + bool operator!=(const PeripheralScanResult& right) const { return !(*this == right); } + + PeripheralType m_type = PERIPHERAL_UNKNOWN; + std::string m_strLocation; + int m_iVendorId = 0; + int m_iProductId = 0; + PeripheralType m_mappedType = PERIPHERAL_UNKNOWN; + std::string m_strDeviceName; + PeripheralBusType m_busType = PERIPHERAL_BUS_UNKNOWN; + PeripheralBusType m_mappedBusType = PERIPHERAL_BUS_UNKNOWN; + unsigned int m_iSequence = 0; // when more than one adapter of the same type is found +}; + +struct PeripheralScanResults +{ + bool GetDeviceOnLocation(const std::string& strLocation, PeripheralScanResult* result) const + { + for (const auto& it : m_results) + { + if (it.m_strLocation == strLocation) + { + *result = it; + return true; + } + } + return false; + } + + bool ContainsResult(const PeripheralScanResult& result) const + { + return std::find(m_results.begin(), m_results.end(), result) != m_results.end(); + } + + std::vector<PeripheralScanResult> m_results; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/Peripherals.cpp b/xbmc/peripherals/Peripherals.cpp new file mode 100644 index 0000000..c178158 --- /dev/null +++ b/xbmc/peripherals/Peripherals.cpp @@ -0,0 +1,1030 @@ +/* + * Copyright (C) 2005-2018 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 "Peripherals.h" + +#include "CompileInfo.h" +#include "EventScanner.h" +#include "addons/AddonButtonMap.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "addons/gui/GUIWindowAddonBrowser.h" +#include "bus/PeripheralBus.h" +#include "bus/PeripheralBusUSB.h" + +#include <mutex> +#include <utility> +#if defined(TARGET_ANDROID) +#include "platform/android/peripherals/PeripheralBusAndroid.h" +#elif defined(TARGET_DARWIN) +#include "platform/darwin/peripherals/PeripheralBusGCController.h" +#endif +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "bus/virtual/PeripheralBusAddon.h" +#include "bus/virtual/PeripheralBusApplication.h" +#include "devices/PeripheralBluetooth.h" +#include "devices/PeripheralCecAdapter.h" +#include "devices/PeripheralDisk.h" +#include "devices/PeripheralHID.h" +#include "devices/PeripheralImon.h" +#include "devices/PeripheralJoystick.h" +#include "devices/PeripheralKeyboard.h" +#include "devices/PeripheralMouse.h" +#include "devices/PeripheralNIC.h" +#include "devices/PeripheralNyxboard.h" +#include "devices/PeripheralTuner.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "input/Key.h" +#include "input/joysticks/interfaces/IButtonMapper.h" +#include "interfaces/AnnouncementManager.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/ThreadMessage.h" +#include "peripherals/dialogs/GUIDialogPeripherals.h" +#include "settings/SettingAddon.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#if defined(HAVE_LIBCEC) +#include "bus/virtual/PeripheralBusCEC.h" +#else +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/LocalizeStrings.h" +#endif + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; +using namespace XFILE; + +CPeripherals::CPeripherals(CInputManager& inputManager, + GAME::CControllerManager& controllerProfiles) + : m_inputManager(inputManager), + m_controllerProfiles(controllerProfiles), + m_eventScanner(new CEventScanner(*this)) +{ + // Register settings + std::set<std::string> settingSet; + settingSet.insert(CSettings::SETTING_INPUT_PERIPHERALS); + settingSet.insert(CSettings::SETTING_INPUT_PERIPHERALLIBRARIES); + settingSet.insert(CSettings::SETTING_INPUT_CONTROLLERCONFIG); + settingSet.insert(CSettings::SETTING_INPUT_TESTRUMBLE); + settingSet.insert(CSettings::SETTING_LOCALE_LANGUAGE); + CServiceBroker::GetSettingsComponent()->GetSettings()->RegisterCallback(this, settingSet); +} + +CPeripherals::~CPeripherals() +{ + // Unregister settings + CServiceBroker::GetSettingsComponent()->GetSettings()->UnregisterCallback(this); + + Clear(); +} + +void CPeripherals::Initialise() +{ + Clear(); + + CDirectory::Create("special://profile/peripheral_data"); + + /* load mappings from peripherals.xml */ + LoadMappings(); + + std::vector<PeripheralBusPtr> busses; + +#if defined(HAVE_PERIPHERAL_BUS_USB) + busses.push_back(std::make_shared<CPeripheralBusUSB>(*this)); +#endif +#if defined(HAVE_LIBCEC) + busses.push_back(std::make_shared<CPeripheralBusCEC>(*this)); +#endif + busses.push_back(std::make_shared<CPeripheralBusAddon>(*this)); +#if defined(TARGET_ANDROID) + busses.push_back(std::make_shared<CPeripheralBusAndroid>(*this)); +#elif defined(TARGET_DARWIN) + busses.push_back(std::make_shared<CPeripheralBusGCController>(*this)); +#endif + busses.push_back(std::make_shared<CPeripheralBusApplication>(*this)); + + { + std::unique_lock<CCriticalSection> bussesLock(m_critSectionBusses); + m_busses = busses; + } + + /* initialise all known busses and run an initial scan for devices */ + for (auto& bus : busses) + bus->Initialise(); + + m_eventScanner->Start(); + + CServiceBroker::GetAppMessenger()->RegisterReceiver(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); +} + +void CPeripherals::Clear() +{ + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + + m_eventScanner->Stop(); + + // avoid deadlocks by copying all busses into a temporary variable and destroying them from there + std::vector<PeripheralBusPtr> busses; + { + std::unique_lock<CCriticalSection> bussesLock(m_critSectionBusses); + /* delete busses and devices */ + busses = m_busses; + m_busses.clear(); + } + + for (const auto& bus : busses) + bus->Clear(); + busses.clear(); + + { + std::unique_lock<CCriticalSection> mappingsLock(m_critSectionMappings); + /* delete mappings */ + for (auto& mapping : m_mappings) + mapping.m_settings.clear(); + m_mappings.clear(); + } + +#if !defined(HAVE_LIBCEC) + m_bMissingLibCecWarningDisplayed = false; +#endif +} + +void CPeripherals::TriggerDeviceScan(const PeripheralBusType type /* = PERIPHERAL_BUS_UNKNOWN */) +{ + std::vector<PeripheralBusPtr> busses; + { + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + busses = m_busses; + } + + for (auto& bus : busses) + { + bool bScan = false; + + if (type == PERIPHERAL_BUS_UNKNOWN) + bScan = true; + else if (bus->Type() == PERIPHERAL_BUS_ADDON) + bScan = true; + else if (bus->Type() == type) + bScan = true; + + if (bScan) + bus->TriggerDeviceScan(); + } +} + +PeripheralBusPtr CPeripherals::GetBusByType(const PeripheralBusType type) const +{ + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + + const auto& bus = + std::find_if(m_busses.cbegin(), m_busses.cend(), + [type](const PeripheralBusPtr& bus) { return bus->Type() == type; }); + if (bus != m_busses.cend()) + return *bus; + + return nullptr; +} + +PeripheralPtr CPeripherals::GetPeripheralAtLocation( + const std::string& strLocation, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + PeripheralPtr result; + + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + for (const auto& bus : m_busses) + { + /* check whether the bus matches if a bus type other than unknown was passed */ + if (busType != PERIPHERAL_BUS_UNKNOWN && bus->Type() != busType) + continue; + + /* return the first device that matches */ + PeripheralPtr peripheral = bus->GetPeripheral(strLocation); + if (peripheral) + { + result = peripheral; + break; + } + } + + return result; +} + +bool CPeripherals::HasPeripheralAtLocation( + const std::string& strLocation, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + return (GetPeripheralAtLocation(strLocation, busType) != nullptr); +} + +PeripheralBusPtr CPeripherals::GetBusWithDevice(const std::string& strLocation) const +{ + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + + const auto& bus = + std::find_if(m_busses.cbegin(), m_busses.cend(), [&strLocation](const PeripheralBusPtr& bus) { + return bus->HasPeripheral(strLocation); + }); + if (bus != m_busses.cend()) + return *bus; + + return nullptr; +} + +bool CPeripherals::SupportsFeature(PeripheralFeature feature) const +{ + bool bSupportsFeature = false; + + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + for (const auto& bus : m_busses) + bSupportsFeature |= bus->SupportsFeature(feature); + + return bSupportsFeature; +} + +int CPeripherals::GetPeripheralsWithFeature( + PeripheralVector& results, + const PeripheralFeature feature, + PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + int iReturn(0); + for (const auto& bus : m_busses) + { + /* check whether the bus matches if a bus type other than unknown was passed */ + if (busType != PERIPHERAL_BUS_UNKNOWN && bus->Type() != busType) + continue; + + iReturn += bus->GetPeripheralsWithFeature(results, feature); + } + + return iReturn; +} + +size_t CPeripherals::GetNumberOfPeripherals() const +{ + size_t iReturn(0); + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + for (const auto& bus : m_busses) + iReturn += bus->GetNumberOfPeripherals(); + + return iReturn; +} + +bool CPeripherals::HasPeripheralWithFeature( + const PeripheralFeature feature, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + PeripheralVector dummy; + return (GetPeripheralsWithFeature(dummy, feature, busType) > 0); +} + +void CPeripherals::CreatePeripheral(CPeripheralBus& bus, const PeripheralScanResult& result) +{ + PeripheralPtr peripheral; + PeripheralScanResult mappedResult = result; + if (mappedResult.m_busType == PERIPHERAL_BUS_UNKNOWN) + mappedResult.m_busType = bus.Type(); + + /* check whether there's something mapped in peripherals.xml */ + GetMappingForDevice(bus, mappedResult); + + switch (mappedResult.m_mappedType) + { + case PERIPHERAL_HID: + peripheral = PeripheralPtr(new CPeripheralHID(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_NIC: + peripheral = PeripheralPtr(new CPeripheralNIC(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_DISK: + peripheral = PeripheralPtr(new CPeripheralDisk(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_NYXBOARD: + peripheral = PeripheralPtr(new CPeripheralNyxboard(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_TUNER: + peripheral = PeripheralPtr(new CPeripheralTuner(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_BLUETOOTH: + peripheral = PeripheralPtr(new CPeripheralBluetooth(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_CEC: +#if defined(HAVE_LIBCEC) + if (bus.Type() == PERIPHERAL_BUS_CEC) + peripheral = PeripheralPtr(new CPeripheralCecAdapter(*this, mappedResult, &bus)); +#else + if (!m_bMissingLibCecWarningDisplayed) + { + m_bMissingLibCecWarningDisplayed = true; + CLog::Log( + LOGWARNING, + "{} - libCEC support has not been compiled in, so the CEC adapter cannot be used.", + __FUNCTION__); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, + g_localizeStrings.Get(36000), + g_localizeStrings.Get(36017)); + } +#endif + break; + + case PERIPHERAL_IMON: + peripheral = PeripheralPtr(new CPeripheralImon(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_JOYSTICK: + peripheral = PeripheralPtr(new CPeripheralJoystick(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_KEYBOARD: + peripheral = PeripheralPtr(new CPeripheralKeyboard(*this, mappedResult, &bus)); + break; + + case PERIPHERAL_MOUSE: + peripheral = PeripheralPtr(new CPeripheralMouse(*this, mappedResult, &bus)); + break; + + default: + break; + } + + if (peripheral) + { + /* try to initialise the new peripheral + * Initialise() will make sure that each device is only initialised once */ + if (peripheral->Initialise()) + bus.Register(peripheral); + else + { + CLog::Log(LOGDEBUG, "{} - failed to initialise peripheral on '{}'", __FUNCTION__, + mappedResult.m_strLocation); + } + } +} + +void CPeripherals::OnDeviceAdded(const CPeripheralBus& bus, const CPeripheral& peripheral) +{ + OnDeviceChanged(); + + //! @todo Improve device notifications in v18 +#if 0 + bool bNotify = true; + + // don't show a notification for devices detected during the initial scan + if (!bus.IsInitialised()) + bNotify = false; + + if (bNotify) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(35005), peripheral.DeviceName()); +#endif +} + +void CPeripherals::OnDeviceDeleted(const CPeripheralBus& bus, const CPeripheral& peripheral) +{ + OnDeviceChanged(); + + //! @todo Improve device notifications in v18 +#if 0 + bool bNotify = true; + + if (bNotify) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(35006), peripheral.DeviceName()); +#endif +} + +void CPeripherals::OnDeviceChanged() +{ + // refresh settings (peripherals manager could be enabled/disabled now) + CGUIMessage msgSettings(GUI_MSG_UPDATE, WINDOW_SETTINGS_SYSTEM, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msgSettings, + WINDOW_SETTINGS_SYSTEM); + + SetChanged(); +} + +bool CPeripherals::GetMappingForDevice(const CPeripheralBus& bus, + PeripheralScanResult& result) const +{ + std::unique_lock<CCriticalSection> lock(m_critSectionMappings); + + /* check all mappings in the order in which they are defined in peripherals.xml */ + for (const auto& mapping : m_mappings) + { + bool bProductMatch = false; + if (mapping.m_PeripheralID.empty()) + bProductMatch = true; + else + { + for (const auto& peripheralID : mapping.m_PeripheralID) + if (peripheralID.m_iVendorId == result.m_iVendorId && + peripheralID.m_iProductId == result.m_iProductId) + bProductMatch = true; + } + + bool bBusMatch = + (mapping.m_busType == PERIPHERAL_BUS_UNKNOWN || mapping.m_busType == bus.Type()); + bool bClassMatch = (mapping.m_class == PERIPHERAL_UNKNOWN || mapping.m_class == result.m_type); + + if (bProductMatch && bBusMatch && bClassMatch) + { + std::string strVendorId, strProductId; + PeripheralTypeTranslator::FormatHexString(result.m_iVendorId, strVendorId); + PeripheralTypeTranslator::FormatHexString(result.m_iProductId, strProductId); + CLog::Log(LOGDEBUG, "{} - device ({}:{}) mapped to {} (type = {})", __FUNCTION__, strVendorId, + strProductId, mapping.m_strDeviceName, + PeripheralTypeTranslator::TypeToString(mapping.m_mappedTo)); + result.m_mappedType = mapping.m_mappedTo; + if (result.m_strDeviceName.empty() && !mapping.m_strDeviceName.empty()) + result.m_strDeviceName = mapping.m_strDeviceName; + return true; + } + } + + return false; +} + +void CPeripherals::GetSettingsFromMapping(CPeripheral& peripheral) const +{ + std::unique_lock<CCriticalSection> lock(m_critSectionMappings); + + /* check all mappings in the order in which they are defined in peripherals.xml */ + for (const auto& mapping : m_mappings) + { + bool bProductMatch = false; + if (mapping.m_PeripheralID.empty()) + bProductMatch = true; + else + { + for (const auto& peripheralID : mapping.m_PeripheralID) + if (peripheralID.m_iVendorId == peripheral.VendorId() && + peripheralID.m_iProductId == peripheral.ProductId()) + bProductMatch = true; + } + + bool bBusMatch = (mapping.m_busType == PERIPHERAL_BUS_UNKNOWN || + mapping.m_busType == peripheral.GetBusType()); + bool bClassMatch = + (mapping.m_class == PERIPHERAL_UNKNOWN || mapping.m_class == peripheral.Type()); + + if (bBusMatch && bProductMatch && bClassMatch) + { + for (auto itr = mapping.m_settings.begin(); itr != mapping.m_settings.end(); ++itr) + peripheral.AddSetting((*itr).first, (*itr).second.m_setting, (*itr).second.m_order); + } + } +} + +#define SS(x) ((x) ? x : "") +bool CPeripherals::LoadMappings() +{ + std::unique_lock<CCriticalSection> lock(m_critSectionMappings); + + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile("special://xbmc/system/peripherals.xml")) + { + CLog::Log(LOGWARNING, "{} - peripherals.xml does not exist", __FUNCTION__); + return true; + } + + TiXmlElement* pRootElement = xmlDoc.RootElement(); + if (!pRootElement || StringUtils::CompareNoCase(pRootElement->Value(), "peripherals") != 0) + { + CLog::Log(LOGERROR, "{} - peripherals.xml does not contain <peripherals>", __FUNCTION__); + return false; + } + + for (TiXmlElement* currentNode = pRootElement->FirstChildElement("peripheral"); currentNode; + currentNode = currentNode->NextSiblingElement("peripheral")) + { + PeripheralID id; + PeripheralDeviceMapping mapping; + + mapping.m_strDeviceName = XMLUtils::GetAttribute(currentNode, "name"); + + // If there is no vendor_product attribute ignore this entry + if (currentNode->Attribute("vendor_product")) + { + // The vendor_product attribute is a list of comma separated vendor:product pairs + std::vector<std::string> vpArray = + StringUtils::Split(currentNode->Attribute("vendor_product"), ","); + for (const auto& i : vpArray) + { + std::vector<std::string> idArray = StringUtils::Split(i, ":"); + if (idArray.size() != 2) + { + CLog::Log(LOGERROR, "{} - ignoring node \"{}\" with invalid vendor_product attribute", + __FUNCTION__, mapping.m_strDeviceName); + continue; + } + + id.m_iVendorId = PeripheralTypeTranslator::HexStringToInt(idArray[0].c_str()); + id.m_iProductId = PeripheralTypeTranslator::HexStringToInt(idArray[1].c_str()); + mapping.m_PeripheralID.push_back(id); + } + } + + mapping.m_busType = + PeripheralTypeTranslator::GetBusTypeFromString(XMLUtils::GetAttribute(currentNode, "bus")); + mapping.m_class = + PeripheralTypeTranslator::GetTypeFromString(XMLUtils::GetAttribute(currentNode, "class")); + mapping.m_mappedTo = + PeripheralTypeTranslator::GetTypeFromString(XMLUtils::GetAttribute(currentNode, "mapTo")); + GetSettingsFromMappingsFile(currentNode, mapping.m_settings); + + m_mappings.push_back(mapping); + CLog::Log(LOGDEBUG, "{} - loaded node \"{}\"", __FUNCTION__, mapping.m_strDeviceName); + } + + return true; +} + +void CPeripherals::GetSettingsFromMappingsFile( + TiXmlElement* xmlNode, std::map<std::string, PeripheralDeviceSetting>& settings) +{ + TiXmlElement* currentNode = xmlNode->FirstChildElement("setting"); + int iMaxOrder = 0; + + while (currentNode) + { + SettingPtr setting; + std::string strKey = XMLUtils::GetAttribute(currentNode, "key"); + if (strKey.empty()) + continue; + + std::string strSettingsType = XMLUtils::GetAttribute(currentNode, "type"); + int iLabelId = currentNode->Attribute("label") ? atoi(currentNode->Attribute("label")) : -1; + const std::string config = XMLUtils::GetAttribute(currentNode, "configurable"); + bool bConfigurable = (config.empty() || (config != "no" && config != "false" && config != "0")); + if (strSettingsType == "bool") + { + const std::string value = XMLUtils::GetAttribute(currentNode, "value"); + bool bValue = (value != "no" && value != "false" && value != "0"); + setting = std::make_shared<CSettingBool>(strKey, iLabelId, bValue); + } + else if (strSettingsType == "int") + { + int iValue = currentNode->Attribute("value") ? atoi(currentNode->Attribute("value")) : 0; + int iMin = currentNode->Attribute("min") ? atoi(currentNode->Attribute("min")) : 0; + int iStep = currentNode->Attribute("step") ? atoi(currentNode->Attribute("step")) : 1; + int iMax = currentNode->Attribute("max") ? atoi(currentNode->Attribute("max")) : 255; + setting = std::make_shared<CSettingInt>(strKey, iLabelId, iValue, iMin, iStep, iMax); + } + else if (strSettingsType == "float") + { + float fValue = + currentNode->Attribute("value") ? (float)atof(currentNode->Attribute("value")) : 0; + float fMin = currentNode->Attribute("min") ? (float)atof(currentNode->Attribute("min")) : 0; + float fStep = + currentNode->Attribute("step") ? (float)atof(currentNode->Attribute("step")) : 0; + float fMax = currentNode->Attribute("max") ? (float)atof(currentNode->Attribute("max")) : 0; + setting = std::make_shared<CSettingNumber>(strKey, iLabelId, fValue, fMin, fStep, fMax); + } + else if (StringUtils::EqualsNoCase(strSettingsType, "enum")) + { + std::string strEnums = XMLUtils::GetAttribute(currentNode, "lvalues"); + if (!strEnums.empty()) + { + TranslatableIntegerSettingOptions enums; + std::vector<std::string> valuesVec; + StringUtils::Tokenize(strEnums, valuesVec, "|"); + for (unsigned int i = 0; i < valuesVec.size(); i++) + enums.emplace_back(atoi(valuesVec[i].c_str()), atoi(valuesVec[i].c_str())); + int iValue = currentNode->Attribute("value") ? atoi(currentNode->Attribute("value")) : 0; + setting = std::make_shared<CSettingInt>(strKey, iLabelId, iValue, enums); + } + } + else if (StringUtils::EqualsNoCase(strSettingsType, "addon")) + { + std::string addonFilter = XMLUtils::GetAttribute(currentNode, "addontype"); + ADDON::AddonType addonType = ADDON::CAddonInfo::TranslateType(addonFilter); + std::string strValue = XMLUtils::GetAttribute(currentNode, "value"); + setting = std::make_shared<CSettingAddon>(strKey, iLabelId, strValue); + static_cast<CSettingAddon&>(*setting).SetAddonType(addonType); + } + else + { + std::string strValue = XMLUtils::GetAttribute(currentNode, "value"); + setting = std::make_shared<CSettingString>(strKey, iLabelId, strValue); + } + + if (setting) + { + //! @todo add more types if needed + + /* set the visibility */ + setting->SetVisible(bConfigurable); + + /* set the order */ + int iOrder = 0; + currentNode->Attribute("order", &iOrder); + /* if the order attribute is invalid or 0, then the setting will be added at the end */ + if (iOrder < 0) + iOrder = 0; + if (iOrder > iMaxOrder) + iMaxOrder = iOrder; + + /* and add this new setting */ + PeripheralDeviceSetting deviceSetting = {setting, iOrder}; + settings[strKey] = deviceSetting; + } + + currentNode = currentNode->NextSiblingElement("setting"); + } + + /* add the settings without an order attribute or an invalid order attribute set at the end */ + for (auto& it : settings) + { + if (it.second.m_order == 0) + it.second.m_order = ++iMaxOrder; + } +} + +void CPeripherals::GetDirectory(const std::string& strPath, CFileItemList& items) const +{ + if (!StringUtils::StartsWithNoCase(strPath, "peripherals://")) + return; + + std::string strPathCut = strPath.substr(14); + std::string strBus = strPathCut.substr(0, strPathCut.find('/')); + + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + for (const auto& bus : m_busses) + { + if (StringUtils::EqualsNoCase(strBus, "all") || + StringUtils::EqualsNoCase(strBus, PeripheralTypeTranslator::BusTypeToString(bus->Type()))) + bus->GetDirectory(strPath, items); + } +} + +PeripheralPtr CPeripherals::GetByPath(const std::string& strPath) const +{ + PeripheralPtr result; + + if (!StringUtils::StartsWithNoCase(strPath, "peripherals://")) + return result; + + std::string strPathCut = strPath.substr(14); + std::string strBus = strPathCut.substr(0, strPathCut.find('/')); + + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + for (const auto& bus : m_busses) + { + if (StringUtils::EqualsNoCase(strBus, PeripheralTypeTranslator::BusTypeToString(bus->Type()))) + { + result = bus->GetByPath(strPath); + break; + } + } + + return result; +} + +bool CPeripherals::OnAction(const CAction& action) +{ + if (action.GetID() == ACTION_MUTE) + { + return ToggleMute(); + } + + if (SupportsCEC() && action.GetAmount() && + (action.GetID() == ACTION_VOLUME_UP || action.GetID() == ACTION_VOLUME_DOWN)) + { + PeripheralVector peripherals; + if (GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (auto& peripheral : peripherals) + { + std::shared_ptr<CPeripheralCecAdapter> cecDevice = + std::static_pointer_cast<CPeripheralCecAdapter>(peripheral); + if (cecDevice->HasAudioControl()) + { + if (action.GetID() == ACTION_VOLUME_UP) + cecDevice->VolumeUp(); + else + cecDevice->VolumeDown(); + return true; + } + } + } + } + + return false; +} + +bool CPeripherals::IsMuted() +{ + PeripheralVector peripherals; + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (const auto& peripheral : peripherals) + { + std::shared_ptr<CPeripheralCecAdapter> cecDevice = + std::static_pointer_cast<CPeripheralCecAdapter>(peripheral); + if (cecDevice->IsMuted()) + return true; + } + } + + return false; +} + +bool CPeripherals::ToggleMute() +{ + PeripheralVector peripherals; + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (auto& peripheral : peripherals) + { + std::shared_ptr<CPeripheralCecAdapter> cecDevice = + std::static_pointer_cast<CPeripheralCecAdapter>(peripheral); + if (cecDevice->HasAudioControl()) + { + cecDevice->ToggleMute(); + return true; + } + } + } + + return false; +} + +bool CPeripherals::ToggleDeviceState(CecStateChange mode /*= STATE_SWITCH_TOGGLE */) +{ + bool ret(false); + PeripheralVector peripherals; + + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (auto& peripheral : peripherals) + { + std::shared_ptr<CPeripheralCecAdapter> cecDevice = + std::static_pointer_cast<CPeripheralCecAdapter>(peripheral); + ret |= cecDevice->ToggleDeviceState(mode); + } + } + + return ret; +} + +bool CPeripherals::GetNextKeypress(float frameTime, CKey& key) +{ + PeripheralVector peripherals; + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (auto& peripheral : peripherals) + { + std::shared_ptr<CPeripheralCecAdapter> cecDevice = + std::static_pointer_cast<CPeripheralCecAdapter>(peripheral); + if (cecDevice->GetButton()) + { + CKey newKey(cecDevice->GetButton(), cecDevice->GetHoldTime()); + cecDevice->ResetButton(); + key = newKey; + return true; + } + } + } + + return false; +} + +EventPollHandlePtr CPeripherals::RegisterEventPoller() +{ + return m_eventScanner->RegisterPollHandle(); +} + +EventLockHandlePtr CPeripherals::RegisterEventLock() +{ + return m_eventScanner->RegisterLock(); +} + +void CPeripherals::OnUserNotification() +{ + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_INPUT_RUMBLENOTIFY)) + return; + + PeripheralVector peripherals; + GetPeripheralsWithFeature(peripherals, FEATURE_RUMBLE); + + for (auto& peripheral : peripherals) + peripheral->OnUserNotification(); +} + +void CPeripherals::TestFeature(PeripheralFeature feature) +{ + PeripheralVector peripherals; + GetPeripheralsWithFeature(peripherals, feature); + + for (auto& peripheral : peripherals) + { + if (peripheral->TestFeature(feature)) + { + CLog::Log(LOGDEBUG, "PERIPHERALS: Device \"{}\" tested {} feature", peripheral->DeviceName(), + PeripheralTypeTranslator::FeatureToString(feature)); + } + else + { + if (peripheral->HasFeature(feature)) + CLog::Log(LOGDEBUG, "PERIPHERALS: Device \"{}\" failed to test {} feature", + peripheral->DeviceName(), PeripheralTypeTranslator::FeatureToString(feature)); + else + CLog::Log(LOGDEBUG, "PERIPHERALS: Device \"{}\" doesn't support {} feature", + peripheral->DeviceName(), PeripheralTypeTranslator::FeatureToString(feature)); + } + } +} + +void CPeripherals::PowerOffDevices() +{ + TestFeature(FEATURE_POWER_OFF); +} + +void CPeripherals::ProcessEvents(void) +{ + std::vector<PeripheralBusPtr> busses; + { + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + busses = m_busses; + } + + for (PeripheralBusPtr& bus : busses) + bus->ProcessEvents(); +} + +void CPeripherals::EnableButtonMapping() +{ + std::vector<PeripheralBusPtr> busses; + { + std::unique_lock<CCriticalSection> lock(m_critSectionBusses); + busses = m_busses; + } + + for (PeripheralBusPtr& bus : busses) + bus->EnableButtonMapping(); +} + +PeripheralAddonPtr CPeripherals::GetAddonWithButtonMap(const CPeripheral* device) +{ + PeripheralBusAddonPtr addonBus = + std::static_pointer_cast<CPeripheralBusAddon>(GetBusByType(PERIPHERAL_BUS_ADDON)); + + PeripheralAddonPtr addon; + + PeripheralAddonPtr addonWithButtonMap; + if (addonBus && addonBus->GetAddonWithButtonMap(device, addonWithButtonMap)) + addon = std::move(addonWithButtonMap); + + return addon; +} + +void CPeripherals::ResetButtonMaps(const std::string& controllerId) +{ + PeripheralBusAddonPtr addonBus = + std::static_pointer_cast<CPeripheralBusAddon>(GetBusByType(PERIPHERAL_BUS_ADDON)); + + PeripheralVector peripherals; + GetPeripheralsWithFeature(peripherals, FEATURE_JOYSTICK); + + for (auto& peripheral : peripherals) + { + PeripheralAddonPtr addon; + if (addonBus->GetAddonWithButtonMap(peripheral.get(), addon)) + { + CAddonButtonMap buttonMap(peripheral.get(), addon, controllerId, *this); + buttonMap.Reset(); + } + } +} + +void CPeripherals::RegisterJoystickButtonMapper(IButtonMapper* mapper) +{ + PeripheralVector peripherals; + GetPeripheralsWithFeature(peripherals, FEATURE_JOYSTICK); + GetPeripheralsWithFeature(peripherals, FEATURE_KEYBOARD); + GetPeripheralsWithFeature(peripherals, FEATURE_MOUSE); + + for (auto& peripheral : peripherals) + peripheral->RegisterJoystickButtonMapper(mapper); +} + +void CPeripherals::UnregisterJoystickButtonMapper(IButtonMapper* mapper) +{ + mapper->ResetButtonMapCallbacks(); + + PeripheralVector peripherals; + GetPeripheralsWithFeature(peripherals, FEATURE_JOYSTICK); + GetPeripheralsWithFeature(peripherals, FEATURE_KEYBOARD); + GetPeripheralsWithFeature(peripherals, FEATURE_MOUSE); + + for (auto& peripheral : peripherals) + peripheral->UnregisterJoystickButtonMapper(mapper); +} + +void CPeripherals::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::SETTING_LOCALE_LANGUAGE) + { + // user set language, no longer use the TV's language + PeripheralVector cecDevices; + if (GetPeripheralsWithFeature(cecDevices, FEATURE_CEC) > 0) + { + for (auto& cecDevice : cecDevices) + cecDevice->SetSetting("use_tv_menu_language", false); + } + } +} + +void CPeripherals::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::SETTING_INPUT_PERIPHERALS) + CGUIDialogPeripherals::Show(*this); + else if (settingId == CSettings::SETTING_INPUT_CONTROLLERCONFIG) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_GAME_CONTROLLERS); + else if (settingId == CSettings::SETTING_INPUT_TESTRUMBLE) + TestFeature(FEATURE_RUMBLE); + else if (settingId == CSettings::SETTING_INPUT_PERIPHERALLIBRARIES) + { + std::string strAddonId; + if (CGUIWindowAddonBrowser::SelectAddonID(ADDON::AddonType::PERIPHERALDLL, strAddonId, false, + true, true, false, true) == 1 && + !strAddonId.empty()) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(strAddonId, addon, ADDON::OnlyEnabled::CHOICE_YES)) + CGUIDialogAddonSettings::ShowForAddon(addon); + } + } +} + +void CPeripherals::OnApplicationMessage(MESSAGING::ThreadMessage* pMsg) +{ + switch (pMsg->dwMessage) + { + case TMSG_CECTOGGLESTATE: + *static_cast<bool*>(pMsg->lpVoid) = ToggleDeviceState(STATE_SWITCH_TOGGLE); + break; + + case TMSG_CECACTIVATESOURCE: + ToggleDeviceState(STATE_ACTIVATE_SOURCE); + break; + + case TMSG_CECSTANDBY: + ToggleDeviceState(STATE_STANDBY); + break; + } +} + +int CPeripherals::GetMessageMask() +{ + return TMSG_MASK_PERIPHERALS; +} + +void CPeripherals::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (flag == ANNOUNCEMENT::Player && + sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER) + { + if (message == "OnQuit") + { + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_INPUT_CONTROLLERPOWEROFF)) + PowerOffDevices(); + } + } +} diff --git a/xbmc/peripherals/Peripherals.h b/xbmc/peripherals/Peripherals.h new file mode 100644 index 0000000..d9931d7 --- /dev/null +++ b/xbmc/peripherals/Peripherals.h @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2005-2018 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 "IEventScannerCallback.h" +#include "bus/PeripheralBus.h" +#include "devices/Peripheral.h" +#include "interfaces/IAnnouncer.h" +#include "messaging/IMessageTarget.h" +#include "settings/lib/ISettingCallback.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" +#include "utils/Observer.h" + +#include <memory> +#include <vector> + +class CFileItemList; +class CInputManager; +class CSetting; +class CSettingsCategory; +class TiXmlElement; +class CAction; +class CKey; + +namespace KODI +{ +namespace GAME +{ +class CControllerManager; +} + +namespace JOYSTICK +{ +class IButtonMapper; +} +} // namespace KODI + +namespace PERIPHERALS +{ +class CEventScanner; + +class CPeripherals : public ISettingCallback, + public Observable, + public KODI::MESSAGING::IMessageTarget, + public IEventScannerCallback, + public ANNOUNCEMENT::IAnnouncer +{ +public: + explicit CPeripherals(CInputManager& inputManager, + KODI::GAME::CControllerManager& controllerProfiles); + + ~CPeripherals() override; + + /*! + * @brief Initialise the peripherals manager. + */ + void Initialise(); + + /*! + * @brief Clear all data known by the peripherals manager. + */ + void Clear(); + + /*! + * @brief Get the instance of the peripheral at the given location. + * @param strLocation The location. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return The peripheral or NULL if it wasn't found. + */ + PeripheralPtr GetPeripheralAtLocation(const std::string& strLocation, + PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + /*! + * @brief Check whether a peripheral is present at the given location. + * @param strLocation The location. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return True when a peripheral was found, false otherwise. + */ + bool HasPeripheralAtLocation(const std::string& strLocation, + PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + /*! + * @brief Get the bus that holds the device with the given location. + * @param strLocation The location. + * @return The bus or NULL if no device was found. + */ + PeripheralBusPtr GetBusWithDevice(const std::string& strLocation) const; + + /*! + * @brief Check if any busses support the given feature + * @param feature The feature to check for + * @return True if a bus supports the feature, false otherwise + */ + bool SupportsFeature(PeripheralFeature feature) const; + + /*! + * @brief Get all peripheral instances that have the given feature. + * @param results The list of results. + * @param feature The feature to search for. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return The number of devices that have been found. + */ + int GetPeripheralsWithFeature(PeripheralVector& results, + const PeripheralFeature feature, + PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + size_t GetNumberOfPeripherals() const; + + /*! + * @brief Check whether there is at least one device present with the given feature. + * @param feature The feature to check for. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return True when at least one device was found with this feature, false otherwise. + */ + bool HasPeripheralWithFeature(const PeripheralFeature feature, + PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + /*! + * @brief Called when a device has been added to a bus. + * @param bus The bus the device was added to. + * @param peripheral The peripheral that has been added. + */ + void OnDeviceAdded(const CPeripheralBus& bus, const CPeripheral& peripheral); + + /*! + * @brief Called when a device has been deleted from a bus. + * @param bus The bus from which the device removed. + * @param peripheral The peripheral that has been removed. + */ + void OnDeviceDeleted(const CPeripheralBus& bus, const CPeripheral& peripheral); + + /*! + * @brief Creates a new instance of a peripheral. + * @param bus The bus on which this peripheral is present. + * @param result The scan result from the device scanning code. + * @return The new peripheral or NULL if it could not be created. + */ + void CreatePeripheral(CPeripheralBus& bus, const PeripheralScanResult& result); + + /*! + * @brief Add the settings that are defined in the mappings file to the peripheral (if there is + * anything defined). + * @param peripheral The peripheral to get the settings for. + */ + void GetSettingsFromMapping(CPeripheral& peripheral) const; + + /*! + * @brief Trigger a device scan on all known busses + */ + void TriggerDeviceScan(const PeripheralBusType type = PERIPHERAL_BUS_UNKNOWN); + + /*! + * @brief Get the instance of a bus given it's type. + * @param type The bus type. + * @return The bus or NULL if it wasn't found. + */ + PeripheralBusPtr GetBusByType(const PeripheralBusType type) const; + + /*! + * @brief Get all fileitems for a path. + * @param strPath The path to the directory to get the items from. + * @param items The item list. + */ + void GetDirectory(const std::string& strPath, CFileItemList& items) const; + + /*! + * @brief Get the instance of a peripheral given it's path. + * @param strPath The path to the peripheral. + * @return The peripheral or NULL if it wasn't found. + */ + PeripheralPtr GetByPath(const std::string& strPath) const; + + /*! + * @brief Try to let one of the peripherals handle an action. + * @param action The change to handle. + * @return True when this change was handled by a peripheral (and should not be handled by + * anything else), false otherwise. + */ + bool OnAction(const CAction& action); + + /*! + * @brief Check whether there's a peripheral that reports to be muted. + * @return True when at least one peripheral reports to be muted, false otherwise. + */ + bool IsMuted(); + + /*! + * @brief Try to toggle the mute status via a peripheral. + * @return True when this change was handled by a peripheral (and should not be handled by + * anything else), false otherwise. + */ + bool ToggleMute(); + + /*! + * @brief Try to toggle the playing device state via a peripheral. + * @param mode Whether to activate, put on standby or toggle the source. + * @return True when the playing device has been switched on, false otherwise. + */ + bool ToggleDeviceState(const CecStateChange mode = STATE_SWITCH_TOGGLE); + + /*! + * @brief Try to mute the audio via a peripheral. + * @return True when this change was handled by a peripheral (and should not be handled by + * anything else), false otherwise. + */ + bool Mute() + { + return ToggleMute(); + } //! @todo CEC only supports toggling the mute status at this time + + /*! + * @brief Try to unmute the audio via a peripheral. + * @return True when this change was handled by a peripheral (and should not be handled by + * anything else), false otherwise. + */ + bool UnMute() + { + return ToggleMute(); + } //! @todo CEC only supports toggling the mute status at this time + + /*! + * @brief Try to get a keypress from a peripheral. + * @param frameTime The current frametime. + * @param key The fetched key. + * @return True when a keypress was fetched, false otherwise. + */ + bool GetNextKeypress(float frameTime, CKey& key); + + /*! + * @brief Register with the event scanner to control scan timing + * @return A handle that unregisters itself when expired + */ + EventPollHandlePtr RegisterEventPoller(); + + /*! + * @brief Register with the event scanner to disable event processing + * @return A handle that unregisters itself when expired + */ + EventLockHandlePtr RegisterEventLock(); + + /*! + * + */ + void OnUserNotification(); + + /*! + * @brief Request peripherals with the specified feature to perform a quick test + * @return true if any peripherals support the feature, false otherwise + */ + void TestFeature(PeripheralFeature feature); + + /*! + * \brief Request all devices with power-off support to power down + */ + void PowerOffDevices(); + + bool SupportsCEC() const + { +#if defined(HAVE_LIBCEC) + return true; +#else + return false; +#endif + } + + // implementation of IEventScannerCallback + void ProcessEvents(void) override; + + /*! + * \brief Initialize button mapping + * + * This command enables button mapping on all busses. Button maps allow + * connect events from the driver to the higher-level features used by + * controller profiles. + * + * If user input is required, a blocking dialog may be shown. + */ + void EnableButtonMapping(); + + /*! + * \brief Get an add-on that can provide button maps for a device + * \return An add-on that provides button maps, or empty if no add-on is found + */ + PeripheralAddonPtr GetAddonWithButtonMap(const CPeripheral* device); + + /*! + * \brief Reset all button maps to the defaults for all devices and the given controller + * \param controllerId The controller profile to reset + * @todo Add a device parameter to allow resetting button maps per-device + */ + void ResetButtonMaps(const std::string& controllerId); + + /*! + * \brief Register a button mapper interface + * \param mapper The button mapper + * + * Clients implementing the IButtonMapper interface call + * \ref CPeripherals::RegisterJoystickButtonMapper to register themselves + * as eligible for button mapping commands. + * + * When registering the mapper is forwarded to all peripherals. See + * \ref CPeripheral::RegisterJoystickButtonMapper for what is done to the + * mapper after being given to the peripheral. + */ + void RegisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper); + + /*! + * \brief Unregister a button mapper interface + * \param mapper The button mapper + */ + void UnregisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper); + + // implementation of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + // implementation of IMessageTarget + void OnApplicationMessage(KODI::MESSAGING::ThreadMessage* pMsg) override; + int GetMessageMask() override; + + // implementation of IAnnouncer + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + + /*! + * \brief Access the input manager passed to the constructor + */ + CInputManager& GetInputManager() { return m_inputManager; } + + /*! + * \brief Access controller profiles through the construction parameter + */ + KODI::GAME::CControllerManager& GetControllerProfiles() { return m_controllerProfiles; } + + /*! + * \brief Get a mutex that allows for add-on install tasks to block on each other + */ + CCriticalSection& GetAddonInstallMutex() { return m_addonInstallMutex; } + +private: + bool LoadMappings(); + bool GetMappingForDevice(const CPeripheralBus& bus, PeripheralScanResult& result) const; + static void GetSettingsFromMappingsFile( + TiXmlElement* xmlNode, std::map<std::string, PeripheralDeviceSetting>& m_settings); + + void OnDeviceChanged(); + + // Construction parameters + CInputManager& m_inputManager; + KODI::GAME::CControllerManager& m_controllerProfiles; + +#if !defined(HAVE_LIBCEC) + bool m_bMissingLibCecWarningDisplayed = false; +#endif + std::vector<PeripheralBusPtr> m_busses; + std::vector<PeripheralDeviceMapping> m_mappings; + std::unique_ptr<CEventScanner> m_eventScanner; + mutable CCriticalSection m_critSectionBusses; + mutable CCriticalSection m_critSectionMappings; + CCriticalSection m_addonInstallMutex; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/addons/AddonButtonMap.cpp b/xbmc/peripherals/addons/AddonButtonMap.cpp new file mode 100644 index 0000000..0d76ec9 --- /dev/null +++ b/xbmc/peripherals/addons/AddonButtonMap.cpp @@ -0,0 +1,734 @@ +/* + * Copyright (C) 2014-2018 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 "AddonButtonMap.h" + +#include "PeripheralAddonTranslator.h" +#include "input/joysticks/JoystickUtils.h" +#include "peripherals/Peripherals.h" +#include "peripherals/devices/Peripheral.h" +#include "utils/log.h" + +#include <algorithm> +#include <assert.h> +#include <mutex> +#include <vector> + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +CAddonButtonMap::CAddonButtonMap(CPeripheral* device, + const std::weak_ptr<CPeripheralAddon>& addon, + const std::string& strControllerId, + CPeripherals& manager) + : m_device(device), m_addon(addon), m_strControllerId(strControllerId), m_manager(manager) +{ + auto peripheralAddon = m_addon.lock(); + assert(peripheralAddon != nullptr); + + peripheralAddon->RegisterButtonMap(device, this); +} + +CAddonButtonMap::~CAddonButtonMap(void) +{ + if (auto addon = m_addon.lock()) + addon->UnregisterButtonMap(this); +} + +std::string CAddonButtonMap::Location(void) const +{ + return m_device->Location(); +} + +bool CAddonButtonMap::Load(void) +{ + std::string controllerAppearance; + FeatureMap features; + DriverMap driverMap; + PrimitiveVector ignoredPrimitives; + + bool bSuccess = false; + if (auto addon = m_addon.lock()) + { + bSuccess |= addon->GetFeatures(m_device, m_strControllerId, features); + bSuccess |= addon->GetIgnoredPrimitives(m_device, ignoredPrimitives); + } + + if (features.empty()) + { + // Check if we can initialize a buttonmap from the peripheral bus + PeripheralBusPtr peripheralBus = m_manager.GetBusByType(m_device->GetBusType()); + if (peripheralBus) + { + CLog::Log(LOGDEBUG, + "Buttonmap not found for {}, attempting to initialize from peripheral bus", + m_device->Location()); + if (peripheralBus->InitializeButtonMap(*m_device, *this)) + { + bSuccess = true; + + if (auto addon = m_addon.lock()) + { + addon->GetFeatures(m_device, m_strControllerId, features); + addon->GetIgnoredPrimitives(m_device, ignoredPrimitives); + } + } + } + } + + // GetFeatures() was changed to always return false if no features were + // retrieved. Check here, just in case its contract is changed or violated in + // the future. + if (bSuccess && features.empty()) + bSuccess = false; + + if (bSuccess) + driverMap = CreateLookupTable(features); + else + CLog::Log(LOGDEBUG, "Failed to load button map for \"{}\"", m_device->Location()); + + { + std::unique_lock<CCriticalSection> lock(m_mutex); + m_controllerAppearance = std::move(controllerAppearance); + m_features = std::move(features); + m_driverMap = std::move(driverMap); + m_ignoredPrimitives = CPeripheralAddonTranslator::TranslatePrimitives(ignoredPrimitives); + } + + return true; +} + +void CAddonButtonMap::Reset(void) +{ + if (auto addon = m_addon.lock()) + addon->ResetButtonMap(m_device, m_strControllerId); +} + +bool CAddonButtonMap::IsEmpty(void) const +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + return m_driverMap.empty(); +} + +std::string CAddonButtonMap::GetAppearance() const +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + return m_controllerAppearance; +} + +bool CAddonButtonMap::SetAppearance(const std::string& controllerId) const +{ + return false; +} + +bool CAddonButtonMap::GetFeature(const CDriverPrimitive& primitive, FeatureName& feature) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + DriverMap::const_iterator it = m_driverMap.find(primitive); + if (it != m_driverMap.end()) + { + feature = it->second; + return true; + } + + return false; +} + +FEATURE_TYPE CAddonButtonMap::GetFeatureType(const FeatureName& feature) +{ + FEATURE_TYPE type = FEATURE_TYPE::UNKNOWN; + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + type = CPeripheralAddonTranslator::TranslateFeatureType(it->second.Type()); + + return type; +} + +bool CAddonButtonMap::GetScalar(const FeatureName& feature, CDriverPrimitive& primitive) +{ + bool retVal(false); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + { + const kodi::addon::JoystickFeature& addonFeature = it->second; + + if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_SCALAR || + addonFeature.Type() == JOYSTICK_FEATURE_TYPE_MOTOR) + { + primitive = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(JOYSTICK_SCALAR_PRIMITIVE)); + retVal = true; + } + } + + return retVal; +} + +void CAddonButtonMap::AddScalar(const FeatureName& feature, const CDriverPrimitive& primitive) +{ + const bool bMotor = (primitive.Type() == PRIMITIVE_TYPE::MOTOR); + + kodi::addon::JoystickFeature scalar(feature, bMotor ? JOYSTICK_FEATURE_TYPE_MOTOR + : JOYSTICK_FEATURE_TYPE_SCALAR); + scalar.SetPrimitive(JOYSTICK_SCALAR_PRIMITIVE, + CPeripheralAddonTranslator::TranslatePrimitive(primitive)); + + if (auto addon = m_addon.lock()) + addon->MapFeature(m_device, m_strControllerId, scalar); +} + +bool CAddonButtonMap::GetAnalogStick(const FeatureName& feature, + JOYSTICK::ANALOG_STICK_DIRECTION direction, + JOYSTICK::CDriverPrimitive& primitive) +{ + bool retVal(false); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + { + const kodi::addon::JoystickFeature& addonFeature = it->second; + + if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_ANALOG_STICK) + { + primitive = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(GetAnalogStickIndex(direction))); + retVal = primitive.IsValid(); + } + } + + return retVal; +} + +void CAddonButtonMap::AddAnalogStick(const FeatureName& feature, + JOYSTICK::ANALOG_STICK_DIRECTION direction, + const JOYSTICK::CDriverPrimitive& primitive) +{ + using namespace JOYSTICK; + + JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetAnalogStickIndex(direction); + kodi::addon::DriverPrimitive addonPrimitive = + CPeripheralAddonTranslator::TranslatePrimitive(primitive); + + kodi::addon::JoystickFeature analogStick(feature, JOYSTICK_FEATURE_TYPE_ANALOG_STICK); + + { + std::unique_lock<CCriticalSection> lock(m_mutex); + auto it = m_features.find(feature); + if (it != m_features.end()) + analogStick = it->second; + } + + const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive( + analogStick.Primitive(primitiveIndex))); + if (bModified) + analogStick.SetPrimitive(primitiveIndex, addonPrimitive); + + if (auto addon = m_addon.lock()) + addon->MapFeature(m_device, m_strControllerId, analogStick); + + // Because each direction is mapped individually, we need to refresh the + // feature each time a new direction is mapped. + if (bModified) + Load(); +} + +bool CAddonButtonMap::GetRelativePointer(const FeatureName& feature, + JOYSTICK::RELATIVE_POINTER_DIRECTION direction, + JOYSTICK::CDriverPrimitive& primitive) +{ + bool retVal(false); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + { + const kodi::addon::JoystickFeature& addonFeature = it->second; + + if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_RELPOINTER) + { + primitive = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(GetRelativePointerIndex(direction))); + retVal = primitive.IsValid(); + } + } + + return retVal; +} + +void CAddonButtonMap::AddRelativePointer(const FeatureName& feature, + JOYSTICK::RELATIVE_POINTER_DIRECTION direction, + const JOYSTICK::CDriverPrimitive& primitive) +{ + using namespace JOYSTICK; + + JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetRelativePointerIndex(direction); + kodi::addon::DriverPrimitive addonPrimitive = + CPeripheralAddonTranslator::TranslatePrimitive(primitive); + + kodi::addon::JoystickFeature relPointer(feature, JOYSTICK_FEATURE_TYPE_RELPOINTER); + + { + std::unique_lock<CCriticalSection> lock(m_mutex); + auto it = m_features.find(feature); + if (it != m_features.end()) + relPointer = it->second; + } + + const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive( + relPointer.Primitive(primitiveIndex))); + if (bModified) + relPointer.SetPrimitive(primitiveIndex, addonPrimitive); + + if (auto addon = m_addon.lock()) + addon->MapFeature(m_device, m_strControllerId, relPointer); + + // Because each direction is mapped individually, we need to refresh the + // feature each time a new direction is mapped. + if (bModified) + Load(); +} + +bool CAddonButtonMap::GetAccelerometer(const FeatureName& feature, + CDriverPrimitive& positiveX, + CDriverPrimitive& positiveY, + CDriverPrimitive& positiveZ) +{ + bool retVal(false); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + { + const kodi::addon::JoystickFeature& addonFeature = it->second; + + if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_ACCELEROMETER) + { + positiveX = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(JOYSTICK_ACCELEROMETER_POSITIVE_X)); + positiveY = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(JOYSTICK_ACCELEROMETER_POSITIVE_Y)); + positiveZ = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(JOYSTICK_ACCELEROMETER_POSITIVE_Z)); + retVal = true; + } + } + + return retVal; +} + +void CAddonButtonMap::AddAccelerometer(const FeatureName& feature, + const CDriverPrimitive& positiveX, + const CDriverPrimitive& positiveY, + const CDriverPrimitive& positiveZ) +{ + using namespace JOYSTICK; + + kodi::addon::JoystickFeature accelerometer(feature, JOYSTICK_FEATURE_TYPE_ACCELEROMETER); + + accelerometer.SetPrimitive(JOYSTICK_ACCELEROMETER_POSITIVE_X, + CPeripheralAddonTranslator::TranslatePrimitive(positiveX)); + accelerometer.SetPrimitive(JOYSTICK_ACCELEROMETER_POSITIVE_Y, + CPeripheralAddonTranslator::TranslatePrimitive(positiveY)); + accelerometer.SetPrimitive(JOYSTICK_ACCELEROMETER_POSITIVE_Z, + CPeripheralAddonTranslator::TranslatePrimitive(positiveZ)); + + if (auto addon = m_addon.lock()) + addon->MapFeature(m_device, m_strControllerId, accelerometer); +} + +bool CAddonButtonMap::GetWheel(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::WHEEL_DIRECTION direction, + KODI::JOYSTICK::CDriverPrimitive& primitive) +{ + bool retVal(false); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + { + const kodi::addon::JoystickFeature& addonFeature = it->second; + + if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_WHEEL) + { + primitive = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(GetPrimitiveIndex(direction))); + retVal = primitive.IsValid(); + } + } + + return retVal; +} + +void CAddonButtonMap::AddWheel(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::WHEEL_DIRECTION direction, + const KODI::JOYSTICK::CDriverPrimitive& primitive) +{ + using namespace JOYSTICK; + + JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetPrimitiveIndex(direction); + kodi::addon::DriverPrimitive addonPrimitive = + CPeripheralAddonTranslator::TranslatePrimitive(primitive); + + kodi::addon::JoystickFeature joystickFeature(feature, JOYSTICK_FEATURE_TYPE_WHEEL); + + { + std::unique_lock<CCriticalSection> lock(m_mutex); + auto it = m_features.find(feature); + if (it != m_features.end()) + joystickFeature = it->second; + } + + const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive( + joystickFeature.Primitive(primitiveIndex))); + if (bModified) + joystickFeature.SetPrimitive(primitiveIndex, addonPrimitive); + + if (auto addon = m_addon.lock()) + addon->MapFeature(m_device, m_strControllerId, joystickFeature); + + // Because each direction is mapped individually, we need to refresh the + // feature each time a new direction is mapped. + if (bModified) + Load(); +} + +bool CAddonButtonMap::GetThrottle(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::THROTTLE_DIRECTION direction, + KODI::JOYSTICK::CDriverPrimitive& primitive) +{ + bool retVal(false); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + { + const kodi::addon::JoystickFeature& addonFeature = it->second; + + if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_THROTTLE) + { + primitive = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(GetPrimitiveIndex(direction))); + retVal = primitive.IsValid(); + } + } + + return retVal; +} + +void CAddonButtonMap::AddThrottle(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::THROTTLE_DIRECTION direction, + const KODI::JOYSTICK::CDriverPrimitive& primitive) +{ + using namespace JOYSTICK; + + JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetPrimitiveIndex(direction); + kodi::addon::DriverPrimitive addonPrimitive = + CPeripheralAddonTranslator::TranslatePrimitive(primitive); + + kodi::addon::JoystickFeature joystickFeature(feature, JOYSTICK_FEATURE_TYPE_THROTTLE); + + { + std::unique_lock<CCriticalSection> lock(m_mutex); + auto it = m_features.find(feature); + if (it != m_features.end()) + joystickFeature = it->second; + } + + const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive( + joystickFeature.Primitive(primitiveIndex))); + if (bModified) + joystickFeature.SetPrimitive(primitiveIndex, addonPrimitive); + + if (auto addon = m_addon.lock()) + addon->MapFeature(m_device, m_strControllerId, joystickFeature); + + // Because each direction is mapped individually, we need to refresh the + // feature each time a new direction is mapped. + if (bModified) + Load(); +} + +bool CAddonButtonMap::GetKey(const FeatureName& feature, CDriverPrimitive& primitive) +{ + bool retVal(false); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + FeatureMap::const_iterator it = m_features.find(feature); + if (it != m_features.end()) + { + const kodi::addon::JoystickFeature& addonFeature = it->second; + + if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_KEY) + { + primitive = CPeripheralAddonTranslator::TranslatePrimitive( + addonFeature.Primitive(JOYSTICK_SCALAR_PRIMITIVE)); + retVal = true; + } + } + + return retVal; +} + +void CAddonButtonMap::AddKey(const FeatureName& feature, const CDriverPrimitive& primitive) +{ + kodi::addon::JoystickFeature scalar(feature, JOYSTICK_FEATURE_TYPE_KEY); + scalar.SetPrimitive(JOYSTICK_SCALAR_PRIMITIVE, + CPeripheralAddonTranslator::TranslatePrimitive(primitive)); + + if (auto addon = m_addon.lock()) + addon->MapFeature(m_device, m_strControllerId, scalar); +} + +void CAddonButtonMap::SetIgnoredPrimitives( + const std::vector<JOYSTICK::CDriverPrimitive>& primitives) +{ + if (auto addon = m_addon.lock()) + addon->SetIgnoredPrimitives(m_device, + CPeripheralAddonTranslator::TranslatePrimitives(primitives)); +} + +bool CAddonButtonMap::IsIgnored(const JOYSTICK::CDriverPrimitive& primitive) +{ + return std::find(m_ignoredPrimitives.begin(), m_ignoredPrimitives.end(), primitive) != + m_ignoredPrimitives.end(); +} + +bool CAddonButtonMap::GetAxisProperties(unsigned int axisIndex, int& center, unsigned int& range) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + for (const auto& it : m_driverMap) + { + const CDriverPrimitive& primitive = it.first; + + if (primitive.Type() != PRIMITIVE_TYPE::SEMIAXIS) + continue; + + if (primitive.Index() != axisIndex) + continue; + + center = primitive.Center(); + range = primitive.Range(); + return true; + } + + return false; +} + +void CAddonButtonMap::SaveButtonMap() +{ + if (auto addon = m_addon.lock()) + addon->SaveButtonMap(m_device); +} + +void CAddonButtonMap::RevertButtonMap() +{ + if (auto addon = m_addon.lock()) + addon->RevertButtonMap(m_device); +} + +CAddonButtonMap::DriverMap CAddonButtonMap::CreateLookupTable(const FeatureMap& features) +{ + using namespace JOYSTICK; + + DriverMap driverMap; + + for (const auto& it : features) + { + const kodi::addon::JoystickFeature& feature = it.second; + + switch (feature.Type()) + { + case JOYSTICK_FEATURE_TYPE_SCALAR: + case JOYSTICK_FEATURE_TYPE_KEY: + { + driverMap[CPeripheralAddonTranslator::TranslatePrimitive( + feature.Primitive(JOYSTICK_SCALAR_PRIMITIVE))] = it.first; + break; + } + + case JOYSTICK_FEATURE_TYPE_ANALOG_STICK: + { + std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = { + JOYSTICK_ANALOG_STICK_UP, + JOYSTICK_ANALOG_STICK_DOWN, + JOYSTICK_ANALOG_STICK_RIGHT, + JOYSTICK_ANALOG_STICK_LEFT, + }; + + for (auto primitive : primitives) + driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] = + it.first; + break; + } + + case JOYSTICK_FEATURE_TYPE_ACCELEROMETER: + { + std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = { + JOYSTICK_ACCELEROMETER_POSITIVE_X, + JOYSTICK_ACCELEROMETER_POSITIVE_Y, + JOYSTICK_ACCELEROMETER_POSITIVE_Z, + }; + + for (auto primitive : primitives) + { + CDriverPrimitive translatedPrimitive = + CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive)); + driverMap[translatedPrimitive] = it.first; + + // Map opposite semiaxis + CDriverPrimitive oppositePrimitive = CDriverPrimitive( + translatedPrimitive.Index(), 0, translatedPrimitive.SemiAxisDirection() * -1, 1); + driverMap[oppositePrimitive] = it.first; + } + break; + } + + case JOYSTICK_FEATURE_TYPE_WHEEL: + { + std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = { + JOYSTICK_WHEEL_LEFT, + JOYSTICK_WHEEL_RIGHT, + }; + + for (auto primitive : primitives) + driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] = + it.first; + break; + } + + case JOYSTICK_FEATURE_TYPE_THROTTLE: + { + std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = { + JOYSTICK_THROTTLE_UP, + JOYSTICK_THROTTLE_DOWN, + }; + + for (auto primitive : primitives) + driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] = + it.first; + break; + } + + case JOYSTICK_FEATURE_TYPE_RELPOINTER: + { + std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = { + JOYSTICK_RELPOINTER_UP, + JOYSTICK_RELPOINTER_DOWN, + JOYSTICK_RELPOINTER_RIGHT, + JOYSTICK_RELPOINTER_LEFT, + }; + + for (auto primitive : primitives) + driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] = + it.first; + break; + } + + default: + break; + } + } + + return driverMap; +} + +JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetAnalogStickIndex( + JOYSTICK::ANALOG_STICK_DIRECTION dir) +{ + using namespace JOYSTICK; + + switch (dir) + { + case ANALOG_STICK_DIRECTION::UP: + return JOYSTICK_ANALOG_STICK_UP; + case ANALOG_STICK_DIRECTION::DOWN: + return JOYSTICK_ANALOG_STICK_DOWN; + case ANALOG_STICK_DIRECTION::RIGHT: + return JOYSTICK_ANALOG_STICK_RIGHT; + case ANALOG_STICK_DIRECTION::LEFT: + return JOYSTICK_ANALOG_STICK_LEFT; + default: + break; + } + + return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0); +} + +JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetRelativePointerIndex( + JOYSTICK::RELATIVE_POINTER_DIRECTION dir) +{ + using namespace JOYSTICK; + + switch (dir) + { + case RELATIVE_POINTER_DIRECTION::UP: + return JOYSTICK_RELPOINTER_UP; + case RELATIVE_POINTER_DIRECTION::DOWN: + return JOYSTICK_RELPOINTER_DOWN; + case RELATIVE_POINTER_DIRECTION::RIGHT: + return JOYSTICK_RELPOINTER_RIGHT; + case RELATIVE_POINTER_DIRECTION::LEFT: + return JOYSTICK_RELPOINTER_LEFT; + default: + break; + } + + return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0); +} + +JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetPrimitiveIndex(JOYSTICK::WHEEL_DIRECTION dir) +{ + using namespace JOYSTICK; + + switch (dir) + { + case WHEEL_DIRECTION::RIGHT: + return JOYSTICK_WHEEL_RIGHT; + case WHEEL_DIRECTION::LEFT: + return JOYSTICK_WHEEL_LEFT; + default: + break; + } + + return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0); +} + +JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetPrimitiveIndex(JOYSTICK::THROTTLE_DIRECTION dir) +{ + using namespace JOYSTICK; + + switch (dir) + { + case THROTTLE_DIRECTION::UP: + return JOYSTICK_THROTTLE_UP; + case THROTTLE_DIRECTION::DOWN: + return JOYSTICK_THROTTLE_DOWN; + default: + break; + } + + return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0); +} diff --git a/xbmc/peripherals/addons/AddonButtonMap.h b/xbmc/peripherals/addons/AddonButtonMap.h new file mode 100644 index 0000000..cade54b --- /dev/null +++ b/xbmc/peripherals/addons/AddonButtonMap.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2014-2018 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 "PeripheralAddon.h" // for FeatureMap +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "peripherals/PeripheralTypes.h" +#include "threads/CriticalSection.h" + +namespace PERIPHERALS +{ +class CPeripheral; +class CPeripherals; + +class CAddonButtonMap : public KODI::JOYSTICK::IButtonMap +{ +public: + CAddonButtonMap(CPeripheral* device, + const std::weak_ptr<CPeripheralAddon>& addon, + const std::string& strControllerId, + CPeripherals& manager); + + ~CAddonButtonMap(void) override; + + // Implementation of IButtonMap + std::string ControllerID(void) const override { return m_strControllerId; } + + std::string Location(void) const override; + + bool Load(void) override; + + void Reset(void) override; + + bool IsEmpty(void) const override; + + std::string GetAppearance() const override; + + bool SetAppearance(const std::string& controllerId) const override; + + bool GetFeature(const KODI::JOYSTICK::CDriverPrimitive& primitive, + KODI::JOYSTICK::FeatureName& feature) override; + + KODI::JOYSTICK::FEATURE_TYPE GetFeatureType(const KODI::JOYSTICK::FeatureName& feature) override; + + bool GetScalar(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + void AddScalar(const KODI::JOYSTICK::FeatureName& feature, + const KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + bool GetAnalogStick(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::ANALOG_STICK_DIRECTION direction, + KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + void AddAnalogStick(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::ANALOG_STICK_DIRECTION direction, + const KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + bool GetRelativePointer(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION direction, + KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + void AddRelativePointer(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION direction, + const KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + bool GetAccelerometer(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::CDriverPrimitive& positiveX, + KODI::JOYSTICK::CDriverPrimitive& positiveY, + KODI::JOYSTICK::CDriverPrimitive& positiveZ) override; + + void AddAccelerometer(const KODI::JOYSTICK::FeatureName& feature, + const KODI::JOYSTICK::CDriverPrimitive& positiveX, + const KODI::JOYSTICK::CDriverPrimitive& positiveY, + const KODI::JOYSTICK::CDriverPrimitive& positiveZ) override; + + bool GetWheel(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::WHEEL_DIRECTION direction, + KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + void AddWheel(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::WHEEL_DIRECTION direction, + const KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + bool GetThrottle(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::THROTTLE_DIRECTION direction, + KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + void AddThrottle(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::THROTTLE_DIRECTION direction, + const KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + bool GetKey(const KODI::JOYSTICK::FeatureName& feature, + KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + void AddKey(const KODI::JOYSTICK::FeatureName& feature, + const KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + void SetIgnoredPrimitives( + const std::vector<KODI::JOYSTICK::CDriverPrimitive>& primitives) override; + + bool IsIgnored(const KODI::JOYSTICK::CDriverPrimitive& primitive) override; + + bool GetAxisProperties(unsigned int axisIndex, int& center, unsigned int& range) override; + + void SaveButtonMap() override; + + void RevertButtonMap() override; + +private: + typedef std::map<KODI::JOYSTICK::CDriverPrimitive, KODI::JOYSTICK::FeatureName> DriverMap; + typedef std::vector<KODI::JOYSTICK::CDriverPrimitive> JoystickPrimitiveVector; + + // Utility functions + static DriverMap CreateLookupTable(const FeatureMap& features); + + static JOYSTICK_FEATURE_PRIMITIVE GetAnalogStickIndex(KODI::JOYSTICK::ANALOG_STICK_DIRECTION dir); + static JOYSTICK_FEATURE_PRIMITIVE GetRelativePointerIndex( + KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION dir); + static JOYSTICK_FEATURE_PRIMITIVE GetPrimitiveIndex(KODI::JOYSTICK::WHEEL_DIRECTION dir); + static JOYSTICK_FEATURE_PRIMITIVE GetPrimitiveIndex(KODI::JOYSTICK::THROTTLE_DIRECTION dir); + + // Construction parameters + CPeripheral* const m_device; + const std::weak_ptr<CPeripheralAddon> m_addon; + const std::string m_strControllerId; + CPeripherals& m_manager; + + // Button map state + std::string m_controllerAppearance; + FeatureMap m_features; + DriverMap m_driverMap; + JoystickPrimitiveVector m_ignoredPrimitives; + + // Synchronization parameters + mutable CCriticalSection m_mutex; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/addons/AddonButtonMapping.cpp b/xbmc/peripherals/addons/AddonButtonMapping.cpp new file mode 100644 index 0000000..abf981f --- /dev/null +++ b/xbmc/peripherals/addons/AddonButtonMapping.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014-2018 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 "AddonButtonMapping.h" + +#include "input/joysticks/generic/ButtonMapping.h" +#include "input/joysticks/interfaces/IButtonMapper.h" +#include "peripherals/Peripherals.h" +#include "peripherals/addons/AddonButtonMap.h" +#include "utils/log.h" + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +CAddonButtonMapping::CAddonButtonMapping(CPeripherals& manager, + CPeripheral* peripheral, + IButtonMapper* mapper) +{ + PeripheralAddonPtr addon = manager.GetAddonWithButtonMap(peripheral); + + if (!addon) + { + CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", peripheral->DeviceName()); + } + else + { + const std::string controllerId = mapper->ControllerID(); + m_buttonMap = std::make_unique<CAddonButtonMap>(peripheral, addon, controllerId, manager); + if (m_buttonMap->Load()) + { + IKeymap* keymap = peripheral->GetKeymap(controllerId); + m_buttonMapping.reset(new CButtonMapping(mapper, m_buttonMap.get(), keymap)); + + // Allow the mapper to save our button map + mapper->SetButtonMapCallback(peripheral->Location(), this); + } + else + m_buttonMap.reset(); + } +} + +CAddonButtonMapping::~CAddonButtonMapping(void) +{ + m_buttonMapping.reset(); + m_buttonMap.reset(); +} + +bool CAddonButtonMapping::OnButtonMotion(unsigned int buttonIndex, bool bPressed) +{ + if (m_buttonMapping) + return m_buttonMapping->OnButtonMotion(buttonIndex, bPressed); + + return false; +} + +bool CAddonButtonMapping::OnHatMotion(unsigned int hatIndex, HAT_STATE state) +{ + if (m_buttonMapping) + return m_buttonMapping->OnHatMotion(hatIndex, state); + + return false; +} + +bool CAddonButtonMapping::OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) +{ + if (m_buttonMapping) + return m_buttonMapping->OnAxisMotion(axisIndex, position, center, range); + + return false; +} + +void CAddonButtonMapping::OnInputFrame(void) +{ + if (m_buttonMapping) + m_buttonMapping->OnInputFrame(); +} + +bool CAddonButtonMapping::OnKeyPress(const CKey& key) +{ + if (m_buttonMapping) + return m_buttonMapping->OnKeyPress(key); + + return false; +} + +void CAddonButtonMapping::OnKeyRelease(const CKey& key) +{ + if (m_buttonMapping) + m_buttonMapping->OnKeyRelease(key); +} + +bool CAddonButtonMapping::OnPosition(int x, int y) +{ + if (m_buttonMapping) + return m_buttonMapping->OnPosition(x, y); + + return false; +} + +bool CAddonButtonMapping::OnButtonPress(MOUSE::BUTTON_ID button) +{ + if (m_buttonMapping) + return m_buttonMapping->OnButtonPress(button); + + return false; +} + +void CAddonButtonMapping::OnButtonRelease(MOUSE::BUTTON_ID button) +{ + if (m_buttonMapping) + m_buttonMapping->OnButtonRelease(button); +} + +void CAddonButtonMapping::SaveButtonMap() +{ + if (m_buttonMapping) + m_buttonMapping->SaveButtonMap(); +} + +void CAddonButtonMapping::ResetIgnoredPrimitives() +{ + if (m_buttonMapping) + m_buttonMapping->ResetIgnoredPrimitives(); +} + +void CAddonButtonMapping::RevertButtonMap() +{ + if (m_buttonMapping) + m_buttonMapping->RevertButtonMap(); +} diff --git a/xbmc/peripherals/addons/AddonButtonMapping.h b/xbmc/peripherals/addons/AddonButtonMapping.h new file mode 100644 index 0000000..245a588 --- /dev/null +++ b/xbmc/peripherals/addons/AddonButtonMapping.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014-2018 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/IButtonMapCallback.h" +#include "input/joysticks/interfaces/IDriverHandler.h" +#include "input/keyboard/interfaces/IKeyboardDriverHandler.h" +#include "input/mouse/interfaces/IMouseDriverHandler.h" + +#include <memory> + +namespace KODI +{ +namespace JOYSTICK +{ +class CButtonMapping; +class IButtonMap; +class IButtonMapper; +} // namespace JOYSTICK +} // namespace KODI + +namespace PERIPHERALS +{ +class CPeripheral; +class CPeripherals; + +class CAddonButtonMapping : public KODI::JOYSTICK::IDriverHandler, + public KODI::KEYBOARD::IKeyboardDriverHandler, + public KODI::MOUSE::IMouseDriverHandler, + public KODI::JOYSTICK::IButtonMapCallback +{ +public: + CAddonButtonMapping(CPeripherals& manager, + CPeripheral* peripheral, + KODI::JOYSTICK::IButtonMapper* mapper); + + ~CAddonButtonMapping(void) override; + + // implementation of IDriverHandler + bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override; + bool OnHatMotion(unsigned int hatIndex, KODI::JOYSTICK::HAT_STATE state) override; + bool OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) override; + void OnInputFrame(void) override; + + // implementation of IKeyboardDriverHandler + bool OnKeyPress(const CKey& key) override; + void OnKeyRelease(const CKey& key) override; + + // implementation of IMouseDriverHandler + bool OnPosition(int x, int y) override; + bool OnButtonPress(KODI::MOUSE::BUTTON_ID button) override; + void OnButtonRelease(KODI::MOUSE::BUTTON_ID button) override; + + // implementation of IButtonMapCallback + void SaveButtonMap() override; + void ResetIgnoredPrimitives() override; + void RevertButtonMap() override; + +private: + std::unique_ptr<KODI::JOYSTICK::CButtonMapping> m_buttonMapping; + std::unique_ptr<KODI::JOYSTICK::IButtonMap> m_buttonMap; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/addons/AddonInputHandling.cpp b/xbmc/peripherals/addons/AddonInputHandling.cpp new file mode 100644 index 0000000..7490b96 --- /dev/null +++ b/xbmc/peripherals/addons/AddonInputHandling.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2014-2018 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 "AddonInputHandling.h" + +#include "input/joysticks/generic/DriverReceiving.h" +#include "input/joysticks/generic/InputHandling.h" +#include "input/joysticks/interfaces/IDriverReceiver.h" +#include "input/joysticks/interfaces/IInputHandler.h" +#include "input/keyboard/generic/KeyboardInputHandling.h" +#include "input/keyboard/interfaces/IKeyboardInputHandler.h" +#include "input/mouse/generic/MouseInputHandling.h" +#include "input/mouse/interfaces/IMouseInputHandler.h" +#include "peripherals/addons/AddonButtonMap.h" +#include "peripherals/devices/Peripheral.h" +#include "utils/log.h" + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, + std::shared_ptr<CPeripheralAddon> addon, + IInputHandler* handler, + IDriverReceiver* receiver) + : m_manager(manager), + m_peripheral(peripheral), + m_addon(std::move(addon)), + m_joystickInputHandler(handler), + m_joystickDriverReceiver(receiver) +{ +} + +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, + std::shared_ptr<CPeripheralAddon> addon, + KEYBOARD::IKeyboardInputHandler* handler) + : m_manager(manager), + m_peripheral(peripheral), + m_addon(std::move(addon)), + m_keyboardInputHandler(handler) +{ +} + +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, + std::shared_ptr<CPeripheralAddon> addon, + MOUSE::IMouseInputHandler* handler) + : m_manager(manager), + m_peripheral(peripheral), + m_addon(std::move(addon)), + m_mouseInputHandler(handler) +{ +} + +CAddonInputHandling::~CAddonInputHandling(void) +{ + m_joystickDriverHandler.reset(); + m_joystickInputReceiver.reset(); + m_keyboardDriverHandler.reset(); + m_mouseDriverHandler.reset(); + m_buttonMap.reset(); +} + +bool CAddonInputHandling::Load() +{ + std::string controllerId; + if (m_joystickInputHandler != nullptr) + controllerId = m_joystickInputHandler->ControllerID(); + else if (m_keyboardInputHandler != nullptr) + controllerId = m_keyboardInputHandler->ControllerID(); + else if (m_mouseInputHandler != nullptr) + controllerId = m_mouseInputHandler->ControllerID(); + + if (!controllerId.empty()) + m_buttonMap = std::make_unique<CAddonButtonMap>(m_peripheral, m_addon, controllerId, m_manager); + + if (m_buttonMap && m_buttonMap->Load()) + { + if (m_joystickInputHandler != nullptr) + { + m_joystickDriverHandler = + std::make_unique<CInputHandling>(m_joystickInputHandler, m_buttonMap.get()); + if (m_joystickDriverReceiver != nullptr) + { + m_joystickInputReceiver = + std::make_unique<CDriverReceiving>(m_joystickDriverReceiver, m_buttonMap.get()); + + // Interfaces are connected here because they share button map as a common resource + m_joystickInputHandler->SetInputReceiver(m_joystickInputReceiver.get()); + } + return true; + } + else if (m_keyboardInputHandler != nullptr) + { + m_keyboardDriverHandler = std::make_unique<KEYBOARD::CKeyboardInputHandling>( + m_keyboardInputHandler, m_buttonMap.get()); + return true; + } + else if (m_mouseInputHandler != nullptr) + { + m_mouseDriverHandler = + std::make_unique<MOUSE::CMouseInputHandling>(m_mouseInputHandler, m_buttonMap.get()); + return true; + } + } + + return false; +} + +bool CAddonInputHandling::OnButtonMotion(unsigned int buttonIndex, bool bPressed) +{ + if (m_joystickDriverHandler) + return m_joystickDriverHandler->OnButtonMotion(buttonIndex, bPressed); + + return false; +} + +bool CAddonInputHandling::OnHatMotion(unsigned int hatIndex, HAT_STATE state) +{ + if (m_joystickDriverHandler) + return m_joystickDriverHandler->OnHatMotion(hatIndex, state); + + return false; +} + +bool CAddonInputHandling::OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) +{ + if (m_joystickDriverHandler) + return m_joystickDriverHandler->OnAxisMotion(axisIndex, position, center, range); + + return false; +} + +void CAddonInputHandling::OnInputFrame(void) +{ + if (m_joystickDriverHandler) + m_joystickDriverHandler->OnInputFrame(); +} + +bool CAddonInputHandling::OnKeyPress(const CKey& key) +{ + if (m_keyboardDriverHandler) + return m_keyboardDriverHandler->OnKeyPress(key); + + return false; +} + +void CAddonInputHandling::OnKeyRelease(const CKey& key) +{ + if (m_keyboardDriverHandler) + m_keyboardDriverHandler->OnKeyRelease(key); +} + +bool CAddonInputHandling::OnPosition(int x, int y) +{ + if (m_mouseDriverHandler) + return m_mouseDriverHandler->OnPosition(x, y); + + return false; +} + +bool CAddonInputHandling::OnButtonPress(MOUSE::BUTTON_ID button) +{ + if (m_mouseDriverHandler) + return m_mouseDriverHandler->OnButtonPress(button); + + return false; +} + +void CAddonInputHandling::OnButtonRelease(MOUSE::BUTTON_ID button) +{ + if (m_mouseDriverHandler) + m_mouseDriverHandler->OnButtonRelease(button); +} + +bool CAddonInputHandling::SetRumbleState(const JOYSTICK::FeatureName& feature, float magnitude) +{ + if (m_joystickInputReceiver) + return m_joystickInputReceiver->SetRumbleState(feature, magnitude); + + return false; +} diff --git a/xbmc/peripherals/addons/AddonInputHandling.h b/xbmc/peripherals/addons/AddonInputHandling.h new file mode 100644 index 0000000..295e6d4 --- /dev/null +++ b/xbmc/peripherals/addons/AddonInputHandling.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014-2018 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/IDriverHandler.h" +#include "input/joysticks/interfaces/IInputReceiver.h" +#include "input/keyboard/interfaces/IKeyboardDriverHandler.h" +#include "input/mouse/interfaces/IMouseDriverHandler.h" + +#include <memory> + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +class IDriverReceiver; +class IInputHandler; +} // namespace JOYSTICK + +namespace KEYBOARD +{ +class IKeyboardInputHandler; +} + +namespace MOUSE +{ +class IMouseInputHandler; +} +} // namespace KODI + +namespace PERIPHERALS +{ +class CPeripheral; +class CPeripherals; +class CPeripheralAddon; + +class CAddonInputHandling : public KODI::JOYSTICK::IDriverHandler, + public KODI::JOYSTICK::IInputReceiver, + public KODI::KEYBOARD::IKeyboardDriverHandler, + public KODI::MOUSE::IMouseDriverHandler +{ +public: + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, + std::shared_ptr<CPeripheralAddon> addon, + KODI::JOYSTICK::IInputHandler* handler, + KODI::JOYSTICK::IDriverReceiver* receiver); + + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, + std::shared_ptr<CPeripheralAddon> addon, + KODI::KEYBOARD::IKeyboardInputHandler* handler); + + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, + std::shared_ptr<CPeripheralAddon> addon, + KODI::MOUSE::IMouseInputHandler* handler); + + ~CAddonInputHandling(void) override; + + bool Load(); + + // implementation of IDriverHandler + bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override; + bool OnHatMotion(unsigned int hatIndex, KODI::JOYSTICK::HAT_STATE state) override; + bool OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) override; + void OnInputFrame(void) override; + + // implementation of IKeyboardDriverHandler + bool OnKeyPress(const CKey& key) override; + void OnKeyRelease(const CKey& key) override; + + // implementation of IMouseDriverHandler + bool OnPosition(int x, int y) override; + bool OnButtonPress(KODI::MOUSE::BUTTON_ID button) override; + void OnButtonRelease(KODI::MOUSE::BUTTON_ID button) override; + + // implementation of IInputReceiver + bool SetRumbleState(const KODI::JOYSTICK::FeatureName& feature, float magnitude) override; + +private: + // Construction parameters + CPeripherals& m_manager; + CPeripheral* const m_peripheral; + const std::shared_ptr<CPeripheralAddon> m_addon; + KODI::JOYSTICK::IInputHandler* const m_joystickInputHandler{nullptr}; + KODI::JOYSTICK::IDriverReceiver* const m_joystickDriverReceiver{nullptr}; + KODI::KEYBOARD::IKeyboardInputHandler* m_keyboardInputHandler{nullptr}; + KODI::MOUSE::IMouseInputHandler* const m_mouseInputHandler{nullptr}; + + // Input parameters + std::unique_ptr<KODI::JOYSTICK::IDriverHandler> m_joystickDriverHandler; + std::unique_ptr<KODI::JOYSTICK::IInputReceiver> m_joystickInputReceiver; + std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler> m_keyboardDriverHandler; + std::unique_ptr<KODI::MOUSE::IMouseDriverHandler> m_mouseDriverHandler; + std::unique_ptr<KODI::JOYSTICK::IButtonMap> m_buttonMap; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/addons/CMakeLists.txt b/xbmc/peripherals/addons/CMakeLists.txt new file mode 100644 index 0000000..9f31978 --- /dev/null +++ b/xbmc/peripherals/addons/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCES AddonButtonMap.cpp + AddonButtonMapping.cpp + AddonInputHandling.cpp + PeripheralAddon.cpp + PeripheralAddonTranslator.cpp) + +set(HEADERS AddonButtonMap.h + AddonButtonMapping.h + AddonInputHandling.h + PeripheralAddon.h + PeripheralAddonTranslator.h) + +core_add_library(peripherals_addons) diff --git a/xbmc/peripherals/addons/PeripheralAddon.cpp b/xbmc/peripherals/addons/PeripheralAddon.cpp new file mode 100644 index 0000000..7a55bb3 --- /dev/null +++ b/xbmc/peripherals/addons/PeripheralAddon.cpp @@ -0,0 +1,990 @@ +/* + * Copyright (C) 2014-2018 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 "PeripheralAddon.h" + +#include "FileItem.h" +#include "PeripheralAddonTranslator.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "filesystem/Directory.h" +#include "filesystem/SpecialProtocol.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerManager.h" +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "peripherals/Peripherals.h" +#include "peripherals/bus/virtual/PeripheralBusAddon.h" +#include "peripherals/devices/PeripheralJoystick.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <mutex> +#include <shared_mutex> +#include <string.h> +#include <utility> + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; +using namespace XFILE; + +#define KEYBOARD_BUTTON_MAP_NAME "Keyboard" +#define KEYBOARD_PROVIDER "application" + +#define MOUSE_BUTTON_MAP_NAME "Mouse" +#define MOUSE_PROVIDER "application" + +#ifndef SAFE_DELETE +#define SAFE_DELETE(p) \ + do \ + { \ + delete (p); \ + (p) = NULL; \ + } while (0) +#endif + +CPeripheralAddon::CPeripheralAddon(const ADDON::AddonInfoPtr& addonInfo, CPeripherals& manager) + : IAddonInstanceHandler(ADDON_INSTANCE_PERIPHERAL, addonInfo), + m_manager(manager), + m_bSupportsJoystickRumble(false), + m_bSupportsJoystickPowerOff(false) +{ + m_bProvidesJoysticks = + addonInfo->Type(ADDON::AddonType::PERIPHERALDLL)->GetValue("@provides_joysticks").asBoolean(); + m_bProvidesButtonMaps = addonInfo->Type(ADDON::AddonType::PERIPHERALDLL) + ->GetValue("@provides_buttonmaps") + .asBoolean(); + + // Create "C" interface structures, used as own parts to prevent API problems on update + m_ifc.peripheral = new AddonInstance_Peripheral; + m_ifc.peripheral->props = new AddonProps_Peripheral(); + m_ifc.peripheral->toAddon = new KodiToAddonFuncTable_Peripheral(); + m_ifc.peripheral->toKodi = new AddonToKodiFuncTable_Peripheral(); + + ResetProperties(); +} + +CPeripheralAddon::~CPeripheralAddon(void) +{ + DestroyAddon(); + + if (m_ifc.peripheral) + { + delete m_ifc.peripheral->toAddon; + delete m_ifc.peripheral->toKodi; + delete m_ifc.peripheral->props; + } + delete m_ifc.peripheral; +} + +void CPeripheralAddon::ResetProperties(void) +{ + // Initialise members + m_strUserPath = CSpecialProtocol::TranslatePath(Profile()); + m_strClientPath = CSpecialProtocol::TranslatePath(Path()); + + m_ifc.peripheral->props->user_path = m_strUserPath.c_str(); + m_ifc.peripheral->props->addon_path = m_strClientPath.c_str(); + + m_ifc.peripheral->toKodi->kodiInstance = this; + m_ifc.peripheral->toKodi->feature_count = cb_feature_count; + m_ifc.peripheral->toKodi->feature_type = cb_feature_type; + m_ifc.peripheral->toKodi->refresh_button_maps = cb_refresh_button_maps; + m_ifc.peripheral->toKodi->trigger_scan = cb_trigger_scan; + + memset(m_ifc.peripheral->toAddon, 0, sizeof(KodiToAddonFuncTable_Peripheral)); +} + +bool CPeripheralAddon::CreateAddon(void) +{ + std::unique_lock<CSharedSection> lock(m_dllSection); + + // Reset all properties to defaults + ResetProperties(); + + // Create directory for user data + if (!CDirectory::Exists(m_strUserPath)) + CDirectory::Create(m_strUserPath); + + // Initialise the add-on + CLog::Log(LOGDEBUG, "PERIPHERAL - {} - creating peripheral add-on instance '{}'", __FUNCTION__, + Name()); + + if (CreateInstance() != ADDON_STATUS_OK) + return false; + + if (!GetAddonProperties()) + { + DestroyInstance(); + return false; + } + + return true; +} + +void CPeripheralAddon::DestroyAddon() +{ + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_peripherals.clear(); + } + + { + std::unique_lock<CCriticalSection> lock(m_buttonMapMutex); + // only clear buttonMaps but don't delete them as they are owned by a + // CAddonJoystickInputHandling instance + m_buttonMaps.clear(); + } + + { + std::unique_lock<CSharedSection> lock(m_dllSection); + DestroyInstance(); + } +} + +bool CPeripheralAddon::GetAddonProperties(void) +{ + PERIPHERAL_CAPABILITIES addonCapabilities = {}; + + // Get the capabilities + m_ifc.peripheral->toAddon->get_capabilities(m_ifc.peripheral, &addonCapabilities); + + // Verify capabilities against addon.xml + if (m_bProvidesJoysticks != addonCapabilities.provides_joysticks) + { + CLog::Log( + LOGERROR, + "PERIPHERAL - Add-on '{}': provides_joysticks'({}) in add-on DLL doesn't match " + "'provides_joysticks'({}) in addon.xml. Please contact the developer of this add-on: {}", + Name(), addonCapabilities.provides_joysticks ? "true" : "false", + m_bProvidesJoysticks ? "true" : "false", Author()); + return false; + } + if (m_bProvidesButtonMaps != addonCapabilities.provides_buttonmaps) + { + CLog::Log( + LOGERROR, + "PERIPHERAL - Add-on '{}': provides_buttonmaps' ({}) in add-on DLL doesn't match " + "'provides_buttonmaps' ({}) in addon.xml. Please contact the developer of this add-on: {}", + Name(), addonCapabilities.provides_buttonmaps ? "true" : "false", + m_bProvidesButtonMaps ? "true" : "false", Author()); + return false; + } + + // Read properties that depend on underlying driver + m_bSupportsJoystickRumble = addonCapabilities.provides_joystick_rumble; + m_bSupportsJoystickPowerOff = addonCapabilities.provides_joystick_power_off; + + return true; +} + +bool CPeripheralAddon::Register(unsigned int peripheralIndex, const PeripheralPtr& peripheral) +{ + if (!peripheral) + return false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_peripherals.find(peripheralIndex) == m_peripherals.end()) + { + if (peripheral->Type() == PERIPHERAL_JOYSTICK) + { + m_peripherals[peripheralIndex] = std::static_pointer_cast<CPeripheralJoystick>(peripheral); + + CLog::Log(LOGINFO, "{} - new {} device registered on {}->{}: {}", __FUNCTION__, + PeripheralTypeTranslator::TypeToString(peripheral->Type()), + PeripheralTypeTranslator::BusTypeToString(PERIPHERAL_BUS_ADDON), + peripheral->Location(), peripheral->DeviceName()); + + return true; + } + } + return false; +} + +void CPeripheralAddon::UnregisterRemovedDevices(const PeripheralScanResults& results, + PeripheralVector& removedPeripherals) +{ + std::vector<unsigned int> removedIndexes; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto& it : m_peripherals) + { + const PeripheralPtr& peripheral = it.second; + PeripheralScanResult updatedDevice(PERIPHERAL_BUS_ADDON); + if (!results.GetDeviceOnLocation(peripheral->Location(), &updatedDevice) || + *peripheral != updatedDevice) + { + // Device removed + removedIndexes.push_back(it.first); + } + } + } + + for (auto index : removedIndexes) + { + auto it = m_peripherals.find(index); + const PeripheralPtr& peripheral = it->second; + CLog::Log(LOGINFO, "{} - device removed from {}/{}: {} ({}:{})", __FUNCTION__, + PeripheralTypeTranslator::TypeToString(peripheral->Type()), peripheral->Location(), + peripheral->DeviceName(), peripheral->VendorIdAsString(), + peripheral->ProductIdAsString()); + UnregisterButtonMap(peripheral.get()); + peripheral->OnDeviceRemoved(); + removedPeripherals.push_back(peripheral); + m_peripherals.erase(it); + } +} + +bool CPeripheralAddon::HasFeature(const PeripheralFeature feature) const +{ + if (feature == FEATURE_JOYSTICK) + return m_bProvidesJoysticks; + + return false; +} + +void CPeripheralAddon::GetFeatures(std::vector<PeripheralFeature>& features) const +{ + if (m_bProvidesJoysticks && + std::find(features.begin(), features.end(), FEATURE_JOYSTICK) == features.end()) + features.push_back(FEATURE_JOYSTICK); +} + +PeripheralPtr CPeripheralAddon::GetPeripheral(unsigned int index) const +{ + PeripheralPtr peripheral; + std::unique_lock<CCriticalSection> lock(m_critSection); + auto it = m_peripherals.find(index); + if (it != m_peripherals.end()) + peripheral = it->second; + return peripheral; +} + +PeripheralPtr CPeripheralAddon::GetByPath(const std::string& strPath) const +{ + PeripheralPtr result; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& it : m_peripherals) + { + if (StringUtils::EqualsNoCase(strPath, it.second->FileLocation())) + { + result = it.second; + break; + } + } + + return result; +} + +bool CPeripheralAddon::SupportsFeature(PeripheralFeature feature) const +{ + switch (feature) + { + case FEATURE_RUMBLE: + return m_bSupportsJoystickRumble; + case FEATURE_POWER_OFF: + return m_bSupportsJoystickPowerOff; + default: + break; + } + + return false; +} + +unsigned int CPeripheralAddon::GetPeripheralsWithFeature(PeripheralVector& results, + const PeripheralFeature feature) const +{ + unsigned int iReturn = 0; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& it : m_peripherals) + { + if (it.second->HasFeature(feature)) + { + results.push_back(it.second); + ++iReturn; + } + } + return iReturn; +} + +unsigned int CPeripheralAddon::GetNumberOfPeripherals(void) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return static_cast<unsigned int>(m_peripherals.size()); +} + +unsigned int CPeripheralAddon::GetNumberOfPeripheralsWithId(const int iVendorId, + const int iProductId) const +{ + unsigned int iReturn = 0; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& it : m_peripherals) + { + if (it.second->VendorId() == iVendorId && it.second->ProductId() == iProductId) + iReturn++; + } + + return iReturn; +} + +void CPeripheralAddon::GetDirectory(const std::string& strPath, CFileItemList& items) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& it : m_peripherals) + { + const PeripheralPtr& peripheral = it.second; + if (peripheral->IsHidden()) + continue; + + CFileItemPtr peripheralFile(new CFileItem(peripheral->DeviceName())); + peripheralFile->SetPath(peripheral->FileLocation()); + peripheralFile->SetProperty("vendor", peripheral->VendorIdAsString()); + peripheralFile->SetProperty("product", peripheral->ProductIdAsString()); + peripheralFile->SetProperty( + "bus", PeripheralTypeTranslator::BusTypeToString(peripheral->GetBusType())); + peripheralFile->SetProperty("location", peripheral->Location()); + peripheralFile->SetProperty("class", + PeripheralTypeTranslator::TypeToString(peripheral->Type())); + peripheralFile->SetProperty("version", peripheral->GetVersionInfo()); + peripheralFile->SetArt("icon", peripheral->GetIcon()); + items.Add(peripheralFile); + } +} + +bool CPeripheralAddon::PerformDeviceScan(PeripheralScanResults& results) +{ + unsigned int peripheralCount; + PERIPHERAL_INFO* pScanResults; + PERIPHERAL_ERROR retVal; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->perform_device_scan) + return false; + + LogError(retVal = m_ifc.peripheral->toAddon->perform_device_scan(m_ifc.peripheral, + &peripheralCount, &pScanResults), + "PerformDeviceScan()"); + + if (retVal == PERIPHERAL_NO_ERROR) + { + for (unsigned int i = 0; i < peripheralCount; i++) + { + kodi::addon::Peripheral peripheral(pScanResults[i]); + PeripheralScanResult result(PERIPHERAL_BUS_ADDON); + switch (peripheral.Type()) + { + case PERIPHERAL_TYPE_JOYSTICK: + result.m_type = PERIPHERAL_JOYSTICK; + break; + default: + continue; + } + + result.m_strDeviceName = peripheral.Name(); + result.m_strLocation = StringUtils::Format("{}/{}", ID(), peripheral.Index()); + result.m_iVendorId = peripheral.VendorID(); + result.m_iProductId = peripheral.ProductID(); + result.m_mappedType = PERIPHERAL_JOYSTICK; + result.m_mappedBusType = PERIPHERAL_BUS_ADDON; + result.m_iSequence = 0; + + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + + m_ifc.peripheral->toAddon->free_scan_results(m_ifc.peripheral, peripheralCount, pScanResults); + + return true; + } + + return false; +} + +bool CPeripheralAddon::ProcessEvents(void) +{ + if (!m_bProvidesJoysticks) + return false; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->get_events) + return false; + + PERIPHERAL_ERROR retVal; + + unsigned int eventCount = 0; + PERIPHERAL_EVENT* pEvents = nullptr; + + LogError(retVal = m_ifc.peripheral->toAddon->get_events(m_ifc.peripheral, &eventCount, &pEvents), + "GetEvents()"); + if (retVal == PERIPHERAL_NO_ERROR) + { + for (unsigned int i = 0; i < eventCount; i++) + { + kodi::addon::PeripheralEvent event(pEvents[i]); + PeripheralPtr device = GetPeripheral(event.PeripheralIndex()); + if (!device) + continue; + + switch (device->Type()) + { + case PERIPHERAL_JOYSTICK: + { + std::shared_ptr<CPeripheralJoystick> joystickDevice = + std::static_pointer_cast<CPeripheralJoystick>(device); + + switch (event.Type()) + { + case PERIPHERAL_EVENT_TYPE_DRIVER_BUTTON: + { + const bool bPressed = (event.ButtonState() == JOYSTICK_STATE_BUTTON_PRESSED); + joystickDevice->OnButtonMotion(event.DriverIndex(), bPressed); + break; + } + case PERIPHERAL_EVENT_TYPE_DRIVER_HAT: + { + const HAT_STATE state = + CPeripheralAddonTranslator::TranslateHatState(event.HatState()); + joystickDevice->OnHatMotion(event.DriverIndex(), state); + break; + } + case PERIPHERAL_EVENT_TYPE_DRIVER_AXIS: + { + joystickDevice->OnAxisMotion(event.DriverIndex(), event.AxisState()); + break; + } + default: + break; + } + break; + } + default: + break; + } + } + + for (const auto& it : m_peripherals) + { + if (it.second->Type() == PERIPHERAL_JOYSTICK) + std::static_pointer_cast<CPeripheralJoystick>(it.second)->OnInputFrame(); + } + + m_ifc.peripheral->toAddon->free_events(m_ifc.peripheral, eventCount, pEvents); + + return true; + } + + return false; +} + +bool CPeripheralAddon::SendRumbleEvent(unsigned int peripheralIndex, + unsigned int driverIndex, + float magnitude) +{ + if (!m_bProvidesJoysticks) + return false; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->send_event) + return false; + + PERIPHERAL_EVENT eventStruct = {}; + + eventStruct.peripheral_index = peripheralIndex; + eventStruct.type = PERIPHERAL_EVENT_TYPE_SET_MOTOR; + eventStruct.driver_index = driverIndex; + eventStruct.motor_state = magnitude; + + return m_ifc.peripheral->toAddon->send_event(m_ifc.peripheral, &eventStruct); +} + +bool CPeripheralAddon::GetJoystickProperties(unsigned int index, CPeripheralJoystick& joystick) +{ + if (!m_bProvidesJoysticks) + return false; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->get_joystick_info) + return false; + + PERIPHERAL_ERROR retVal; + + JOYSTICK_INFO joystickStruct; + + LogError(retVal = m_ifc.peripheral->toAddon->get_joystick_info(m_ifc.peripheral, index, + &joystickStruct), + "GetJoystickInfo()"); + if (retVal == PERIPHERAL_NO_ERROR) + { + kodi::addon::Joystick addonJoystick(joystickStruct); + SetJoystickInfo(joystick, addonJoystick); + + m_ifc.peripheral->toAddon->free_joystick_info(m_ifc.peripheral, &joystickStruct); + + return true; + } + + return false; +} + +bool CPeripheralAddon::GetFeatures(const CPeripheral* device, + const std::string& strControllerId, + FeatureMap& features) +{ + if (!m_bProvidesButtonMaps) + return false; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->get_features) + return false; + + PERIPHERAL_ERROR retVal; + + kodi::addon::Joystick joystickInfo; + GetJoystickInfo(device, joystickInfo); + + JOYSTICK_INFO joystickStruct; + joystickInfo.ToStruct(joystickStruct); + + unsigned int featureCount = 0; + JOYSTICK_FEATURE* pFeatures = nullptr; + + LogError(retVal = m_ifc.peripheral->toAddon->get_features(m_ifc.peripheral, &joystickStruct, + strControllerId.c_str(), &featureCount, + &pFeatures), + "GetFeatures()"); + + kodi::addon::Joystick::FreeStruct(joystickStruct); + + if (retVal == PERIPHERAL_NO_ERROR) + { + for (unsigned int i = 0; i < featureCount; i++) + { + kodi::addon::JoystickFeature feature(pFeatures[i]); + if (feature.Type() != JOYSTICK_FEATURE_TYPE_UNKNOWN) + features[feature.Name()] = feature; + } + + m_ifc.peripheral->toAddon->free_features(m_ifc.peripheral, featureCount, pFeatures); + + return true; + } + + return false; +} + +bool CPeripheralAddon::MapFeature(const CPeripheral* device, + const std::string& strControllerId, + const kodi::addon::JoystickFeature& feature) +{ + if (!m_bProvidesButtonMaps) + return false; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->map_features) + return false; + + PERIPHERAL_ERROR retVal; + + kodi::addon::Joystick joystickInfo; + GetJoystickInfo(device, joystickInfo); + + JOYSTICK_INFO joystickStruct; + joystickInfo.ToStruct(joystickStruct); + + JOYSTICK_FEATURE addonFeature; + feature.ToStruct(addonFeature); + + LogError(retVal = m_ifc.peripheral->toAddon->map_features( + m_ifc.peripheral, &joystickStruct, strControllerId.c_str(), 1, &addonFeature), + "MapFeatures()"); + + kodi::addon::Joystick::FreeStruct(joystickStruct); + kodi::addon::JoystickFeature::FreeStruct(addonFeature); + + return retVal == PERIPHERAL_NO_ERROR; +} + +bool CPeripheralAddon::GetIgnoredPrimitives(const CPeripheral* device, PrimitiveVector& primitives) +{ + if (!m_bProvidesButtonMaps) + return false; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->get_ignored_primitives) + return false; + + PERIPHERAL_ERROR retVal; + + kodi::addon::Joystick joystickInfo; + GetJoystickInfo(device, joystickInfo); + + JOYSTICK_INFO joystickStruct; + joystickInfo.ToStruct(joystickStruct); + + unsigned int primitiveCount = 0; + JOYSTICK_DRIVER_PRIMITIVE* pPrimitives = nullptr; + + LogError(retVal = m_ifc.peripheral->toAddon->get_ignored_primitives( + m_ifc.peripheral, &joystickStruct, &primitiveCount, &pPrimitives), + "GetIgnoredPrimitives()"); + + kodi::addon::Joystick::FreeStruct(joystickStruct); + + if (retVal == PERIPHERAL_NO_ERROR) + { + for (unsigned int i = 0; i < primitiveCount; i++) + primitives.emplace_back(pPrimitives[i]); + + m_ifc.peripheral->toAddon->free_primitives(m_ifc.peripheral, primitiveCount, pPrimitives); + + return true; + } + + return false; +} + +bool CPeripheralAddon::SetIgnoredPrimitives(const CPeripheral* device, + const PrimitiveVector& primitives) +{ + if (!m_bProvidesButtonMaps) + return false; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->set_ignored_primitives) + return false; + + PERIPHERAL_ERROR retVal; + + kodi::addon::Joystick joystickInfo; + GetJoystickInfo(device, joystickInfo); + + JOYSTICK_INFO joystickStruct; + joystickInfo.ToStruct(joystickStruct); + + JOYSTICK_DRIVER_PRIMITIVE* addonPrimitives = nullptr; + kodi::addon::DriverPrimitives::ToStructs(primitives, &addonPrimitives); + const unsigned int primitiveCount = static_cast<unsigned int>(primitives.size()); + + LogError(retVal = m_ifc.peripheral->toAddon->set_ignored_primitives( + m_ifc.peripheral, &joystickStruct, primitiveCount, addonPrimitives), + "SetIgnoredPrimitives()"); + + kodi::addon::Joystick::FreeStruct(joystickStruct); + kodi::addon::DriverPrimitives::FreeStructs(primitiveCount, addonPrimitives); + + return retVal == PERIPHERAL_NO_ERROR; +} + +void CPeripheralAddon::SaveButtonMap(const CPeripheral* device) +{ + if (!m_bProvidesButtonMaps) + return; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->save_button_map) + return; + + kodi::addon::Joystick joystickInfo; + GetJoystickInfo(device, joystickInfo); + + JOYSTICK_INFO joystickStruct; + joystickInfo.ToStruct(joystickStruct); + + m_ifc.peripheral->toAddon->save_button_map(m_ifc.peripheral, &joystickStruct); + + kodi::addon::Joystick::FreeStruct(joystickStruct); + + // Notify observing button maps + RefreshButtonMaps(device->DeviceName()); +} + +void CPeripheralAddon::RevertButtonMap(const CPeripheral* device) +{ + if (!m_bProvidesButtonMaps) + return; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->revert_button_map) + return; + + kodi::addon::Joystick joystickInfo; + GetJoystickInfo(device, joystickInfo); + + JOYSTICK_INFO joystickStruct; + joystickInfo.ToStruct(joystickStruct); + + m_ifc.peripheral->toAddon->revert_button_map(m_ifc.peripheral, &joystickStruct); + + kodi::addon::Joystick::FreeStruct(joystickStruct); +} + +void CPeripheralAddon::ResetButtonMap(const CPeripheral* device, const std::string& strControllerId) +{ + if (!m_bProvidesButtonMaps) + return; + + kodi::addon::Joystick joystickInfo; + GetJoystickInfo(device, joystickInfo); + + JOYSTICK_INFO joystickStruct; + joystickInfo.ToStruct(joystickStruct); + + m_ifc.peripheral->toAddon->reset_button_map(m_ifc.peripheral, &joystickStruct, + strControllerId.c_str()); + + kodi::addon::Joystick::FreeStruct(joystickStruct); + + // Notify observing button maps + RefreshButtonMaps(device->DeviceName()); +} + +void CPeripheralAddon::PowerOffJoystick(unsigned int index) +{ + if (!HasFeature(FEATURE_JOYSTICK)) + return; + + if (!SupportsFeature(FEATURE_POWER_OFF)) + return; + + std::shared_lock<CSharedSection> lock(m_dllSection); + + if (!m_ifc.peripheral->toAddon->power_off_joystick) + return; + + m_ifc.peripheral->toAddon->power_off_joystick(m_ifc.peripheral, index); +} + +void CPeripheralAddon::RegisterButtonMap(CPeripheral* device, IButtonMap* buttonMap) +{ + std::unique_lock<CCriticalSection> lock(m_buttonMapMutex); + + UnregisterButtonMap(buttonMap); + m_buttonMaps.emplace_back(device, buttonMap); +} + +void CPeripheralAddon::UnregisterButtonMap(IButtonMap* buttonMap) +{ + std::unique_lock<CCriticalSection> lock(m_buttonMapMutex); + + for (auto it = m_buttonMaps.begin(); it != m_buttonMaps.end(); ++it) + { + if (it->second == buttonMap) + { + m_buttonMaps.erase(it); + break; + } + } +} + +void CPeripheralAddon::UnregisterButtonMap(CPeripheral* device) +{ + std::unique_lock<CCriticalSection> lock(m_buttonMapMutex); + + m_buttonMaps.erase( + std::remove_if(m_buttonMaps.begin(), m_buttonMaps.end(), + [device](const std::pair<CPeripheral*, JOYSTICK::IButtonMap*>& buttonMap) { + return buttonMap.first == device; + }), + m_buttonMaps.end()); +} + +void CPeripheralAddon::RefreshButtonMaps(const std::string& strDeviceName /* = "" */) +{ + std::unique_lock<CCriticalSection> lock(m_buttonMapMutex); + + for (auto it = m_buttonMaps.begin(); it != m_buttonMaps.end(); ++it) + { + if (strDeviceName.empty() || strDeviceName == it->first->DeviceName()) + it->second->Load(); + } +} + +bool CPeripheralAddon::ProvidesJoysticks(const ADDON::AddonInfoPtr& addonInfo) +{ + return addonInfo->Type(ADDON::AddonType::PERIPHERALDLL) + ->GetValue("@provides_joysticks") + .asBoolean(); +} + +bool CPeripheralAddon::ProvidesButtonMaps(const ADDON::AddonInfoPtr& addonInfo) +{ + return addonInfo->Type(ADDON::AddonType::PERIPHERALDLL) + ->GetValue("@provides_buttonmaps") + .asBoolean(); +} + +void CPeripheralAddon::TriggerDeviceScan() +{ + m_manager.TriggerDeviceScan(PERIPHERAL_BUS_ADDON); +} + +unsigned int CPeripheralAddon::FeatureCount(const std::string& controllerId, + JOYSTICK_FEATURE_TYPE type) const +{ + using namespace GAME; + + unsigned int count = 0; + + CControllerManager& controllerProfiles = m_manager.GetControllerProfiles(); + ControllerPtr controller = controllerProfiles.GetController(controllerId); + if (controller) + count = controller->FeatureCount(CPeripheralAddonTranslator::TranslateFeatureType(type)); + + return count; +} + +JOYSTICK_FEATURE_TYPE CPeripheralAddon::FeatureType(const std::string& controllerId, + const std::string& featureName) const +{ + using namespace GAME; + + JOYSTICK_FEATURE_TYPE type = JOYSTICK_FEATURE_TYPE_UNKNOWN; + + CControllerManager& controllerProfiles = m_manager.GetControllerProfiles(); + ControllerPtr controller = controllerProfiles.GetController(controllerId); + if (controller) + type = CPeripheralAddonTranslator::TranslateFeatureType(controller->FeatureType(featureName)); + + return type; +} + +void CPeripheralAddon::GetPeripheralInfo(const CPeripheral* device, + kodi::addon::Peripheral& peripheralInfo) +{ + peripheralInfo.SetType(CPeripheralAddonTranslator::TranslateType(device->Type())); + peripheralInfo.SetName(device->DeviceName()); + peripheralInfo.SetVendorID(device->VendorId()); + peripheralInfo.SetProductID(device->ProductId()); +} + +void CPeripheralAddon::GetJoystickInfo(const CPeripheral* device, + kodi::addon::Joystick& joystickInfo) +{ + GetPeripheralInfo(device, joystickInfo); + + if (device->Type() == PERIPHERAL_JOYSTICK) + { + const CPeripheralJoystick* joystick = static_cast<const CPeripheralJoystick*>(device); + joystickInfo.SetProvider(joystick->Provider()); + joystickInfo.SetButtonCount(joystick->ButtonCount()); + joystickInfo.SetHatCount(joystick->HatCount()); + joystickInfo.SetAxisCount(joystick->AxisCount()); + joystickInfo.SetMotorCount(joystick->MotorCount()); + joystickInfo.SetSupportsPowerOff(joystick->SupportsPowerOff()); + } + else if (device->Type() == PERIPHERAL_KEYBOARD || device->Type() == PERIPHERAL_MOUSE) + { + joystickInfo.SetName(GetDeviceName(device->Type())); // Override name with non-localized version + joystickInfo.SetProvider(GetProvider(device->Type())); + } +} + +void CPeripheralAddon::SetJoystickInfo(CPeripheralJoystick& joystick, + const kodi::addon::Joystick& joystickInfo) +{ + joystick.SetProvider(joystickInfo.Provider()); + joystick.SetRequestedPort(joystickInfo.RequestedPort()); + joystick.SetButtonCount(joystickInfo.ButtonCount()); + joystick.SetHatCount(joystickInfo.HatCount()); + joystick.SetAxisCount(joystickInfo.AxisCount()); + joystick.SetMotorCount(joystickInfo.MotorCount()); + joystick.SetSupportsPowerOff(joystickInfo.SupportsPowerOff()); +} + +bool CPeripheralAddon::LogError(const PERIPHERAL_ERROR error, const char* strMethod) const +{ + if (error != PERIPHERAL_NO_ERROR) + { + CLog::Log(LOGERROR, "PERIPHERAL - {} - addon '{}' returned an error: {}", strMethod, Name(), + CPeripheralAddonTranslator::TranslateError(error)); + return false; + } + return true; +} + +std::string CPeripheralAddon::GetDeviceName(PeripheralType type) +{ + switch (type) + { + case PERIPHERAL_KEYBOARD: + return KEYBOARD_BUTTON_MAP_NAME; + case PERIPHERAL_MOUSE: + return MOUSE_BUTTON_MAP_NAME; + default: + break; + } + + return ""; +} + +std::string CPeripheralAddon::GetProvider(PeripheralType type) +{ + switch (type) + { + case PERIPHERAL_KEYBOARD: + return KEYBOARD_PROVIDER; + case PERIPHERAL_MOUSE: + return MOUSE_PROVIDER; + default: + break; + } + + return ""; +} + +void CPeripheralAddon::cb_trigger_scan(void* kodiInstance) +{ + if (kodiInstance == nullptr) + return; + + static_cast<CPeripheralAddon*>(kodiInstance)->TriggerDeviceScan(); +} + +void CPeripheralAddon::cb_refresh_button_maps(void* kodiInstance, + const char* deviceName, + const char* controllerId) +{ + if (!kodiInstance) + return; + + static_cast<CPeripheralAddon*>(kodiInstance)->RefreshButtonMaps(deviceName ? deviceName : ""); +} + +unsigned int CPeripheralAddon::cb_feature_count(void* kodiInstance, + const char* controllerId, + JOYSTICK_FEATURE_TYPE type) +{ + if (kodiInstance == nullptr || controllerId == nullptr) + return 0; + + return static_cast<CPeripheralAddon*>(kodiInstance)->FeatureCount(controllerId, type); +} + +JOYSTICK_FEATURE_TYPE CPeripheralAddon::cb_feature_type(void* kodiInstance, + const char* controllerId, + const char* featureName) +{ + if (kodiInstance == nullptr || controllerId == nullptr || featureName == nullptr) + return JOYSTICK_FEATURE_TYPE_UNKNOWN; + + return static_cast<CPeripheralAddon*>(kodiInstance)->FeatureType(controllerId, featureName); +} diff --git a/xbmc/peripherals/addons/PeripheralAddon.h b/xbmc/peripherals/addons/PeripheralAddon.h new file mode 100644 index 0000000..2dd9d22 --- /dev/null +++ b/xbmc/peripherals/addons/PeripheralAddon.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2014-2018 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 "addons/binary-addons/AddonInstanceHandler.h" +#include "addons/kodi-dev-kit/include/kodi/addon-instance/Peripheral.h" +#include "input/joysticks/JoystickTypes.h" +#include "peripherals/PeripheralTypes.h" +#include "threads/CriticalSection.h" +#include "threads/SharedSection.h" + +#include <map> +#include <memory> +#include <vector> + +class CFileItemList; + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +class IDriverHandler; +} // namespace JOYSTICK +} // namespace KODI + +namespace PERIPHERALS +{ +class CPeripheral; +class CPeripheralJoystick; +class CPeripherals; + +typedef std::vector<kodi::addon::DriverPrimitive> PrimitiveVector; +typedef std::map<KODI::JOYSTICK::FeatureName, kodi::addon::JoystickFeature> FeatureMap; + +class CPeripheralAddon : public ADDON::IAddonInstanceHandler +{ +public: + explicit CPeripheralAddon(const ADDON::AddonInfoPtr& addonInfo, CPeripherals& manager); + ~CPeripheralAddon(void) override; + + /*! + * @brief Initialise the instance of this add-on + */ + bool CreateAddon(void); + + /*! + * \brief Deinitialize the instance of this add-on + */ + void DestroyAddon(); + + bool Register(unsigned int peripheralIndex, const PeripheralPtr& peripheral); + void UnregisterRemovedDevices(const PeripheralScanResults& results, + PeripheralVector& removedPeripherals); + void GetFeatures(std::vector<PeripheralFeature>& features) const; + bool HasFeature(const PeripheralFeature feature) const; + PeripheralPtr GetPeripheral(unsigned int index) const; + PeripheralPtr GetByPath(const std::string& strPath) const; + bool SupportsFeature(PeripheralFeature feature) const; + unsigned int GetPeripheralsWithFeature(PeripheralVector& results, + const PeripheralFeature feature) const; + unsigned int GetNumberOfPeripherals(void) const; + unsigned int GetNumberOfPeripheralsWithId(const int iVendorId, const int iProductId) const; + void GetDirectory(const std::string& strPath, CFileItemList& items) const; + + /** @name Peripheral add-on methods */ + //@{ + bool PerformDeviceScan(PeripheralScanResults& results); + bool ProcessEvents(void); + bool SendRumbleEvent(unsigned int index, unsigned int driverIndex, float magnitude); + //@} + + /** @name Joystick methods */ + //@{ + bool GetJoystickProperties(unsigned int index, CPeripheralJoystick& joystick); + bool HasButtonMaps(void) const { return m_bProvidesButtonMaps; } + bool GetFeatures(const CPeripheral* device, + const std::string& strControllerId, + FeatureMap& features); + bool MapFeature(const CPeripheral* device, + const std::string& strControllerId, + const kodi::addon::JoystickFeature& feature); + bool GetIgnoredPrimitives(const CPeripheral* device, PrimitiveVector& primitives); + bool SetIgnoredPrimitives(const CPeripheral* device, const PrimitiveVector& primitives); + void SaveButtonMap(const CPeripheral* device); + void RevertButtonMap(const CPeripheral* device); + void ResetButtonMap(const CPeripheral* device, const std::string& strControllerId); + void PowerOffJoystick(unsigned int index); + //@} + + void RegisterButtonMap(CPeripheral* device, KODI::JOYSTICK::IButtonMap* buttonMap); + void UnregisterButtonMap(KODI::JOYSTICK::IButtonMap* buttonMap); + + static bool ProvidesJoysticks(const ADDON::AddonInfoPtr& addonInfo); + static bool ProvidesButtonMaps(const ADDON::AddonInfoPtr& addonInfo); + +private: + void UnregisterButtonMap(CPeripheral* device); + + // Binary add-on callbacks + void TriggerDeviceScan(); + void RefreshButtonMaps(const std::string& strDeviceName = ""); + unsigned int FeatureCount(const std::string& controllerId, JOYSTICK_FEATURE_TYPE type) const; + JOYSTICK_FEATURE_TYPE FeatureType(const std::string& controllerId, + const std::string& featureName) const; + + /*! + * @brief Helper functions + */ + static void GetPeripheralInfo(const CPeripheral* device, kodi::addon::Peripheral& peripheralInfo); + + static void GetJoystickInfo(const CPeripheral* device, kodi::addon::Joystick& joystickInfo); + static void SetJoystickInfo(CPeripheralJoystick& joystick, + const kodi::addon::Joystick& joystickInfo); + + /*! + * @brief Reset all class members to their defaults. Called by the constructors + */ + void ResetProperties(void); + + /*! + * @brief Retrieve add-on properties from the add-on + */ + bool GetAddonProperties(void); + + bool LogError(const PERIPHERAL_ERROR error, const char* strMethod) const; + + static std::string GetDeviceName(PeripheralType type); + static std::string GetProvider(PeripheralType type); + + // Construction parameters + CPeripherals& m_manager; + + /* @brief Cache for const char* members in PERIPHERAL_PROPERTIES */ + std::string m_strUserPath; /*!< @brief translated path to the user profile */ + std::string m_strClientPath; /*!< @brief translated path to this add-on */ + + /*! + * @brief Callback functions from addon to kodi + */ + //@{ + static void cb_trigger_scan(void* kodiInstance); + static void cb_refresh_button_maps(void* kodiInstance, + const char* deviceName, + const char* controllerId); + static unsigned int cb_feature_count(void* kodiInstance, + const char* controllerId, + JOYSTICK_FEATURE_TYPE type); + static JOYSTICK_FEATURE_TYPE cb_feature_type(void* kodiInstance, + const char* controllerId, + const char* featureName); + //@} + + /* @brief Add-on properties */ + bool m_bProvidesJoysticks; + bool m_bSupportsJoystickRumble; + bool m_bSupportsJoystickPowerOff; + bool m_bProvidesButtonMaps; + + /* @brief Map of peripherals belonging to the add-on */ + std::map<unsigned int, PeripheralPtr> m_peripherals; + + /* @brief Button map observers */ + std::vector<std::pair<CPeripheral*, KODI::JOYSTICK::IButtonMap*>> m_buttonMaps; + CCriticalSection m_buttonMapMutex; + + /* @brief Thread synchronization */ + mutable CCriticalSection m_critSection; + + CSharedSection m_dllSection; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/addons/PeripheralAddonTranslator.cpp b/xbmc/peripherals/addons/PeripheralAddonTranslator.cpp new file mode 100644 index 0000000..16dd529 --- /dev/null +++ b/xbmc/peripherals/addons/PeripheralAddonTranslator.cpp @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2015-2018 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 "PeripheralAddonTranslator.h" + +#include "games/controllers/ControllerTranslator.h" +#include "input/joysticks/JoystickUtils.h" + +#include <algorithm> +#include <iterator> + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +// --- Helper function --------------------------------------------------------- + +JOYSTICK_DRIVER_SEMIAXIS_DIRECTION operator*(JOYSTICK_DRIVER_SEMIAXIS_DIRECTION dir, int i) +{ + return static_cast<JOYSTICK_DRIVER_SEMIAXIS_DIRECTION>(static_cast<int>(dir) * i); +} + +// --- CPeripheralAddonTranslator ---------------------------------------------- + +const char* CPeripheralAddonTranslator::TranslateError(const PERIPHERAL_ERROR error) +{ + switch (error) + { + case PERIPHERAL_NO_ERROR: + return "no error"; + case PERIPHERAL_ERROR_FAILED: + return "command failed"; + case PERIPHERAL_ERROR_INVALID_PARAMETERS: + return "invalid parameters"; + case PERIPHERAL_ERROR_NOT_IMPLEMENTED: + return "not implemented"; + case PERIPHERAL_ERROR_NOT_CONNECTED: + return "not connected"; + case PERIPHERAL_ERROR_CONNECTION_FAILED: + return "connection failed"; + case PERIPHERAL_ERROR_UNKNOWN: + default: + return "unknown error"; + } +} + +PeripheralType CPeripheralAddonTranslator::TranslateType(PERIPHERAL_TYPE type) +{ + switch (type) + { + case PERIPHERAL_TYPE_JOYSTICK: + return PERIPHERAL_JOYSTICK; + default: + break; + } + return PERIPHERAL_UNKNOWN; +} + +PERIPHERAL_TYPE CPeripheralAddonTranslator::TranslateType(PeripheralType type) +{ + switch (type) + { + case PERIPHERAL_JOYSTICK: + return PERIPHERAL_TYPE_JOYSTICK; + default: + break; + } + return PERIPHERAL_TYPE_UNKNOWN; +} + +CDriverPrimitive CPeripheralAddonTranslator::TranslatePrimitive( + const kodi::addon::DriverPrimitive& primitive) +{ + CDriverPrimitive retVal; + + switch (primitive.Type()) + { + case JOYSTICK_DRIVER_PRIMITIVE_TYPE_BUTTON: + { + retVal = CDriverPrimitive(PRIMITIVE_TYPE::BUTTON, primitive.DriverIndex()); + break; + } + case JOYSTICK_DRIVER_PRIMITIVE_TYPE_HAT_DIRECTION: + { + retVal = CDriverPrimitive(primitive.DriverIndex(), + TranslateHatDirection(primitive.HatDirection())); + break; + } + case JOYSTICK_DRIVER_PRIMITIVE_TYPE_SEMIAXIS: + { + retVal = CDriverPrimitive(primitive.DriverIndex(), primitive.Center(), + TranslateSemiAxisDirection(primitive.SemiAxisDirection()), + primitive.Range()); + break; + } + case JOYSTICK_DRIVER_PRIMITIVE_TYPE_MOTOR: + { + retVal = CDriverPrimitive(PRIMITIVE_TYPE::MOTOR, primitive.DriverIndex()); + break; + } + case JOYSTICK_DRIVER_PRIMITIVE_TYPE_KEY: + { + KEYBOARD::KeySymbol keycode = + GAME::CControllerTranslator::TranslateKeysym(primitive.Keycode()); + retVal = CDriverPrimitive(keycode); + break; + } + case JOYSTICK_DRIVER_PRIMITIVE_TYPE_MOUSE_BUTTON: + { + retVal = CDriverPrimitive(TranslateMouseButton(primitive.MouseIndex())); + break; + } + case JOYSTICK_DRIVER_PRIMITIVE_TYPE_RELPOINTER_DIRECTION: + { + retVal = CDriverPrimitive(TranslateRelPointerDirection(primitive.RelPointerDirection())); + break; + } + default: + break; + } + + return retVal; +} + +kodi::addon::DriverPrimitive CPeripheralAddonTranslator::TranslatePrimitive( + const CDriverPrimitive& primitive) +{ + kodi::addon::DriverPrimitive retVal; + + switch (primitive.Type()) + { + case PRIMITIVE_TYPE::BUTTON: + { + retVal = kodi::addon::DriverPrimitive::CreateButton(primitive.Index()); + break; + } + case PRIMITIVE_TYPE::HAT: + { + retVal = kodi::addon::DriverPrimitive(primitive.Index(), + TranslateHatDirection(primitive.HatDirection())); + break; + } + case PRIMITIVE_TYPE::SEMIAXIS: + { + retVal = kodi::addon::DriverPrimitive( + primitive.Index(), primitive.Center(), + TranslateSemiAxisDirection(primitive.SemiAxisDirection()), primitive.Range()); + break; + } + case PRIMITIVE_TYPE::MOTOR: + { + retVal = kodi::addon::DriverPrimitive::CreateMotor(primitive.Index()); + break; + } + case PRIMITIVE_TYPE::KEY: + { + std::string keysym = GAME::CControllerTranslator::TranslateKeycode(primitive.Keycode()); + retVal = kodi::addon::DriverPrimitive(keysym); + break; + } + case PRIMITIVE_TYPE::MOUSE_BUTTON: + { + retVal = kodi::addon::DriverPrimitive::CreateMouseButton( + TranslateMouseButton(primitive.MouseButton())); + break; + } + case PRIMITIVE_TYPE::RELATIVE_POINTER: + { + retVal = + kodi::addon::DriverPrimitive(TranslateRelPointerDirection(primitive.PointerDirection())); + break; + } + default: + break; + } + + return retVal; +} + +std::vector<JOYSTICK::CDriverPrimitive> CPeripheralAddonTranslator::TranslatePrimitives( + const std::vector<kodi::addon::DriverPrimitive>& primitives) +{ + std::vector<JOYSTICK::CDriverPrimitive> ret; + std::transform(primitives.begin(), primitives.end(), std::back_inserter(ret), + [](const kodi::addon::DriverPrimitive& primitive) { + return CPeripheralAddonTranslator::TranslatePrimitive(primitive); + }); + return ret; +} + +std::vector<kodi::addon::DriverPrimitive> CPeripheralAddonTranslator::TranslatePrimitives( + const std::vector<JOYSTICK::CDriverPrimitive>& primitives) +{ + std::vector<kodi::addon::DriverPrimitive> ret; + std::transform(primitives.begin(), primitives.end(), std::back_inserter(ret), + [](const JOYSTICK::CDriverPrimitive& primitive) { + return CPeripheralAddonTranslator::TranslatePrimitive(primitive); + }); + return ret; +} + +HAT_DIRECTION CPeripheralAddonTranslator::TranslateHatDirection(JOYSTICK_DRIVER_HAT_DIRECTION dir) +{ + switch (dir) + { + case JOYSTICK_DRIVER_HAT_LEFT: + return HAT_DIRECTION::LEFT; + case JOYSTICK_DRIVER_HAT_RIGHT: + return HAT_DIRECTION::RIGHT; + case JOYSTICK_DRIVER_HAT_UP: + return HAT_DIRECTION::UP; + case JOYSTICK_DRIVER_HAT_DOWN: + return HAT_DIRECTION::DOWN; + default: + break; + } + return HAT_DIRECTION::NONE; +} + +JOYSTICK_DRIVER_HAT_DIRECTION CPeripheralAddonTranslator::TranslateHatDirection(HAT_DIRECTION dir) +{ + switch (dir) + { + case HAT_DIRECTION::UP: + return JOYSTICK_DRIVER_HAT_UP; + case HAT_DIRECTION::DOWN: + return JOYSTICK_DRIVER_HAT_DOWN; + case HAT_DIRECTION::RIGHT: + return JOYSTICK_DRIVER_HAT_RIGHT; + case HAT_DIRECTION::LEFT: + return JOYSTICK_DRIVER_HAT_LEFT; + default: + break; + } + return JOYSTICK_DRIVER_HAT_UNKNOWN; +} + +HAT_STATE CPeripheralAddonTranslator::TranslateHatState(JOYSTICK_STATE_HAT state) +{ + HAT_STATE translatedState = HAT_STATE::NONE; + + if (state & JOYSTICK_STATE_HAT_UP) + translatedState |= HAT_STATE::UP; + if (state & JOYSTICK_STATE_HAT_DOWN) + translatedState |= HAT_STATE::DOWN; + if (state & JOYSTICK_STATE_HAT_RIGHT) + translatedState |= HAT_STATE::RIGHT; + if (state & JOYSTICK_STATE_HAT_LEFT) + translatedState |= HAT_STATE::LEFT; + + return translatedState; +} + +SEMIAXIS_DIRECTION CPeripheralAddonTranslator::TranslateSemiAxisDirection( + JOYSTICK_DRIVER_SEMIAXIS_DIRECTION dir) +{ + switch (dir) + { + case JOYSTICK_DRIVER_SEMIAXIS_POSITIVE: + return SEMIAXIS_DIRECTION::POSITIVE; + case JOYSTICK_DRIVER_SEMIAXIS_NEGATIVE: + return SEMIAXIS_DIRECTION::NEGATIVE; + default: + break; + } + return SEMIAXIS_DIRECTION::ZERO; +} + +JOYSTICK_DRIVER_SEMIAXIS_DIRECTION CPeripheralAddonTranslator::TranslateSemiAxisDirection( + SEMIAXIS_DIRECTION dir) +{ + switch (dir) + { + case SEMIAXIS_DIRECTION::POSITIVE: + return JOYSTICK_DRIVER_SEMIAXIS_POSITIVE; + case SEMIAXIS_DIRECTION::NEGATIVE: + return JOYSTICK_DRIVER_SEMIAXIS_NEGATIVE; + default: + break; + } + return JOYSTICK_DRIVER_SEMIAXIS_UNKNOWN; +} + +MOUSE::BUTTON_ID CPeripheralAddonTranslator::TranslateMouseButton( + JOYSTICK_DRIVER_MOUSE_INDEX button) +{ + switch (button) + { + case JOYSTICK_DRIVER_MOUSE_INDEX_LEFT: + return MOUSE::BUTTON_ID::LEFT; + case JOYSTICK_DRIVER_MOUSE_INDEX_RIGHT: + return MOUSE::BUTTON_ID::RIGHT; + case JOYSTICK_DRIVER_MOUSE_INDEX_MIDDLE: + return MOUSE::BUTTON_ID::MIDDLE; + case JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON4: + return MOUSE::BUTTON_ID::BUTTON4; + case JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON5: + return MOUSE::BUTTON_ID::BUTTON5; + case JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_UP: + return MOUSE::BUTTON_ID::WHEEL_UP; + case JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_DOWN: + return MOUSE::BUTTON_ID::WHEEL_DOWN; + case JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_LEFT: + return MOUSE::BUTTON_ID::HORIZ_WHEEL_LEFT; + case JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_RIGHT: + return MOUSE::BUTTON_ID::HORIZ_WHEEL_RIGHT; + default: + break; + } + + return MOUSE::BUTTON_ID::UNKNOWN; +} + +JOYSTICK_DRIVER_MOUSE_INDEX CPeripheralAddonTranslator::TranslateMouseButton( + MOUSE::BUTTON_ID button) +{ + switch (button) + { + case MOUSE::BUTTON_ID::LEFT: + return JOYSTICK_DRIVER_MOUSE_INDEX_LEFT; + case MOUSE::BUTTON_ID::RIGHT: + return JOYSTICK_DRIVER_MOUSE_INDEX_RIGHT; + case MOUSE::BUTTON_ID::MIDDLE: + return JOYSTICK_DRIVER_MOUSE_INDEX_MIDDLE; + case MOUSE::BUTTON_ID::BUTTON4: + return JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON4; + case MOUSE::BUTTON_ID::BUTTON5: + return JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON5; + case MOUSE::BUTTON_ID::WHEEL_UP: + return JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_UP; + case MOUSE::BUTTON_ID::WHEEL_DOWN: + return JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_DOWN; + case MOUSE::BUTTON_ID::HORIZ_WHEEL_LEFT: + return JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_LEFT; + case MOUSE::BUTTON_ID::HORIZ_WHEEL_RIGHT: + return JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_RIGHT; + default: + break; + } + + return JOYSTICK_DRIVER_MOUSE_INDEX_UNKNOWN; +} + +RELATIVE_POINTER_DIRECTION CPeripheralAddonTranslator::TranslateRelPointerDirection( + JOYSTICK_DRIVER_RELPOINTER_DIRECTION dir) +{ + switch (dir) + { + case JOYSTICK_DRIVER_RELPOINTER_LEFT: + return RELATIVE_POINTER_DIRECTION::LEFT; + case JOYSTICK_DRIVER_RELPOINTER_RIGHT: + return RELATIVE_POINTER_DIRECTION::RIGHT; + case JOYSTICK_DRIVER_RELPOINTER_UP: + return RELATIVE_POINTER_DIRECTION::UP; + case JOYSTICK_DRIVER_RELPOINTER_DOWN: + return RELATIVE_POINTER_DIRECTION::DOWN; + default: + break; + } + + return RELATIVE_POINTER_DIRECTION::NONE; +} + +JOYSTICK_DRIVER_RELPOINTER_DIRECTION CPeripheralAddonTranslator::TranslateRelPointerDirection( + RELATIVE_POINTER_DIRECTION dir) +{ + switch (dir) + { + case RELATIVE_POINTER_DIRECTION::UP: + return JOYSTICK_DRIVER_RELPOINTER_UP; + case RELATIVE_POINTER_DIRECTION::DOWN: + return JOYSTICK_DRIVER_RELPOINTER_DOWN; + case RELATIVE_POINTER_DIRECTION::RIGHT: + return JOYSTICK_DRIVER_RELPOINTER_RIGHT; + case RELATIVE_POINTER_DIRECTION::LEFT: + return JOYSTICK_DRIVER_RELPOINTER_LEFT; + default: + break; + } + return JOYSTICK_DRIVER_RELPOINTER_UNKNOWN; +} + +JOYSTICK::FEATURE_TYPE CPeripheralAddonTranslator::TranslateFeatureType(JOYSTICK_FEATURE_TYPE type) +{ + switch (type) + { + case JOYSTICK_FEATURE_TYPE_SCALAR: + return JOYSTICK::FEATURE_TYPE::SCALAR; + case JOYSTICK_FEATURE_TYPE_ANALOG_STICK: + return JOYSTICK::FEATURE_TYPE::ANALOG_STICK; + case JOYSTICK_FEATURE_TYPE_ACCELEROMETER: + return JOYSTICK::FEATURE_TYPE::ACCELEROMETER; + case JOYSTICK_FEATURE_TYPE_MOTOR: + return JOYSTICK::FEATURE_TYPE::MOTOR; + case JOYSTICK_FEATURE_TYPE_RELPOINTER: + return JOYSTICK::FEATURE_TYPE::RELPOINTER; + case JOYSTICK_FEATURE_TYPE_ABSPOINTER: + return JOYSTICK::FEATURE_TYPE::ABSPOINTER; + case JOYSTICK_FEATURE_TYPE_WHEEL: + return JOYSTICK::FEATURE_TYPE::WHEEL; + case JOYSTICK_FEATURE_TYPE_THROTTLE: + return JOYSTICK::FEATURE_TYPE::THROTTLE; + case JOYSTICK_FEATURE_TYPE_KEY: + return JOYSTICK::FEATURE_TYPE::KEY; + default: + break; + } + return JOYSTICK::FEATURE_TYPE::UNKNOWN; +} + +JOYSTICK_FEATURE_TYPE CPeripheralAddonTranslator::TranslateFeatureType(JOYSTICK::FEATURE_TYPE type) +{ + switch (type) + { + case JOYSTICK::FEATURE_TYPE::SCALAR: + return JOYSTICK_FEATURE_TYPE_SCALAR; + case JOYSTICK::FEATURE_TYPE::ANALOG_STICK: + return JOYSTICK_FEATURE_TYPE_ANALOG_STICK; + case JOYSTICK::FEATURE_TYPE::ACCELEROMETER: + return JOYSTICK_FEATURE_TYPE_ACCELEROMETER; + case JOYSTICK::FEATURE_TYPE::MOTOR: + return JOYSTICK_FEATURE_TYPE_MOTOR; + case JOYSTICK::FEATURE_TYPE::RELPOINTER: + return JOYSTICK_FEATURE_TYPE_RELPOINTER; + case JOYSTICK::FEATURE_TYPE::ABSPOINTER: + return JOYSTICK_FEATURE_TYPE_ABSPOINTER; + case JOYSTICK::FEATURE_TYPE::WHEEL: + return JOYSTICK_FEATURE_TYPE_WHEEL; + case JOYSTICK::FEATURE_TYPE::THROTTLE: + return JOYSTICK_FEATURE_TYPE_THROTTLE; + case JOYSTICK::FEATURE_TYPE::KEY: + return JOYSTICK_FEATURE_TYPE_KEY; + default: + break; + } + return JOYSTICK_FEATURE_TYPE_UNKNOWN; +} + +kodi::addon::DriverPrimitive CPeripheralAddonTranslator::Opposite( + const kodi::addon::DriverPrimitive& primitive) +{ + return kodi::addon::DriverPrimitive(primitive.DriverIndex(), primitive.Center() * -1, + primitive.SemiAxisDirection() * -1, primitive.Range()); +} diff --git a/xbmc/peripherals/addons/PeripheralAddonTranslator.h b/xbmc/peripherals/addons/PeripheralAddonTranslator.h new file mode 100644 index 0000000..308d62c --- /dev/null +++ b/xbmc/peripherals/addons/PeripheralAddonTranslator.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015-2018 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 "addons/kodi-dev-kit/include/kodi/addon-instance/Peripheral.h" +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/JoystickTypes.h" +#include "input/mouse/MouseTypes.h" +#include "peripherals/PeripheralTypes.h" + +#include <vector> + +namespace PERIPHERALS +{ +class CPeripheralAddonTranslator +{ +public: + static const char* TranslateError(PERIPHERAL_ERROR error); + + static PeripheralType TranslateType(PERIPHERAL_TYPE type); + static PERIPHERAL_TYPE TranslateType(PeripheralType type); + + static KODI::JOYSTICK::CDriverPrimitive TranslatePrimitive( + const kodi::addon::DriverPrimitive& primitive); + static kodi::addon::DriverPrimitive TranslatePrimitive( + const KODI::JOYSTICK::CDriverPrimitive& primitive); + + static std::vector<KODI::JOYSTICK::CDriverPrimitive> TranslatePrimitives( + const std::vector<kodi::addon::DriverPrimitive>& primitives); + static std::vector<kodi::addon::DriverPrimitive> TranslatePrimitives( + const std::vector<KODI::JOYSTICK::CDriverPrimitive>& primitives); + + static KODI::JOYSTICK::HAT_DIRECTION TranslateHatDirection(JOYSTICK_DRIVER_HAT_DIRECTION dir); + static JOYSTICK_DRIVER_HAT_DIRECTION TranslateHatDirection(KODI::JOYSTICK::HAT_DIRECTION dir); + + static KODI::JOYSTICK::HAT_STATE TranslateHatState(JOYSTICK_STATE_HAT state); + + static KODI::JOYSTICK::SEMIAXIS_DIRECTION TranslateSemiAxisDirection( + JOYSTICK_DRIVER_SEMIAXIS_DIRECTION dir); + static JOYSTICK_DRIVER_SEMIAXIS_DIRECTION TranslateSemiAxisDirection( + KODI::JOYSTICK::SEMIAXIS_DIRECTION dir); + + static KODI::MOUSE::BUTTON_ID TranslateMouseButton(JOYSTICK_DRIVER_MOUSE_INDEX button); + static JOYSTICK_DRIVER_MOUSE_INDEX TranslateMouseButton(KODI::MOUSE::BUTTON_ID button); + + static KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION TranslateRelPointerDirection( + JOYSTICK_DRIVER_RELPOINTER_DIRECTION dir); + static JOYSTICK_DRIVER_RELPOINTER_DIRECTION TranslateRelPointerDirection( + KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION dir); + + static KODI::JOYSTICK::FEATURE_TYPE TranslateFeatureType(JOYSTICK_FEATURE_TYPE type); + static JOYSTICK_FEATURE_TYPE TranslateFeatureType(KODI::JOYSTICK::FEATURE_TYPE type); + + static kodi::addon::DriverPrimitive Opposite(const kodi::addon::DriverPrimitive& semiaxis); +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/bus/CMakeLists.txt b/xbmc/peripherals/bus/CMakeLists.txt new file mode 100644 index 0000000..81c80c5 --- /dev/null +++ b/xbmc/peripherals/bus/CMakeLists.txt @@ -0,0 +1,6 @@ +set(SOURCES PeripheralBus.cpp) + +set(HEADERS PeripheralBus.h + PeripheralBusUSB.h) + +core_add_library(peripherals_bus) diff --git a/xbmc/peripherals/bus/PeripheralBus.cpp b/xbmc/peripherals/bus/PeripheralBus.cpp new file mode 100644 index 0000000..59a4c61 --- /dev/null +++ b/xbmc/peripherals/bus/PeripheralBus.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralBus.h" + +#include "FileItem.h" +#include "guilib/LocalizeStrings.h" +#include "peripherals/Peripherals.h" +#include "peripherals/devices/Peripheral.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <mutex> + +using namespace PERIPHERALS; +using namespace std::chrono_literals; + +namespace +{ +constexpr auto PERIPHERAL_DEFAULT_RESCAN_INTERVAL = 5000ms; +} + +CPeripheralBus::CPeripheralBus(const std::string& threadname, + CPeripherals& manager, + PeripheralBusType type) + : CThread(threadname.c_str()), + m_iRescanTime(PERIPHERAL_DEFAULT_RESCAN_INTERVAL), + m_bNeedsPolling(true), + m_manager(manager), + m_type(type), + m_triggerEvent(true) +{ +} + +bool CPeripheralBus::InitializeProperties(CPeripheral& peripheral) +{ + return true; +} + +void CPeripheralBus::OnDeviceAdded(const std::string& strLocation) +{ + ScanForDevices(); +} + +void CPeripheralBus::OnDeviceChanged(const std::string& strLocation) +{ + ScanForDevices(); +} + +void CPeripheralBus::OnDeviceRemoved(const std::string& strLocation) +{ + ScanForDevices(); +} + +void CPeripheralBus::Clear(void) +{ + if (m_bNeedsPolling) + { + StopThread(false); + m_triggerEvent.Set(); + StopThread(true); + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + + m_peripherals.clear(); +} + +void CPeripheralBus::UnregisterRemovedDevices(const PeripheralScanResults& results) +{ + PeripheralVector removedPeripherals; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + for (int iDevicePtr = (int)m_peripherals.size() - 1; iDevicePtr >= 0; iDevicePtr--) + { + const PeripheralPtr& peripheral = m_peripherals.at(iDevicePtr); + PeripheralScanResult updatedDevice(m_type); + if (!results.GetDeviceOnLocation(peripheral->Location(), &updatedDevice) || + *peripheral != updatedDevice) + { + /* device removed */ + removedPeripherals.push_back(peripheral); + m_peripherals.erase(m_peripherals.begin() + iDevicePtr); + } + } + } + + for (auto& peripheral : removedPeripherals) + { + std::vector<PeripheralFeature> features; + peripheral->GetFeatures(features); + bool peripheralHasFeatures = + features.size() > 1 || (features.size() == 1 && features.at(0) != FEATURE_UNKNOWN); + if (peripheral->Type() != PERIPHERAL_UNKNOWN || peripheralHasFeatures) + { + CLog::Log(LOGINFO, "{} - device removed from {}/{}: {} ({}:{})", __FUNCTION__, + PeripheralTypeTranslator::TypeToString(peripheral->Type()), peripheral->Location(), + peripheral->DeviceName(), peripheral->VendorIdAsString(), + peripheral->ProductIdAsString()); + peripheral->OnDeviceRemoved(); + } + + m_manager.OnDeviceDeleted(*this, *peripheral); + } +} + +void CPeripheralBus::RegisterNewDevices(const PeripheralScanResults& results) +{ + for (unsigned int iResultPtr = 0; iResultPtr < results.m_results.size(); iResultPtr++) + { + const PeripheralScanResult& result = results.m_results.at(iResultPtr); + if (!HasPeripheral(result.m_strLocation)) + m_manager.CreatePeripheral(*this, result); + } +} + +bool CPeripheralBus::ScanForDevices(void) +{ + bool bReturn(false); + + PeripheralScanResults results; + if (PerformDeviceScan(results)) + { + UnregisterRemovedDevices(results); + RegisterNewDevices(results); + + m_manager.NotifyObservers(ObservableMessagePeripheralsChanged); + + bReturn = true; + } + + return bReturn; +} + +bool CPeripheralBus::HasFeature(const PeripheralFeature feature) const +{ + bool bReturn(false); + std::unique_lock<CCriticalSection> lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + { + if (m_peripherals.at(iPeripheralPtr)->HasFeature(feature)) + { + bReturn = true; + break; + } + } + return bReturn; +} + +void CPeripheralBus::GetFeatures(std::vector<PeripheralFeature>& features) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + m_peripherals.at(iPeripheralPtr)->GetFeatures(features); +} + +PeripheralPtr CPeripheralBus::GetPeripheral(const std::string& strLocation) const +{ + PeripheralPtr result; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto& peripheral : m_peripherals) + { + if (peripheral->Location() == strLocation) + { + result = peripheral; + break; + } + } + return result; +} + +unsigned int CPeripheralBus::GetPeripheralsWithFeature(PeripheralVector& results, + const PeripheralFeature feature) const +{ + unsigned int iReturn = 0; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto& peripheral : m_peripherals) + { + if (peripheral->HasFeature(feature)) + { + results.push_back(peripheral); + ++iReturn; + } + } + + return iReturn; +} + +unsigned int CPeripheralBus::GetNumberOfPeripheralsWithId(const int iVendorId, + const int iProductId) const +{ + unsigned int iReturn = 0; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& peripheral : m_peripherals) + { + if (peripheral->VendorId() == iVendorId && peripheral->ProductId() == iProductId) + iReturn++; + } + + return iReturn; +} + +void CPeripheralBus::Process(void) +{ + while (!m_bStop) + { + m_triggerEvent.Reset(); + + if (!ScanForDevices()) + break; + + // depending on bus implementation + // needsPolling can be set properly + // only after initial scan. + // if this is the case, bail out. + if (!m_bNeedsPolling) + break; + + if (!m_bStop) + m_triggerEvent.Wait(m_iRescanTime); + } +} + +void CPeripheralBus::Initialise(void) +{ + bool bNeedsPolling = false; + + if (!IsRunning()) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bNeedsPolling = m_bNeedsPolling; + } + + if (bNeedsPolling) + { + m_triggerEvent.Reset(); + Create(); + SetPriority(ThreadPriority::BELOW_NORMAL); + } +} + +void CPeripheralBus::Register(const PeripheralPtr& peripheral) +{ + if (!peripheral) + return; + + bool bPeripheralAdded = false; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!HasPeripheral(peripheral->Location())) + { + m_peripherals.push_back(peripheral); + bPeripheralAdded = true; + } + } + + if (bPeripheralAdded) + { + CLog::Log(LOGINFO, "{} - new {} device registered on {}->{}: {} ({}:{})", __FUNCTION__, + PeripheralTypeTranslator::TypeToString(peripheral->Type()), + PeripheralTypeTranslator::BusTypeToString(m_type), peripheral->Location(), + peripheral->DeviceName(), peripheral->VendorIdAsString(), + peripheral->ProductIdAsString()); + m_manager.OnDeviceAdded(*this, *peripheral); + } +} + +void CPeripheralBus::TriggerDeviceScan(void) +{ + bool bNeedsPolling; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bNeedsPolling = m_bNeedsPolling; + } + + if (bNeedsPolling) + m_triggerEvent.Set(); + else + ScanForDevices(); +} + +bool CPeripheralBus::HasPeripheral(const std::string& strLocation) const +{ + return (GetPeripheral(strLocation) != NULL); +} + +void CPeripheralBus::GetDirectory(const std::string& strPath, CFileItemList& items) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& peripheral : m_peripherals) + { + if (peripheral->IsHidden()) + continue; + + CFileItemPtr peripheralFile(new CFileItem(peripheral->DeviceName())); + peripheralFile->SetPath(peripheral->FileLocation()); + peripheralFile->SetProperty("vendor", peripheral->VendorIdAsString()); + peripheralFile->SetProperty("product", peripheral->ProductIdAsString()); + peripheralFile->SetProperty( + "bus", PeripheralTypeTranslator::BusTypeToString(peripheral->GetBusType())); + peripheralFile->SetProperty("location", peripheral->Location()); + peripheralFile->SetProperty("class", + PeripheralTypeTranslator::TypeToString(peripheral->Type())); + + std::string strVersion(peripheral->GetVersionInfo()); + if (strVersion.empty()) + strVersion = g_localizeStrings.Get(13205); + + std::string strDetails = StringUtils::Format("{} {}", g_localizeStrings.Get(24051), strVersion); + if (peripheral->GetBusType() == PERIPHERAL_BUS_CEC && !peripheral->GetSettingBool("enabled")) + strDetails = + StringUtils::Format("{}: {}", g_localizeStrings.Get(126), g_localizeStrings.Get(13106)); + + peripheralFile->SetProperty("version", strVersion); + peripheralFile->SetLabel2(strDetails); + peripheralFile->SetArt("icon", peripheral->GetIcon()); + + items.Add(peripheralFile); + } +} + +PeripheralPtr CPeripheralBus::GetByPath(const std::string& strPath) const +{ + PeripheralPtr result; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (auto& peripheral : m_peripherals) + { + if (StringUtils::EqualsNoCase(strPath, peripheral->FileLocation())) + { + result = peripheral; + break; + } + } + + return result; +} + +unsigned int CPeripheralBus::GetNumberOfPeripherals() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return static_cast<unsigned int>(m_peripherals.size()); +} diff --git a/xbmc/peripherals/bus/PeripheralBus.h b/xbmc/peripherals/bus/PeripheralBus.h new file mode 100644 index 0000000..6b67a7f --- /dev/null +++ b/xbmc/peripherals/bus/PeripheralBus.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2005-2018 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 "peripherals/PeripheralTypes.h" +#include "threads/Thread.h" + +#include <memory> +#include <mutex> +#include <vector> + +class CFileItemList; + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +} // namespace JOYSTICK +} // namespace KODI + +namespace PERIPHERALS +{ +class CPeripheral; +class CPeripherals; + +/*! + * @class CPeripheralBus + * This represents a bus on the system. By default, this bus instance will scan for changes every 5 + * seconds. If this bus only has to be updated after a notification sent by the system, set + * m_bNeedsPolling to false in the constructor, and implement the OnDeviceAdded(), OnDeviceChanged() + * and OnDeviceRemoved() methods. + * + * The PerformDeviceScan() method has to be implemented by each specific bus implementation. + */ +class CPeripheralBus : protected CThread +{ +public: + CPeripheralBus(const std::string& threadname, CPeripherals& manager, PeripheralBusType type); + ~CPeripheralBus(void) override { Clear(); } + + /*! + * @return The bus type + */ + PeripheralBusType Type(void) const { return m_type; } + + /*! + * @return True if this bus needs to be polled for changes, false if this bus performs updates via + * callbacks + */ + bool NeedsPolling(void) const + { + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bNeedsPolling; + } + + /*! + * \brief Initialize the properties of a peripheral with a known location + */ + virtual bool InitializeProperties(CPeripheral& peripheral); + + /*! + * \brief Initialize a joystick buttonmap, if possible + */ + virtual bool InitializeButtonMap(const CPeripheral& peripheral, + KODI::JOYSTICK::IButtonMap& buttonMap) const + { + return false; + } + + /*! + * @brief Get the instance of the peripheral at the given location. + * @param strLocation The location. + * @return The peripheral or NULL if it wasn't found. + */ + virtual PeripheralPtr GetPeripheral(const std::string& strLocation) const; + + /*! + * @brief Check whether a peripheral is present at the given location. + * @param strLocation The location. + * @return True when a peripheral was found, false otherwise. + */ + virtual bool HasPeripheral(const std::string& strLocation) const; + + /*! + * @brief Check if the bus supports the given feature + * @param feature The feature to check for + * @return True if the bus supports the feature, false otherwise + */ + virtual bool SupportsFeature(PeripheralFeature feature) const { return false; } + + /*! + * @brief Get all peripheral instances that have the given feature. + * @param results The list of results. + * @param feature The feature to search for. + * @return The number of devices that have been found. + */ + virtual unsigned int GetPeripheralsWithFeature(PeripheralVector& results, + const PeripheralFeature feature) const; + + virtual unsigned int GetNumberOfPeripherals() const; + virtual unsigned int GetNumberOfPeripheralsWithId(const int iVendorId, + const int iProductId) const; + + /*! + * @brief Get all features that are supported by devices on this bus. + * @param features All features. + */ + virtual void GetFeatures(std::vector<PeripheralFeature>& features) const; + + /*! + * @brief Check whether there is at least one device present with the given feature. + * @param feature The feature to check for. + * @return True when at least one device was found with this feature, false otherwise. + */ + virtual bool HasFeature(const PeripheralFeature feature) const; + + /*! + * @brief Callback method for when a device has been added. Will perform a device scan. + * @param strLocation The location of the device that has been added. + */ + virtual void OnDeviceAdded(const std::string& strLocation); + + /*! + * @brief Callback method for when a device has been changed. Will perform a device scan. + * @param strLocation The location of the device that has been changed. + */ + virtual void OnDeviceChanged(const std::string& strLocation); + + /*! + * @brief Callback method for when a device has been removed. Will perform a device scan. + * @param strLocation The location of the device that has been removed. + */ + virtual void OnDeviceRemoved(const std::string& strLocation); + + /*! + * @brief Initialise this bus and start a polling thread if this bus needs polling. + */ + virtual void Initialise(void); + + /*! + * @brief Stop the polling thread and clear all known devices on this bus. + */ + virtual void Clear(void); + + /*! + * @brief Scan for devices. + */ + virtual void TriggerDeviceScan(void); + + /*! + * @brief Get all fileitems for a path. + * @param strPath The path to the directory to get the items from. + * @param items The item list. + */ + virtual void GetDirectory(const std::string& strPath, CFileItemList& items) const; + + /*! + * @brief Get the instance of a peripheral given it's path. + * @param strPath The path to the peripheral. + * @return The peripheral or NULL if it wasn't found. + */ + virtual PeripheralPtr GetByPath(const std::string& strPath) const; + + /*! + * @brief Register a new peripheral on this bus. + * @param peripheral The peripheral to register. + */ + virtual void Register(const PeripheralPtr& peripheral); + + virtual bool FindComPort(std::string& strLocation) { return false; } + + /*! + * \brief Poll for events + */ + virtual void ProcessEvents(void) {} + + /*! + * \brief Initialize button mapping + * \return True if button mapping is enabled for this bus + */ + virtual void EnableButtonMapping() {} + + /*! + * \brief Power off the specified device + * \param strLocation The device's location + */ + virtual void PowerOff(const std::string& strLocation) {} + +protected: + void Process(void) override; + virtual bool ScanForDevices(void); + virtual void UnregisterRemovedDevices(const PeripheralScanResults& results); + virtual void RegisterNewDevices(const PeripheralScanResults& results); + + /*! + * @brief Scan for devices on this bus and add them to the results list. This will have to be + * implemented for each bus. + * @param results The result list. + * @return True when the scan was successful, false otherwise. + */ + virtual bool PerformDeviceScan(PeripheralScanResults& results) = 0; + + PeripheralVector m_peripherals; + std::chrono::milliseconds m_iRescanTime; + bool m_bNeedsPolling; /*!< true when this bus needs to be polled for new devices, false when it + uses callbacks to notify this bus of changed */ + CPeripherals& m_manager; + const PeripheralBusType m_type; + mutable CCriticalSection m_critSection; + CEvent m_triggerEvent; +}; +using PeripheralBusPtr = std::shared_ptr<CPeripheralBus>; +using PeripheralBusVector = std::vector<PeripheralBusPtr>; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/bus/PeripheralBusUSB.h b/xbmc/peripherals/bus/PeripheralBusUSB.h new file mode 100644 index 0000000..dd32ff6 --- /dev/null +++ b/xbmc/peripherals/bus/PeripheralBusUSB.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2018 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 + +#if defined(TARGET_WINDOWS_DESKTOP) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "platform/win32/peripherals/PeripheralBusUSB.h" +#elif defined(TARGET_WINDOWS_STORE) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "platform/win10/peripherals/PeripheralBusUSB.h" +#elif defined(TARGET_LINUX) && defined(HAVE_LIBUDEV) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "platform/linux/peripherals/PeripheralBusUSBLibUdev.h" +#elif defined(TARGET_LINUX) && defined(HAVE_LIBUSB) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "platform/linux/peripherals/PeripheralBusUSBLibUSB.h" +#elif defined(TARGET_FREEBSD) && defined(HAVE_LIBUSB) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "platform/linux/peripherals/PeripheralBusUSBLibUSB.h" +#elif defined(TARGET_DARWIN_OSX) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "platform/darwin/osx/peripherals/PeripheralBusUSB.h" +#endif diff --git a/xbmc/peripherals/bus/virtual/CMakeLists.txt b/xbmc/peripherals/bus/virtual/CMakeLists.txt new file mode 100644 index 0000000..bf59695 --- /dev/null +++ b/xbmc/peripherals/bus/virtual/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SOURCES PeripheralBusAddon.cpp + PeripheralBusApplication.cpp) + +set(HEADERS PeripheralBusAddon.h + PeripheralBusApplication.h) + +if(CEC_FOUND) + list(APPEND SOURCES PeripheralBusCEC.cpp) + list(APPEND HEADERS PeripheralBusCEC.h) +endif() + +core_add_library(peripheral_bus_virtual) diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp new file mode 100644 index 0000000..2162c45 --- /dev/null +++ b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2014-2018 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 "PeripheralBusAddon.h" + +#include "ServiceBroker.h" +#include "addons/AddonEvents.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "messaging/helpers/DialogHelper.h" +#include "peripherals/Peripherals.h" +#include "peripherals/addons/PeripheralAddon.h" +#include "peripherals/devices/PeripheralJoystick.h" +#include "threads/SingleLock.h" +#include "utils/log.h" + +#include <algorithm> +#include <memory> +#include <mutex> + +using namespace KODI; +using namespace PERIPHERALS; + +CPeripheralBusAddon::CPeripheralBusAddon(CPeripherals& manager) + : CPeripheralBus("PeripBusAddon", manager, PERIPHERAL_BUS_ADDON) +{ + using namespace ADDON; + + CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CPeripheralBusAddon::OnEvent); + + UpdateAddons(); +} + +CPeripheralBusAddon::~CPeripheralBusAddon() +{ + using namespace ADDON; + + CServiceBroker::GetAddonMgr().Events().Unsubscribe(this); + + // stop everything before destroying any (loaded) addons + Clear(); + + // destroy any (loaded) addons + for (const auto& addon : m_addons) + addon->DestroyAddon(); + + m_failedAddons.clear(); + m_addons.clear(); +} + +bool CPeripheralBusAddon::GetAddonWithButtonMap(PeripheralAddonPtr& addon) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + auto it = std::find_if(m_addons.begin(), m_addons.end(), + [](const PeripheralAddonPtr& addon) { return addon->HasButtonMaps(); }); + + if (it != m_addons.end()) + { + addon = *it; + return true; + } + + return false; +} + +bool CPeripheralBusAddon::GetAddonWithButtonMap(const CPeripheral* device, + PeripheralAddonPtr& addon) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + // If device is from an add-on, try to use that add-on + if (device && device->GetBusType() == PERIPHERAL_BUS_ADDON) + { + PeripheralAddonPtr addonWithButtonMap; + unsigned int index; + if (SplitLocation(device->Location(), addonWithButtonMap, index)) + { + if (addonWithButtonMap->HasButtonMaps()) + addon = std::move(addonWithButtonMap); + else + CLog::Log(LOGDEBUG, "Add-on {} doesn't provide button maps for its controllers", + addonWithButtonMap->ID()); + } + } + + if (!addon) + { + auto it = std::find_if(m_addons.begin(), m_addons.end(), + [](const PeripheralAddonPtr& addon) { return addon->HasButtonMaps(); }); + + if (it != m_addons.end()) + addon = *it; + } + + return addon.get() != nullptr; +} + +bool CPeripheralBusAddon::PerformDeviceScan(PeripheralScanResults& results) +{ + PeripheralAddonVector addons; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + addons = m_addons; + } + + for (const auto& addon : addons) + addon->PerformDeviceScan(results); + + // Scan during bus initialization must return true or bus gets deleted + return true; +} + +bool CPeripheralBusAddon::InitializeProperties(CPeripheral& peripheral) +{ + if (!CPeripheralBus::InitializeProperties(peripheral)) + return false; + + bool bSuccess = false; + + PeripheralAddonPtr addon; + unsigned int index; + + if (SplitLocation(peripheral.Location(), addon, index)) + { + switch (peripheral.Type()) + { + case PERIPHERAL_JOYSTICK: + bSuccess = + addon->GetJoystickProperties(index, static_cast<CPeripheralJoystick&>(peripheral)); + break; + + default: + break; + } + } + + return bSuccess; +} + +bool CPeripheralBusAddon::SendRumbleEvent(const std::string& strLocation, + unsigned int motorIndex, + float magnitude) +{ + bool bHandled = false; + + PeripheralAddonPtr addon; + unsigned int peripheralIndex; + if (SplitLocation(strLocation, addon, peripheralIndex)) + bHandled = addon->SendRumbleEvent(peripheralIndex, motorIndex, magnitude); + + return bHandled; +} + +void CPeripheralBusAddon::ProcessEvents(void) +{ + PeripheralAddonVector addons; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + addons = m_addons; + } + + for (const auto& addon : addons) + addon->ProcessEvents(); +} + +void CPeripheralBusAddon::EnableButtonMapping() +{ + using namespace ADDON; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + PeripheralAddonPtr dummy; + + if (!GetAddonWithButtonMap(dummy)) + { + std::vector<AddonInfoPtr> disabledAddons; + CServiceBroker::GetAddonMgr().GetDisabledAddonInfos(disabledAddons, AddonType::PERIPHERALDLL); + if (!disabledAddons.empty()) + PromptEnableAddons(disabledAddons); + } +} + +void CPeripheralBusAddon::PowerOff(const std::string& strLocation) +{ + PeripheralAddonPtr addon; + unsigned int peripheralIndex; + if (SplitLocation(strLocation, addon, peripheralIndex)) + addon->PowerOffJoystick(peripheralIndex); +} + +void CPeripheralBusAddon::UnregisterRemovedDevices(const PeripheralScanResults& results) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + PeripheralVector removedPeripherals; + + for (const auto& addon : m_addons) + addon->UnregisterRemovedDevices(results, removedPeripherals); + + for (const auto& peripheral : removedPeripherals) + m_manager.OnDeviceDeleted(*this, *peripheral); +} + +void CPeripheralBusAddon::Register(const PeripheralPtr& peripheral) +{ + if (!peripheral) + return; + + PeripheralAddonPtr addon; + unsigned int peripheralIndex; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (SplitLocation(peripheral->Location(), addon, peripheralIndex)) + { + if (addon->Register(peripheralIndex, peripheral)) + m_manager.OnDeviceAdded(*this, *peripheral); + } +} + +void CPeripheralBusAddon::GetFeatures(std::vector<PeripheralFeature>& features) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addon : m_addons) + addon->GetFeatures(features); +} + +bool CPeripheralBusAddon::HasFeature(const PeripheralFeature feature) const +{ + bool bReturn(false); + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addon : m_addons) + bReturn = bReturn || addon->HasFeature(feature); + return bReturn; +} + +PeripheralPtr CPeripheralBusAddon::GetPeripheral(const std::string& strLocation) const +{ + PeripheralPtr peripheral; + PeripheralAddonPtr addon; + unsigned int peripheralIndex; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (SplitLocation(strLocation, addon, peripheralIndex)) + peripheral = addon->GetPeripheral(peripheralIndex); + + return peripheral; +} + +PeripheralPtr CPeripheralBusAddon::GetByPath(const std::string& strPath) const +{ + PeripheralPtr result; + + std::unique_lock<CCriticalSection> lock(m_critSection); + + for (const auto& addon : m_addons) + { + PeripheralPtr peripheral = addon->GetByPath(strPath); + if (peripheral) + { + result = peripheral; + break; + } + } + + return result; +} + +bool CPeripheralBusAddon::SupportsFeature(PeripheralFeature feature) const +{ + bool bSupportsFeature = false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addon : m_addons) + bSupportsFeature |= addon->SupportsFeature(feature); + + return bSupportsFeature; +} + +unsigned int CPeripheralBusAddon::GetPeripheralsWithFeature(PeripheralVector& results, + const PeripheralFeature feature) const +{ + unsigned int iReturn = 0; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addon : m_addons) + iReturn += addon->GetPeripheralsWithFeature(results, feature); + return iReturn; +} + +unsigned int CPeripheralBusAddon::GetNumberOfPeripherals(void) const +{ + unsigned int iReturn = 0; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addon : m_addons) + iReturn += addon->GetNumberOfPeripherals(); + return iReturn; +} + +unsigned int CPeripheralBusAddon::GetNumberOfPeripheralsWithId(const int iVendorId, + const int iProductId) const +{ + unsigned int iReturn = 0; + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addon : m_addons) + iReturn += addon->GetNumberOfPeripheralsWithId(iVendorId, iProductId); + return iReturn; +} + +void CPeripheralBusAddon::GetDirectory(const std::string& strPath, CFileItemList& items) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& addon : m_addons) + addon->GetDirectory(strPath, items); +} + +void CPeripheralBusAddon::OnEvent(const ADDON::AddonEvent& event) +{ + if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) || + typeid(event) == typeid(ADDON::AddonEvents::ReInstalled)) + { + if (CServiceBroker::GetAddonMgr().HasType(event.addonId, ADDON::AddonType::PERIPHERALDLL)) + UpdateAddons(); + } + else if (typeid(event) == typeid(ADDON::AddonEvents::Disabled)) + { + if (CServiceBroker::GetAddonMgr().HasType(event.addonId, ADDON::AddonType::PERIPHERALDLL)) + UnRegisterAddon(event.addonId); + } + else if (typeid(event) == typeid(ADDON::AddonEvents::UnInstalled)) + { + UnRegisterAddon(event.addonId); + } +} + +bool CPeripheralBusAddon::SplitLocation(const std::string& strLocation, + PeripheralAddonPtr& addon, + unsigned int& peripheralIndex) const +{ + std::vector<std::string> parts = StringUtils::Split(strLocation, "/"); + if (parts.size() == 2) + { + addon.reset(); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + const std::string& strAddonId = parts[0]; + for (const auto& addonIt : m_addons) + { + if (addonIt->ID() == strAddonId) + { + addon = addonIt; + break; + } + } + + if (addon) + { + const char* strJoystickIndex = parts[1].c_str(); + char* p = NULL; + peripheralIndex = strtol(strJoystickIndex, &p, 10); + if (strJoystickIndex != p) + return true; + } + } + return false; +} + +void CPeripheralBusAddon::UpdateAddons(void) +{ + using namespace ADDON; + + auto GetPeripheralAddonID = [](const PeripheralAddonPtr& addon) { return addon->ID(); }; + auto GetAddonID = [](const AddonInfoPtr& addon) { return addon->ID(); }; + + std::set<std::string> currentIds; + std::set<std::string> newIds; + + std::set<std::string> added; + std::set<std::string> removed; + + // Get new add-ons + std::vector<AddonInfoPtr> newAddons; + CServiceBroker::GetAddonMgr().GetAddonInfos(newAddons, true, AddonType::PERIPHERALDLL); + std::transform(newAddons.begin(), newAddons.end(), std::inserter(newIds, newIds.end()), + GetAddonID); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + // Get current add-ons + std::transform(m_addons.begin(), m_addons.end(), std::inserter(currentIds, currentIds.end()), + GetPeripheralAddonID); + std::transform(m_failedAddons.begin(), m_failedAddons.end(), + std::inserter(currentIds, currentIds.end()), GetPeripheralAddonID); + + // Differences + std::set_difference(newIds.begin(), newIds.end(), currentIds.begin(), currentIds.end(), + std::inserter(added, added.end())); + std::set_difference(currentIds.begin(), currentIds.end(), newIds.begin(), newIds.end(), + std::inserter(removed, removed.end())); + + // Register new add-ons + for (const std::string& addonId : added) + { + CLog::Log(LOGDEBUG, "Add-on bus: Registering add-on {}", addonId); + + auto GetAddon = [&addonId](const AddonInfoPtr& addon) { return addon->ID() == addonId; }; + + auto it = std::find_if(newAddons.begin(), newAddons.end(), GetAddon); + if (it != newAddons.end()) + { + PeripheralAddonPtr newAddon = std::make_shared<CPeripheralAddon>(*it, m_manager); + if (newAddon) + { + bool bCreated; + + { + CSingleExit exit(m_critSection); + bCreated = newAddon->CreateAddon(); + } + + if (bCreated) + m_addons.emplace_back(std::move(newAddon)); + else + m_failedAddons.emplace_back(std::move(newAddon)); + } + } + } + + // Destroy removed add-ons + for (const std::string& addonId : removed) + { + UnRegisterAddon(addonId); + } +} + +void CPeripheralBusAddon::UnRegisterAddon(const std::string& addonId) +{ + PeripheralAddonPtr erased; + auto ErasePeripheralAddon = [&addonId, &erased](const PeripheralAddonPtr& addon) { + if (addon->ID() == addonId) + { + erased = addon; + return true; + } + return false; + }; + + m_addons.erase(std::remove_if(m_addons.begin(), m_addons.end(), ErasePeripheralAddon), + m_addons.end()); + if (!erased) + m_failedAddons.erase( + std::remove_if(m_failedAddons.begin(), m_failedAddons.end(), ErasePeripheralAddon), + m_failedAddons.end()); + + if (erased) + { + CLog::Log(LOGDEBUG, "Add-on bus: Unregistered add-on {}", addonId); + CSingleExit exit(m_critSection); + erased->DestroyAddon(); + } +} + +void CPeripheralBusAddon::PromptEnableAddons( + const std::vector<std::shared_ptr<ADDON::CAddonInfo>>& disabledAddons) +{ + using namespace ADDON; + using namespace MESSAGING::HELPERS; + + // True if the user confirms enabling the disabled peripheral add-on + bool bAccepted = false; + + auto itAddon = + std::find_if(disabledAddons.begin(), disabledAddons.end(), [](const AddonInfoPtr& addonInfo) { + return CPeripheralAddon::ProvidesJoysticks(addonInfo); + }); + + if (itAddon != disabledAddons.end()) + { + // "Unable to configure controllers" + // "Controller configuration depends on a disabled add-on. Would you like to enable it?" + bAccepted = + (ShowYesNoDialogLines(CVariant{35017}, CVariant{35018}) == DialogResponse::CHOICE_YES); + } + + if (bAccepted) + { + for (const auto& addonInfo : disabledAddons) + { + if (CPeripheralAddon::ProvidesJoysticks(addonInfo)) + CServiceBroker::GetAddonMgr().EnableAddon(addonInfo->ID()); + } + } +} diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusAddon.h b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.h new file mode 100644 index 0000000..696b189 --- /dev/null +++ b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014-2018 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 "peripherals/PeripheralTypes.h" +#include "peripherals/bus/PeripheralBus.h" + +#include <memory> +#include <string> +#include <vector> + +namespace ADDON +{ +struct AddonEvent; +class CAddonInfo; +} // namespace ADDON + +namespace PERIPHERALS +{ +class CPeripheralBusAddon : public CPeripheralBus +{ +public: + explicit CPeripheralBusAddon(CPeripherals& manager); + ~CPeripheralBusAddon(void) override; + + void UpdateAddons(void); + + /*! + * \brief Get peripheral add-on that can provide button maps + */ + bool GetAddonWithButtonMap(PeripheralAddonPtr& addon) const; + + /*! + * \brief Get peripheral add-on that can provide button maps for the given device + */ + bool GetAddonWithButtonMap(const CPeripheral* device, PeripheralAddonPtr& addon) const; + + /*! + * \brief Set the rumble state of a rumble motor + * + * \param strLocation The location of the peripheral with the motor + * \param motorIndex The index of the motor being rumbled + * \param magnitude The amount of vibration in the closed interval [0.0, 1.0] + * + * \return true if the rumble motor's state is set, false otherwise + * + * TODO: Move declaration to parent class + */ + bool SendRumbleEvent(const std::string& strLocation, unsigned int motorIndex, float magnitude); + + // Inherited from CPeripheralBus + bool InitializeProperties(CPeripheral& peripheral) override; + void Register(const PeripheralPtr& peripheral) override; + void GetFeatures(std::vector<PeripheralFeature>& features) const override; + bool HasFeature(const PeripheralFeature feature) const override; + PeripheralPtr GetPeripheral(const std::string& strLocation) const override; + PeripheralPtr GetByPath(const std::string& strPath) const override; + bool SupportsFeature(PeripheralFeature feature) const override; + unsigned int GetPeripheralsWithFeature(PeripheralVector& results, + const PeripheralFeature feature) const override; + unsigned int GetNumberOfPeripherals(void) const override; + unsigned int GetNumberOfPeripheralsWithId(const int iVendorId, + const int iProductId) const override; + void GetDirectory(const std::string& strPath, CFileItemList& items) const override; + void ProcessEvents(void) override; + void EnableButtonMapping() override; + void PowerOff(const std::string& strLocation) override; + + bool SplitLocation(const std::string& strLocation, + PeripheralAddonPtr& addon, + unsigned int& peripheralIndex) const; + +protected: + // Inherited from CPeripheralBus + bool PerformDeviceScan(PeripheralScanResults& results) override; + void UnregisterRemovedDevices(const PeripheralScanResults& results) override; + +private: + void OnEvent(const ADDON::AddonEvent& event); + void UnRegisterAddon(const std::string& addonId); + + void PromptEnableAddons(const std::vector<std::shared_ptr<ADDON::CAddonInfo>>& disabledAddons); + + PeripheralAddonVector m_addons; + PeripheralAddonVector m_failedAddons; +}; +using PeripheralBusAddonPtr = std::shared_ptr<CPeripheralBusAddon>; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp new file mode 100644 index 0000000..4dad03c --- /dev/null +++ b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015-2018 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 "PeripheralBusApplication.h" + +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" + +using namespace PERIPHERALS; + +CPeripheralBusApplication::CPeripheralBusApplication(CPeripherals& manager) + : CPeripheralBus("PeripBusApplication", manager, PERIPHERAL_BUS_APPLICATION) +{ + // Initialize CPeripheralBus + m_bNeedsPolling = false; +} + +void CPeripheralBusApplication::Initialise(void) +{ + CPeripheralBus::Initialise(); + TriggerDeviceScan(); +} + +bool CPeripheralBusApplication::PerformDeviceScan(PeripheralScanResults& results) +{ + { + PeripheralScanResult result(Type()); + result.m_type = PERIPHERAL_KEYBOARD; + result.m_strDeviceName = g_localizeStrings.Get(35150); // "Keyboard" + result.m_strLocation = PeripheralTypeTranslator::TypeToString(PERIPHERAL_KEYBOARD); + result.m_iVendorId = 0; + result.m_iProductId = 0; + result.m_mappedType = PERIPHERAL_KEYBOARD; + result.m_mappedBusType = Type(); + result.m_iSequence = 0; + + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + + bool bHasMouse = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_INPUT_ENABLEMOUSE); + + //! @todo Fix game clients to handle mouse disconnecting + //! For now mouse is always connected + bHasMouse = true; + + if (bHasMouse) + { + PeripheralScanResult result(Type()); + result.m_type = PERIPHERAL_MOUSE; + result.m_strDeviceName = g_localizeStrings.Get(35171); // "Mouse" + result.m_strLocation = PeripheralTypeTranslator::TypeToString(PERIPHERAL_MOUSE); + result.m_iVendorId = 0; + result.m_iProductId = 0; + result.m_mappedType = PERIPHERAL_MOUSE; + result.m_mappedBusType = Type(); + result.m_iSequence = 0; + + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + + return true; +} + +void CPeripheralBusApplication::GetDirectory(const std::string& strPath, CFileItemList& items) const +{ + // Don't list virtual devices in the GUI +} + +std::string CPeripheralBusApplication::MakeLocation(unsigned int controllerIndex) const +{ + return std::to_string(controllerIndex); +} diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusApplication.h b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.h new file mode 100644 index 0000000..4fcc40d --- /dev/null +++ b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015-2018 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 "peripherals/bus/PeripheralBus.h" + +namespace PERIPHERALS +{ +/*! + * @class CPeripheralBusApplication + * + * This exposes peripherals that exist logically at the application level, + * such as emulated joysticks. + */ +class CPeripheralBusApplication : public CPeripheralBus +{ +public: + explicit CPeripheralBusApplication(CPeripherals& manager); + ~CPeripheralBusApplication(void) override = default; + + // implementation of CPeripheralBus + void Initialise(void) override; + void GetDirectory(const std::string& strPath, CFileItemList& items) const override; + + /*! + * \brief Get the location for the specified controller index + */ + std::string MakeLocation(unsigned int controllerIndex) const; + +protected: + // implementation of CPeripheralBus + bool PerformDeviceScan(PeripheralScanResults& results) override; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp new file mode 100644 index 0000000..e119638 --- /dev/null +++ b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralBusCEC.h" + +#include <libcec/cec.h> + +using namespace PERIPHERALS; +using namespace CEC; + +CPeripheralBusCEC::CPeripheralBusCEC(CPeripherals& manager) + : CPeripheralBus("PeripBusCEC", manager, PERIPHERAL_BUS_CEC) +{ + m_cecAdapter = CECInitialise(&m_configuration); +} + +CPeripheralBusCEC::~CPeripheralBusCEC(void) +{ + if (m_cecAdapter) + CECDestroy(m_cecAdapter); +} + +bool CPeripheralBusCEC::PerformDeviceScan(PeripheralScanResults& results) +{ + cec_adapter_descriptor deviceList[10]; + int8_t iFound = m_cecAdapter->DetectAdapters(deviceList, 10, NULL, true); + + for (uint8_t iDevicePtr = 0; iDevicePtr < iFound; iDevicePtr++) + { + PeripheralScanResult result(m_type); + result.m_iVendorId = deviceList[iDevicePtr].iVendorId; + result.m_iProductId = deviceList[iDevicePtr].iProductId; + result.m_strLocation = deviceList[iDevicePtr].strComName; + result.m_type = PERIPHERAL_CEC; + + // override the bus type, so users don't have to reconfigure their adapters + switch (deviceList[iDevicePtr].adapterType) + { + case ADAPTERTYPE_P8_EXTERNAL: + case ADAPTERTYPE_P8_DAUGHTERBOARD: + result.m_mappedBusType = PERIPHERAL_BUS_USB; + break; + default: + break; + } + + result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId); + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + return true; +} diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusCEC.h b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.h new file mode 100644 index 0000000..9aa2643 --- /dev/null +++ b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 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 "peripherals/bus/PeripheralBus.h" + +// undefine macro isset, it collides with function in cectypes.h +#ifdef isset +#undef isset +#endif +#include <libcec/cectypes.h> + +namespace CEC +{ +class ICECAdapter; +} + +namespace PERIPHERALS +{ +class CPeripherals; + +class CPeripheralBusCEC : public CPeripheralBus +{ +public: + explicit CPeripheralBusCEC(CPeripherals& manager); + ~CPeripheralBusCEC(void) override; + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults& results) override; + +private: + CEC::ICECAdapter* m_cecAdapter; + CEC::libcec_configuration m_configuration; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/CMakeLists.txt b/xbmc/peripherals/devices/CMakeLists.txt new file mode 100644 index 0000000..57795f5 --- /dev/null +++ b/xbmc/peripherals/devices/CMakeLists.txt @@ -0,0 +1,30 @@ +set(SOURCES Peripheral.cpp + PeripheralBluetooth.cpp + PeripheralDisk.cpp + PeripheralHID.cpp + PeripheralImon.cpp + PeripheralJoystick.cpp + PeripheralKeyboard.cpp + PeripheralMouse.cpp + PeripheralNIC.cpp + PeripheralNyxboard.cpp + PeripheralTuner.cpp) + +set(HEADERS Peripheral.h + PeripheralBluetooth.h + PeripheralDisk.h + PeripheralHID.h + PeripheralImon.h + PeripheralJoystick.h + PeripheralKeyboard.h + PeripheralMouse.h + PeripheralNIC.h + PeripheralNyxboard.h + PeripheralTuner.h) + +if(CEC_FOUND) + list(APPEND SOURCES PeripheralCecAdapter.cpp) + list(APPEND HEADERS PeripheralCecAdapter.h) +endif() + +core_add_library(peripherals_devices) diff --git a/xbmc/peripherals/devices/Peripheral.cpp b/xbmc/peripherals/devices/Peripheral.cpp new file mode 100644 index 0000000..6c3f779 --- /dev/null +++ b/xbmc/peripherals/devices/Peripheral.cpp @@ -0,0 +1,771 @@ +/* + * Copyright (C) 2005-2018 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 "Peripheral.h" + +#include "Util.h" +#include "XBDateTime.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerLayout.h" +#include "guilib/LocalizeStrings.h" +#include "input/joysticks/interfaces/IInputHandler.h" +#include "peripherals/Peripherals.h" +#include "peripherals/addons/AddonButtonMapping.h" +#include "peripherals/addons/AddonInputHandling.h" +#include "peripherals/addons/PeripheralAddon.h" +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/bus/virtual/PeripheralBusAddon.h" +#include "settings/SettingAddon.h" +#include "settings/lib/Setting.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <utility> + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +struct SortBySettingsOrder +{ + bool operator()(const PeripheralDeviceSetting& left, const PeripheralDeviceSetting& right) + { + return left.m_order < right.m_order; + } +}; + +CPeripheral::CPeripheral(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : m_manager(manager), + m_type(scanResult.m_mappedType), + m_busType(scanResult.m_busType), + m_mappedBusType(scanResult.m_mappedBusType), + m_strLocation(scanResult.m_strLocation), + m_strDeviceName(scanResult.m_strDeviceName), + m_iVendorId(scanResult.m_iVendorId), + m_iProductId(scanResult.m_iProductId), + m_strVersionInfo(g_localizeStrings.Get(13205)), // "unknown" + m_bInitialised(false), + m_bHidden(false), + m_bError(false), + m_bus(bus) +{ + PeripheralTypeTranslator::FormatHexString(scanResult.m_iVendorId, m_strVendorId); + PeripheralTypeTranslator::FormatHexString(scanResult.m_iProductId, m_strProductId); + if (scanResult.m_iSequence > 0) + { + m_strFileLocation = + StringUtils::Format("peripherals://{}/{}_{}.dev", + PeripheralTypeTranslator::BusTypeToString(scanResult.m_busType), + scanResult.m_strLocation, scanResult.m_iSequence); + } + else + { + m_strFileLocation = StringUtils::Format( + "peripherals://{}/{}.dev", PeripheralTypeTranslator::BusTypeToString(scanResult.m_busType), + scanResult.m_strLocation); + } +} + +CPeripheral::~CPeripheral(void) +{ + PersistSettings(true); + + m_subDevices.clear(); + + ClearSettings(); +} + +bool CPeripheral::operator==(const CPeripheral& right) const +{ + return m_type == right.m_type && m_strLocation == right.m_strLocation && + m_iVendorId == right.m_iVendorId && m_iProductId == right.m_iProductId; +} + +bool CPeripheral::operator!=(const CPeripheral& right) const +{ + return !(*this == right); +} + +bool CPeripheral::HasFeature(const PeripheralFeature feature) const +{ + bool bReturn(false); + + for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++) + { + if (m_features.at(iFeaturePtr) == feature) + { + bReturn = true; + break; + } + } + + if (!bReturn) + { + for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++) + { + if (m_subDevices.at(iSubdevicePtr)->HasFeature(feature)) + { + bReturn = true; + break; + } + } + } + + return bReturn; +} + +void CPeripheral::GetFeatures(std::vector<PeripheralFeature>& features) const +{ + for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++) + features.push_back(m_features.at(iFeaturePtr)); + + for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++) + m_subDevices.at(iSubdevicePtr)->GetFeatures(features); +} + +bool CPeripheral::Initialise(void) +{ + bool bReturn(false); + + if (m_bError) + return bReturn; + + bReturn = true; + if (m_bInitialised) + return bReturn; + + m_manager.GetSettingsFromMapping(*this); + + std::string safeDeviceName = m_strDeviceName; + StringUtils::Replace(safeDeviceName, ' ', '_'); + + if (m_iVendorId == 0x0000 && m_iProductId == 0x0000) + { + m_strSettingsFile = + StringUtils::Format("special://profile/peripheral_data/{}_{}.xml", + PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), + CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT)); + } + else + { + // Backwards compatibility - old settings files didn't include the device name + m_strSettingsFile = StringUtils::Format( + "special://profile/peripheral_data/{}_{}_{}.xml", + PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), m_strVendorId, m_strProductId); + + if (!CFileUtils::Exists(m_strSettingsFile)) + m_strSettingsFile = StringUtils::Format( + "special://profile/peripheral_data/{}_{}_{}_{}.xml", + PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), m_strVendorId, m_strProductId, + CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT)); + } + + LoadPersistedSettings(); + + for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++) + { + PeripheralFeature feature = m_features.at(iFeaturePtr); + bReturn &= InitialiseFeature(feature); + } + + for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++) + bReturn &= m_subDevices.at(iSubdevicePtr)->Initialise(); + + if (bReturn) + { + CLog::Log(LOGDEBUG, "{} - initialised peripheral on '{}' with {} features and {} sub devices", + __FUNCTION__, m_strLocation, (int)m_features.size(), (int)m_subDevices.size()); + m_bInitialised = true; + } + + return bReturn; +} + +void CPeripheral::GetSubdevices(PeripheralVector& subDevices) const +{ + subDevices = m_subDevices; +} + +bool CPeripheral::IsMultiFunctional(void) const +{ + return m_subDevices.size() > 0; +} + +std::vector<std::shared_ptr<CSetting>> CPeripheral::GetSettings(void) const +{ + std::vector<PeripheralDeviceSetting> tmpSettings; + tmpSettings.reserve(m_settings.size()); + for (const auto& it : m_settings) + tmpSettings.push_back(it.second); + sort(tmpSettings.begin(), tmpSettings.end(), SortBySettingsOrder()); + + std::vector<std::shared_ptr<CSetting>> settings; + settings.reserve(tmpSettings.size()); + for (const auto& it : tmpSettings) + settings.push_back(it.m_setting); + return settings; +} + +void CPeripheral::AddSetting(const std::string& strKey, const SettingConstPtr& setting, int order) +{ + if (!setting) + { + CLog::Log(LOGERROR, "{} - invalid setting", __FUNCTION__); + return; + } + + if (!HasSetting(strKey)) + { + PeripheralDeviceSetting deviceSetting = {NULL, order}; + switch (setting->GetType()) + { + case SettingType::Boolean: + { + std::shared_ptr<const CSettingBool> mappedSetting = + std::static_pointer_cast<const CSettingBool>(setting); + std::shared_ptr<CSettingBool> boolSetting = + std::make_shared<CSettingBool>(strKey, *mappedSetting); + if (boolSetting) + { + boolSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = boolSetting; + } + } + break; + case SettingType::Integer: + { + std::shared_ptr<const CSettingInt> mappedSetting = + std::static_pointer_cast<const CSettingInt>(setting); + std::shared_ptr<CSettingInt> intSetting = + std::make_shared<CSettingInt>(strKey, *mappedSetting); + if (intSetting) + { + intSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = intSetting; + } + } + break; + case SettingType::Number: + { + std::shared_ptr<const CSettingNumber> mappedSetting = + std::static_pointer_cast<const CSettingNumber>(setting); + std::shared_ptr<CSettingNumber> floatSetting = + std::make_shared<CSettingNumber>(strKey, *mappedSetting); + if (floatSetting) + { + floatSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = floatSetting; + } + } + break; + case SettingType::String: + { + if (std::dynamic_pointer_cast<const CSettingAddon>(setting)) + { + std::shared_ptr<const CSettingAddon> mappedSetting = + std::static_pointer_cast<const CSettingAddon>(setting); + std::shared_ptr<CSettingAddon> addonSetting = + std::make_shared<CSettingAddon>(strKey, *mappedSetting); + addonSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = addonSetting; + } + else + { + std::shared_ptr<const CSettingString> mappedSetting = + std::static_pointer_cast<const CSettingString>(setting); + std::shared_ptr<CSettingString> stringSetting = + std::make_shared<CSettingString>(strKey, *mappedSetting); + stringSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = stringSetting; + } + } + break; + default: + //! @todo add more types if needed + break; + } + + if (deviceSetting.m_setting != NULL) + m_settings.insert(make_pair(strKey, deviceSetting)); + } +} + +bool CPeripheral::HasSetting(const std::string& strKey) const +{ + std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + return it != m_settings.end(); +} + +bool CPeripheral::HasSettings(void) const +{ + return !m_settings.empty(); +} + +bool CPeripheral::HasConfigurableSettings(void) const +{ + bool bReturn(false); + std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.begin(); + while (it != m_settings.end() && !bReturn) + { + if ((*it).second.m_setting->IsVisible()) + { + bReturn = true; + break; + } + + ++it; + } + + return bReturn; +} + +bool CPeripheral::GetSettingBool(const std::string& strKey) const +{ + std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Boolean) + { + std::shared_ptr<CSettingBool> boolSetting = + std::static_pointer_cast<CSettingBool>((*it).second.m_setting); + if (boolSetting) + return boolSetting->GetValue(); + } + + return false; +} + +int CPeripheral::GetSettingInt(const std::string& strKey) const +{ + std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Integer) + { + std::shared_ptr<CSettingInt> intSetting = + std::static_pointer_cast<CSettingInt>((*it).second.m_setting); + if (intSetting) + return intSetting->GetValue(); + } + + return 0; +} + +float CPeripheral::GetSettingFloat(const std::string& strKey) const +{ + std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Number) + { + std::shared_ptr<CSettingNumber> floatSetting = + std::static_pointer_cast<CSettingNumber>((*it).second.m_setting); + if (floatSetting) + return (float)floatSetting->GetValue(); + } + + return 0; +} + +const std::string CPeripheral::GetSettingString(const std::string& strKey) const +{ + std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::String) + { + std::shared_ptr<CSettingString> stringSetting = + std::static_pointer_cast<CSettingString>((*it).second.m_setting); + if (stringSetting) + return stringSetting->GetValue(); + } + + return ""; +} + +bool CPeripheral::SetSetting(const std::string& strKey, bool bValue) +{ + bool bChanged(false); + std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Boolean) + { + std::shared_ptr<CSettingBool> boolSetting = + std::static_pointer_cast<CSettingBool>((*it).second.m_setting); + if (boolSetting) + { + bChanged = boolSetting->GetValue() != bValue; + boolSetting->SetValue(bValue); + if (bChanged && m_bInitialised) + m_changedSettings.insert(strKey); + } + } + return bChanged; +} + +bool CPeripheral::SetSetting(const std::string& strKey, int iValue) +{ + bool bChanged(false); + std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Integer) + { + std::shared_ptr<CSettingInt> intSetting = + std::static_pointer_cast<CSettingInt>((*it).second.m_setting); + if (intSetting) + { + bChanged = intSetting->GetValue() != iValue; + intSetting->SetValue(iValue); + if (bChanged && m_bInitialised) + m_changedSettings.insert(strKey); + } + } + return bChanged; +} + +bool CPeripheral::SetSetting(const std::string& strKey, float fValue) +{ + bool bChanged(false); + std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Number) + { + std::shared_ptr<CSettingNumber> floatSetting = + std::static_pointer_cast<CSettingNumber>((*it).second.m_setting); + if (floatSetting) + { + bChanged = floatSetting->GetValue() != static_cast<double>(fValue); + floatSetting->SetValue(static_cast<double>(fValue)); + if (bChanged && m_bInitialised) + m_changedSettings.insert(strKey); + } + } + return bChanged; +} + +void CPeripheral::SetSettingVisible(const std::string& strKey, bool bSetTo) +{ + std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end()) + (*it).second.m_setting->SetVisible(bSetTo); +} + +bool CPeripheral::IsSettingVisible(const std::string& strKey) const +{ + std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end()) + return (*it).second.m_setting->IsVisible(); + return false; +} + +bool CPeripheral::SetSetting(const std::string& strKey, const std::string& strValue) +{ + bool bChanged(false); + std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end()) + { + if ((*it).second.m_setting->GetType() == SettingType::String) + { + std::shared_ptr<CSettingString> stringSetting = + std::static_pointer_cast<CSettingString>((*it).second.m_setting); + if (stringSetting) + { + bChanged = !StringUtils::EqualsNoCase(stringSetting->GetValue(), strValue); + stringSetting->SetValue(strValue); + if (bChanged && m_bInitialised) + m_changedSettings.insert(strKey); + } + } + else if ((*it).second.m_setting->GetType() == SettingType::Integer) + bChanged = SetSetting(strKey, strValue.empty() ? 0 : atoi(strValue.c_str())); + else if ((*it).second.m_setting->GetType() == SettingType::Number) + bChanged = SetSetting(strKey, (float)(strValue.empty() ? 0 : atof(strValue.c_str()))); + else if ((*it).second.m_setting->GetType() == SettingType::Boolean) + bChanged = SetSetting(strKey, strValue == "1"); + } + return bChanged; +} + +void CPeripheral::PersistSettings(bool bExiting /* = false */) +{ + CXBMCTinyXML doc; + TiXmlElement node("settings"); + doc.InsertEndChild(node); + for (const auto& itr : m_settings) + { + TiXmlElement nodeSetting("setting"); + nodeSetting.SetAttribute("id", itr.first.c_str()); + std::string strValue; + switch (itr.second.m_setting->GetType()) + { + case SettingType::String: + { + std::shared_ptr<CSettingString> stringSetting = + std::static_pointer_cast<CSettingString>(itr.second.m_setting); + if (stringSetting) + strValue = stringSetting->GetValue(); + } + break; + case SettingType::Integer: + { + std::shared_ptr<CSettingInt> intSetting = + std::static_pointer_cast<CSettingInt>(itr.second.m_setting); + if (intSetting) + strValue = std::to_string(intSetting->GetValue()); + } + break; + case SettingType::Number: + { + std::shared_ptr<CSettingNumber> floatSetting = + std::static_pointer_cast<CSettingNumber>(itr.second.m_setting); + if (floatSetting) + strValue = StringUtils::Format("{:.2f}", floatSetting->GetValue()); + } + break; + case SettingType::Boolean: + { + std::shared_ptr<CSettingBool> boolSetting = + std::static_pointer_cast<CSettingBool>(itr.second.m_setting); + if (boolSetting) + strValue = std::to_string(boolSetting->GetValue() ? 1 : 0); + } + break; + default: + break; + } + nodeSetting.SetAttribute("value", strValue.c_str()); + doc.RootElement()->InsertEndChild(nodeSetting); + } + + doc.SaveFile(m_strSettingsFile); + + if (!bExiting) + { + for (const auto& it : m_changedSettings) + OnSettingChanged(it); + } + m_changedSettings.clear(); +} + +void CPeripheral::LoadPersistedSettings(void) +{ + CXBMCTinyXML doc; + if (doc.LoadFile(m_strSettingsFile)) + { + const TiXmlElement* setting = doc.RootElement()->FirstChildElement("setting"); + while (setting) + { + std::string strId = XMLUtils::GetAttribute(setting, "id"); + std::string strValue = XMLUtils::GetAttribute(setting, "value"); + SetSetting(strId, strValue); + + setting = setting->NextSiblingElement("setting"); + } + } +} + +void CPeripheral::ResetDefaultSettings(void) +{ + ClearSettings(); + m_manager.GetSettingsFromMapping(*this); + + std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.begin(); + while (it != m_settings.end()) + { + m_changedSettings.insert((*it).first); + ++it; + } + + PersistSettings(); +} + +void CPeripheral::ClearSettings(void) +{ + m_settings.clear(); +} + +void CPeripheral::RegisterInputHandler(IInputHandler* handler, bool bPromiscuous) +{ + auto it = m_inputHandlers.find(handler); + if (it == m_inputHandlers.end()) + { + PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this); + if (addon) + { + std::unique_ptr<CAddonInputHandling> addonInput = std::make_unique<CAddonInputHandling>( + m_manager, this, std::move(addon), handler, GetDriverReceiver()); + if (addonInput->Load()) + { + RegisterJoystickDriverHandler(addonInput.get(), bPromiscuous); + m_inputHandlers[handler] = std::move(addonInput); + } + } + else + { + CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation); + } + } +} + +void CPeripheral::UnregisterInputHandler(IInputHandler* handler) +{ + handler->ResetInputReceiver(); + + auto it = m_inputHandlers.find(handler); + if (it != m_inputHandlers.end()) + { + UnregisterJoystickDriverHandler(it->second.get()); + m_inputHandlers.erase(it); + } +} + +void CPeripheral::RegisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handler, + bool bPromiscuous) +{ + auto it = m_keyboardHandlers.find(handler); + if (it == m_keyboardHandlers.end()) + { + std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler> keyboardDriverHandler; + + PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this); + if (addon) + { + std::unique_ptr<CAddonInputHandling> addonInput = + std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler); + if (addonInput->Load()) + keyboardDriverHandler = std::move(addonInput); + } + else + { + CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation); + } + + if (keyboardDriverHandler) + { + RegisterKeyboardDriverHandler(keyboardDriverHandler.get(), bPromiscuous); + m_keyboardHandlers[handler] = std::move(keyboardDriverHandler); + } + } +} + +void CPeripheral::UnregisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handler) +{ + auto it = m_keyboardHandlers.find(handler); + if (it != m_keyboardHandlers.end()) + { + UnregisterKeyboardDriverHandler(it->second.get()); + m_keyboardHandlers.erase(it); + } +} + +void CPeripheral::RegisterMouseHandler(MOUSE::IMouseInputHandler* handler, bool bPromiscuous) +{ + auto it = m_mouseHandlers.find(handler); + if (it == m_mouseHandlers.end()) + { + std::unique_ptr<KODI::MOUSE::IMouseDriverHandler> mouseDriverHandler; + + PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this); + if (addon) + { + std::unique_ptr<CAddonInputHandling> addonInput = + std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler); + if (addonInput->Load()) + mouseDriverHandler = std::move(addonInput); + } + else + { + CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation); + } + + if (mouseDriverHandler) + { + RegisterMouseDriverHandler(mouseDriverHandler.get(), bPromiscuous); + m_mouseHandlers[handler] = std::move(mouseDriverHandler); + } + } +} + +void CPeripheral::UnregisterMouseHandler(MOUSE::IMouseInputHandler* handler) +{ + auto it = m_mouseHandlers.find(handler); + if (it != m_mouseHandlers.end()) + { + UnregisterMouseDriverHandler(it->second.get()); + m_mouseHandlers.erase(it); + } +} + +void CPeripheral::RegisterJoystickButtonMapper(IButtonMapper* mapper) +{ + auto it = m_buttonMappers.find(mapper); + if (it == m_buttonMappers.end()) + { + std::unique_ptr<CAddonButtonMapping> addonMapping( + new CAddonButtonMapping(m_manager, this, mapper)); + + RegisterJoystickDriverHandler(addonMapping.get(), false); + RegisterKeyboardDriverHandler(addonMapping.get(), false); + RegisterMouseDriverHandler(addonMapping.get(), false); + + m_buttonMappers[mapper] = std::move(addonMapping); + } +} + +void CPeripheral::UnregisterJoystickButtonMapper(IButtonMapper* mapper) +{ + auto it = m_buttonMappers.find(mapper); + if (it != m_buttonMappers.end()) + { + UnregisterMouseDriverHandler(it->second.get()); + UnregisterKeyboardDriverHandler(it->second.get()); + UnregisterJoystickDriverHandler(it->second.get()); + + m_buttonMappers.erase(it); + } +} + +std::string CPeripheral::GetIcon() const +{ + std::string icon; + + // Try controller profile + const GAME::ControllerPtr controller = ControllerProfile(); + if (controller) + icon = controller->Layout().ImagePath(); + + // Try add-on + if (icon.empty() && m_busType == PERIPHERAL_BUS_ADDON) + { + CPeripheralBusAddon* bus = static_cast<CPeripheralBusAddon*>(m_bus); + + PeripheralAddonPtr addon; + unsigned int index; + if (bus->SplitLocation(m_strLocation, addon, index)) + { + std::string addonIcon = addon->Icon(); + if (!addonIcon.empty()) + icon = std::move(addonIcon); + } + } + + // Fallback + if (icon.empty()) + icon = "DefaultAddon.png"; + + return icon; +} + +bool CPeripheral::operator==(const PeripheralScanResult& right) const +{ + return StringUtils::EqualsNoCase(m_strLocation, right.m_strLocation); +} + +bool CPeripheral::operator!=(const PeripheralScanResult& right) const +{ + return !(*this == right); +} + +CDateTime CPeripheral::LastActive() +{ + return CDateTime(); +} diff --git a/xbmc/peripherals/devices/Peripheral.h b/xbmc/peripherals/devices/Peripheral.h new file mode 100644 index 0000000..8c4049b --- /dev/null +++ b/xbmc/peripherals/devices/Peripheral.h @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2005-2018 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 "input/joysticks/interfaces/IInputProvider.h" +#include "input/keyboard/interfaces/IKeyboardInputProvider.h" +#include "input/mouse/interfaces/IMouseInputProvider.h" +#include "peripherals/PeripheralTypes.h" + +#include <map> +#include <set> +#include <string> +#include <vector> + +class TiXmlDocument; +class CDateTime; +class CSetting; +class IKeymap; + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMapper; +class IDriverHandler; +class IDriverReceiver; +class IInputHandler; +} // namespace JOYSTICK + +namespace KEYBOARD +{ +class IKeyboardDriverHandler; +} + +namespace MOUSE +{ +class IMouseDriverHandler; +} +} // namespace KODI + +namespace PERIPHERALS +{ +class CAddonButtonMapping; +class CGUIDialogPeripheralSettings; +class CPeripheralBus; +class CPeripherals; + +typedef enum +{ + STATE_SWITCH_TOGGLE, + STATE_ACTIVATE_SOURCE, + STATE_STANDBY +} CecStateChange; + +class CPeripheral : public KODI::JOYSTICK::IInputProvider, + public KODI::KEYBOARD::IKeyboardInputProvider, + public KODI::MOUSE::IMouseInputProvider +{ + friend class CGUIDialogPeripheralSettings; + +public: + CPeripheral(CPeripherals& manager, const PeripheralScanResult& scanResult, CPeripheralBus* bus); + ~CPeripheral(void) override; + + bool operator==(const CPeripheral& right) const; + bool operator!=(const CPeripheral& right) const; + bool operator==(const PeripheralScanResult& right) const; + bool operator!=(const PeripheralScanResult& right) const; + + const std::string& FileLocation(void) const { return m_strFileLocation; } + const std::string& Location(void) const { return m_strLocation; } + int VendorId(void) const { return m_iVendorId; } + const char* VendorIdAsString(void) const { return m_strVendorId.c_str(); } + int ProductId(void) const { return m_iProductId; } + const char* ProductIdAsString(void) const { return m_strProductId.c_str(); } + PeripheralType Type(void) const { return m_type; } + PeripheralBusType GetBusType(void) const { return m_busType; } + const std::string& DeviceName(void) const { return m_strDeviceName; } + bool IsHidden(void) const { return m_bHidden; } + void SetHidden(bool bSetTo = true) { m_bHidden = bSetTo; } + const std::string& GetVersionInfo(void) const { return m_strVersionInfo; } + + /*! + * @brief Get an icon for this peripheral + * @return Path to an icon, or skin icon file name + */ + virtual std::string GetIcon() const; + + /*! + * @brief Check whether this device has the given feature. + * @param feature The feature to check for. + * @return True when the device has the feature, false otherwise. + */ + bool HasFeature(const PeripheralFeature feature) const; + + /*! + * @brief Get all features that are supported by this device. + * @param features The features. + */ + void GetFeatures(std::vector<PeripheralFeature>& features) const; + + /*! + * @brief Initialises the peripheral. + * @return True when the peripheral has been initialised successfully, false otherwise. + */ + bool Initialise(void); + + /*! + * @brief Initialise one of the features of this peripheral. + * @param feature The feature to initialise. + * @return True when the feature has been initialised successfully, false otherwise. + */ + virtual bool InitialiseFeature(const PeripheralFeature feature) { return true; } + + /*! + * @brief Briefly activate a feature to notify the user + */ + virtual void OnUserNotification() {} + + /*! + * @brief Briefly test one of the features of this peripheral. + * @param feature The feature to test. + * @return True if the test succeeded, false otherwise. + */ + virtual bool TestFeature(PeripheralFeature feature) { return false; } + + /*! + * @brief Called when a setting changed. + * @param strChangedSetting The changed setting. + */ + virtual void OnSettingChanged(const std::string& strChangedSetting) {} + + /*! + * @brief Called when this device is removed, before calling the destructor. + */ + virtual void OnDeviceRemoved(void) {} + + /*! + * @brief Get all subdevices if this device is multifunctional. + * @param subDevices The subdevices. + */ + virtual void GetSubdevices(PeripheralVector& subDevices) const; + + /*! + * @return True when this device is multifunctional, false otherwise. + */ + virtual bool IsMultiFunctional(void) const; + + /*! + * @brief Add a setting to this peripheral. This will overwrite a previous setting with the same + * key. + * @param strKey The key of the setting. + * @param setting The setting. + */ + virtual void AddSetting(const std::string& strKey, + const std::shared_ptr<const CSetting>& setting, + int order); + + /*! + * @brief Check whether a setting is known with the given key. + * @param strKey The key to search. + * @return True when found, false otherwise. + */ + virtual bool HasSetting(const std::string& strKey) const; + + /*! + * @return True when this device has any settings, false otherwise. + */ + virtual bool HasSettings(void) const; + + /*! + * @return True when this device has any configurable settings, false otherwise. + */ + virtual bool HasConfigurableSettings(void) const; + + /*! + * @brief Get the value of a setting. + * @param strKey The key to search. + * @return The value or an empty string if it wasn't found. + */ + virtual const std::string GetSettingString(const std::string& strKey) const; + virtual bool SetSetting(const std::string& strKey, const std::string& strValue); + virtual void SetSettingVisible(const std::string& strKey, bool bSetTo); + virtual bool IsSettingVisible(const std::string& strKey) const; + + virtual int GetSettingInt(const std::string& strKey) const; + virtual bool SetSetting(const std::string& strKey, int iValue); + + virtual bool GetSettingBool(const std::string& strKey) const; + virtual bool SetSetting(const std::string& strKey, bool bValue); + + virtual float GetSettingFloat(const std::string& strKey) const; + virtual bool SetSetting(const std::string& strKey, float fValue); + + virtual void PersistSettings(bool bExiting = false); + virtual void LoadPersistedSettings(void); + virtual void ResetDefaultSettings(void); + + virtual std::vector<std::shared_ptr<CSetting>> GetSettings(void) const; + + virtual bool ErrorOccured(void) const { return m_bError; } + + virtual void RegisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler, + bool bPromiscuous) + { + } + virtual void UnregisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler) {} + + virtual void RegisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler, + bool bPromiscuous) + { + } + virtual void UnregisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler) {} + + virtual void RegisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler, + bool bPromiscuous) + { + } + virtual void UnregisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler) {} + + // implementation of IInputProvider + void RegisterInputHandler(KODI::JOYSTICK::IInputHandler* handler, bool bPromiscuous) override; + void UnregisterInputHandler(KODI::JOYSTICK::IInputHandler* handler) override; + + // implementation of IKeyboardInputProvider + void RegisterKeyboardHandler(KODI::KEYBOARD::IKeyboardInputHandler* handler, + bool bPromiscuous) override; + void UnregisterKeyboardHandler(KODI::KEYBOARD::IKeyboardInputHandler* handler) override; + + // implementation of IMouseInputProvider + void RegisterMouseHandler(KODI::MOUSE::IMouseInputHandler* handler, bool bPromiscuous) override; + void UnregisterMouseHandler(KODI::MOUSE::IMouseInputHandler* handler) override; + + virtual void RegisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper); + virtual void UnregisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper); + + virtual KODI::JOYSTICK::IDriverReceiver* GetDriverReceiver() { return nullptr; } + + virtual IKeymap* GetKeymap(const std::string& controllerId) { return nullptr; } + + /*! + * \brief Return the last time this peripheral was active + * + * \return The time of last activation, or invalid if unknown/never active + */ + virtual CDateTime LastActive(); + + /*! + * \brief Get the controller profile that best represents this peripheral + * + * \return The controller profile, or empty if unknown + */ + virtual KODI::GAME::ControllerPtr ControllerProfile() const { return m_controllerProfile; } + + /*! + * \brief Set the controller profile for this peripheral + * + * \param controller The new controller profile + */ + virtual void SetControllerProfile(const KODI::GAME::ControllerPtr& controller) + { + m_controllerProfile = controller; + } + +protected: + virtual void ClearSettings(void); + + CPeripherals& m_manager; + PeripheralType m_type; + PeripheralBusType m_busType; + PeripheralBusType m_mappedBusType; + std::string m_strLocation; + std::string m_strDeviceName; + std::string m_strSettingsFile; + std::string m_strFileLocation; + int m_iVendorId; + std::string m_strVendorId; + int m_iProductId; + std::string m_strProductId; + std::string m_strVersionInfo; + bool m_bInitialised; + bool m_bHidden; + bool m_bError; + std::vector<PeripheralFeature> m_features; + PeripheralVector m_subDevices; + std::map<std::string, PeripheralDeviceSetting> m_settings; + std::set<std::string> m_changedSettings; + CPeripheralBus* m_bus; + std::map<KODI::JOYSTICK::IInputHandler*, std::unique_ptr<KODI::JOYSTICK::IDriverHandler>> + m_inputHandlers; + std::map<KODI::KEYBOARD::IKeyboardInputHandler*, + std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler>> + m_keyboardHandlers; + std::map<KODI::MOUSE::IMouseInputHandler*, std::unique_ptr<KODI::MOUSE::IMouseDriverHandler>> + m_mouseHandlers; + std::map<KODI::JOYSTICK::IButtonMapper*, std::unique_ptr<CAddonButtonMapping>> m_buttonMappers; + KODI::GAME::ControllerPtr m_controllerProfile; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralBluetooth.cpp b/xbmc/peripherals/devices/PeripheralBluetooth.cpp new file mode 100644 index 0000000..6f3802d --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralBluetooth.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralBluetooth.h" + +using namespace PERIPHERALS; + +CPeripheralBluetooth::CPeripheralBluetooth(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus) +{ + m_features.push_back(FEATURE_BLUETOOTH); +} diff --git a/xbmc/peripherals/devices/PeripheralBluetooth.h b/xbmc/peripherals/devices/PeripheralBluetooth.h new file mode 100644 index 0000000..11fa7ca --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralBluetooth.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 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 "Peripheral.h" + +namespace PERIPHERALS +{ +class CPeripheralBluetooth : public CPeripheral +{ +public: + CPeripheralBluetooth(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralBluetooth(void) override = default; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.cpp b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp new file mode 100644 index 0000000..bc62bea --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp @@ -0,0 +1,1861 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralCecAdapter.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationEnums.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationPowerHandling.h" +#include "application/ApplicationVolumeHandling.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "input/remote/IRRemote.h" +#include "messaging/ApplicationMessenger.h" +#include "pictures/GUIWindowSlideShow.h" +#include "utils/JobManager.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "xbmc/interfaces/AnnouncementManager.h" + +#include <mutex> + +#include <libcec/cec.h> + +using namespace PERIPHERALS; +using namespace CEC; +using namespace ANNOUNCEMENT; +using namespace std::chrono_literals; + +#define CEC_LIB_SUPPORTED_VERSION LIBCEC_VERSION_TO_UINT(4, 0, 0) + +/* time in seconds to ignore standby commands from devices after the screensaver has been activated + */ +#define SCREENSAVER_TIMEOUT 20 +#define VOLUME_CHANGE_TIMEOUT 250 +#define VOLUME_REFRESH_TIMEOUT 100 + +#define LOCALISED_ID_TV 36037 +#define LOCALISED_ID_AVR 36038 +#define LOCALISED_ID_TV_AVR 36039 +#define LOCALISED_ID_STOP 36044 +#define LOCALISED_ID_PAUSE 36045 +#define LOCALISED_ID_POWEROFF 13005 +#define LOCALISED_ID_SUSPEND 13011 +#define LOCALISED_ID_HIBERNATE 13010 +#define LOCALISED_ID_QUIT 13009 +#define LOCALISED_ID_IGNORE 36028 +#define LOCALISED_ID_RECORDING_DEVICE 36051 +#define LOCALISED_ID_PLAYBACK_DEVICE 36052 +#define LOCALISED_ID_TUNER_DEVICE 36053 + +#define LOCALISED_ID_NONE 231 + +/* time in seconds to suppress source activation after receiving OnStop */ +#define CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP 2 + +CPeripheralCecAdapter::CPeripheralCecAdapter(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheralHID(manager, scanResult, bus), CThread("CECAdapter"), m_cecAdapter(NULL) +{ + ResetMembers(); + m_features.push_back(FEATURE_CEC); + m_strComPort = scanResult.m_strLocation; +} + +CPeripheralCecAdapter::~CPeripheralCecAdapter(void) +{ + { + std::unique_lock<CCriticalSection> lock(m_critSection); + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + m_bStop = true; + } + + StopThread(true); + delete m_queryThread; + + if (m_cecAdapter) + { + CECDestroy(m_cecAdapter); + m_cecAdapter = NULL; + } +} + +void CPeripheralCecAdapter::ResetMembers(void) +{ + if (m_cecAdapter) + CECDestroy(m_cecAdapter); + m_cecAdapter = NULL; + m_bStarted = false; + m_bHasButton = false; + m_bIsReady = false; + m_bHasConnectedAudioSystem = false; + m_strMenuLanguage = "???"; + m_lastKeypress = {}; + m_lastChange = VOLUME_CHANGE_NONE; + m_iExitCode = EXITCODE_QUIT; + + //! @todo fetch the correct initial value when system audiostatus is + //! implemented in libCEC + m_bIsMuted = false; + + m_bGoingToStandby = false; + m_bIsRunning = false; + m_bDeviceRemoved = false; + m_bActiveSourcePending = false; + m_bStandbyPending = false; + m_bActiveSourceBeforeStandby = false; + m_bOnPlayReceived = false; + m_bPlaybackPaused = false; + m_queryThread = NULL; + m_bPowerOnScreensaver = false; + m_bUseTVMenuLanguage = false; + m_bSendInactiveSource = false; + m_bPowerOffScreensaver = false; + m_bShutdownOnStandby = false; + + m_currentButton.iButton = 0; + m_currentButton.iDuration = 0; + m_standbySent.SetValid(false); + m_configuration.Clear(); +} + +void CPeripheralCecAdapter::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER && + message == "OnQuit" && m_bIsReady) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_iExitCode = static_cast<int>(data["exitcode"].asInteger(EXITCODE_QUIT)); + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + StopThread(false); + } + else if (flag == ANNOUNCEMENT::GUI && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER && + message == "OnScreensaverDeactivated" && m_bIsReady) + { + bool bIgnoreDeactivate(false); + if (data["shuttingdown"].isBoolean()) + { + // don't respond to the deactivation if we are just going to suspend/shutdown anyway + // the tv will not have time to switch on before being told to standby and + // may not action the standby command. + bIgnoreDeactivate = data["shuttingdown"].asBoolean(); + if (bIgnoreDeactivate) + CLog::Log(LOGDEBUG, "{} - ignoring OnScreensaverDeactivated for power action", + __FUNCTION__); + } + if (m_bPowerOnScreensaver && !bIgnoreDeactivate && m_configuration.bActivateSource) + { + ActivateSource(); + } + } + else if (flag == ANNOUNCEMENT::GUI && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER && + message == "OnScreensaverActivated" && m_bIsReady) + { + // Don't put devices to standby if application is currently playing + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->IsPlaying() && m_bPowerOffScreensaver) + { + // only power off when we're the active source + if (m_cecAdapter->IsLibCECActiveSource()) + StandbyDevices(); + } + } + else if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER && + message == "OnSleep") + { + // this will also power off devices when we're the active source + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bGoingToStandby = true; + } + StopThread(); + } + else if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER && + message == "OnWake") + { + CLog::Log(LOGDEBUG, "{} - reconnecting to the CEC adapter after standby mode", __FUNCTION__); + if (ReopenConnection()) + { + bool bActivate(false); + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bActivate = m_bActiveSourceBeforeStandby; + m_bActiveSourceBeforeStandby = false; + } + if (bActivate) + ActivateSource(); + } + } + else if (flag == ANNOUNCEMENT::Player && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER && + message == "OnStop") + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_preventActivateSourceOnPlay = CDateTime::GetCurrentDateTime(); + m_bOnPlayReceived = false; + } + else if (flag == ANNOUNCEMENT::Player && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER && + (message == "OnPlay" || message == "OnResume")) + { + // activate the source when playback started, and the option is enabled + bool bActivateSource(false); + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bActivateSource = (m_configuration.bActivateSource && !m_bOnPlayReceived && + !m_cecAdapter->IsLibCECActiveSource() && + (!m_preventActivateSourceOnPlay.IsValid() || + CDateTime::GetCurrentDateTime() - m_preventActivateSourceOnPlay > + CDateTimeSpan(0, 0, 0, CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP))); + m_bOnPlayReceived = true; + } + if (bActivateSource) + ActivateSource(); + } +} + +bool CPeripheralCecAdapter::InitialiseFeature(const PeripheralFeature feature) +{ + if (feature == FEATURE_CEC && !m_bStarted && GetSettingBool("enabled")) + { + // hide settings that have an override set + if (!GetSettingString("wake_devices_advanced").empty()) + SetSettingVisible("wake_devices", false); + if (!GetSettingString("standby_devices_advanced").empty()) + SetSettingVisible("standby_devices", false); + + SetConfigurationFromSettings(); + m_callbacks.Clear(); + m_callbacks.logMessage = &CecLogMessage; + m_callbacks.keyPress = &CecKeyPress; + m_callbacks.commandReceived = &CecCommand; + m_callbacks.configurationChanged = &CecConfiguration; + m_callbacks.alert = &CecAlert; + m_callbacks.sourceActivated = &CecSourceActivated; + m_configuration.callbackParam = this; + m_configuration.callbacks = &m_callbacks; + + m_cecAdapter = CECInitialise(&m_configuration); + + if (m_configuration.serverVersion < CEC_LIB_SUPPORTED_VERSION) + { + /* unsupported libcec version */ + CLog::Log( + LOGERROR, + "Detected version of libCEC interface ({0:x}) is lower than the supported version {1:x}", + m_cecAdapter ? m_configuration.serverVersion : -1, CEC_LIB_SUPPORTED_VERSION); + + // display warning: incompatible libCEC + std::string strMessage = StringUtils::Format( + g_localizeStrings.Get(36040), m_cecAdapter ? m_configuration.serverVersion : -1, + CEC_LIB_SUPPORTED_VERSION); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), + strMessage); + m_bError = true; + if (m_cecAdapter) + CECDestroy(m_cecAdapter); + m_cecAdapter = NULL; + + m_features.clear(); + return false; + } + else + { + CLog::Log(LOGDEBUG, "{} - using libCEC v{}", __FUNCTION__, + m_cecAdapter->VersionToString(m_configuration.serverVersion)); + SetVersionInfo(m_configuration); + } + + m_bStarted = true; + Create(); + } + + return CPeripheral::InitialiseFeature(feature); +} + +void CPeripheralCecAdapter::SetVersionInfo(const libcec_configuration& configuration) +{ + m_strVersionInfo = StringUtils::Format("libCEC {} - firmware v{}", + m_cecAdapter->VersionToString(configuration.serverVersion), + configuration.iFirmwareVersion); + + // append firmware build date + if (configuration.iFirmwareBuildDate != CEC_FW_BUILD_UNKNOWN) + { + CDateTime dt((time_t)configuration.iFirmwareBuildDate); + m_strVersionInfo += StringUtils::Format(" ({})", dt.GetAsDBDate()); + } +} + +bool CPeripheralCecAdapter::OpenConnection(void) +{ + bool bIsOpen(false); + + if (!GetSettingBool("enabled")) + { + CLog::Log(LOGDEBUG, "{} - CEC adapter is disabled in peripheral settings", __FUNCTION__); + m_bStarted = false; + return bIsOpen; + } + + // open the CEC adapter + CLog::Log(LOGDEBUG, "{} - opening a connection to the CEC adapter: {}", __FUNCTION__, + m_strComPort); + + // scanning the CEC bus takes about 5 seconds, so display a notification to inform users that + // we're busy + std::string strMessage = + StringUtils::Format(g_localizeStrings.Get(21336), g_localizeStrings.Get(36000)); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), + strMessage); + + bool bConnectionFailedDisplayed(false); + + while (!m_bStop && !bIsOpen) + { + if ((bIsOpen = m_cecAdapter->Open(m_strComPort.c_str(), 10000)) == false) + { + // display warning: couldn't initialise libCEC + CLog::Log(LOGERROR, "{} - could not opening a connection to the CEC adapter", __FUNCTION__); + if (!bConnectionFailedDisplayed) + CGUIDialogKaiToast::QueueNotification( + CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36012)); + bConnectionFailedDisplayed = true; + + CThread::Sleep(10000ms); + } + } + + if (bIsOpen) + { + CLog::Log(LOGDEBUG, "{} - connection to the CEC adapter opened", __FUNCTION__); + + // read the configuration + libcec_configuration config; + if (m_cecAdapter->GetCurrentConfiguration(&config)) + { + // update the local configuration + std::unique_lock<CCriticalSection> lock(m_critSection); + SetConfigurationFromLibCEC(config); + } + } + + return bIsOpen; +} + +void CPeripheralCecAdapter::Process(void) +{ + if (!OpenConnection()) + return; + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_iExitCode = EXITCODE_QUIT; + m_bGoingToStandby = false; + m_bIsRunning = true; + m_bActiveSourceBeforeStandby = false; + } + + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + + m_queryThread = new CPeripheralCecAdapterUpdateThread(this, &m_configuration); + m_queryThread->Create(false); + + while (!m_bStop) + { + if (!m_bStop) + ProcessVolumeChange(); + + if (!m_bStop) + ProcessActivateSource(); + + if (!m_bStop) + ProcessStandbyDevices(); + + if (!m_bStop) + CThread::Sleep(5ms); + } + + m_queryThread->StopThread(true); + + bool bSendStandbyCommands(false); + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bSendStandbyCommands = m_iExitCode != EXITCODE_REBOOT && m_iExitCode != EXITCODE_RESTARTAPP && + !m_bDeviceRemoved && + (!m_bGoingToStandby || GetSettingBool("standby_tv_on_pc_standby")) && + GetSettingBool("enabled"); + + if (m_bGoingToStandby) + m_bActiveSourceBeforeStandby = m_cecAdapter->IsLibCECActiveSource(); + } + + if (bSendStandbyCommands) + { + if (m_cecAdapter->IsLibCECActiveSource()) + { + if (!m_configuration.powerOffDevices.IsEmpty()) + { + CLog::Log(LOGDEBUG, "{} - sending standby commands", __FUNCTION__); + m_standbySent = CDateTime::GetCurrentDateTime(); + m_cecAdapter->StandbyDevices(); + } + else if (m_bSendInactiveSource) + { + CLog::Log(LOGDEBUG, "{} - sending inactive source commands", __FUNCTION__); + m_cecAdapter->SetInactiveView(); + } + } + else + { + CLog::Log(LOGDEBUG, "{} - XBMC is not the active source, not sending any standby commands", + __FUNCTION__); + } + } + + m_cecAdapter->Close(); + + CLog::Log(LOGDEBUG, "{} - CEC adapter processor thread ended", __FUNCTION__); + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bStarted = false; + m_bIsRunning = false; + } +} + +bool CPeripheralCecAdapter::HasAudioControl(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bHasConnectedAudioSystem; +} + +void CPeripheralCecAdapter::SetAudioSystemConnected(bool bSetTo) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bHasConnectedAudioSystem = bSetTo; +} + +void CPeripheralCecAdapter::ProcessVolumeChange(void) +{ + bool bSendRelease(false); + CecVolumeChange pendingVolumeChange = VOLUME_CHANGE_NONE; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastKeypress); + if (!m_volumeChangeQueue.empty()) + { + /* get the first change from the queue */ + pendingVolumeChange = m_volumeChangeQueue.front(); + m_volumeChangeQueue.pop(); + + /* remove all dupe entries */ + while (!m_volumeChangeQueue.empty() && m_volumeChangeQueue.front() == pendingVolumeChange) + m_volumeChangeQueue.pop(); + + /* send another keypress after VOLUME_REFRESH_TIMEOUT ms */ + + bool bRefresh(duration.count() > VOLUME_REFRESH_TIMEOUT); + + /* only send the keypress when it hasn't been sent yet */ + if (pendingVolumeChange != m_lastChange) + { + m_lastKeypress = std::chrono::steady_clock::now(); + m_lastChange = pendingVolumeChange; + } + else if (bRefresh) + { + m_lastKeypress = std::chrono::steady_clock::now(); + pendingVolumeChange = m_lastChange; + } + else + pendingVolumeChange = VOLUME_CHANGE_NONE; + } + else if (m_lastKeypress.time_since_epoch().count() > 0 && + duration.count() > VOLUME_CHANGE_TIMEOUT) + { + /* send a key release */ + m_lastKeypress = {}; + bSendRelease = true; + m_lastChange = VOLUME_CHANGE_NONE; + } + } + + switch (pendingVolumeChange) + { + case VOLUME_CHANGE_UP: + m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_UP, false); + break; + case VOLUME_CHANGE_DOWN: + m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_DOWN, false); + break; + case VOLUME_CHANGE_MUTE: + m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_MUTE, false); + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bIsMuted = !m_bIsMuted; + } + break; + case VOLUME_CHANGE_NONE: + if (bSendRelease) + m_cecAdapter->SendKeyRelease(CECDEVICE_AUDIOSYSTEM, false); + break; + } +} + +void CPeripheralCecAdapter::VolumeUp(void) +{ + if (HasAudioControl()) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_volumeChangeQueue.push(VOLUME_CHANGE_UP); + } +} + +void CPeripheralCecAdapter::VolumeDown(void) +{ + if (HasAudioControl()) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_volumeChangeQueue.push(VOLUME_CHANGE_DOWN); + } +} + +void CPeripheralCecAdapter::ToggleMute(void) +{ + if (HasAudioControl()) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_volumeChangeQueue.push(VOLUME_CHANGE_MUTE); + } +} + +bool CPeripheralCecAdapter::IsMuted(void) +{ + if (HasAudioControl()) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bIsMuted; + } + return false; +} + +void CPeripheralCecAdapter::SetMenuLanguage(const char* strLanguage) +{ + if (StringUtils::EqualsNoCase(m_strMenuLanguage, strLanguage)) + return; + + std::string strGuiLanguage; + + if (!strcmp(strLanguage, "bul")) + strGuiLanguage = "bg_bg"; + else if (!strcmp(strLanguage, "hrv")) + strGuiLanguage = "hr_hr"; + else if (!strcmp(strLanguage, "cze")) + strGuiLanguage = "cs_cz"; + else if (!strcmp(strLanguage, "dan")) + strGuiLanguage = "da_dk"; + else if (!strcmp(strLanguage, "deu")) + strGuiLanguage = "de_de"; + else if (!strcmp(strLanguage, "dut")) + strGuiLanguage = "nl_nl"; + else if (!strcmp(strLanguage, "eng")) + strGuiLanguage = "en_gb"; + else if (!strcmp(strLanguage, "fin")) + strGuiLanguage = "fi_fi"; + else if (!strcmp(strLanguage, "fre")) + strGuiLanguage = "fr_fr"; + else if (!strcmp(strLanguage, "ger")) + strGuiLanguage = "de_de"; + else if (!strcmp(strLanguage, "gre")) + strGuiLanguage = "el_gr"; + else if (!strcmp(strLanguage, "hun")) + strGuiLanguage = "hu_hu"; + else if (!strcmp(strLanguage, "ita")) + strGuiLanguage = "it_it"; + else if (!strcmp(strLanguage, "nor")) + strGuiLanguage = "nb_no"; + else if (!strcmp(strLanguage, "pol")) + strGuiLanguage = "pl_pl"; + else if (!strcmp(strLanguage, "por")) + strGuiLanguage = "pt_pt"; + else if (!strcmp(strLanguage, "rum")) + strGuiLanguage = "ro_ro"; + else if (!strcmp(strLanguage, "rus")) + strGuiLanguage = "ru_ru"; + else if (!strcmp(strLanguage, "srp")) + strGuiLanguage = "sr_rs@latin"; + else if (!strcmp(strLanguage, "slo")) + strGuiLanguage = "sk_sk"; + else if (!strcmp(strLanguage, "slv")) + strGuiLanguage = "sl_si"; + else if (!strcmp(strLanguage, "spa")) + strGuiLanguage = "es_es"; + else if (!strcmp(strLanguage, "swe")) + strGuiLanguage = "sv_se"; + else if (!strcmp(strLanguage, "tur")) + strGuiLanguage = "tr_tr"; + + if (!strGuiLanguage.empty()) + { + strGuiLanguage = "resource.language." + strGuiLanguage; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SETLANGUAGE, -1, -1, nullptr, strGuiLanguage); + CLog::Log(LOGDEBUG, "{} - language set to '{}'", __FUNCTION__, strGuiLanguage); + } + else + CLog::Log(LOGWARNING, "{} - TV menu language set to unknown value '{}'", __FUNCTION__, + strLanguage); +} + +void CPeripheralCecAdapter::OnTvStandby(void) +{ + int iActionOnTvStandby = GetSettingInt("standby_pc_on_tv_standby"); + switch (iActionOnTvStandby) + { + case LOCALISED_ID_POWEROFF: + m_bStarted = false; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_SHUTDOWN); + break; + case LOCALISED_ID_SUSPEND: + m_bStarted = false; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_SUSPEND); + break; + case LOCALISED_ID_HIBERNATE: + m_bStarted = false; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_HIBERNATE); + break; + case LOCALISED_ID_QUIT: + m_bStarted = false; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + break; + case LOCALISED_ID_PAUSE: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PAUSE_IF_PLAYING); + break; + case LOCALISED_ID_STOP: + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlaying()) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_STOP); + break; + } + case LOCALISED_ID_IGNORE: + break; + default: + CLog::Log(LOGERROR, "{} - Unexpected [standby_pc_on_tv_standby] setting value", __FUNCTION__); + break; + } +} + +void CPeripheralCecAdapter::CecCommand(void* cbParam, const cec_command* command) +{ + CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam); + if (!adapter) + return; + + if (adapter->m_bIsReady) + { + switch (command->opcode) + { + case CEC_OPCODE_STANDBY: + if (command->initiator == CECDEVICE_TV && + (!adapter->m_standbySent.IsValid() || + CDateTime::GetCurrentDateTime() - adapter->m_standbySent > + CDateTimeSpan(0, 0, 0, SCREENSAVER_TIMEOUT))) + { + adapter->OnTvStandby(); + } + break; + case CEC_OPCODE_SET_MENU_LANGUAGE: + if (adapter->m_bUseTVMenuLanguage == 1 && command->initiator == CECDEVICE_TV && + command->parameters.size == 3) + { + char strNewLanguage[4]; + for (int iPtr = 0; iPtr < 3; iPtr++) + strNewLanguage[iPtr] = command->parameters[iPtr]; + strNewLanguage[3] = 0; + adapter->SetMenuLanguage(strNewLanguage); + } + break; + case CEC_OPCODE_DECK_CONTROL: + if (command->initiator == CECDEVICE_TV && command->parameters.size == 1 && + command->parameters[0] == CEC_DECK_CONTROL_MODE_STOP) + { + cec_keypress key; + key.duration = 500; + key.keycode = CEC_USER_CONTROL_CODE_STOP; + adapter->PushCecKeypress(key); + } + break; + case CEC_OPCODE_PLAY: + if (command->initiator == CECDEVICE_TV && command->parameters.size == 1) + { + if (command->parameters[0] == CEC_PLAY_MODE_PLAY_FORWARD) + { + cec_keypress key; + key.duration = 500; + key.keycode = CEC_USER_CONTROL_CODE_PLAY; + adapter->PushCecKeypress(key); + } + else if (command->parameters[0] == CEC_PLAY_MODE_PLAY_STILL) + { + cec_keypress key; + key.duration = 500; + key.keycode = CEC_USER_CONTROL_CODE_PAUSE; + adapter->PushCecKeypress(key); + } + } + break; + default: + break; + } + } +} + +void CPeripheralCecAdapter::CecConfiguration(void* cbParam, const libcec_configuration* config) +{ + CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam); + if (!adapter) + return; + + std::unique_lock<CCriticalSection> lock(adapter->m_critSection); + adapter->SetConfigurationFromLibCEC(*config); +} + +void CPeripheralCecAdapter::CecAlert(void* cbParam, + const libcec_alert alert, + const libcec_parameter data) +{ + CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam); + if (!adapter) + return; + + bool bReopenConnection(false); + int iAlertString(0); + switch (alert) + { + case CEC_ALERT_SERVICE_DEVICE: + iAlertString = 36027; + break; + case CEC_ALERT_CONNECTION_LOST: + bReopenConnection = true; + iAlertString = 36030; + break; +#if defined(CEC_ALERT_PERMISSION_ERROR) + case CEC_ALERT_PERMISSION_ERROR: + bReopenConnection = true; + iAlertString = 36031; + break; + case CEC_ALERT_PORT_BUSY: + bReopenConnection = true; + iAlertString = 36032; + break; +#endif + default: + break; + } + + // display the alert + if (iAlertString) + { + std::string strLog(g_localizeStrings.Get(iAlertString)); + if (data.paramType == CEC_PARAMETER_TYPE_STRING && data.paramData) + strLog += StringUtils::Format(" - {}", (const char*)data.paramData); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), + strLog); + } + + if (bReopenConnection) + { + // Reopen the connection asynchronously. Otherwise a deadlock may occur. + // Reconnect means destruction and recreation of our libcec instance, but libcec + // calls this callback function synchronously and must not be destroyed meanwhile. + adapter->ReopenConnection(true); + } +} + +void CPeripheralCecAdapter::CecKeyPress(void* cbParam, const cec_keypress* key) +{ + CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam); + if (!!adapter) + adapter->PushCecKeypress(*key); +} + +void CPeripheralCecAdapter::GetNextKey(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bHasButton = false; + if (m_bIsReady) + { + std::vector<CecButtonPress>::iterator it = m_buttonQueue.begin(); + if (it != m_buttonQueue.end()) + { + m_currentButton = (*it); + m_buttonQueue.erase(it); + m_bHasButton = true; + } + } +} + +void CPeripheralCecAdapter::PushCecKeypress(const CecButtonPress& key) +{ + CLog::Log(LOGDEBUG, "{} - received key {:2x} duration {}", __FUNCTION__, key.iButton, + key.iDuration); + + std::unique_lock<CCriticalSection> lock(m_critSection); + // avoid the queue getting too long + if (m_configuration.iButtonRepeatRateMs && m_buttonQueue.size() > 5) + return; + if (m_configuration.iButtonRepeatRateMs == 0 && key.iDuration > 0) + { + if (m_currentButton.iButton == key.iButton && m_currentButton.iDuration == 0) + { + // update the duration + if (m_bHasButton) + m_currentButton.iDuration = key.iDuration; + // ignore this one, since it's already been handled by xbmc + return; + } + // if we received a keypress with a duration set, try to find the same one without a duration + // set, and replace it + for (std::vector<CecButtonPress>::reverse_iterator it = m_buttonQueue.rbegin(); + it != m_buttonQueue.rend(); ++it) + { + if ((*it).iButton == key.iButton) + { + if ((*it).iDuration == 0) + { + // replace this entry + (*it).iDuration = key.iDuration; + return; + } + // add a new entry + break; + } + } + } + + m_buttonQueue.push_back(key); +} + +void CPeripheralCecAdapter::PushCecKeypress(const cec_keypress& key) +{ + CecButtonPress xbmcKey; + xbmcKey.iDuration = key.duration; + + switch (key.keycode) + { + case CEC_USER_CONTROL_CODE_SELECT: + xbmcKey.iButton = XINPUT_IR_REMOTE_SELECT; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_UP: + xbmcKey.iButton = XINPUT_IR_REMOTE_UP; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_DOWN: + xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_LEFT: + xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_LEFT_UP: + xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT; + PushCecKeypress(xbmcKey); + xbmcKey.iButton = XINPUT_IR_REMOTE_UP; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_LEFT_DOWN: + xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT; + PushCecKeypress(xbmcKey); + xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_RIGHT: + xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_RIGHT_UP: + xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT; + PushCecKeypress(xbmcKey); + xbmcKey.iButton = XINPUT_IR_REMOTE_UP; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_RIGHT_DOWN: + xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT; + PushCecKeypress(xbmcKey); + xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_SETUP_MENU: + xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_CONTENTS_MENU: + xbmcKey.iButton = XINPUT_IR_REMOTE_CONTENTS_MENU; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_ROOT_MENU: + xbmcKey.iButton = XINPUT_IR_REMOTE_ROOT_MENU; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_TOP_MENU: + xbmcKey.iButton = XINPUT_IR_REMOTE_TOP_MENU; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_DVD_MENU: + xbmcKey.iButton = XINPUT_IR_REMOTE_DVD_MENU; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_FAVORITE_MENU: + xbmcKey.iButton = XINPUT_IR_REMOTE_MENU; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_EXIT: + xbmcKey.iButton = XINPUT_IR_REMOTE_BACK; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_ENTER: + xbmcKey.iButton = XINPUT_IR_REMOTE_ENTER; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_CHANNEL_DOWN: + xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_CHANNEL_UP: + xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_PREVIOUS_CHANNEL: + xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_SOUND_SELECT: + xbmcKey.iButton = XINPUT_IR_REMOTE_LANGUAGE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_POWER: + case CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION: + case CEC_USER_CONTROL_CODE_POWER_OFF_FUNCTION: + xbmcKey.iButton = XINPUT_IR_REMOTE_POWER; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_VOLUME_UP: + xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_PLUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_VOLUME_DOWN: + xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_MINUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_MUTE: + case CEC_USER_CONTROL_CODE_MUTE_FUNCTION: + case CEC_USER_CONTROL_CODE_RESTORE_VOLUME_FUNCTION: + xbmcKey.iButton = XINPUT_IR_REMOTE_MUTE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_PLAY: + xbmcKey.iButton = XINPUT_IR_REMOTE_PLAY; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_STOP: + xbmcKey.iButton = XINPUT_IR_REMOTE_STOP; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_PAUSE: + xbmcKey.iButton = XINPUT_IR_REMOTE_PAUSE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_REWIND: + xbmcKey.iButton = XINPUT_IR_REMOTE_REVERSE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_FAST_FORWARD: + xbmcKey.iButton = XINPUT_IR_REMOTE_FORWARD; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER0: + xbmcKey.iButton = XINPUT_IR_REMOTE_0; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER1: + xbmcKey.iButton = XINPUT_IR_REMOTE_1; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER2: + xbmcKey.iButton = XINPUT_IR_REMOTE_2; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER3: + xbmcKey.iButton = XINPUT_IR_REMOTE_3; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER4: + xbmcKey.iButton = XINPUT_IR_REMOTE_4; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER5: + xbmcKey.iButton = XINPUT_IR_REMOTE_5; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER6: + xbmcKey.iButton = XINPUT_IR_REMOTE_6; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER7: + xbmcKey.iButton = XINPUT_IR_REMOTE_7; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER8: + xbmcKey.iButton = XINPUT_IR_REMOTE_8; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NUMBER9: + xbmcKey.iButton = XINPUT_IR_REMOTE_9; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_RECORD: + xbmcKey.iButton = XINPUT_IR_REMOTE_RECORD; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_CLEAR: + xbmcKey.iButton = XINPUT_IR_REMOTE_CLEAR; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION: + xbmcKey.iButton = XINPUT_IR_REMOTE_INFO; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_PAGE_UP: + xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_PAGE_DOWN: + xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_FORWARD: + xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_PLUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_BACKWARD: + xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_MINUS; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_F1_BLUE: + xbmcKey.iButton = XINPUT_IR_REMOTE_BLUE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_F2_RED: + xbmcKey.iButton = XINPUT_IR_REMOTE_RED; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_F3_GREEN: + xbmcKey.iButton = XINPUT_IR_REMOTE_GREEN; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_F4_YELLOW: + xbmcKey.iButton = XINPUT_IR_REMOTE_YELLOW; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_ELECTRONIC_PROGRAM_GUIDE: + xbmcKey.iButton = XINPUT_IR_REMOTE_GUIDE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST: + xbmcKey.iButton = XINPUT_IR_REMOTE_LIVE_TV; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_NEXT_FAVORITE: + case CEC_USER_CONTROL_CODE_DOT: + case CEC_USER_CONTROL_CODE_AN_RETURN: + xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE; // context menu + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_DATA: + xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_SUB_PICTURE: + xbmcKey.iButton = XINPUT_IR_REMOTE_SUBTITLE; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_EJECT: + xbmcKey.iButton = XINPUT_IR_REMOTE_EJECT; + PushCecKeypress(xbmcKey); + break; + case CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION: + case CEC_USER_CONTROL_CODE_INPUT_SELECT: + case CEC_USER_CONTROL_CODE_INITIAL_CONFIGURATION: + case CEC_USER_CONTROL_CODE_HELP: + case CEC_USER_CONTROL_CODE_STOP_RECORD: + case CEC_USER_CONTROL_CODE_PAUSE_RECORD: + case CEC_USER_CONTROL_CODE_ANGLE: + case CEC_USER_CONTROL_CODE_VIDEO_ON_DEMAND: + case CEC_USER_CONTROL_CODE_TIMER_PROGRAMMING: + case CEC_USER_CONTROL_CODE_PLAY_FUNCTION: + case CEC_USER_CONTROL_CODE_PAUSE_PLAY_FUNCTION: + case CEC_USER_CONTROL_CODE_RECORD_FUNCTION: + case CEC_USER_CONTROL_CODE_PAUSE_RECORD_FUNCTION: + case CEC_USER_CONTROL_CODE_STOP_FUNCTION: + case CEC_USER_CONTROL_CODE_TUNE_FUNCTION: + case CEC_USER_CONTROL_CODE_SELECT_MEDIA_FUNCTION: + case CEC_USER_CONTROL_CODE_SELECT_AV_INPUT_FUNCTION: + case CEC_USER_CONTROL_CODE_SELECT_AUDIO_INPUT_FUNCTION: + case CEC_USER_CONTROL_CODE_F5: + case CEC_USER_CONTROL_CODE_NUMBER_ENTRY_MODE: + case CEC_USER_CONTROL_CODE_NUMBER11: + case CEC_USER_CONTROL_CODE_NUMBER12: + case CEC_USER_CONTROL_CODE_SELECT_BROADCAST_TYPE: + case CEC_USER_CONTROL_CODE_SELECT_SOUND_PRESENTATION: + case CEC_USER_CONTROL_CODE_UNKNOWN: + default: + break; + } +} + +int CPeripheralCecAdapter::GetButton(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_bHasButton) + GetNextKey(); + + return m_bHasButton ? m_currentButton.iButton : 0; +} + +unsigned int CPeripheralCecAdapter::GetHoldTime(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_bHasButton) + GetNextKey(); + + return m_bHasButton ? m_currentButton.iDuration : 0; +} + +void CPeripheralCecAdapter::ResetButton(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bHasButton = false; + + // wait for the key release if the duration isn't 0 + if (m_currentButton.iDuration > 0) + { + m_currentButton.iButton = 0; + m_currentButton.iDuration = 0; + } +} + +void CPeripheralCecAdapter::OnSettingChanged(const std::string& strChangedSetting) +{ + if (StringUtils::EqualsNoCase(strChangedSetting, "enabled")) + { + bool bEnabled(GetSettingBool("enabled")); + if (!bEnabled && IsRunning()) + { + CLog::Log(LOGDEBUG, "{} - closing the CEC connection", __FUNCTION__); + StopThread(true); + } + else if (bEnabled && !IsRunning()) + { + CLog::Log(LOGDEBUG, "{} - starting the CEC connection", __FUNCTION__); + SetConfigurationFromSettings(); + InitialiseFeature(FEATURE_CEC); + } + } + else if (IsRunning()) + { + if (m_queryThread->IsRunning()) + { + CLog::Log(LOGDEBUG, "{} - sending the updated configuration to libCEC", __FUNCTION__); + SetConfigurationFromSettings(); + m_queryThread->UpdateConfiguration(&m_configuration); + } + } + else + { + CLog::Log(LOGDEBUG, "{} - restarting the CEC connection", __FUNCTION__); + SetConfigurationFromSettings(); + InitialiseFeature(FEATURE_CEC); + } +} + +void CPeripheralCecAdapter::CecSourceActivated(void* cbParam, + const CEC::cec_logical_address address, + const uint8_t activated) +{ + CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam); + if (!adapter) + return; + + // wake up the screensaver, so the user doesn't switch to a black screen + if (activated == 1) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->WakeUpScreenSaverAndDPMS(); + } + + if (adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") != LOCALISED_ID_NONE) + { + bool bShowingSlideshow = + (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW); + CGUIWindowSlideShow* pSlideShow = + bShowingSlideshow + ? CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>( + WINDOW_SLIDESHOW) + : NULL; + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + bool bPlayingAndDeactivated = activated == 0 && ((pSlideShow && pSlideShow->IsPlaying()) || + !appPlayer->IsPausedPlayback()); + bool bPausedAndActivated = + activated == 1 && adapter->m_bPlaybackPaused && + ((pSlideShow && pSlideShow->IsPaused()) || (appPlayer && appPlayer->IsPausedPlayback())); + if (bPlayingAndDeactivated) + adapter->m_bPlaybackPaused = true; + else if (bPausedAndActivated) + adapter->m_bPlaybackPaused = false; + + if ((bPlayingAndDeactivated || bPausedAndActivated) && + adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") == LOCALISED_ID_PAUSE) + { + if (pSlideShow) + // pause/resume slideshow + pSlideShow->OnAction(CAction(ACTION_PAUSE)); + else + // pause/resume player + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE); + } + else if (bPlayingAndDeactivated && + adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") == LOCALISED_ID_STOP) + { + if (pSlideShow) + pSlideShow->OnAction(CAction(ACTION_STOP)); + else + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_STOP); + } + } +} + +void CPeripheralCecAdapter::CecLogMessage(void* cbParam, const cec_log_message* message) +{ + CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam); + if (!adapter) + return; + + int iLevel = -1; + switch (message->level) + { + case CEC_LOG_ERROR: + iLevel = LOGERROR; + break; + case CEC_LOG_WARNING: + iLevel = LOGWARNING; + break; + case CEC_LOG_NOTICE: + iLevel = LOGDEBUG; + break; + case CEC_LOG_TRAFFIC: + case CEC_LOG_DEBUG: + iLevel = LOGDEBUG; + break; + default: + break; + } + + if (iLevel >= CEC_LOG_NOTICE || + (iLevel >= 0 && CServiceBroker::GetLogging().IsLogLevelLogged(LOGDEBUG))) + CLog::Log(iLevel, LOGCEC, "{} - {}", __FUNCTION__, message->message); +} + +void CPeripheralCecAdapter::SetConfigurationFromLibCEC(const CEC::libcec_configuration& config) +{ + bool bChanged(false); + + // set the primary device type + m_configuration.deviceTypes.Clear(); + m_configuration.deviceTypes.Add(config.deviceTypes[0]); + + // hide the "connected device" and "hdmi port number" settings when the PA was autodetected + bool bPAAutoDetected(config.bAutodetectAddress == 1); + + SetSettingVisible("connected_device", !bPAAutoDetected); + SetSettingVisible("cec_hdmi_port", !bPAAutoDetected); + + // set the connected device + m_configuration.baseDevice = config.baseDevice; + bChanged |= + SetSetting("connected_device", + config.baseDevice == CECDEVICE_AUDIOSYSTEM ? LOCALISED_ID_AVR : LOCALISED_ID_TV); + + // set the HDMI port number + m_configuration.iHDMIPort = config.iHDMIPort; + bChanged |= SetSetting("cec_hdmi_port", config.iHDMIPort); + + // set the physical address, when baseDevice or iHDMIPort are not set + std::string strPhysicalAddress("0"); + if (!bPAAutoDetected && (m_configuration.baseDevice == CECDEVICE_UNKNOWN || + m_configuration.iHDMIPort < CEC_MIN_HDMI_PORTNUMBER || + m_configuration.iHDMIPort > CEC_MAX_HDMI_PORTNUMBER)) + { + m_configuration.iPhysicalAddress = config.iPhysicalAddress; + strPhysicalAddress = StringUtils::Format("{:x}", config.iPhysicalAddress); + } + bChanged |= SetSetting("physical_address", strPhysicalAddress); + + // set the devices to wake when starting + m_configuration.wakeDevices = config.wakeDevices; + bChanged |= WriteLogicalAddresses(config.wakeDevices, "wake_devices", "wake_devices_advanced"); + + // set the devices to power off when stopping + m_configuration.powerOffDevices = config.powerOffDevices; + bChanged |= + WriteLogicalAddresses(config.powerOffDevices, "standby_devices", "standby_devices_advanced"); + + // set the boolean settings + m_configuration.bActivateSource = config.bActivateSource; + bChanged |= SetSetting("activate_source", m_configuration.bActivateSource == 1); + + m_configuration.iDoubleTapTimeoutMs = config.iDoubleTapTimeoutMs; + bChanged |= SetSetting("double_tap_timeout_ms", (int)m_configuration.iDoubleTapTimeoutMs); + + m_configuration.iButtonRepeatRateMs = config.iButtonRepeatRateMs; + bChanged |= SetSetting("button_repeat_rate_ms", (int)m_configuration.iButtonRepeatRateMs); + + m_configuration.iButtonReleaseDelayMs = config.iButtonReleaseDelayMs; + bChanged |= SetSetting("button_release_delay_ms", (int)m_configuration.iButtonReleaseDelayMs); + + m_configuration.bPowerOffOnStandby = config.bPowerOffOnStandby; + + m_configuration.iFirmwareVersion = config.iFirmwareVersion; + + memcpy(m_configuration.strDeviceLanguage, config.strDeviceLanguage, 3); + m_configuration.iFirmwareBuildDate = config.iFirmwareBuildDate; + + SetVersionInfo(m_configuration); + + if (bChanged) + CLog::Log(LOGDEBUG, "SetConfigurationFromLibCEC - settings updated by libCEC"); +} + +void CPeripheralCecAdapter::SetConfigurationFromSettings(void) +{ + // client version matches the version of libCEC that we originally used the API from + m_configuration.clientVersion = LIBCEC_VERSION_TO_UINT(4, 0, 0); + + // device name 'XBMC' + snprintf(m_configuration.strDeviceName, 13, "%s", GetSettingString("device_name").c_str()); + + // set the primary device type + m_configuration.deviceTypes.Clear(); + switch (GetSettingInt("device_type")) + { + case LOCALISED_ID_PLAYBACK_DEVICE: + m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_PLAYBACK_DEVICE); + break; + case LOCALISED_ID_TUNER_DEVICE: + m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_TUNER); + break; + case LOCALISED_ID_RECORDING_DEVICE: + default: + m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE); + break; + } + + // always try to autodetect the address. + // when the firmware supports this, it will override the physical address, connected device and + // hdmi port settings + m_configuration.bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS; + + // set the physical address + // when set, it will override the connected device and hdmi port settings + std::string strPhysicalAddress = GetSettingString("physical_address"); + int iPhysicalAddress; + if (sscanf(strPhysicalAddress.c_str(), "%x", &iPhysicalAddress) && + iPhysicalAddress >= CEC_PHYSICAL_ADDRESS_TV && iPhysicalAddress <= CEC_MAX_PHYSICAL_ADDRESS) + m_configuration.iPhysicalAddress = iPhysicalAddress; + else + m_configuration.iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV; + + // set the connected device + int iConnectedDevice = GetSettingInt("connected_device"); + if (iConnectedDevice == LOCALISED_ID_AVR) + m_configuration.baseDevice = CECDEVICE_AUDIOSYSTEM; + else if (iConnectedDevice == LOCALISED_ID_TV) + m_configuration.baseDevice = CECDEVICE_TV; + + // set the HDMI port number + int iHDMIPort = GetSettingInt("cec_hdmi_port"); + if (iHDMIPort >= CEC_MIN_HDMI_PORTNUMBER && iHDMIPort <= CEC_MAX_HDMI_PORTNUMBER) + m_configuration.iHDMIPort = iHDMIPort; + + // set the tv vendor override + int iVendor = GetSettingInt("tv_vendor"); + if (iVendor >= CEC_MIN_VENDORID && iVendor <= CEC_MAX_VENDORID) + m_configuration.tvVendor = iVendor; + + // read the devices to wake when starting + std::string strWakeDevices = GetSettingString("wake_devices_advanced"); + StringUtils::Trim(strWakeDevices); + m_configuration.wakeDevices.Clear(); + if (!strWakeDevices.empty()) + ReadLogicalAddresses(strWakeDevices, m_configuration.wakeDevices); + else + ReadLogicalAddresses(GetSettingInt("wake_devices"), m_configuration.wakeDevices); + + // read the devices to power off when stopping + std::string strStandbyDevices = GetSettingString("standby_devices_advanced"); + StringUtils::Trim(strStandbyDevices); + m_configuration.powerOffDevices.Clear(); + if (!strStandbyDevices.empty()) + ReadLogicalAddresses(strStandbyDevices, m_configuration.powerOffDevices); + else + ReadLogicalAddresses(GetSettingInt("standby_devices"), m_configuration.powerOffDevices); + + // read the boolean settings + m_bUseTVMenuLanguage = GetSettingBool("use_tv_menu_language"); + m_configuration.bActivateSource = GetSettingBool("activate_source") ? 1 : 0; + m_bPowerOffScreensaver = GetSettingBool("cec_standby_screensaver"); + m_bPowerOnScreensaver = GetSettingBool("cec_wake_screensaver"); + m_bSendInactiveSource = GetSettingBool("send_inactive_source"); + m_configuration.bAutoWakeAVR = GetSettingBool("power_avr_on_as") ? 1 : 0; + + // read the mutually exclusive boolean settings + int iStandbyAction(GetSettingInt("standby_pc_on_tv_standby")); + m_configuration.bPowerOffOnStandby = + (iStandbyAction == LOCALISED_ID_SUSPEND || iStandbyAction == LOCALISED_ID_HIBERNATE) ? 1 : 0; + m_bShutdownOnStandby = iStandbyAction == LOCALISED_ID_POWEROFF; + + // double tap prevention timeout in ms + m_configuration.iDoubleTapTimeoutMs = GetSettingInt("double_tap_timeout_ms"); + m_configuration.iButtonRepeatRateMs = GetSettingInt("button_repeat_rate_ms"); + m_configuration.iButtonReleaseDelayMs = GetSettingInt("button_release_delay_ms"); + + if (GetSettingBool("pause_playback_on_deactivate")) + { + SetSetting("pause_or_stop_playback_on_deactivate", LOCALISED_ID_PAUSE); + SetSetting("pause_playback_on_deactivate", false); + } +} + +void CPeripheralCecAdapter::ReadLogicalAddresses(const std::string& strString, + cec_logical_addresses& addresses) +{ + for (size_t iPtr = 0; iPtr < strString.size(); iPtr++) + { + std::string strDevice = strString.substr(iPtr, 1); + StringUtils::Trim(strDevice); + if (!strDevice.empty()) + { + int iDevice(0); + if (sscanf(strDevice.c_str(), "%x", &iDevice) == 1 && iDevice >= 0 && iDevice <= 0xF) + addresses.Set((cec_logical_address)iDevice); + } + } +} + +void CPeripheralCecAdapter::ReadLogicalAddresses(int iLocalisedId, cec_logical_addresses& addresses) +{ + addresses.Clear(); + switch (iLocalisedId) + { + case LOCALISED_ID_TV: + addresses.Set(CECDEVICE_TV); + break; + case LOCALISED_ID_AVR: + addresses.Set(CECDEVICE_AUDIOSYSTEM); + break; + case LOCALISED_ID_TV_AVR: + addresses.Set(CECDEVICE_TV); + addresses.Set(CECDEVICE_AUDIOSYSTEM); + break; + case LOCALISED_ID_NONE: + default: + break; + } +} + +bool CPeripheralCecAdapter::WriteLogicalAddresses(const cec_logical_addresses& addresses, + const std::string& strSettingName, + const std::string& strAdvancedSettingName) +{ + bool bChanged(false); + + // only update the advanced setting if it was set by the user + if (!GetSettingString(strAdvancedSettingName).empty()) + { + std::string strPowerOffDevices; + for (unsigned int iPtr = CECDEVICE_TV; iPtr <= CECDEVICE_BROADCAST; iPtr++) + if (addresses[iPtr]) + strPowerOffDevices += StringUtils::Format(" {:X}", iPtr); + StringUtils::Trim(strPowerOffDevices); + bChanged = SetSetting(strAdvancedSettingName, strPowerOffDevices); + } + + int iSettingPowerOffDevices = LOCALISED_ID_NONE; + if (addresses[CECDEVICE_TV] && addresses[CECDEVICE_AUDIOSYSTEM]) + iSettingPowerOffDevices = LOCALISED_ID_TV_AVR; + else if (addresses[CECDEVICE_TV]) + iSettingPowerOffDevices = LOCALISED_ID_TV; + else if (addresses[CECDEVICE_AUDIOSYSTEM]) + iSettingPowerOffDevices = LOCALISED_ID_AVR; + return SetSetting(strSettingName, iSettingPowerOffDevices) || bChanged; +} + +CPeripheralCecAdapterUpdateThread::CPeripheralCecAdapterUpdateThread( + CPeripheralCecAdapter* adapter, libcec_configuration* configuration) + : CThread("CECAdapterUpdate"), + m_adapter(adapter), + m_configuration(*configuration), + m_bNextConfigurationScheduled(false), + m_bIsUpdating(true) +{ + m_nextConfiguration.Clear(); + m_event.Reset(); +} + +CPeripheralCecAdapterUpdateThread::~CPeripheralCecAdapterUpdateThread(void) +{ + StopThread(false); + m_event.Set(); + StopThread(true); +} + +void CPeripheralCecAdapterUpdateThread::Signal(void) +{ + m_event.Set(); +} + +bool CPeripheralCecAdapterUpdateThread::UpdateConfiguration(libcec_configuration* configuration) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!configuration) + return false; + + if (m_bIsUpdating) + { + m_bNextConfigurationScheduled = true; + m_nextConfiguration = *configuration; + } + else + { + m_configuration = *configuration; + m_event.Set(); + } + return true; +} + +bool CPeripheralCecAdapterUpdateThread::WaitReady(void) +{ + // don't wait if we're not powering up anything + if (m_configuration.wakeDevices.IsEmpty() && m_configuration.bActivateSource == 0) + return true; + + // wait for the TV if we're configured to become the active source. + // wait for the first device in the wake list otherwise. + cec_logical_address waitFor = + (m_configuration.bActivateSource == 1) ? CECDEVICE_TV : m_configuration.wakeDevices.primary; + + cec_power_status powerStatus(CEC_POWER_STATUS_UNKNOWN); + bool bContinue(true); + while (bContinue && !m_adapter->m_bStop && !m_bStop && powerStatus != CEC_POWER_STATUS_ON) + { + powerStatus = m_adapter->m_cecAdapter->GetDevicePowerStatus(waitFor); + if (powerStatus != CEC_POWER_STATUS_ON) + bContinue = !m_event.Wait(1000ms); + } + + return powerStatus == CEC_POWER_STATUS_ON; +} + +void CPeripheralCecAdapterUpdateThread::UpdateMenuLanguage(void) +{ + // request the menu language of the TV + if (m_adapter->m_bUseTVMenuLanguage == 1) + { + CLog::Log(LOGDEBUG, "{} - requesting the menu language of the TV", __FUNCTION__); + std::string language(m_adapter->m_cecAdapter->GetDeviceMenuLanguage(CECDEVICE_TV)); + m_adapter->SetMenuLanguage(language.c_str()); + } + else + { + CLog::Log(LOGDEBUG, "{} - using TV menu language is disabled", __FUNCTION__); + } +} + +std::string CPeripheralCecAdapterUpdateThread::UpdateAudioSystemStatus(void) +{ + std::string strAmpName; + + /* disable the mute setting when an amp is found, because the amp handles the mute setting and + set PCM output to 100% */ + if (m_adapter->m_cecAdapter->IsActiveDeviceType(CEC_DEVICE_TYPE_AUDIO_SYSTEM)) + { + // request the OSD name of the amp + std::string ampName(m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_AUDIOSYSTEM)); + CLog::Log(LOGDEBUG, + "{} - CEC capable amplifier found ({}). volume will be controlled on the amp", + __FUNCTION__, ampName); + strAmpName += ampName; + + // set amp present + m_adapter->SetAudioSystemConnected(true); + auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + appVolume->SetMute(false); + appVolume->SetVolume(CApplicationVolumeHandling::VOLUME_MAXIMUM, false); + } + else + { + // set amp present + CLog::Log(LOGDEBUG, "{} - no CEC capable amplifier found", __FUNCTION__); + m_adapter->SetAudioSystemConnected(false); + } + + return strAmpName; +} + +bool CPeripheralCecAdapterUpdateThread::SetInitialConfiguration(void) +{ + // the option to make XBMC the active source is set + if (m_configuration.bActivateSource == 1) + m_adapter->m_cecAdapter->SetActiveSource(); + + // devices to wake are set + cec_logical_addresses tvOnly; + tvOnly.Clear(); + tvOnly.Set(CECDEVICE_TV); + if (!m_configuration.wakeDevices.IsEmpty() && + (m_configuration.wakeDevices != tvOnly || m_configuration.bActivateSource == 0)) + m_adapter->m_cecAdapter->PowerOnDevices(CECDEVICE_BROADCAST); + + // wait until devices are powered up + if (!WaitReady()) + return false; + + UpdateMenuLanguage(); + + // request the OSD name of the TV + std::string strNotification; + std::string tvName(m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_TV)); + strNotification = StringUtils::Format("{}: {}", g_localizeStrings.Get(36016), tvName); + + std::string strAmpName = UpdateAudioSystemStatus(); + if (!strAmpName.empty()) + strNotification += StringUtils::Format("- {}", strAmpName); + + m_adapter->m_bIsReady = true; + + // and let the gui know that we're done + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), + strNotification); + + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bIsUpdating = false; + return true; +} + +bool CPeripheralCecAdapter::IsRunning(void) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_bIsRunning; +} + +void CPeripheralCecAdapterUpdateThread::Process(void) +{ + // set the initial configuration + if (!SetInitialConfiguration()) + return; + + // and wait for updates + bool bUpdate(false); + while (!m_bStop) + { + // update received + if (bUpdate || m_event.Wait(500ms)) + { + if (m_bStop) + return; + // set the new configuration + libcec_configuration configuration; + { + std::unique_lock<CCriticalSection> lock(m_critSection); + configuration = m_configuration; + m_bIsUpdating = false; + } + + CLog::Log(LOGDEBUG, "{} - updating the configuration", __FUNCTION__); + bool bConfigSet(m_adapter->m_cecAdapter->SetConfiguration(&configuration)); + // display message: config updated / failed to update + if (!bConfigSet) + CLog::Log(LOGERROR, "{} - libCEC couldn't set the new configuration", __FUNCTION__); + else + { + UpdateMenuLanguage(); + UpdateAudioSystemStatus(); + } + + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), + g_localizeStrings.Get(bConfigSet ? 36023 : 36024)); + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + if ((bUpdate = m_bNextConfigurationScheduled) == true) + { + // another update is scheduled + m_bNextConfigurationScheduled = false; + m_configuration = m_nextConfiguration; + } + else + { + // nothing left to do, wait for updates + m_bIsUpdating = false; + m_event.Reset(); + } + } + } + } +} + +void CPeripheralCecAdapter::OnDeviceRemoved(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bDeviceRemoved = true; +} + +namespace PERIPHERALS +{ + +class CPeripheralCecAdapterReopenJob : public CJob +{ +public: + CPeripheralCecAdapterReopenJob(CPeripheralCecAdapter* adapter) : m_adapter(adapter) {} + ~CPeripheralCecAdapterReopenJob() override = default; + + bool DoWork(void) override { return m_adapter->ReopenConnection(false); } + +private: + CPeripheralCecAdapter* m_adapter; +}; + +}; // namespace PERIPHERALS + +bool CPeripheralCecAdapter::ReopenConnection(bool bAsync /* = false */) +{ + if (bAsync) + { + CServiceBroker::GetJobManager()->AddJob(new CPeripheralCecAdapterReopenJob(this), nullptr, + CJob::PRIORITY_NORMAL); + return true; + } + + // stop running thread + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_iExitCode = EXITCODE_RESTARTAPP; + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + StopThread(false); + } + StopThread(); + + // reset all members to their defaults + ResetMembers(); + + // reopen the connection + return InitialiseFeature(FEATURE_CEC); +} + +void CPeripheralCecAdapter::ActivateSource(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bActiveSourcePending = true; +} + +void CPeripheralCecAdapter::ProcessActivateSource(void) +{ + bool bActivate(false); + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bActivate = m_bActiveSourcePending; + m_bActiveSourcePending = false; + } + + if (bActivate) + m_cecAdapter->SetActiveSource(); +} + +void CPeripheralCecAdapter::StandbyDevices(void) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_bStandbyPending = true; +} + +void CPeripheralCecAdapter::ProcessStandbyDevices(void) +{ + bool bStandby(false); + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + bStandby = m_bStandbyPending; + m_bStandbyPending = false; + if (bStandby) + m_bGoingToStandby = true; + } + + if (bStandby) + { + if (!m_configuration.powerOffDevices.IsEmpty()) + { + m_standbySent = CDateTime::GetCurrentDateTime(); + m_cecAdapter->StandbyDevices(CECDEVICE_BROADCAST); + } + else if (m_bSendInactiveSource == 1) + { + CLog::Log(LOGDEBUG, "{} - sending inactive source commands", __FUNCTION__); + m_cecAdapter->SetInactiveView(); + } + } +} + +bool CPeripheralCecAdapter::ToggleDeviceState(CecStateChange mode /*= STATE_SWITCH_TOGGLE */, + bool forceType /*= false */) +{ + if (!IsRunning()) + return false; + if (m_cecAdapter->IsLibCECActiveSource() && + (mode == STATE_SWITCH_TOGGLE || mode == STATE_STANDBY)) + { + CLog::Log(LOGDEBUG, "{} - putting CEC device on standby...", __FUNCTION__); + StandbyDevices(); + return false; + } + else if (mode == STATE_SWITCH_TOGGLE || mode == STATE_ACTIVATE_SOURCE) + { + CLog::Log(LOGDEBUG, "{} - waking up CEC device...", __FUNCTION__); + ActivateSource(); + return true; + } + + return false; +} diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.h b/xbmc/peripherals/devices/PeripheralCecAdapter.h new file mode 100644 index 0000000..b3755b3 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralCecAdapter.h @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2005-2018 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 + +#if !defined(HAVE_LIBCEC) +#include "Peripheral.h" + +// an empty implementation, so CPeripherals can be compiled without a bunch of #ifdef's when libCEC +// is not available +namespace PERIPHERALS +{ +class CPeripheralCecAdapter : public CPeripheral +{ +public: + bool HasAudioControl(void) { return false; } + void VolumeUp(void) {} + void VolumeDown(void) {} + bool IsMuted(void) { return false; } + void ToggleMute(void) {} + bool ToggleDeviceState(CecStateChange mode = STATE_SWITCH_TOGGLE, bool forceType = false) + { + return false; + } + + int GetButton(void) { return 0; } + unsigned int GetHoldTime(void) { return 0; } + void ResetButton(void) {} +}; +} // namespace PERIPHERALS + +#else + +#include "PeripheralHID.h" +#include "XBDateTime.h" +#include "interfaces/AnnouncementManager.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" + +#include <chrono> +#include <queue> +#include <vector> + +// undefine macro isset, it collides with function in cectypes.h +#ifdef isset +#undef isset +#endif +#include <libcec/cectypes.h> + +class CVariant; + +namespace CEC +{ +class ICECAdapter; +}; + +namespace PERIPHERALS +{ +class CPeripheralCecAdapterUpdateThread; +class CPeripheralCecAdapterReopenJob; + +typedef struct +{ + int iButton; + unsigned int iDuration; +} CecButtonPress; + +typedef enum +{ + VOLUME_CHANGE_NONE, + VOLUME_CHANGE_UP, + VOLUME_CHANGE_DOWN, + VOLUME_CHANGE_MUTE +} CecVolumeChange; + +class CPeripheralCecAdapter : public CPeripheralHID, + public ANNOUNCEMENT::IAnnouncer, + private CThread +{ + friend class CPeripheralCecAdapterUpdateThread; + friend class CPeripheralCecAdapterReopenJob; + +public: + CPeripheralCecAdapter(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralCecAdapter(void) override; + + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + + // audio control + bool HasAudioControl(void); + void VolumeUp(void); + void VolumeDown(void); + void ToggleMute(void); + bool IsMuted(void); + + // CPeripheral callbacks + void OnSettingChanged(const std::string& strChangedSetting) override; + void OnDeviceRemoved(void) override; + + // input + int GetButton(void); + unsigned int GetHoldTime(void); + void ResetButton(void); + + // public CEC methods + void ActivateSource(void); + void StandbyDevices(void); + bool ToggleDeviceState(CecStateChange mode = STATE_SWITCH_TOGGLE, bool forceType = false); + +private: + bool InitialiseFeature(const PeripheralFeature feature) override; + void ResetMembers(void); + void Process(void) override; + bool IsRunning(void) const; + + bool OpenConnection(void); + bool ReopenConnection(bool bAsync = false); + + void SetConfigurationFromSettings(void); + void SetConfigurationFromLibCEC(const CEC::libcec_configuration& config); + void SetVersionInfo(const CEC::libcec_configuration& configuration); + + static void ReadLogicalAddresses(const std::string& strString, + CEC::cec_logical_addresses& addresses); + static void ReadLogicalAddresses(int iLocalisedId, CEC::cec_logical_addresses& addresses); + bool WriteLogicalAddresses(const CEC::cec_logical_addresses& addresses, + const std::string& strSettingName, + const std::string& strAdvancedSettingName); + + void ProcessActivateSource(void); + void ProcessStandbyDevices(void); + void ProcessVolumeChange(void); + + void PushCecKeypress(const CEC::cec_keypress& key); + void PushCecKeypress(const CecButtonPress& key); + void GetNextKey(void); + + void SetAudioSystemConnected(bool bSetTo); + void SetMenuLanguage(const char* strLanguage); + void OnTvStandby(void); + + // callbacks from libCEC + static void CecLogMessage(void* cbParam, const CEC::cec_log_message* message); + static void CecCommand(void* cbParam, const CEC::cec_command* command); + static void CecConfiguration(void* cbParam, const CEC::libcec_configuration* config); + static void CecAlert(void* cbParam, + const CEC::libcec_alert alert, + const CEC::libcec_parameter data); + static void CecSourceActivated(void* param, + const CEC::cec_logical_address address, + const uint8_t activated); + static void CecKeyPress(void* cbParam, const CEC::cec_keypress* key); + + CEC::ICECAdapter* m_cecAdapter; + bool m_bStarted; + bool m_bHasButton; + bool m_bIsReady; + bool m_bHasConnectedAudioSystem; + std::string m_strMenuLanguage; + CDateTime m_standbySent; + std::vector<CecButtonPress> m_buttonQueue; + CecButtonPress m_currentButton; + std::queue<CecVolumeChange> m_volumeChangeQueue; + std::chrono::time_point<std::chrono::steady_clock> m_lastKeypress; + CecVolumeChange m_lastChange; + int m_iExitCode; + bool m_bIsMuted; + bool m_bGoingToStandby; + bool m_bIsRunning; + bool m_bDeviceRemoved; + CPeripheralCecAdapterUpdateThread* m_queryThread; + CEC::ICECCallbacks m_callbacks; + mutable CCriticalSection m_critSection; + CEC::libcec_configuration m_configuration; + bool m_bActiveSourcePending; + bool m_bStandbyPending; + CDateTime m_preventActivateSourceOnPlay; + bool m_bActiveSourceBeforeStandby; + bool m_bOnPlayReceived; + bool m_bPlaybackPaused; + std::string m_strComPort; + bool m_bPowerOnScreensaver; + bool m_bUseTVMenuLanguage; + bool m_bSendInactiveSource; + bool m_bPowerOffScreensaver; + bool m_bShutdownOnStandby; +}; + +class CPeripheralCecAdapterUpdateThread : public CThread +{ +public: + CPeripheralCecAdapterUpdateThread(CPeripheralCecAdapter* adapter, + CEC::libcec_configuration* configuration); + ~CPeripheralCecAdapterUpdateThread(void) override; + + void Signal(void); + bool UpdateConfiguration(CEC::libcec_configuration* configuration); + +protected: + void UpdateMenuLanguage(void); + std::string UpdateAudioSystemStatus(void); + bool WaitReady(void); + bool SetInitialConfiguration(void); + void Process(void) override; + + CPeripheralCecAdapter* m_adapter; + CEvent m_event; + CCriticalSection m_critSection; + CEC::libcec_configuration m_configuration; + CEC::libcec_configuration m_nextConfiguration; + bool m_bNextConfigurationScheduled; + bool m_bIsUpdating; +}; +} // namespace PERIPHERALS + +#endif diff --git a/xbmc/peripherals/devices/PeripheralDisk.cpp b/xbmc/peripherals/devices/PeripheralDisk.cpp new file mode 100644 index 0000000..9b0682c --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralDisk.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralDisk.h" + +#include "guilib/LocalizeStrings.h" + +using namespace PERIPHERALS; + +CPeripheralDisk::CPeripheralDisk(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus) +{ + m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35003) + : scanResult.m_strDeviceName; + m_features.push_back(FEATURE_DISK); +} diff --git a/xbmc/peripherals/devices/PeripheralDisk.h b/xbmc/peripherals/devices/PeripheralDisk.h new file mode 100644 index 0000000..f81b31e --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralDisk.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 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 "Peripheral.h" + +namespace PERIPHERALS +{ +class CPeripheralDisk : public CPeripheral +{ +public: + CPeripheralDisk(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralDisk(void) override = default; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralHID.cpp b/xbmc/peripherals/devices/PeripheralHID.cpp new file mode 100644 index 0000000..e3d061b --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralHID.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralHID.h" + +#include "guilib/LocalizeStrings.h" +#include "input/InputManager.h" +#include "peripherals/Peripherals.h" +#include "utils/log.h" + +using namespace PERIPHERALS; + +CPeripheralHID::CPeripheralHID(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus) +{ + m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35001) + : scanResult.m_strDeviceName; + m_features.push_back(FEATURE_HID); +} + +CPeripheralHID::~CPeripheralHID(void) +{ + if (!m_strKeymap.empty() && !GetSettingBool("do_not_use_custom_keymap")) + { + CLog::Log(LOGDEBUG, "{} - switching active keymapping to: default", __FUNCTION__); + m_manager.GetInputManager().RemoveKeymap(m_strKeymap); + } +} + +bool CPeripheralHID::InitialiseFeature(const PeripheralFeature feature) +{ + if (feature == FEATURE_HID && !m_bInitialised) + { + m_bInitialised = true; + + if (HasSetting("keymap")) + m_strKeymap = GetSettingString("keymap"); + + if (m_strKeymap.empty()) + { + m_strKeymap = StringUtils::Format("v{}p{}", VendorIdAsString(), ProductIdAsString()); + SetSetting("keymap", m_strKeymap); + } + + if (!IsSettingVisible("keymap")) + SetSettingVisible("do_not_use_custom_keymap", false); + + if (!m_strKeymap.empty()) + { + bool bKeymapEnabled(!GetSettingBool("do_not_use_custom_keymap")); + if (bKeymapEnabled) + { + CLog::Log(LOGDEBUG, "{} - adding keymapping for: {}", __FUNCTION__, m_strKeymap); + m_manager.GetInputManager().AddKeymap(m_strKeymap); + } + else + { + CLog::Log(LOGDEBUG, "{} - removing keymapping for: {}", __FUNCTION__, m_strKeymap); + m_manager.GetInputManager().RemoveKeymap(m_strKeymap); + } + } + + CLog::Log(LOGDEBUG, "{} - initialised HID device ({}:{})", __FUNCTION__, m_strVendorId, + m_strProductId); + } + + return CPeripheral::InitialiseFeature(feature); +} + +void CPeripheralHID::OnSettingChanged(const std::string& strChangedSetting) +{ + if (m_bInitialised && ((StringUtils::EqualsNoCase(strChangedSetting, "keymap") && + !GetSettingBool("do_not_use_custom_keymap")) || + StringUtils::EqualsNoCase(strChangedSetting, "keymap_enabled"))) + { + m_bInitialised = false; + InitialiseFeature(FEATURE_HID); + } +} diff --git a/xbmc/peripherals/devices/PeripheralHID.h b/xbmc/peripherals/devices/PeripheralHID.h new file mode 100644 index 0000000..3b35dfd --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralHID.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2018 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 "Peripheral.h" +#include "input/XBMC_keyboard.h" + +namespace PERIPHERALS +{ +class CPeripheralHID : public CPeripheral +{ +public: + CPeripheralHID(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralHID(void) override; + bool InitialiseFeature(const PeripheralFeature feature) override; + virtual bool LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode) + { + return false; + } + void OnSettingChanged(const std::string& strChangedSetting) override; + +protected: + std::string m_strKeymap; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralImon.cpp b/xbmc/peripherals/devices/PeripheralImon.cpp new file mode 100644 index 0000000..6683f8b --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralImon.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012-2018 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 "PeripheralImon.h" + +#include "input/InputManager.h" +#include "settings/Settings.h" +#include "utils/log.h" + +using namespace PERIPHERALS; + +std::atomic<long> CPeripheralImon::m_lCountOfImonsConflictWithDInput(0L); + +CPeripheralImon::CPeripheralImon(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheralHID(manager, scanResult, bus) +{ + m_features.push_back(FEATURE_IMON); + m_bImonConflictsWithDInput = false; +} + +void CPeripheralImon::OnDeviceRemoved() +{ + if (m_bImonConflictsWithDInput) + { + if (--m_lCountOfImonsConflictWithDInput == 0) + ActionOnImonConflict(false); + } +} + +bool CPeripheralImon::InitialiseFeature(const PeripheralFeature feature) +{ + if (feature == FEATURE_IMON) + { +#if defined(TARGET_WINDOWS) + if (HasSetting("disable_winjoystick") && GetSettingBool("disable_winjoystick")) + m_bImonConflictsWithDInput = true; + else +#endif // TARGET_WINDOWS + m_bImonConflictsWithDInput = false; + + if (m_bImonConflictsWithDInput) + { + ++m_lCountOfImonsConflictWithDInput; + ActionOnImonConflict(true); + } + return CPeripheral::InitialiseFeature(feature); + } + + return CPeripheralHID::InitialiseFeature(feature); +} + +void CPeripheralImon::AddSetting(const std::string& strKey, + const std::shared_ptr<const CSetting>& setting, + int order) +{ +#if !defined(TARGET_WINDOWS) + if (strKey.compare("disable_winjoystick") != 0) +#endif // !TARGET_WINDOWS + CPeripheralHID::AddSetting(strKey, setting, order); +} + +void CPeripheralImon::OnSettingChanged(const std::string& strChangedSetting) +{ + if (strChangedSetting.compare("disable_winjoystick") == 0) + { + if (m_bImonConflictsWithDInput && !GetSettingBool("disable_winjoystick")) + { + m_bImonConflictsWithDInput = false; + if (--m_lCountOfImonsConflictWithDInput == 0) + ActionOnImonConflict(false); + } + else if (!m_bImonConflictsWithDInput && GetSettingBool("disable_winjoystick")) + { + m_bImonConflictsWithDInput = true; + ++m_lCountOfImonsConflictWithDInput; + ActionOnImonConflict(true); + } + } +} + +void CPeripheralImon::ActionOnImonConflict(bool deviceInserted /*= true*/) +{ +} diff --git a/xbmc/peripherals/devices/PeripheralImon.h b/xbmc/peripherals/devices/PeripheralImon.h new file mode 100644 index 0000000..612d059 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralImon.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012-2018 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 "PeripheralHID.h" + +#include <atomic> + +class CSetting; + +namespace PERIPHERALS +{ +class CPeripheralImon : public CPeripheralHID +{ +public: + CPeripheralImon(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralImon(void) override = default; + bool InitialiseFeature(const PeripheralFeature feature) override; + void OnSettingChanged(const std::string& strChangedSetting) override; + void OnDeviceRemoved() override; + void AddSetting(const std::string& strKey, + const std::shared_ptr<const CSetting>& setting, + int order) override; + inline bool IsImonConflictsWithDInput() { return m_bImonConflictsWithDInput; } + static inline long GetCountOfImonsConflictWithDInput() + { + return m_lCountOfImonsConflictWithDInput; + } + static void ActionOnImonConflict(bool deviceInserted = true); + +private: + bool m_bImonConflictsWithDInput; + static std::atomic<long> m_lCountOfImonsConflictWithDInput; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralJoystick.cpp b/xbmc/peripherals/devices/PeripheralJoystick.cpp new file mode 100644 index 0000000..bccad91 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralJoystick.cpp @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2014-2018 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 "PeripheralJoystick.h" + +#include "ServiceBroker.h" +#include "addons/AddonInstaller.h" +#include "addons/AddonManager.h" +#include "application/Application.h" +#include "games/GameServices.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerIDs.h" +#include "games/controllers/ControllerManager.h" +#include "input/InputManager.h" +#include "input/joysticks/DeadzoneFilter.h" +#include "input/joysticks/JoystickMonitor.h" +#include "input/joysticks/JoystickTranslator.h" +#include "input/joysticks/RumbleGenerator.h" +#include "input/joysticks/interfaces/IDriverHandler.h" +#include "input/joysticks/keymaps/KeymapHandling.h" +#include "peripherals/Peripherals.h" +#include "peripherals/addons/AddonButtonMap.h" +#include "peripherals/bus/virtual/PeripheralBusAddon.h" +#include "utils/log.h" + +#include <algorithm> +#include <mutex> + +using namespace KODI; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +CPeripheralJoystick::CPeripheralJoystick(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus), + m_requestedPort(JOYSTICK_PORT_UNKNOWN), + m_buttonCount(0), + m_hatCount(0), + m_axisCount(0), + m_motorCount(0), + m_supportsPowerOff(false), + m_rumbleGenerator(new CRumbleGenerator) +{ + m_features.push_back(FEATURE_JOYSTICK); + // FEATURE_RUMBLE conditionally added via SetMotorCount() +} + +CPeripheralJoystick::~CPeripheralJoystick(void) +{ + if (m_rumbleGenerator) + { + m_rumbleGenerator->AbortRumble(); + m_rumbleGenerator.reset(); + } + + if (m_joystickMonitor) + { + UnregisterInputHandler(m_joystickMonitor.get()); + m_joystickMonitor.reset(); + } + + m_appInput.reset(); + m_deadzoneFilter.reset(); + m_buttonMap.reset(); + + // Wait for remaining install tasks + for (std::future<void>& task : m_installTasks) + task.wait(); +} + +bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature) +{ + bool bSuccess = false; + + if (CPeripheral::InitialiseFeature(feature)) + { + if (feature == FEATURE_JOYSTICK) + { + // Ensure an add-on is present to translate input + PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this); + if (!addon) + { + CLog::Log(LOGERROR, "CPeripheralJoystick: No button mapping add-on for {}", m_strLocation); + } + else + { + if (m_bus->InitializeProperties(*this)) + bSuccess = true; + else + CLog::Log(LOGERROR, "CPeripheralJoystick: Invalid location ({})", m_strLocation); + } + + if (bSuccess) + { + m_buttonMap = + std::make_unique<CAddonButtonMap>(this, addon, DEFAULT_CONTROLLER_ID, m_manager); + if (m_buttonMap->Load()) + { + InitializeDeadzoneFiltering(*m_buttonMap); + InitializeControllerProfile(*m_buttonMap); + } + else + { + CLog::Log(LOGERROR, "CPeripheralJoystick: Failed to load button map for {}", + m_strLocation); + m_buttonMap.reset(); + } + + // Give joystick monitor priority over default controller + m_appInput.reset( + new CKeymapHandling(this, false, m_manager.GetInputManager().KeymapEnvironment())); + m_joystickMonitor.reset(new CJoystickMonitor); + RegisterInputHandler(m_joystickMonitor.get(), false); + } + } + else if (feature == FEATURE_RUMBLE) + { + bSuccess = true; // Nothing to do + } + else if (feature == FEATURE_POWER_OFF) + { + bSuccess = true; // Nothing to do + } + } + + return bSuccess; +} + +void CPeripheralJoystick::InitializeDeadzoneFiltering(IButtonMap& buttonMap) +{ + m_deadzoneFilter.reset(new CDeadzoneFilter(&buttonMap, this)); +} + +void CPeripheralJoystick::InitializeControllerProfile(IButtonMap& buttonMap) +{ + const std::string controllerId = buttonMap.GetAppearance(); + if (controllerId.empty()) + return; + + auto controller = m_manager.GetControllerProfiles().GetController(controllerId); + if (controller) + CPeripheral::SetControllerProfile(controller); + else + { + std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex); + + // Deposit controller into queue + m_controllersToInstall.emplace(controllerId); + + // Clean up finished install tasks + m_installTasks.erase(std::remove_if(m_installTasks.begin(), m_installTasks.end(), + [](std::future<void>& task) { + return task.wait_for(std::chrono::seconds(0)) == + std::future_status::ready; + }), + m_installTasks.end()); + + // Install controller off-thread + std::future<void> installTask = std::async(std::launch::async, [this]() { + // Withdraw controller from queue + std::string controllerToInstall; + { + std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex); + if (!m_controllersToInstall.empty()) + { + controllerToInstall = m_controllersToInstall.front(); + m_controllersToInstall.pop(); + } + } + + // Do the install + GAME::ControllerPtr controller = InstallAsync(controllerToInstall); + if (controller) + CPeripheral::SetControllerProfile(controller); + }); + + // Hold the task to prevent the destructor from completing during an install + m_installTasks.emplace_back(std::move(installTask)); + } +} + +void CPeripheralJoystick::OnUserNotification() +{ + IInputReceiver* inputReceiver = m_appInput->GetInputReceiver(m_rumbleGenerator->ControllerID()); + m_rumbleGenerator->NotifyUser(inputReceiver); +} + +bool CPeripheralJoystick::TestFeature(PeripheralFeature feature) +{ + bool bSuccess = false; + + switch (feature) + { + case FEATURE_RUMBLE: + { + IInputReceiver* inputReceiver = + m_appInput->GetInputReceiver(m_rumbleGenerator->ControllerID()); + bSuccess = m_rumbleGenerator->DoTest(inputReceiver); + break; + } + case FEATURE_POWER_OFF: + if (m_supportsPowerOff) + { + PowerOff(); + bSuccess = true; + } + break; + default: + break; + } + + return bSuccess; +} + +void CPeripheralJoystick::PowerOff() +{ + m_bus->PowerOff(m_strLocation); +} + +void CPeripheralJoystick::RegisterJoystickDriverHandler(IDriverHandler* handler, bool bPromiscuous) +{ + std::unique_lock<CCriticalSection> lock(m_handlerMutex); + + DriverHandler driverHandler = {handler, bPromiscuous}; + m_driverHandlers.insert(m_driverHandlers.begin(), driverHandler); +} + +void CPeripheralJoystick::UnregisterJoystickDriverHandler(IDriverHandler* handler) +{ + std::unique_lock<CCriticalSection> lock(m_handlerMutex); + + m_driverHandlers.erase(std::remove_if(m_driverHandlers.begin(), m_driverHandlers.end(), + [handler](const DriverHandler& driverHandler) { + return driverHandler.handler == handler; + }), + m_driverHandlers.end()); +} + +IKeymap* CPeripheralJoystick::GetKeymap(const std::string& controllerId) +{ + return m_appInput->GetKeymap(controllerId); +} + +GAME::ControllerPtr CPeripheralJoystick::ControllerProfile() const +{ + // Button map has the freshest state + if (m_buttonMap) + { + const std::string controllerId = m_buttonMap->GetAppearance(); + if (!controllerId.empty()) + { + auto controller = m_manager.GetControllerProfiles().GetController(controllerId); + if (controller) + return controller; + } + } + + // Fall back to last set controller profile + if (m_controllerProfile) + return m_controllerProfile; + + // Fall back to default controller + return m_manager.GetControllerProfiles().GetDefaultController(); +} + +void CPeripheralJoystick::SetControllerProfile(const KODI::GAME::ControllerPtr& controller) +{ + CPeripheral::SetControllerProfile(controller); + + // Save preference to buttonmap + if (m_buttonMap) + { + if (m_buttonMap->SetAppearance(controller->ID())) + m_buttonMap->SaveButtonMap(); + } +} + +bool CPeripheralJoystick::OnButtonMotion(unsigned int buttonIndex, bool bPressed) +{ + // Silence debug log if controllers are not enabled + if (m_manager.GetInputManager().IsControllerEnabled()) + { + CLog::Log(LOGDEBUG, "BUTTON [ {} ] on \"{}\" {}", buttonIndex, DeviceName(), + bPressed ? "pressed" : "released"); + } + + // Avoid sending activated input if the app is in the background + if (bPressed && !g_application.IsAppFocused()) + return false; + + m_lastActive = CDateTime::GetCurrentDateTime(); + + std::unique_lock<CCriticalSection> lock(m_handlerMutex); + + // Check GUI setting and send button release if controllers are disabled + if (!m_manager.GetInputManager().IsControllerEnabled()) + { + for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin(); + it != m_driverHandlers.end(); ++it) + it->handler->OnButtonMotion(buttonIndex, false); + return true; + } + + // Process promiscuous handlers + for (auto& it : m_driverHandlers) + { + if (it.bPromiscuous) + it.handler->OnButtonMotion(buttonIndex, bPressed); + } + + bool bHandled = false; + + // Process regular handlers until one is handled + for (auto& it : m_driverHandlers) + { + if (!it.bPromiscuous) + { + bHandled |= it.handler->OnButtonMotion(buttonIndex, bPressed); + + // If button is released, force bHandled to false to notify all handlers. + // This avoids "sticking". + if (!bPressed) + bHandled = false; + + // Once a button is handled, we're done + if (bHandled) + break; + } + } + + return bHandled; +} + +bool CPeripheralJoystick::OnHatMotion(unsigned int hatIndex, HAT_STATE state) +{ + // Silence debug log if controllers are not enabled + if (m_manager.GetInputManager().IsControllerEnabled()) + { + CLog::Log(LOGDEBUG, "HAT [ {} ] on \"{}\" {}", hatIndex, DeviceName(), + CJoystickTranslator::HatStateToString(state)); + } + + // Avoid sending activated input if the app is in the background + if (state != HAT_STATE::NONE && !g_application.IsAppFocused()) + return false; + + m_lastActive = CDateTime::GetCurrentDateTime(); + + std::unique_lock<CCriticalSection> lock(m_handlerMutex); + + // Check GUI setting and send hat unpressed if controllers are disabled + if (!m_manager.GetInputManager().IsControllerEnabled()) + { + for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin(); + it != m_driverHandlers.end(); ++it) + it->handler->OnHatMotion(hatIndex, HAT_STATE::NONE); + return true; + } + + // Process promiscuous handlers + for (auto& it : m_driverHandlers) + { + if (it.bPromiscuous) + it.handler->OnHatMotion(hatIndex, state); + } + + bool bHandled = false; + + // Process regular handlers until one is handled + for (auto& it : m_driverHandlers) + { + if (!it.bPromiscuous) + { + bHandled |= it.handler->OnHatMotion(hatIndex, state); + + // If hat is centered, force bHandled to false to notify all handlers. + // This avoids "sticking". + if (state == HAT_STATE::NONE) + bHandled = false; + + // Once a hat is handled, we're done + if (bHandled) + break; + } + } + + return bHandled; +} + +bool CPeripheralJoystick::OnAxisMotion(unsigned int axisIndex, float position) +{ + // Get axis properties + int center = 0; + unsigned int range = 1; + if (m_buttonMap) + m_buttonMap->GetAxisProperties(axisIndex, center, range); + + // Apply deadzone filtering + if (center == 0 && m_deadzoneFilter) + position = m_deadzoneFilter->FilterAxis(axisIndex, position); + + // Avoid sending activated input if the app is in the background + if (position != static_cast<float>(center) && !g_application.IsAppFocused()) + return false; + + std::unique_lock<CCriticalSection> lock(m_handlerMutex); + + // Check GUI setting and send analog axis centered if controllers are disabled + if (!m_manager.GetInputManager().IsControllerEnabled()) + { + for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin(); + it != m_driverHandlers.end(); ++it) + it->handler->OnAxisMotion(axisIndex, static_cast<float>(center), center, range); + return true; + } + + // Process promiscuous handlers + for (auto& it : m_driverHandlers) + { + if (it.bPromiscuous) + it.handler->OnAxisMotion(axisIndex, position, center, range); + } + + bool bHandled = false; + + // Process regular handlers until one is handled + for (auto& it : m_driverHandlers) + { + if (!it.bPromiscuous) + { + bHandled |= it.handler->OnAxisMotion(axisIndex, position, center, range); + + // If axis is centered, force bHandled to false to notify all handlers. + // This avoids "sticking". + if (position == static_cast<float>(center)) + bHandled = false; + + // Once an axis is handled, we're done + if (bHandled) + break; + } + } + + if (bHandled) + m_lastActive = CDateTime::GetCurrentDateTime(); + + return bHandled; +} + +void CPeripheralJoystick::OnInputFrame(void) +{ + std::unique_lock<CCriticalSection> lock(m_handlerMutex); + + for (auto& it : m_driverHandlers) + it.handler->OnInputFrame(); +} + +bool CPeripheralJoystick::SetMotorState(unsigned int motorIndex, float magnitude) +{ + bool bHandled = false; + + if (m_mappedBusType == PERIPHERAL_BUS_ADDON) + { + CPeripheralBusAddon* addonBus = static_cast<CPeripheralBusAddon*>(m_bus); + if (addonBus) + { + bHandled = addonBus->SendRumbleEvent(m_strLocation, motorIndex, magnitude); + } + } + return bHandled; +} + +void CPeripheralJoystick::SetMotorCount(unsigned int motorCount) +{ + m_motorCount = motorCount; + + if (m_motorCount == 0) + m_features.erase(std::remove(m_features.begin(), m_features.end(), FEATURE_RUMBLE), + m_features.end()); + else if (std::find(m_features.begin(), m_features.end(), FEATURE_RUMBLE) == m_features.end()) + m_features.push_back(FEATURE_RUMBLE); +} + +void CPeripheralJoystick::SetSupportsPowerOff(bool bSupportsPowerOff) +{ + m_supportsPowerOff = bSupportsPowerOff; + + if (!m_supportsPowerOff) + m_features.erase(std::remove(m_features.begin(), m_features.end(), FEATURE_POWER_OFF), + m_features.end()); + else if (std::find(m_features.begin(), m_features.end(), FEATURE_POWER_OFF) == m_features.end()) + m_features.push_back(FEATURE_POWER_OFF); +} + +GAME::ControllerPtr CPeripheralJoystick::InstallAsync(const std::string& controllerId) +{ + GAME::ControllerPtr controller; + + // Only 1 install at a time. Remaining installs will wake when this one + // is done. + std::unique_lock<CCriticalSection> lockInstall(m_manager.GetAddonInstallMutex()); + + CLog::LogF(LOGDEBUG, "Installing {}", controllerId); + + if (InstallSync(controllerId)) + controller = m_manager.GetControllerProfiles().GetController(controllerId); + else + CLog::LogF(LOGERROR, "Failed to install {}", controllerId); + + return controller; +} + +bool CPeripheralJoystick::InstallSync(const std::string& controllerId) +{ + // If the addon isn't installed we need to install it + bool installed = CServiceBroker::GetAddonMgr().IsAddonInstalled(controllerId); + if (!installed) + { + ADDON::AddonPtr installedAddon; + installed = ADDON::CAddonInstaller::GetInstance().InstallModal( + controllerId, installedAddon, ADDON::InstallModalPrompt::CHOICE_NO); + } + + if (installed) + { + // Make sure add-on is enabled + if (CServiceBroker::GetAddonMgr().IsAddonDisabled(controllerId)) + CServiceBroker::GetAddonMgr().EnableAddon(controllerId); + } + + return installed; +} diff --git a/xbmc/peripherals/devices/PeripheralJoystick.h b/xbmc/peripherals/devices/PeripheralJoystick.h new file mode 100644 index 0000000..aa55ad7 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralJoystick.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2014-2018 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 "Peripheral.h" +#include "XBDateTime.h" +#include "games/controllers/ControllerTypes.h" +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IDriverReceiver.h" +#include "threads/CriticalSection.h" + +#include <future> +#include <memory> +#include <queue> +#include <string> +#include <vector> + +#define JOYSTICK_PORT_UNKNOWN (-1) + +namespace KODI +{ +namespace JOYSTICK +{ +class CDeadzoneFilter; +class CKeymapHandling; +class CRumbleGenerator; +class IButtonMap; +class IDriverHandler; +class IInputHandler; +} // namespace JOYSTICK +} // namespace KODI + +namespace PERIPHERALS +{ +class CPeripherals; + +class CPeripheralJoystick : public CPeripheral, //! @todo extend CPeripheralHID + public KODI::JOYSTICK::IDriverReceiver +{ +public: + CPeripheralJoystick(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + + ~CPeripheralJoystick(void) override; + + // implementation of CPeripheral + bool InitialiseFeature(const PeripheralFeature feature) override; + void OnUserNotification() override; + bool TestFeature(PeripheralFeature feature) override; + void RegisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler, + bool bPromiscuous) override; + void UnregisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler) override; + KODI::JOYSTICK::IDriverReceiver* GetDriverReceiver() override { return this; } + IKeymap* GetKeymap(const std::string& controllerId) override; + CDateTime LastActive() override { return m_lastActive; } + KODI::GAME::ControllerPtr ControllerProfile() const override; + void SetControllerProfile(const KODI::GAME::ControllerPtr& controller) override; + + bool OnButtonMotion(unsigned int buttonIndex, bool bPressed); + bool OnHatMotion(unsigned int hatIndex, KODI::JOYSTICK::HAT_STATE state); + bool OnAxisMotion(unsigned int axisIndex, float position); + void OnInputFrame(void); + + // implementation of IDriverReceiver + bool SetMotorState(unsigned int motorIndex, float magnitude) override; + + /*! + * \brief Get the name of the driver or API providing this joystick + */ + const std::string& Provider(void) const { return m_strProvider; } + + /*! + * \brief Get the specific port number requested by this joystick + * + * This could indicate that the joystick is connected to a hardware port + * with a number label; some controllers, such as the Xbox 360 controller, + * also have LEDs that indicate the controller is on a specific port. + * + * \return The 0-indexed port number, or JOYSTICK_PORT_UNKNOWN if no port is requested + */ + int RequestedPort(void) const { return m_requestedPort; } + + /*! + * \brief Get the number of elements reported by the driver + */ + unsigned int ButtonCount(void) const { return m_buttonCount; } + unsigned int HatCount(void) const { return m_hatCount; } + unsigned int AxisCount(void) const { return m_axisCount; } + unsigned int MotorCount(void) const { return m_motorCount; } + bool SupportsPowerOff(void) const { return m_supportsPowerOff; } + + /*! + * \brief Set joystick properties + */ + void SetProvider(const std::string& provider) { m_strProvider = provider; } + void SetRequestedPort(int port) { m_requestedPort = port; } + void SetButtonCount(unsigned int buttonCount) { m_buttonCount = buttonCount; } + void SetHatCount(unsigned int hatCount) { m_hatCount = hatCount; } + void SetAxisCount(unsigned int axisCount) { m_axisCount = axisCount; } + void SetMotorCount(unsigned int motorCount); // specialized to update m_features + void SetSupportsPowerOff(bool bSupportsPowerOff); // specialized to update m_features + +protected: + void InitializeDeadzoneFiltering(KODI::JOYSTICK::IButtonMap& buttonMap); + void InitializeControllerProfile(KODI::JOYSTICK::IButtonMap& buttonMap); + + void PowerOff(); + + // Helper functions + KODI::GAME::ControllerPtr InstallAsync(const std::string& controllerId); + static bool InstallSync(const std::string& controllerId); + + struct DriverHandler + { + KODI::JOYSTICK::IDriverHandler* handler; + bool bPromiscuous; + }; + + // State parameters + std::string m_strProvider; + int m_requestedPort; + unsigned int m_buttonCount; + unsigned int m_hatCount; + unsigned int m_axisCount; + unsigned int m_motorCount; + bool m_supportsPowerOff; + CDateTime m_lastActive; + std::queue<std::string> m_controllersToInstall; + std::vector<std::future<void>> m_installTasks; + + // Input clients + std::unique_ptr<KODI::JOYSTICK::CKeymapHandling> m_appInput; + std::unique_ptr<KODI::JOYSTICK::CRumbleGenerator> m_rumbleGenerator; + std::unique_ptr<KODI::JOYSTICK::IInputHandler> m_joystickMonitor; + std::unique_ptr<KODI::JOYSTICK::IButtonMap> m_buttonMap; + std::unique_ptr<KODI::JOYSTICK::CDeadzoneFilter> m_deadzoneFilter; + std::vector<DriverHandler> m_driverHandlers; + + // Synchronization parameters + CCriticalSection m_handlerMutex; + CCriticalSection m_controllerInstallMutex; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralKeyboard.cpp b/xbmc/peripherals/devices/PeripheralKeyboard.cpp new file mode 100644 index 0000000..271d8b1 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralKeyboard.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017-2018 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 "PeripheralKeyboard.h" + +#include "games/GameServices.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerManager.h" +#include "input/InputManager.h" +#include "peripherals/Peripherals.h" + +#include <mutex> +#include <sstream> + +using namespace KODI; +using namespace PERIPHERALS; + +CPeripheralKeyboard::CPeripheralKeyboard(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus) +{ + // Initialize CPeripheral + m_features.push_back(FEATURE_KEYBOARD); +} + +CPeripheralKeyboard::~CPeripheralKeyboard(void) +{ + m_manager.GetInputManager().UnregisterKeyboardDriverHandler(this); +} + +bool CPeripheralKeyboard::InitialiseFeature(const PeripheralFeature feature) +{ + bool bSuccess = false; + + if (CPeripheral::InitialiseFeature(feature)) + { + switch (feature) + { + case FEATURE_KEYBOARD: + { + m_manager.GetInputManager().RegisterKeyboardDriverHandler(this); + break; + } + default: + break; + } + + bSuccess = true; + } + + return bSuccess; +} + +void CPeripheralKeyboard::RegisterKeyboardDriverHandler( + KODI::KEYBOARD::IKeyboardDriverHandler* handler, bool bPromiscuous) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + KeyboardHandle handle{handler, bPromiscuous}; + m_keyboardHandlers.insert(m_keyboardHandlers.begin(), handle); +} + +void CPeripheralKeyboard::UnregisterKeyboardDriverHandler( + KODI::KEYBOARD::IKeyboardDriverHandler* handler) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + auto it = + std::find_if(m_keyboardHandlers.begin(), m_keyboardHandlers.end(), + [handler](const KeyboardHandle& handle) { return handle.handler == handler; }); + + if (it != m_keyboardHandlers.end()) + m_keyboardHandlers.erase(it); +} + +GAME::ControllerPtr CPeripheralKeyboard::ControllerProfile() const +{ + if (m_controllerProfile) + return m_controllerProfile; + + return m_manager.GetControllerProfiles().GetDefaultKeyboard(); +} + +bool CPeripheralKeyboard::OnKeyPress(const CKey& key) +{ + m_lastActive = CDateTime::GetCurrentDateTime(); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + bool bHandled = false; + + // Process promiscuous handlers + for (const KeyboardHandle& handle : m_keyboardHandlers) + { + if (handle.bPromiscuous) + handle.handler->OnKeyPress(key); + } + + // Process handlers until one is handled + for (const KeyboardHandle& handle : m_keyboardHandlers) + { + if (!handle.bPromiscuous) + { + bHandled = handle.handler->OnKeyPress(key); + if (bHandled) + break; + } + } + + return bHandled; +} + +void CPeripheralKeyboard::OnKeyRelease(const CKey& key) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + for (const KeyboardHandle& handle : m_keyboardHandlers) + handle.handler->OnKeyRelease(key); +} diff --git a/xbmc/peripherals/devices/PeripheralKeyboard.h b/xbmc/peripherals/devices/PeripheralKeyboard.h new file mode 100644 index 0000000..2cf2934 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralKeyboard.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2018 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 "Peripheral.h" +#include "XBDateTime.h" +#include "input/keyboard/interfaces/IKeyboardDriverHandler.h" +#include "threads/CriticalSection.h" + +#include <vector> + +namespace PERIPHERALS +{ +class CPeripheralKeyboard : public CPeripheral, public KODI::KEYBOARD::IKeyboardDriverHandler +{ +public: + CPeripheralKeyboard(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + + ~CPeripheralKeyboard(void) override; + + // implementation of CPeripheral + bool InitialiseFeature(const PeripheralFeature feature) override; + void RegisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler, + bool bPromiscuous) override; + void UnregisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler) override; + CDateTime LastActive() override { return m_lastActive; } + KODI::GAME::ControllerPtr ControllerProfile() const override; + + // implementation of IKeyboardDriverHandler + bool OnKeyPress(const CKey& key) override; + void OnKeyRelease(const CKey& key) override; + +private: + struct KeyboardHandle + { + KODI::KEYBOARD::IKeyboardDriverHandler* handler; + bool bPromiscuous; + }; + + std::vector<KeyboardHandle> m_keyboardHandlers; + CCriticalSection m_mutex; + CDateTime m_lastActive; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralMouse.cpp b/xbmc/peripherals/devices/PeripheralMouse.cpp new file mode 100644 index 0000000..4f26236 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralMouse.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017-2018 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 "PeripheralMouse.h" + +#include "games/GameServices.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerManager.h" +#include "input/InputManager.h" +#include "peripherals/Peripherals.h" + +#include <mutex> +#include <sstream> + +using namespace KODI; +using namespace PERIPHERALS; + +CPeripheralMouse::CPeripheralMouse(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus) +{ + // Initialize CPeripheral + m_features.push_back(FEATURE_MOUSE); +} + +CPeripheralMouse::~CPeripheralMouse(void) +{ + m_manager.GetInputManager().UnregisterMouseDriverHandler(this); +} + +bool CPeripheralMouse::InitialiseFeature(const PeripheralFeature feature) +{ + bool bSuccess = false; + + if (CPeripheral::InitialiseFeature(feature)) + { + if (feature == FEATURE_MOUSE) + { + m_manager.GetInputManager().RegisterMouseDriverHandler(this); + } + + bSuccess = true; + } + + return bSuccess; +} + +void CPeripheralMouse::RegisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler, + bool bPromiscuous) +{ + using namespace KEYBOARD; + + std::unique_lock<CCriticalSection> lock(m_mutex); + + MouseHandle handle{handler, bPromiscuous}; + m_mouseHandlers.insert(m_mouseHandlers.begin(), handle); +} + +void CPeripheralMouse::UnregisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + auto it = + std::find_if(m_mouseHandlers.begin(), m_mouseHandlers.end(), + [handler](const MouseHandle& handle) { return handle.handler == handler; }); + + if (it != m_mouseHandlers.end()) + m_mouseHandlers.erase(it); +} + +GAME::ControllerPtr CPeripheralMouse::ControllerProfile() const +{ + if (m_controllerProfile) + return m_controllerProfile; + + return m_manager.GetControllerProfiles().GetDefaultMouse(); +} + +bool CPeripheralMouse::OnPosition(int x, int y) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + bool bHandled = false; + + // Process promiscuous handlers + for (const MouseHandle& handle : m_mouseHandlers) + { + if (handle.bPromiscuous) + handle.handler->OnPosition(x, y); + } + + // Process handlers until one is handled + for (const MouseHandle& handle : m_mouseHandlers) + { + if (!handle.bPromiscuous) + { + bHandled = handle.handler->OnPosition(x, y); + if (bHandled) + break; + } + } + + if (bHandled) + m_lastActive = CDateTime::GetCurrentDateTime(); + + return bHandled; +} + +bool CPeripheralMouse::OnButtonPress(MOUSE::BUTTON_ID button) +{ + m_lastActive = CDateTime::GetCurrentDateTime(); + + std::unique_lock<CCriticalSection> lock(m_mutex); + + bool bHandled = false; + + // Process promiscuous handlers + for (const MouseHandle& handle : m_mouseHandlers) + { + if (handle.bPromiscuous) + handle.handler->OnButtonPress(button); + } + + // Process handlers until one is handled + for (const MouseHandle& handle : m_mouseHandlers) + { + if (!handle.bPromiscuous) + { + bHandled = handle.handler->OnButtonPress(button); + if (bHandled) + break; + } + } + + return bHandled; +} + +void CPeripheralMouse::OnButtonRelease(MOUSE::BUTTON_ID button) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + + for (const MouseHandle& handle : m_mouseHandlers) + handle.handler->OnButtonRelease(button); +} diff --git a/xbmc/peripherals/devices/PeripheralMouse.h b/xbmc/peripherals/devices/PeripheralMouse.h new file mode 100644 index 0000000..b46776d --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralMouse.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2018 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 "Peripheral.h" +#include "XBDateTime.h" +#include "input/mouse/interfaces/IMouseDriverHandler.h" +#include "threads/CriticalSection.h" + +#include <vector> + +namespace PERIPHERALS +{ +class CPeripheralMouse : public CPeripheral, public KODI::MOUSE::IMouseDriverHandler +{ +public: + CPeripheralMouse(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + + ~CPeripheralMouse(void) override; + + // implementation of CPeripheral + bool InitialiseFeature(const PeripheralFeature feature) override; + void RegisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler, + bool bPromiscuous) override; + void UnregisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler) override; + CDateTime LastActive() override { return m_lastActive; } + KODI::GAME::ControllerPtr ControllerProfile() const override; + + // implementation of IMouseDriverHandler + bool OnPosition(int x, int y) override; + bool OnButtonPress(KODI::MOUSE::BUTTON_ID button) override; + void OnButtonRelease(KODI::MOUSE::BUTTON_ID button) override; + +private: + struct MouseHandle + { + KODI::MOUSE::IMouseDriverHandler* handler; + bool bPromiscuous; + }; + + std::vector<MouseHandle> m_mouseHandlers; + CCriticalSection m_mutex; + CDateTime m_lastActive; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralNIC.cpp b/xbmc/peripherals/devices/PeripheralNIC.cpp new file mode 100644 index 0000000..a5b5c32 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralNIC.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralNIC.h" + +#include "guilib/LocalizeStrings.h" + +using namespace PERIPHERALS; + +CPeripheralNIC::CPeripheralNIC(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus) +{ + m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35002) + : scanResult.m_strDeviceName; + m_features.push_back(FEATURE_NIC); +} diff --git a/xbmc/peripherals/devices/PeripheralNIC.h b/xbmc/peripherals/devices/PeripheralNIC.h new file mode 100644 index 0000000..01fe5f3 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralNIC.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 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 "Peripheral.h" + +namespace PERIPHERALS +{ +class CPeripheralNIC : public CPeripheral +{ +public: + CPeripheralNIC(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralNIC(void) override = default; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralNyxboard.cpp b/xbmc/peripherals/devices/PeripheralNyxboard.cpp new file mode 100644 index 0000000..548d8cf --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralNyxboard.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralNyxboard.h" + +#include "PeripheralHID.h" +#include "application/Application.h" +#include "utils/log.h" + +using namespace PERIPHERALS; + +CPeripheralNyxboard::CPeripheralNyxboard(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheralHID(manager, scanResult, bus) +{ + m_features.push_back(FEATURE_NYXBOARD); +} + +bool CPeripheralNyxboard::LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode) +{ + std::string strCommand; + if (keysym.sym == XBMCK_F7 && keysym.mod == XBMCKMOD_NONE && + GetSettingBool("enable_flip_commands")) + { + /* switched to keyboard side */ + CLog::Log(LOGDEBUG, "{} - switched to keyboard side", __FUNCTION__); + strCommand = GetSettingString("flip_keyboard"); + } + else if (keysym.sym == XBMCK_F7 && keysym.mod == XBMCKMOD_LCTRL && + GetSettingBool("enable_flip_commands")) + { + /* switched to remote side */ + CLog::Log(LOGDEBUG, "{} - switched to remote side", __FUNCTION__); + strCommand = GetSettingString("flip_remote"); + } + + if (!strCommand.empty()) + { + CLog::Log(LOGDEBUG, "{} - executing command '{}'", __FUNCTION__, strCommand); + if (g_application.ExecuteXBMCAction(strCommand)) + { + *key = 0; + *unicode = (char)0; + return true; + } + } + + return false; +} diff --git a/xbmc/peripherals/devices/PeripheralNyxboard.h b/xbmc/peripherals/devices/PeripheralNyxboard.h new file mode 100644 index 0000000..f81c3ef --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralNyxboard.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralHID.h" + +namespace PERIPHERALS +{ +class CPeripheralNyxboard : public CPeripheralHID +{ +public: + CPeripheralNyxboard(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralNyxboard(void) override = default; + bool LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode) override; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/devices/PeripheralTuner.cpp b/xbmc/peripherals/devices/PeripheralTuner.cpp new file mode 100644 index 0000000..770656d --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralTuner.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 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 "PeripheralTuner.h" + +using namespace PERIPHERALS; + +CPeripheralTuner::CPeripheralTuner(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus) + : CPeripheral(manager, scanResult, bus) +{ + m_features.push_back(FEATURE_TUNER); +} diff --git a/xbmc/peripherals/devices/PeripheralTuner.h b/xbmc/peripherals/devices/PeripheralTuner.h new file mode 100644 index 0000000..aedfc12 --- /dev/null +++ b/xbmc/peripherals/devices/PeripheralTuner.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 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 "Peripheral.h" + +namespace PERIPHERALS +{ +class CPeripheralTuner : public CPeripheral +{ +public: + CPeripheralTuner(CPeripherals& manager, + const PeripheralScanResult& scanResult, + CPeripheralBus* bus); + ~CPeripheralTuner(void) override = default; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/dialogs/CMakeLists.txt b/xbmc/peripherals/dialogs/CMakeLists.txt new file mode 100644 index 0000000..28aca66 --- /dev/null +++ b/xbmc/peripherals/dialogs/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES GUIDialogPeripherals.cpp + GUIDialogPeripheralSettings.cpp) + +set(HEADERS GUIDialogPeripherals.h + GUIDialogPeripheralSettings.h) + +core_add_library(peripherals_dialogs) diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp new file mode 100644 index 0000000..73816df --- /dev/null +++ b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2005-2018 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 "GUIDialogPeripheralSettings.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "addons/Skin.h" +#include "dialogs/GUIDialogYesNo.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerManager.h" +#include "guilib/GUIMessage.h" +#include "peripherals/Peripherals.h" +#include "settings/SettingAddon.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingSection.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <string_view> +#include <utility> + +using namespace KODI; +using namespace PERIPHERALS; + +// Settings for peripherals +constexpr std::string_view SETTING_APPEARANCE = "appearance"; + +CGUIDialogPeripheralSettings::CGUIDialogPeripheralSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_PERIPHERAL_SETTINGS, "DialogSettings.xml"), + m_item(NULL) +{ +} + +CGUIDialogPeripheralSettings::~CGUIDialogPeripheralSettings() +{ + if (m_item != NULL) + delete m_item; + + m_settingsMap.clear(); +} + +bool CGUIDialogPeripheralSettings::OnMessage(CGUIMessage& message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED && + message.GetSenderId() == CONTROL_SETTINGS_CUSTOM_BUTTON) + { + OnResetSettings(); + return true; + } + + return CGUIDialogSettingsManualBase::OnMessage(message); +} + +void CGUIDialogPeripheralSettings::RegisterPeripheralManager(CPeripherals& manager) +{ + m_manager = &manager; +} + +void CGUIDialogPeripheralSettings::UnregisterPeripheralManager() +{ + m_manager = nullptr; +} + +void CGUIDialogPeripheralSettings::SetFileItem(const CFileItem* item) +{ + if (item == NULL) + return; + + if (m_item != NULL) + delete m_item; + + m_item = new CFileItem(*item); +} + +void CGUIDialogPeripheralSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string& settingId = setting->GetId(); + + // we need to copy the new value of the setting from the copy to the + // original setting + std::map<std::string, std::shared_ptr<CSetting>>::iterator itSetting = + m_settingsMap.find(settingId); + if (itSetting == m_settingsMap.end()) + return; + + itSetting->second->FromString(setting->ToString()); + + // Get peripheral associated with this setting + PeripheralPtr peripheral; + if (m_item != nullptr) + peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath()); + + if (!peripheral) + return; + + if (settingId == SETTING_APPEARANCE) + { + // Get the controller profile of the new appearance + GAME::ControllerPtr controller; + + if (setting->GetType() == SettingType::String) + { + std::shared_ptr<const CSettingString> settingString = + std::static_pointer_cast<const CSettingString>(setting); + const std::string& addonId = settingString->GetValue(); + + if (m_manager != nullptr) + controller = m_manager->GetControllerProfiles().GetController(addonId); + } + + if (controller) + peripheral->SetControllerProfile(controller); + } +} + +bool CGUIDialogPeripheralSettings::Save() +{ + if (m_item == NULL || m_initialising) + return true; + + PeripheralPtr peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath()); + if (!peripheral) + return true; + + peripheral->PersistSettings(); + + return true; +} + +void CGUIDialogPeripheralSettings::OnResetSettings() +{ + if (m_item == NULL) + return; + + PeripheralPtr peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath()); + if (!peripheral) + return; + + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{10041}, CVariant{10042})) + return; + + // reset the settings in the peripheral + peripheral->ResetDefaultSettings(); + + // re-create all settings and their controls + SetupView(); +} + +void CGUIDialogPeripheralSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + + SetHeading(m_item->GetLabel()); + SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 186); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CUSTOM_BUTTON, 409); +} + +void CGUIDialogPeripheralSettings::InitializeSettings() +{ + if (m_item == NULL) + { + m_initialising = false; + return; + } + + m_initialising = true; + bool usePopup = g_SkinInfo->HasSkinFile("DialogSlider.xml"); + + PeripheralPtr peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath()); + if (!peripheral) + { + CLog::Log(LOGDEBUG, "{} - no peripheral", __FUNCTION__); + m_initialising = false; + return; + } + + m_settingsMap.clear(); + CGUIDialogSettingsManualBase::InitializeSettings(); + + const std::shared_ptr<CSettingCategory> category = AddCategory("peripheralsettings", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogPeripheralSettings: unable to setup settings"); + return; + } + + const std::shared_ptr<CSettingGroup> group = AddGroup(category); + if (group == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogPeripheralSettings: unable to setup settings"); + return; + } + + std::vector<SettingPtr> settings = peripheral->GetSettings(); + for (auto& setting : settings) + { + if (setting == NULL) + continue; + + if (!setting->IsVisible()) + { + CLog::Log(LOGDEBUG, "{} - invisible", __FUNCTION__); + continue; + } + + // we need to create a copy of the setting because the CSetting instances + // are destroyed when leaving the dialog + SettingPtr settingCopy; + switch (setting->GetType()) + { + case SettingType::Boolean: + { + std::shared_ptr<CSettingBool> settingBool = std::make_shared<CSettingBool>( + setting->GetId(), *std::static_pointer_cast<CSettingBool>(setting)); + settingBool->SetControl(GetCheckmarkControl()); + + settingCopy = std::static_pointer_cast<CSetting>(settingBool); + break; + } + + case SettingType::Integer: + { + std::shared_ptr<CSettingInt> settingInt = std::make_shared<CSettingInt>( + setting->GetId(), *std::static_pointer_cast<CSettingInt>(setting)); + if (settingInt->GetTranslatableOptions().empty()) + settingInt->SetControl(GetSliderControl("integer", false, -1, usePopup, -1, "{:d}")); + else + settingInt->SetControl(GetSpinnerControl("string")); + + settingCopy = std::static_pointer_cast<CSetting>(settingInt); + break; + } + + case SettingType::Number: + { + std::shared_ptr<CSettingNumber> settingNumber = std::make_shared<CSettingNumber>( + setting->GetId(), *std::static_pointer_cast<CSettingNumber>(setting)); + settingNumber->SetControl(GetSliderControl("number", false, -1, usePopup, -1, "{:2.2f}")); + + settingCopy = std::static_pointer_cast<CSetting>(settingNumber); + break; + } + + case SettingType::String: + { + if (auto settingAsAddon = std::dynamic_pointer_cast<const CSettingAddon>(setting)) + { + std::shared_ptr<CSettingAddon> settingAddon = + std::make_shared<CSettingAddon>(setting->GetId(), *settingAsAddon); + + // Control properties + const std::string format = "addon"; + const bool delayed = false; + const int heading = -1; + const bool hideValue = false; + const bool showInstalledAddons = true; + const bool showInstallableAddons = true; + const bool showMoreAddons = false; + + settingAddon->SetControl(GetButtonControl(format, delayed, heading, hideValue, + showInstalledAddons, showInstallableAddons, + showMoreAddons)); + + GAME::ControllerPtr controller = peripheral->ControllerProfile(); + if (controller) + settingAddon->SetValue(controller->ID()); + + settingCopy = std::static_pointer_cast<CSetting>(settingAddon); + } + else + { + std::shared_ptr<CSettingString> settingString = std::make_shared<CSettingString>( + setting->GetId(), *std::static_pointer_cast<CSettingString>(setting)); + settingString->SetControl(GetEditControl("string")); + + settingCopy = std::static_pointer_cast<CSetting>(settingString); + } + break; + } + + default: + //! @todo add more types if needed + CLog::Log(LOGDEBUG, "{} - unknown type", __FUNCTION__); + break; + } + + if (settingCopy != NULL && settingCopy->GetControl() != NULL) + { + settingCopy->SetLevel(SettingLevel::Basic); + group->AddSetting(settingCopy); + m_settingsMap.insert(std::make_pair(setting->GetId(), setting)); + } + } + + m_initialising = false; +} diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.h b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.h new file mode 100644 index 0000000..33ad2ee --- /dev/null +++ b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2018 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 "settings/dialogs/GUIDialogSettingsManualBase.h" + +class CFileItem; + +namespace PERIPHERALS +{ +class CPeripherals; + +class CGUIDialogPeripheralSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogPeripheralSettings(); + ~CGUIDialogPeripheralSettings() override; + + // specializations of CGUIControl + bool OnMessage(CGUIMessage& message) override; + + void RegisterPeripheralManager(CPeripherals& manager); + void UnregisterPeripheralManager(); + + virtual void SetFileItem(const CFileItem* item); + +protected: + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool Save() override; + void OnResetSettings() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + + // Dialog state + CPeripherals* m_manager{nullptr}; + CFileItem* m_item; + bool m_initialising = false; + std::map<std::string, std::shared_ptr<CSetting>> m_settingsMap; +}; +} // namespace PERIPHERALS diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripherals.cpp b/xbmc/peripherals/dialogs/GUIDialogPeripherals.cpp new file mode 100644 index 0000000..7594cb2 --- /dev/null +++ b/xbmc/peripherals/dialogs/GUIDialogPeripherals.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017-2018 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 "GUIDialogPeripherals.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "dialogs/GUIDialogSelect.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "peripherals/Peripherals.h" +#include "peripherals/dialogs/GUIDialogPeripheralSettings.h" +#include "utils/Variant.h" + +#include <mutex> + +using namespace KODI; +using namespace PERIPHERALS; + +CGUIDialogPeripherals::CGUIDialogPeripherals() +{ + // Initialize CGUIControl via CGUIDialogSelect + SetID(WINDOW_DIALOG_PERIPHERALS); +} + +CGUIDialogPeripherals::~CGUIDialogPeripherals() = default; + +void CGUIDialogPeripherals::OnInitWindow() +{ + UpdatePeripheralsSync(); + CGUIDialogSelect::OnInitWindow(); +} + +void CGUIDialogPeripherals::RegisterPeripheralManager(CPeripherals& manager) +{ + m_manager = &manager; + m_manager->RegisterObserver(this); +} + +void CGUIDialogPeripherals::UnregisterPeripheralManager() +{ + if (m_manager != nullptr) + { + m_manager->UnregisterObserver(this); + m_manager = nullptr; + } +} + +CFileItemPtr CGUIDialogPeripherals::GetItem(unsigned int pos) const +{ + CFileItemPtr item; + + std::unique_lock<CCriticalSection> lock(m_peripheralsMutex); + + if (static_cast<int>(pos) < m_peripherals.Size()) + item = m_peripherals[pos]; + + return item; +} + +void CGUIDialogPeripherals::Show(CPeripherals& manager) +{ + CGUIDialogPeripherals* pDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPeripherals>( + WINDOW_DIALOG_PERIPHERALS); + if (pDialog == nullptr) + return; + + pDialog->Reset(); + + int iPos = -1; + do + { + pDialog->SetHeading(CVariant{35000}); + pDialog->SetUseDetails(true); + + pDialog->RegisterPeripheralManager(manager); + + pDialog->Open(); + + pDialog->UnregisterPeripheralManager(); + + iPos = pDialog->IsConfirmed() ? pDialog->GetSelectedItem() : -1; + + if (iPos >= 0) + { + CFileItemPtr pItem = pDialog->GetItem(iPos); + + // Show an error if the peripheral doesn't have any settings + PeripheralPtr peripheral = manager.GetByPath(pItem->GetPath()); + if (!peripheral || peripheral->GetSettings().empty()) + { + MESSAGING::HELPERS::ShowOKDialogText(CVariant{35000}, CVariant{35004}); + continue; + } + + CGUIDialogPeripheralSettings* pSettingsDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPeripheralSettings>( + WINDOW_DIALOG_PERIPHERAL_SETTINGS); + if (pItem && pSettingsDialog) + { + // Pass peripheral item properties to settings dialog so skin authors + // Can use it to show more detailed information about the device + pSettingsDialog->SetProperty("vendor", pItem->GetProperty("vendor")); + pSettingsDialog->SetProperty("product", pItem->GetProperty("product")); + pSettingsDialog->SetProperty("bus", pItem->GetProperty("bus")); + pSettingsDialog->SetProperty("location", pItem->GetProperty("location")); + pSettingsDialog->SetProperty("class", pItem->GetProperty("class")); + pSettingsDialog->SetProperty("version", pItem->GetProperty("version")); + + // Open settings dialog + pSettingsDialog->SetFileItem(pItem.get()); + pSettingsDialog->RegisterPeripheralManager(manager); + pSettingsDialog->Open(); + pSettingsDialog->UnregisterPeripheralManager(); + } + } + } while (pDialog->IsConfirmed()); +} + +bool CGUIDialogPeripherals::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_REFRESH_LIST: + { + if (m_manager && message.GetControlId() == -1) + UpdatePeripheralsSync(); + return true; + } + default: + break; + } + + return CGUIDialogSelect::OnMessage(message); +} + +void CGUIDialogPeripherals::Notify(const Observable& obs, const ObservableMessage msg) +{ + switch (msg) + { + case ObservableMessagePeripheralsChanged: + UpdatePeripheralsAsync(); + break; + default: + break; + } +} + +void CGUIDialogPeripherals::UpdatePeripheralsAsync() +{ + CGUIMessage msg(GUI_MSG_REFRESH_LIST, GetID(), -1); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg); +} + +void CGUIDialogPeripherals::UpdatePeripheralsSync() +{ + int iPos = GetSelectedItem(); + + std::unique_lock<CCriticalSection> lock(m_peripheralsMutex); + + CFileItemPtr selectedItem; + if (iPos > 0) + selectedItem = GetItem(iPos); + + m_peripherals.Clear(); + m_manager->GetDirectory("peripherals://all/", m_peripherals); + SetItems(m_peripherals); + + if (selectedItem) + { + for (int i = 0; i < m_peripherals.Size(); i++) + { + if (m_peripherals[i]->GetPath() == selectedItem->GetPath()) + SetSelected(i); + } + } +} diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripherals.h b/xbmc/peripherals/dialogs/GUIDialogPeripherals.h new file mode 100644 index 0000000..d4a2f5d --- /dev/null +++ b/xbmc/peripherals/dialogs/GUIDialogPeripherals.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-2018 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 "FileItem.h" +#include "dialogs/GUIDialogSelect.h" +#include "threads/CriticalSection.h" +#include "utils/Observer.h" + +namespace PERIPHERALS +{ +class CPeripherals; + +class CGUIDialogPeripherals : public CGUIDialogSelect, protected Observer +{ +public: + CGUIDialogPeripherals(); + ~CGUIDialogPeripherals() override; + + void RegisterPeripheralManager(CPeripherals& manager); + void UnregisterPeripheralManager(); + + CFileItemPtr GetItem(unsigned int pos) const; + + static void Show(CPeripherals& manager); + + // implementation of CGUIControl via CGUIDialogSelect + bool OnMessage(CGUIMessage& message) override; + + // implementation of Observer + void Notify(const Observable& obs, const ObservableMessage msg) override; + +private: + // implementation of CGUIWindow via CGUIDialogSelect + void OnInitWindow() override; + + void ShowInternal(); + void UpdatePeripheralsAsync(); + void UpdatePeripheralsSync(); + + CPeripherals* m_manager = nullptr; + CFileItemList m_peripherals; + mutable CCriticalSection m_peripheralsMutex; +}; +} // namespace PERIPHERALS |