diff options
Diffstat (limited to 'xbmc/peripherals/bus')
-rw-r--r-- | xbmc/peripherals/bus/CMakeLists.txt | 6 | ||||
-rw-r--r-- | xbmc/peripherals/bus/PeripheralBus.cpp | 352 | ||||
-rw-r--r-- | xbmc/peripherals/bus/PeripheralBus.h | 221 | ||||
-rw-r--r-- | xbmc/peripherals/bus/PeripheralBusUSB.h | 29 | ||||
-rw-r--r-- | xbmc/peripherals/bus/virtual/CMakeLists.txt | 12 | ||||
-rw-r--r-- | xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp | 502 | ||||
-rw-r--r-- | xbmc/peripherals/bus/virtual/PeripheralBusAddon.h | 94 | ||||
-rw-r--r-- | xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp | 83 | ||||
-rw-r--r-- | xbmc/peripherals/bus/virtual/PeripheralBusApplication.h | 40 | ||||
-rw-r--r-- | xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp | 57 | ||||
-rw-r--r-- | xbmc/peripherals/bus/virtual/PeripheralBusCEC.h | 43 |
11 files changed, 1439 insertions, 0 deletions
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 |