summaryrefslogtreecommitdiffstats
path: root/xbmc/peripherals/Peripherals.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/peripherals/Peripherals.cpp
parentInitial commit. (diff)
downloadkodi-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.cpp1030
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();
+ }
+ }
+}