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/Peripherals.cpp | |
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/Peripherals.cpp')
-rw-r--r-- | xbmc/peripherals/Peripherals.cpp | 1030 |
1 files changed, 1030 insertions, 0 deletions
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(); + } + } +} |