summaryrefslogtreecommitdiffstats
path: root/xbmc/peripherals/devices
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/peripherals/devices/CMakeLists.txt30
-rw-r--r--xbmc/peripherals/devices/Peripheral.cpp771
-rw-r--r--xbmc/peripherals/devices/Peripheral.h306
-rw-r--r--xbmc/peripherals/devices/PeripheralBluetooth.cpp19
-rw-r--r--xbmc/peripherals/devices/PeripheralBluetooth.h23
-rw-r--r--xbmc/peripherals/devices/PeripheralCecAdapter.cpp1861
-rw-r--r--xbmc/peripherals/devices/PeripheralCecAdapter.h226
-rw-r--r--xbmc/peripherals/devices/PeripheralDisk.cpp23
-rw-r--r--xbmc/peripherals/devices/PeripheralDisk.h23
-rw-r--r--xbmc/peripherals/devices/PeripheralHID.cpp86
-rw-r--r--xbmc/peripherals/devices/PeripheralHID.h33
-rw-r--r--xbmc/peripherals/devices/PeripheralImon.cpp90
-rw-r--r--xbmc/peripherals/devices/PeripheralImon.h43
-rw-r--r--xbmc/peripherals/devices/PeripheralJoystick.cpp538
-rw-r--r--xbmc/peripherals/devices/PeripheralJoystick.h150
-rw-r--r--xbmc/peripherals/devices/PeripheralKeyboard.cpp125
-rw-r--r--xbmc/peripherals/devices/PeripheralKeyboard.h52
-rw-r--r--xbmc/peripherals/devices/PeripheralMouse.cpp150
-rw-r--r--xbmc/peripherals/devices/PeripheralMouse.h53
-rw-r--r--xbmc/peripherals/devices/PeripheralNIC.cpp23
-rw-r--r--xbmc/peripherals/devices/PeripheralNIC.h23
-rw-r--r--xbmc/peripherals/devices/PeripheralNyxboard.cpp55
-rw-r--r--xbmc/peripherals/devices/PeripheralNyxboard.h24
-rw-r--r--xbmc/peripherals/devices/PeripheralTuner.cpp19
-rw-r--r--xbmc/peripherals/devices/PeripheralTuner.h23
25 files changed, 4769 insertions, 0 deletions
diff --git a/xbmc/peripherals/devices/CMakeLists.txt b/xbmc/peripherals/devices/CMakeLists.txt
new file mode 100644
index 0000000..57795f5
--- /dev/null
+++ b/xbmc/peripherals/devices/CMakeLists.txt
@@ -0,0 +1,30 @@
+set(SOURCES Peripheral.cpp
+ PeripheralBluetooth.cpp
+ PeripheralDisk.cpp
+ PeripheralHID.cpp
+ PeripheralImon.cpp
+ PeripheralJoystick.cpp
+ PeripheralKeyboard.cpp
+ PeripheralMouse.cpp
+ PeripheralNIC.cpp
+ PeripheralNyxboard.cpp
+ PeripheralTuner.cpp)
+
+set(HEADERS Peripheral.h
+ PeripheralBluetooth.h
+ PeripheralDisk.h
+ PeripheralHID.h
+ PeripheralImon.h
+ PeripheralJoystick.h
+ PeripheralKeyboard.h
+ PeripheralMouse.h
+ PeripheralNIC.h
+ PeripheralNyxboard.h
+ PeripheralTuner.h)
+
+if(CEC_FOUND)
+ list(APPEND SOURCES PeripheralCecAdapter.cpp)
+ list(APPEND HEADERS PeripheralCecAdapter.h)
+endif()
+
+core_add_library(peripherals_devices)
diff --git a/xbmc/peripherals/devices/Peripheral.cpp b/xbmc/peripherals/devices/Peripheral.cpp
new file mode 100644
index 0000000..6c3f779
--- /dev/null
+++ b/xbmc/peripherals/devices/Peripheral.cpp
@@ -0,0 +1,771 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Peripheral.h"
+
+#include "Util.h"
+#include "XBDateTime.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerLayout.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/addons/AddonButtonMapping.h"
+#include "peripherals/addons/AddonInputHandling.h"
+#include "peripherals/addons/PeripheralAddon.h"
+#include "peripherals/bus/PeripheralBus.h"
+#include "peripherals/bus/virtual/PeripheralBusAddon.h"
+#include "settings/SettingAddon.h"
+#include "settings/lib/Setting.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <utility>
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+struct SortBySettingsOrder
+{
+ bool operator()(const PeripheralDeviceSetting& left, const PeripheralDeviceSetting& right)
+ {
+ return left.m_order < right.m_order;
+ }
+};
+
+CPeripheral::CPeripheral(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : m_manager(manager),
+ m_type(scanResult.m_mappedType),
+ m_busType(scanResult.m_busType),
+ m_mappedBusType(scanResult.m_mappedBusType),
+ m_strLocation(scanResult.m_strLocation),
+ m_strDeviceName(scanResult.m_strDeviceName),
+ m_iVendorId(scanResult.m_iVendorId),
+ m_iProductId(scanResult.m_iProductId),
+ m_strVersionInfo(g_localizeStrings.Get(13205)), // "unknown"
+ m_bInitialised(false),
+ m_bHidden(false),
+ m_bError(false),
+ m_bus(bus)
+{
+ PeripheralTypeTranslator::FormatHexString(scanResult.m_iVendorId, m_strVendorId);
+ PeripheralTypeTranslator::FormatHexString(scanResult.m_iProductId, m_strProductId);
+ if (scanResult.m_iSequence > 0)
+ {
+ m_strFileLocation =
+ StringUtils::Format("peripherals://{}/{}_{}.dev",
+ PeripheralTypeTranslator::BusTypeToString(scanResult.m_busType),
+ scanResult.m_strLocation, scanResult.m_iSequence);
+ }
+ else
+ {
+ m_strFileLocation = StringUtils::Format(
+ "peripherals://{}/{}.dev", PeripheralTypeTranslator::BusTypeToString(scanResult.m_busType),
+ scanResult.m_strLocation);
+ }
+}
+
+CPeripheral::~CPeripheral(void)
+{
+ PersistSettings(true);
+
+ m_subDevices.clear();
+
+ ClearSettings();
+}
+
+bool CPeripheral::operator==(const CPeripheral& right) const
+{
+ return m_type == right.m_type && m_strLocation == right.m_strLocation &&
+ m_iVendorId == right.m_iVendorId && m_iProductId == right.m_iProductId;
+}
+
+bool CPeripheral::operator!=(const CPeripheral& right) const
+{
+ return !(*this == right);
+}
+
+bool CPeripheral::HasFeature(const PeripheralFeature feature) const
+{
+ bool bReturn(false);
+
+ for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++)
+ {
+ if (m_features.at(iFeaturePtr) == feature)
+ {
+ bReturn = true;
+ break;
+ }
+ }
+
+ if (!bReturn)
+ {
+ for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++)
+ {
+ if (m_subDevices.at(iSubdevicePtr)->HasFeature(feature))
+ {
+ bReturn = true;
+ break;
+ }
+ }
+ }
+
+ return bReturn;
+}
+
+void CPeripheral::GetFeatures(std::vector<PeripheralFeature>& features) const
+{
+ for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++)
+ features.push_back(m_features.at(iFeaturePtr));
+
+ for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++)
+ m_subDevices.at(iSubdevicePtr)->GetFeatures(features);
+}
+
+bool CPeripheral::Initialise(void)
+{
+ bool bReturn(false);
+
+ if (m_bError)
+ return bReturn;
+
+ bReturn = true;
+ if (m_bInitialised)
+ return bReturn;
+
+ m_manager.GetSettingsFromMapping(*this);
+
+ std::string safeDeviceName = m_strDeviceName;
+ StringUtils::Replace(safeDeviceName, ' ', '_');
+
+ if (m_iVendorId == 0x0000 && m_iProductId == 0x0000)
+ {
+ m_strSettingsFile =
+ StringUtils::Format("special://profile/peripheral_data/{}_{}.xml",
+ PeripheralTypeTranslator::BusTypeToString(m_mappedBusType),
+ CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT));
+ }
+ else
+ {
+ // Backwards compatibility - old settings files didn't include the device name
+ m_strSettingsFile = StringUtils::Format(
+ "special://profile/peripheral_data/{}_{}_{}.xml",
+ PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), m_strVendorId, m_strProductId);
+
+ if (!CFileUtils::Exists(m_strSettingsFile))
+ m_strSettingsFile = StringUtils::Format(
+ "special://profile/peripheral_data/{}_{}_{}_{}.xml",
+ PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), m_strVendorId, m_strProductId,
+ CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT));
+ }
+
+ LoadPersistedSettings();
+
+ for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++)
+ {
+ PeripheralFeature feature = m_features.at(iFeaturePtr);
+ bReturn &= InitialiseFeature(feature);
+ }
+
+ for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++)
+ bReturn &= m_subDevices.at(iSubdevicePtr)->Initialise();
+
+ if (bReturn)
+ {
+ CLog::Log(LOGDEBUG, "{} - initialised peripheral on '{}' with {} features and {} sub devices",
+ __FUNCTION__, m_strLocation, (int)m_features.size(), (int)m_subDevices.size());
+ m_bInitialised = true;
+ }
+
+ return bReturn;
+}
+
+void CPeripheral::GetSubdevices(PeripheralVector& subDevices) const
+{
+ subDevices = m_subDevices;
+}
+
+bool CPeripheral::IsMultiFunctional(void) const
+{
+ return m_subDevices.size() > 0;
+}
+
+std::vector<std::shared_ptr<CSetting>> CPeripheral::GetSettings(void) const
+{
+ std::vector<PeripheralDeviceSetting> tmpSettings;
+ tmpSettings.reserve(m_settings.size());
+ for (const auto& it : m_settings)
+ tmpSettings.push_back(it.second);
+ sort(tmpSettings.begin(), tmpSettings.end(), SortBySettingsOrder());
+
+ std::vector<std::shared_ptr<CSetting>> settings;
+ settings.reserve(tmpSettings.size());
+ for (const auto& it : tmpSettings)
+ settings.push_back(it.m_setting);
+ return settings;
+}
+
+void CPeripheral::AddSetting(const std::string& strKey, const SettingConstPtr& setting, int order)
+{
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "{} - invalid setting", __FUNCTION__);
+ return;
+ }
+
+ if (!HasSetting(strKey))
+ {
+ PeripheralDeviceSetting deviceSetting = {NULL, order};
+ switch (setting->GetType())
+ {
+ case SettingType::Boolean:
+ {
+ std::shared_ptr<const CSettingBool> mappedSetting =
+ std::static_pointer_cast<const CSettingBool>(setting);
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::make_shared<CSettingBool>(strKey, *mappedSetting);
+ if (boolSetting)
+ {
+ boolSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = boolSetting;
+ }
+ }
+ break;
+ case SettingType::Integer:
+ {
+ std::shared_ptr<const CSettingInt> mappedSetting =
+ std::static_pointer_cast<const CSettingInt>(setting);
+ std::shared_ptr<CSettingInt> intSetting =
+ std::make_shared<CSettingInt>(strKey, *mappedSetting);
+ if (intSetting)
+ {
+ intSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = intSetting;
+ }
+ }
+ break;
+ case SettingType::Number:
+ {
+ std::shared_ptr<const CSettingNumber> mappedSetting =
+ std::static_pointer_cast<const CSettingNumber>(setting);
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::make_shared<CSettingNumber>(strKey, *mappedSetting);
+ if (floatSetting)
+ {
+ floatSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = floatSetting;
+ }
+ }
+ break;
+ case SettingType::String:
+ {
+ if (std::dynamic_pointer_cast<const CSettingAddon>(setting))
+ {
+ std::shared_ptr<const CSettingAddon> mappedSetting =
+ std::static_pointer_cast<const CSettingAddon>(setting);
+ std::shared_ptr<CSettingAddon> addonSetting =
+ std::make_shared<CSettingAddon>(strKey, *mappedSetting);
+ addonSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = addonSetting;
+ }
+ else
+ {
+ std::shared_ptr<const CSettingString> mappedSetting =
+ std::static_pointer_cast<const CSettingString>(setting);
+ std::shared_ptr<CSettingString> stringSetting =
+ std::make_shared<CSettingString>(strKey, *mappedSetting);
+ stringSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = stringSetting;
+ }
+ }
+ break;
+ default:
+ //! @todo add more types if needed
+ break;
+ }
+
+ if (deviceSetting.m_setting != NULL)
+ m_settings.insert(make_pair(strKey, deviceSetting));
+ }
+}
+
+bool CPeripheral::HasSetting(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ return it != m_settings.end();
+}
+
+bool CPeripheral::HasSettings(void) const
+{
+ return !m_settings.empty();
+}
+
+bool CPeripheral::HasConfigurableSettings(void) const
+{
+ bool bReturn(false);
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.begin();
+ while (it != m_settings.end() && !bReturn)
+ {
+ if ((*it).second.m_setting->IsVisible())
+ {
+ bReturn = true;
+ break;
+ }
+
+ ++it;
+ }
+
+ return bReturn;
+}
+
+bool CPeripheral::GetSettingBool(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Boolean)
+ {
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::static_pointer_cast<CSettingBool>((*it).second.m_setting);
+ if (boolSetting)
+ return boolSetting->GetValue();
+ }
+
+ return false;
+}
+
+int CPeripheral::GetSettingInt(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Integer)
+ {
+ std::shared_ptr<CSettingInt> intSetting =
+ std::static_pointer_cast<CSettingInt>((*it).second.m_setting);
+ if (intSetting)
+ return intSetting->GetValue();
+ }
+
+ return 0;
+}
+
+float CPeripheral::GetSettingFloat(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Number)
+ {
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::static_pointer_cast<CSettingNumber>((*it).second.m_setting);
+ if (floatSetting)
+ return (float)floatSetting->GetValue();
+ }
+
+ return 0;
+}
+
+const std::string CPeripheral::GetSettingString(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::String)
+ {
+ std::shared_ptr<CSettingString> stringSetting =
+ std::static_pointer_cast<CSettingString>((*it).second.m_setting);
+ if (stringSetting)
+ return stringSetting->GetValue();
+ }
+
+ return "";
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, bool bValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Boolean)
+ {
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::static_pointer_cast<CSettingBool>((*it).second.m_setting);
+ if (boolSetting)
+ {
+ bChanged = boolSetting->GetValue() != bValue;
+ boolSetting->SetValue(bValue);
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ return bChanged;
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, int iValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Integer)
+ {
+ std::shared_ptr<CSettingInt> intSetting =
+ std::static_pointer_cast<CSettingInt>((*it).second.m_setting);
+ if (intSetting)
+ {
+ bChanged = intSetting->GetValue() != iValue;
+ intSetting->SetValue(iValue);
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ return bChanged;
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, float fValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Number)
+ {
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::static_pointer_cast<CSettingNumber>((*it).second.m_setting);
+ if (floatSetting)
+ {
+ bChanged = floatSetting->GetValue() != static_cast<double>(fValue);
+ floatSetting->SetValue(static_cast<double>(fValue));
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ return bChanged;
+}
+
+void CPeripheral::SetSettingVisible(const std::string& strKey, bool bSetTo)
+{
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end())
+ (*it).second.m_setting->SetVisible(bSetTo);
+}
+
+bool CPeripheral::IsSettingVisible(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end())
+ return (*it).second.m_setting->IsVisible();
+ return false;
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, const std::string& strValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end())
+ {
+ if ((*it).second.m_setting->GetType() == SettingType::String)
+ {
+ std::shared_ptr<CSettingString> stringSetting =
+ std::static_pointer_cast<CSettingString>((*it).second.m_setting);
+ if (stringSetting)
+ {
+ bChanged = !StringUtils::EqualsNoCase(stringSetting->GetValue(), strValue);
+ stringSetting->SetValue(strValue);
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ else if ((*it).second.m_setting->GetType() == SettingType::Integer)
+ bChanged = SetSetting(strKey, strValue.empty() ? 0 : atoi(strValue.c_str()));
+ else if ((*it).second.m_setting->GetType() == SettingType::Number)
+ bChanged = SetSetting(strKey, (float)(strValue.empty() ? 0 : atof(strValue.c_str())));
+ else if ((*it).second.m_setting->GetType() == SettingType::Boolean)
+ bChanged = SetSetting(strKey, strValue == "1");
+ }
+ return bChanged;
+}
+
+void CPeripheral::PersistSettings(bool bExiting /* = false */)
+{
+ CXBMCTinyXML doc;
+ TiXmlElement node("settings");
+ doc.InsertEndChild(node);
+ for (const auto& itr : m_settings)
+ {
+ TiXmlElement nodeSetting("setting");
+ nodeSetting.SetAttribute("id", itr.first.c_str());
+ std::string strValue;
+ switch (itr.second.m_setting->GetType())
+ {
+ case SettingType::String:
+ {
+ std::shared_ptr<CSettingString> stringSetting =
+ std::static_pointer_cast<CSettingString>(itr.second.m_setting);
+ if (stringSetting)
+ strValue = stringSetting->GetValue();
+ }
+ break;
+ case SettingType::Integer:
+ {
+ std::shared_ptr<CSettingInt> intSetting =
+ std::static_pointer_cast<CSettingInt>(itr.second.m_setting);
+ if (intSetting)
+ strValue = std::to_string(intSetting->GetValue());
+ }
+ break;
+ case SettingType::Number:
+ {
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::static_pointer_cast<CSettingNumber>(itr.second.m_setting);
+ if (floatSetting)
+ strValue = StringUtils::Format("{:.2f}", floatSetting->GetValue());
+ }
+ break;
+ case SettingType::Boolean:
+ {
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::static_pointer_cast<CSettingBool>(itr.second.m_setting);
+ if (boolSetting)
+ strValue = std::to_string(boolSetting->GetValue() ? 1 : 0);
+ }
+ break;
+ default:
+ break;
+ }
+ nodeSetting.SetAttribute("value", strValue.c_str());
+ doc.RootElement()->InsertEndChild(nodeSetting);
+ }
+
+ doc.SaveFile(m_strSettingsFile);
+
+ if (!bExiting)
+ {
+ for (const auto& it : m_changedSettings)
+ OnSettingChanged(it);
+ }
+ m_changedSettings.clear();
+}
+
+void CPeripheral::LoadPersistedSettings(void)
+{
+ CXBMCTinyXML doc;
+ if (doc.LoadFile(m_strSettingsFile))
+ {
+ const TiXmlElement* setting = doc.RootElement()->FirstChildElement("setting");
+ while (setting)
+ {
+ std::string strId = XMLUtils::GetAttribute(setting, "id");
+ std::string strValue = XMLUtils::GetAttribute(setting, "value");
+ SetSetting(strId, strValue);
+
+ setting = setting->NextSiblingElement("setting");
+ }
+ }
+}
+
+void CPeripheral::ResetDefaultSettings(void)
+{
+ ClearSettings();
+ m_manager.GetSettingsFromMapping(*this);
+
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.begin();
+ while (it != m_settings.end())
+ {
+ m_changedSettings.insert((*it).first);
+ ++it;
+ }
+
+ PersistSettings();
+}
+
+void CPeripheral::ClearSettings(void)
+{
+ m_settings.clear();
+}
+
+void CPeripheral::RegisterInputHandler(IInputHandler* handler, bool bPromiscuous)
+{
+ auto it = m_inputHandlers.find(handler);
+ if (it == m_inputHandlers.end())
+ {
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (addon)
+ {
+ std::unique_ptr<CAddonInputHandling> addonInput = std::make_unique<CAddonInputHandling>(
+ m_manager, this, std::move(addon), handler, GetDriverReceiver());
+ if (addonInput->Load())
+ {
+ RegisterJoystickDriverHandler(addonInput.get(), bPromiscuous);
+ m_inputHandlers[handler] = std::move(addonInput);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation);
+ }
+ }
+}
+
+void CPeripheral::UnregisterInputHandler(IInputHandler* handler)
+{
+ handler->ResetInputReceiver();
+
+ auto it = m_inputHandlers.find(handler);
+ if (it != m_inputHandlers.end())
+ {
+ UnregisterJoystickDriverHandler(it->second.get());
+ m_inputHandlers.erase(it);
+ }
+}
+
+void CPeripheral::RegisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handler,
+ bool bPromiscuous)
+{
+ auto it = m_keyboardHandlers.find(handler);
+ if (it == m_keyboardHandlers.end())
+ {
+ std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler> keyboardDriverHandler;
+
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (addon)
+ {
+ std::unique_ptr<CAddonInputHandling> addonInput =
+ std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler);
+ if (addonInput->Load())
+ keyboardDriverHandler = std::move(addonInput);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation);
+ }
+
+ if (keyboardDriverHandler)
+ {
+ RegisterKeyboardDriverHandler(keyboardDriverHandler.get(), bPromiscuous);
+ m_keyboardHandlers[handler] = std::move(keyboardDriverHandler);
+ }
+ }
+}
+
+void CPeripheral::UnregisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handler)
+{
+ auto it = m_keyboardHandlers.find(handler);
+ if (it != m_keyboardHandlers.end())
+ {
+ UnregisterKeyboardDriverHandler(it->second.get());
+ m_keyboardHandlers.erase(it);
+ }
+}
+
+void CPeripheral::RegisterMouseHandler(MOUSE::IMouseInputHandler* handler, bool bPromiscuous)
+{
+ auto it = m_mouseHandlers.find(handler);
+ if (it == m_mouseHandlers.end())
+ {
+ std::unique_ptr<KODI::MOUSE::IMouseDriverHandler> mouseDriverHandler;
+
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (addon)
+ {
+ std::unique_ptr<CAddonInputHandling> addonInput =
+ std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler);
+ if (addonInput->Load())
+ mouseDriverHandler = std::move(addonInput);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation);
+ }
+
+ if (mouseDriverHandler)
+ {
+ RegisterMouseDriverHandler(mouseDriverHandler.get(), bPromiscuous);
+ m_mouseHandlers[handler] = std::move(mouseDriverHandler);
+ }
+ }
+}
+
+void CPeripheral::UnregisterMouseHandler(MOUSE::IMouseInputHandler* handler)
+{
+ auto it = m_mouseHandlers.find(handler);
+ if (it != m_mouseHandlers.end())
+ {
+ UnregisterMouseDriverHandler(it->second.get());
+ m_mouseHandlers.erase(it);
+ }
+}
+
+void CPeripheral::RegisterJoystickButtonMapper(IButtonMapper* mapper)
+{
+ auto it = m_buttonMappers.find(mapper);
+ if (it == m_buttonMappers.end())
+ {
+ std::unique_ptr<CAddonButtonMapping> addonMapping(
+ new CAddonButtonMapping(m_manager, this, mapper));
+
+ RegisterJoystickDriverHandler(addonMapping.get(), false);
+ RegisterKeyboardDriverHandler(addonMapping.get(), false);
+ RegisterMouseDriverHandler(addonMapping.get(), false);
+
+ m_buttonMappers[mapper] = std::move(addonMapping);
+ }
+}
+
+void CPeripheral::UnregisterJoystickButtonMapper(IButtonMapper* mapper)
+{
+ auto it = m_buttonMappers.find(mapper);
+ if (it != m_buttonMappers.end())
+ {
+ UnregisterMouseDriverHandler(it->second.get());
+ UnregisterKeyboardDriverHandler(it->second.get());
+ UnregisterJoystickDriverHandler(it->second.get());
+
+ m_buttonMappers.erase(it);
+ }
+}
+
+std::string CPeripheral::GetIcon() const
+{
+ std::string icon;
+
+ // Try controller profile
+ const GAME::ControllerPtr controller = ControllerProfile();
+ if (controller)
+ icon = controller->Layout().ImagePath();
+
+ // Try add-on
+ if (icon.empty() && m_busType == PERIPHERAL_BUS_ADDON)
+ {
+ CPeripheralBusAddon* bus = static_cast<CPeripheralBusAddon*>(m_bus);
+
+ PeripheralAddonPtr addon;
+ unsigned int index;
+ if (bus->SplitLocation(m_strLocation, addon, index))
+ {
+ std::string addonIcon = addon->Icon();
+ if (!addonIcon.empty())
+ icon = std::move(addonIcon);
+ }
+ }
+
+ // Fallback
+ if (icon.empty())
+ icon = "DefaultAddon.png";
+
+ return icon;
+}
+
+bool CPeripheral::operator==(const PeripheralScanResult& right) const
+{
+ return StringUtils::EqualsNoCase(m_strLocation, right.m_strLocation);
+}
+
+bool CPeripheral::operator!=(const PeripheralScanResult& right) const
+{
+ return !(*this == right);
+}
+
+CDateTime CPeripheral::LastActive()
+{
+ return CDateTime();
+}
diff --git a/xbmc/peripherals/devices/Peripheral.h b/xbmc/peripherals/devices/Peripheral.h
new file mode 100644
index 0000000..8c4049b
--- /dev/null
+++ b/xbmc/peripherals/devices/Peripheral.h
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "games/controllers/ControllerTypes.h"
+#include "input/joysticks/interfaces/IInputProvider.h"
+#include "input/keyboard/interfaces/IKeyboardInputProvider.h"
+#include "input/mouse/interfaces/IMouseInputProvider.h"
+#include "peripherals/PeripheralTypes.h"
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+class TiXmlDocument;
+class CDateTime;
+class CSetting;
+class IKeymap;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IButtonMapper;
+class IDriverHandler;
+class IDriverReceiver;
+class IInputHandler;
+} // namespace JOYSTICK
+
+namespace KEYBOARD
+{
+class IKeyboardDriverHandler;
+}
+
+namespace MOUSE
+{
+class IMouseDriverHandler;
+}
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CAddonButtonMapping;
+class CGUIDialogPeripheralSettings;
+class CPeripheralBus;
+class CPeripherals;
+
+typedef enum
+{
+ STATE_SWITCH_TOGGLE,
+ STATE_ACTIVATE_SOURCE,
+ STATE_STANDBY
+} CecStateChange;
+
+class CPeripheral : public KODI::JOYSTICK::IInputProvider,
+ public KODI::KEYBOARD::IKeyboardInputProvider,
+ public KODI::MOUSE::IMouseInputProvider
+{
+ friend class CGUIDialogPeripheralSettings;
+
+public:
+ CPeripheral(CPeripherals& manager, const PeripheralScanResult& scanResult, CPeripheralBus* bus);
+ ~CPeripheral(void) override;
+
+ bool operator==(const CPeripheral& right) const;
+ bool operator!=(const CPeripheral& right) const;
+ bool operator==(const PeripheralScanResult& right) const;
+ bool operator!=(const PeripheralScanResult& right) const;
+
+ const std::string& FileLocation(void) const { return m_strFileLocation; }
+ const std::string& Location(void) const { return m_strLocation; }
+ int VendorId(void) const { return m_iVendorId; }
+ const char* VendorIdAsString(void) const { return m_strVendorId.c_str(); }
+ int ProductId(void) const { return m_iProductId; }
+ const char* ProductIdAsString(void) const { return m_strProductId.c_str(); }
+ PeripheralType Type(void) const { return m_type; }
+ PeripheralBusType GetBusType(void) const { return m_busType; }
+ const std::string& DeviceName(void) const { return m_strDeviceName; }
+ bool IsHidden(void) const { return m_bHidden; }
+ void SetHidden(bool bSetTo = true) { m_bHidden = bSetTo; }
+ const std::string& GetVersionInfo(void) const { return m_strVersionInfo; }
+
+ /*!
+ * @brief Get an icon for this peripheral
+ * @return Path to an icon, or skin icon file name
+ */
+ virtual std::string GetIcon() const;
+
+ /*!
+ * @brief Check whether this device has the given feature.
+ * @param feature The feature to check for.
+ * @return True when the device has the feature, false otherwise.
+ */
+ bool HasFeature(const PeripheralFeature feature) const;
+
+ /*!
+ * @brief Get all features that are supported by this device.
+ * @param features The features.
+ */
+ void GetFeatures(std::vector<PeripheralFeature>& features) const;
+
+ /*!
+ * @brief Initialises the peripheral.
+ * @return True when the peripheral has been initialised successfully, false otherwise.
+ */
+ bool Initialise(void);
+
+ /*!
+ * @brief Initialise one of the features of this peripheral.
+ * @param feature The feature to initialise.
+ * @return True when the feature has been initialised successfully, false otherwise.
+ */
+ virtual bool InitialiseFeature(const PeripheralFeature feature) { return true; }
+
+ /*!
+ * @brief Briefly activate a feature to notify the user
+ */
+ virtual void OnUserNotification() {}
+
+ /*!
+ * @brief Briefly test one of the features of this peripheral.
+ * @param feature The feature to test.
+ * @return True if the test succeeded, false otherwise.
+ */
+ virtual bool TestFeature(PeripheralFeature feature) { return false; }
+
+ /*!
+ * @brief Called when a setting changed.
+ * @param strChangedSetting The changed setting.
+ */
+ virtual void OnSettingChanged(const std::string& strChangedSetting) {}
+
+ /*!
+ * @brief Called when this device is removed, before calling the destructor.
+ */
+ virtual void OnDeviceRemoved(void) {}
+
+ /*!
+ * @brief Get all subdevices if this device is multifunctional.
+ * @param subDevices The subdevices.
+ */
+ virtual void GetSubdevices(PeripheralVector& subDevices) const;
+
+ /*!
+ * @return True when this device is multifunctional, false otherwise.
+ */
+ virtual bool IsMultiFunctional(void) const;
+
+ /*!
+ * @brief Add a setting to this peripheral. This will overwrite a previous setting with the same
+ * key.
+ * @param strKey The key of the setting.
+ * @param setting The setting.
+ */
+ virtual void AddSetting(const std::string& strKey,
+ const std::shared_ptr<const CSetting>& setting,
+ int order);
+
+ /*!
+ * @brief Check whether a setting is known with the given key.
+ * @param strKey The key to search.
+ * @return True when found, false otherwise.
+ */
+ virtual bool HasSetting(const std::string& strKey) const;
+
+ /*!
+ * @return True when this device has any settings, false otherwise.
+ */
+ virtual bool HasSettings(void) const;
+
+ /*!
+ * @return True when this device has any configurable settings, false otherwise.
+ */
+ virtual bool HasConfigurableSettings(void) const;
+
+ /*!
+ * @brief Get the value of a setting.
+ * @param strKey The key to search.
+ * @return The value or an empty string if it wasn't found.
+ */
+ virtual const std::string GetSettingString(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, const std::string& strValue);
+ virtual void SetSettingVisible(const std::string& strKey, bool bSetTo);
+ virtual bool IsSettingVisible(const std::string& strKey) const;
+
+ virtual int GetSettingInt(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, int iValue);
+
+ virtual bool GetSettingBool(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, bool bValue);
+
+ virtual float GetSettingFloat(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, float fValue);
+
+ virtual void PersistSettings(bool bExiting = false);
+ virtual void LoadPersistedSettings(void);
+ virtual void ResetDefaultSettings(void);
+
+ virtual std::vector<std::shared_ptr<CSetting>> GetSettings(void) const;
+
+ virtual bool ErrorOccured(void) const { return m_bError; }
+
+ virtual void RegisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler,
+ bool bPromiscuous)
+ {
+ }
+ virtual void UnregisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler) {}
+
+ virtual void RegisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler,
+ bool bPromiscuous)
+ {
+ }
+ virtual void UnregisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler) {}
+
+ virtual void RegisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler,
+ bool bPromiscuous)
+ {
+ }
+ virtual void UnregisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler) {}
+
+ // implementation of IInputProvider
+ void RegisterInputHandler(KODI::JOYSTICK::IInputHandler* handler, bool bPromiscuous) override;
+ void UnregisterInputHandler(KODI::JOYSTICK::IInputHandler* handler) override;
+
+ // implementation of IKeyboardInputProvider
+ void RegisterKeyboardHandler(KODI::KEYBOARD::IKeyboardInputHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterKeyboardHandler(KODI::KEYBOARD::IKeyboardInputHandler* handler) override;
+
+ // implementation of IMouseInputProvider
+ void RegisterMouseHandler(KODI::MOUSE::IMouseInputHandler* handler, bool bPromiscuous) override;
+ void UnregisterMouseHandler(KODI::MOUSE::IMouseInputHandler* handler) override;
+
+ virtual void RegisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper);
+ virtual void UnregisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper);
+
+ virtual KODI::JOYSTICK::IDriverReceiver* GetDriverReceiver() { return nullptr; }
+
+ virtual IKeymap* GetKeymap(const std::string& controllerId) { return nullptr; }
+
+ /*!
+ * \brief Return the last time this peripheral was active
+ *
+ * \return The time of last activation, or invalid if unknown/never active
+ */
+ virtual CDateTime LastActive();
+
+ /*!
+ * \brief Get the controller profile that best represents this peripheral
+ *
+ * \return The controller profile, or empty if unknown
+ */
+ virtual KODI::GAME::ControllerPtr ControllerProfile() const { return m_controllerProfile; }
+
+ /*!
+ * \brief Set the controller profile for this peripheral
+ *
+ * \param controller The new controller profile
+ */
+ virtual void SetControllerProfile(const KODI::GAME::ControllerPtr& controller)
+ {
+ m_controllerProfile = controller;
+ }
+
+protected:
+ virtual void ClearSettings(void);
+
+ CPeripherals& m_manager;
+ PeripheralType m_type;
+ PeripheralBusType m_busType;
+ PeripheralBusType m_mappedBusType;
+ std::string m_strLocation;
+ std::string m_strDeviceName;
+ std::string m_strSettingsFile;
+ std::string m_strFileLocation;
+ int m_iVendorId;
+ std::string m_strVendorId;
+ int m_iProductId;
+ std::string m_strProductId;
+ std::string m_strVersionInfo;
+ bool m_bInitialised;
+ bool m_bHidden;
+ bool m_bError;
+ std::vector<PeripheralFeature> m_features;
+ PeripheralVector m_subDevices;
+ std::map<std::string, PeripheralDeviceSetting> m_settings;
+ std::set<std::string> m_changedSettings;
+ CPeripheralBus* m_bus;
+ std::map<KODI::JOYSTICK::IInputHandler*, std::unique_ptr<KODI::JOYSTICK::IDriverHandler>>
+ m_inputHandlers;
+ std::map<KODI::KEYBOARD::IKeyboardInputHandler*,
+ std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler>>
+ m_keyboardHandlers;
+ std::map<KODI::MOUSE::IMouseInputHandler*, std::unique_ptr<KODI::MOUSE::IMouseDriverHandler>>
+ m_mouseHandlers;
+ std::map<KODI::JOYSTICK::IButtonMapper*, std::unique_ptr<CAddonButtonMapping>> m_buttonMappers;
+ KODI::GAME::ControllerPtr m_controllerProfile;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralBluetooth.cpp b/xbmc/peripherals/devices/PeripheralBluetooth.cpp
new file mode 100644
index 0000000..6f3802d
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralBluetooth.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralBluetooth.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralBluetooth::CPeripheralBluetooth(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_BLUETOOTH);
+}
diff --git a/xbmc/peripherals/devices/PeripheralBluetooth.h b/xbmc/peripherals/devices/PeripheralBluetooth.h
new file mode 100644
index 0000000..11fa7ca
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralBluetooth.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralBluetooth : public CPeripheral
+{
+public:
+ CPeripheralBluetooth(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralBluetooth(void) override = default;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.cpp b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp
new file mode 100644
index 0000000..bc62bea
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp
@@ -0,0 +1,1861 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralCecAdapter.h"
+
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationEnums.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationPowerHandling.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "input/remote/IRRemote.h"
+#include "messaging/ApplicationMessenger.h"
+#include "pictures/GUIWindowSlideShow.h"
+#include "utils/JobManager.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "xbmc/interfaces/AnnouncementManager.h"
+
+#include <mutex>
+
+#include <libcec/cec.h>
+
+using namespace PERIPHERALS;
+using namespace CEC;
+using namespace ANNOUNCEMENT;
+using namespace std::chrono_literals;
+
+#define CEC_LIB_SUPPORTED_VERSION LIBCEC_VERSION_TO_UINT(4, 0, 0)
+
+/* time in seconds to ignore standby commands from devices after the screensaver has been activated
+ */
+#define SCREENSAVER_TIMEOUT 20
+#define VOLUME_CHANGE_TIMEOUT 250
+#define VOLUME_REFRESH_TIMEOUT 100
+
+#define LOCALISED_ID_TV 36037
+#define LOCALISED_ID_AVR 36038
+#define LOCALISED_ID_TV_AVR 36039
+#define LOCALISED_ID_STOP 36044
+#define LOCALISED_ID_PAUSE 36045
+#define LOCALISED_ID_POWEROFF 13005
+#define LOCALISED_ID_SUSPEND 13011
+#define LOCALISED_ID_HIBERNATE 13010
+#define LOCALISED_ID_QUIT 13009
+#define LOCALISED_ID_IGNORE 36028
+#define LOCALISED_ID_RECORDING_DEVICE 36051
+#define LOCALISED_ID_PLAYBACK_DEVICE 36052
+#define LOCALISED_ID_TUNER_DEVICE 36053
+
+#define LOCALISED_ID_NONE 231
+
+/* time in seconds to suppress source activation after receiving OnStop */
+#define CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP 2
+
+CPeripheralCecAdapter::CPeripheralCecAdapter(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheralHID(manager, scanResult, bus), CThread("CECAdapter"), m_cecAdapter(NULL)
+{
+ ResetMembers();
+ m_features.push_back(FEATURE_CEC);
+ m_strComPort = scanResult.m_strLocation;
+}
+
+CPeripheralCecAdapter::~CPeripheralCecAdapter(void)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+ m_bStop = true;
+ }
+
+ StopThread(true);
+ delete m_queryThread;
+
+ if (m_cecAdapter)
+ {
+ CECDestroy(m_cecAdapter);
+ m_cecAdapter = NULL;
+ }
+}
+
+void CPeripheralCecAdapter::ResetMembers(void)
+{
+ if (m_cecAdapter)
+ CECDestroy(m_cecAdapter);
+ m_cecAdapter = NULL;
+ m_bStarted = false;
+ m_bHasButton = false;
+ m_bIsReady = false;
+ m_bHasConnectedAudioSystem = false;
+ m_strMenuLanguage = "???";
+ m_lastKeypress = {};
+ m_lastChange = VOLUME_CHANGE_NONE;
+ m_iExitCode = EXITCODE_QUIT;
+
+ //! @todo fetch the correct initial value when system audiostatus is
+ //! implemented in libCEC
+ m_bIsMuted = false;
+
+ m_bGoingToStandby = false;
+ m_bIsRunning = false;
+ m_bDeviceRemoved = false;
+ m_bActiveSourcePending = false;
+ m_bStandbyPending = false;
+ m_bActiveSourceBeforeStandby = false;
+ m_bOnPlayReceived = false;
+ m_bPlaybackPaused = false;
+ m_queryThread = NULL;
+ m_bPowerOnScreensaver = false;
+ m_bUseTVMenuLanguage = false;
+ m_bSendInactiveSource = false;
+ m_bPowerOffScreensaver = false;
+ m_bShutdownOnStandby = false;
+
+ m_currentButton.iButton = 0;
+ m_currentButton.iDuration = 0;
+ m_standbySent.SetValid(false);
+ m_configuration.Clear();
+}
+
+void CPeripheralCecAdapter::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnQuit" && m_bIsReady)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iExitCode = static_cast<int>(data["exitcode"].asInteger(EXITCODE_QUIT));
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+ StopThread(false);
+ }
+ else if (flag == ANNOUNCEMENT::GUI && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnScreensaverDeactivated" && m_bIsReady)
+ {
+ bool bIgnoreDeactivate(false);
+ if (data["shuttingdown"].isBoolean())
+ {
+ // don't respond to the deactivation if we are just going to suspend/shutdown anyway
+ // the tv will not have time to switch on before being told to standby and
+ // may not action the standby command.
+ bIgnoreDeactivate = data["shuttingdown"].asBoolean();
+ if (bIgnoreDeactivate)
+ CLog::Log(LOGDEBUG, "{} - ignoring OnScreensaverDeactivated for power action",
+ __FUNCTION__);
+ }
+ if (m_bPowerOnScreensaver && !bIgnoreDeactivate && m_configuration.bActivateSource)
+ {
+ ActivateSource();
+ }
+ }
+ else if (flag == ANNOUNCEMENT::GUI && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnScreensaverActivated" && m_bIsReady)
+ {
+ // Don't put devices to standby if application is currently playing
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer->IsPlaying() && m_bPowerOffScreensaver)
+ {
+ // only power off when we're the active source
+ if (m_cecAdapter->IsLibCECActiveSource())
+ StandbyDevices();
+ }
+ }
+ else if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnSleep")
+ {
+ // this will also power off devices when we're the active source
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bGoingToStandby = true;
+ }
+ StopThread();
+ }
+ else if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnWake")
+ {
+ CLog::Log(LOGDEBUG, "{} - reconnecting to the CEC adapter after standby mode", __FUNCTION__);
+ if (ReopenConnection())
+ {
+ bool bActivate(false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bActivate = m_bActiveSourceBeforeStandby;
+ m_bActiveSourceBeforeStandby = false;
+ }
+ if (bActivate)
+ ActivateSource();
+ }
+ }
+ else if (flag == ANNOUNCEMENT::Player && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnStop")
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_preventActivateSourceOnPlay = CDateTime::GetCurrentDateTime();
+ m_bOnPlayReceived = false;
+ }
+ else if (flag == ANNOUNCEMENT::Player && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ (message == "OnPlay" || message == "OnResume"))
+ {
+ // activate the source when playback started, and the option is enabled
+ bool bActivateSource(false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bActivateSource = (m_configuration.bActivateSource && !m_bOnPlayReceived &&
+ !m_cecAdapter->IsLibCECActiveSource() &&
+ (!m_preventActivateSourceOnPlay.IsValid() ||
+ CDateTime::GetCurrentDateTime() - m_preventActivateSourceOnPlay >
+ CDateTimeSpan(0, 0, 0, CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP)));
+ m_bOnPlayReceived = true;
+ }
+ if (bActivateSource)
+ ActivateSource();
+ }
+}
+
+bool CPeripheralCecAdapter::InitialiseFeature(const PeripheralFeature feature)
+{
+ if (feature == FEATURE_CEC && !m_bStarted && GetSettingBool("enabled"))
+ {
+ // hide settings that have an override set
+ if (!GetSettingString("wake_devices_advanced").empty())
+ SetSettingVisible("wake_devices", false);
+ if (!GetSettingString("standby_devices_advanced").empty())
+ SetSettingVisible("standby_devices", false);
+
+ SetConfigurationFromSettings();
+ m_callbacks.Clear();
+ m_callbacks.logMessage = &CecLogMessage;
+ m_callbacks.keyPress = &CecKeyPress;
+ m_callbacks.commandReceived = &CecCommand;
+ m_callbacks.configurationChanged = &CecConfiguration;
+ m_callbacks.alert = &CecAlert;
+ m_callbacks.sourceActivated = &CecSourceActivated;
+ m_configuration.callbackParam = this;
+ m_configuration.callbacks = &m_callbacks;
+
+ m_cecAdapter = CECInitialise(&m_configuration);
+
+ if (m_configuration.serverVersion < CEC_LIB_SUPPORTED_VERSION)
+ {
+ /* unsupported libcec version */
+ CLog::Log(
+ LOGERROR,
+ "Detected version of libCEC interface ({0:x}) is lower than the supported version {1:x}",
+ m_cecAdapter ? m_configuration.serverVersion : -1, CEC_LIB_SUPPORTED_VERSION);
+
+ // display warning: incompatible libCEC
+ std::string strMessage = StringUtils::Format(
+ g_localizeStrings.Get(36040), m_cecAdapter ? m_configuration.serverVersion : -1,
+ CEC_LIB_SUPPORTED_VERSION);
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000),
+ strMessage);
+ m_bError = true;
+ if (m_cecAdapter)
+ CECDestroy(m_cecAdapter);
+ m_cecAdapter = NULL;
+
+ m_features.clear();
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - using libCEC v{}", __FUNCTION__,
+ m_cecAdapter->VersionToString(m_configuration.serverVersion));
+ SetVersionInfo(m_configuration);
+ }
+
+ m_bStarted = true;
+ Create();
+ }
+
+ return CPeripheral::InitialiseFeature(feature);
+}
+
+void CPeripheralCecAdapter::SetVersionInfo(const libcec_configuration& configuration)
+{
+ m_strVersionInfo = StringUtils::Format("libCEC {} - firmware v{}",
+ m_cecAdapter->VersionToString(configuration.serverVersion),
+ configuration.iFirmwareVersion);
+
+ // append firmware build date
+ if (configuration.iFirmwareBuildDate != CEC_FW_BUILD_UNKNOWN)
+ {
+ CDateTime dt((time_t)configuration.iFirmwareBuildDate);
+ m_strVersionInfo += StringUtils::Format(" ({})", dt.GetAsDBDate());
+ }
+}
+
+bool CPeripheralCecAdapter::OpenConnection(void)
+{
+ bool bIsOpen(false);
+
+ if (!GetSettingBool("enabled"))
+ {
+ CLog::Log(LOGDEBUG, "{} - CEC adapter is disabled in peripheral settings", __FUNCTION__);
+ m_bStarted = false;
+ return bIsOpen;
+ }
+
+ // open the CEC adapter
+ CLog::Log(LOGDEBUG, "{} - opening a connection to the CEC adapter: {}", __FUNCTION__,
+ m_strComPort);
+
+ // scanning the CEC bus takes about 5 seconds, so display a notification to inform users that
+ // we're busy
+ std::string strMessage =
+ StringUtils::Format(g_localizeStrings.Get(21336), g_localizeStrings.Get(36000));
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ strMessage);
+
+ bool bConnectionFailedDisplayed(false);
+
+ while (!m_bStop && !bIsOpen)
+ {
+ if ((bIsOpen = m_cecAdapter->Open(m_strComPort.c_str(), 10000)) == false)
+ {
+ // display warning: couldn't initialise libCEC
+ CLog::Log(LOGERROR, "{} - could not opening a connection to the CEC adapter", __FUNCTION__);
+ if (!bConnectionFailedDisplayed)
+ CGUIDialogKaiToast::QueueNotification(
+ CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36012));
+ bConnectionFailedDisplayed = true;
+
+ CThread::Sleep(10000ms);
+ }
+ }
+
+ if (bIsOpen)
+ {
+ CLog::Log(LOGDEBUG, "{} - connection to the CEC adapter opened", __FUNCTION__);
+
+ // read the configuration
+ libcec_configuration config;
+ if (m_cecAdapter->GetCurrentConfiguration(&config))
+ {
+ // update the local configuration
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ SetConfigurationFromLibCEC(config);
+ }
+ }
+
+ return bIsOpen;
+}
+
+void CPeripheralCecAdapter::Process(void)
+{
+ if (!OpenConnection())
+ return;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iExitCode = EXITCODE_QUIT;
+ m_bGoingToStandby = false;
+ m_bIsRunning = true;
+ m_bActiveSourceBeforeStandby = false;
+ }
+
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+
+ m_queryThread = new CPeripheralCecAdapterUpdateThread(this, &m_configuration);
+ m_queryThread->Create(false);
+
+ while (!m_bStop)
+ {
+ if (!m_bStop)
+ ProcessVolumeChange();
+
+ if (!m_bStop)
+ ProcessActivateSource();
+
+ if (!m_bStop)
+ ProcessStandbyDevices();
+
+ if (!m_bStop)
+ CThread::Sleep(5ms);
+ }
+
+ m_queryThread->StopThread(true);
+
+ bool bSendStandbyCommands(false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bSendStandbyCommands = m_iExitCode != EXITCODE_REBOOT && m_iExitCode != EXITCODE_RESTARTAPP &&
+ !m_bDeviceRemoved &&
+ (!m_bGoingToStandby || GetSettingBool("standby_tv_on_pc_standby")) &&
+ GetSettingBool("enabled");
+
+ if (m_bGoingToStandby)
+ m_bActiveSourceBeforeStandby = m_cecAdapter->IsLibCECActiveSource();
+ }
+
+ if (bSendStandbyCommands)
+ {
+ if (m_cecAdapter->IsLibCECActiveSource())
+ {
+ if (!m_configuration.powerOffDevices.IsEmpty())
+ {
+ CLog::Log(LOGDEBUG, "{} - sending standby commands", __FUNCTION__);
+ m_standbySent = CDateTime::GetCurrentDateTime();
+ m_cecAdapter->StandbyDevices();
+ }
+ else if (m_bSendInactiveSource)
+ {
+ CLog::Log(LOGDEBUG, "{} - sending inactive source commands", __FUNCTION__);
+ m_cecAdapter->SetInactiveView();
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - XBMC is not the active source, not sending any standby commands",
+ __FUNCTION__);
+ }
+ }
+
+ m_cecAdapter->Close();
+
+ CLog::Log(LOGDEBUG, "{} - CEC adapter processor thread ended", __FUNCTION__);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bStarted = false;
+ m_bIsRunning = false;
+ }
+}
+
+bool CPeripheralCecAdapter::HasAudioControl(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bHasConnectedAudioSystem;
+}
+
+void CPeripheralCecAdapter::SetAudioSystemConnected(bool bSetTo)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bHasConnectedAudioSystem = bSetTo;
+}
+
+void CPeripheralCecAdapter::ProcessVolumeChange(void)
+{
+ bool bSendRelease(false);
+ CecVolumeChange pendingVolumeChange = VOLUME_CHANGE_NONE;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastKeypress);
+ if (!m_volumeChangeQueue.empty())
+ {
+ /* get the first change from the queue */
+ pendingVolumeChange = m_volumeChangeQueue.front();
+ m_volumeChangeQueue.pop();
+
+ /* remove all dupe entries */
+ while (!m_volumeChangeQueue.empty() && m_volumeChangeQueue.front() == pendingVolumeChange)
+ m_volumeChangeQueue.pop();
+
+ /* send another keypress after VOLUME_REFRESH_TIMEOUT ms */
+
+ bool bRefresh(duration.count() > VOLUME_REFRESH_TIMEOUT);
+
+ /* only send the keypress when it hasn't been sent yet */
+ if (pendingVolumeChange != m_lastChange)
+ {
+ m_lastKeypress = std::chrono::steady_clock::now();
+ m_lastChange = pendingVolumeChange;
+ }
+ else if (bRefresh)
+ {
+ m_lastKeypress = std::chrono::steady_clock::now();
+ pendingVolumeChange = m_lastChange;
+ }
+ else
+ pendingVolumeChange = VOLUME_CHANGE_NONE;
+ }
+ else if (m_lastKeypress.time_since_epoch().count() > 0 &&
+ duration.count() > VOLUME_CHANGE_TIMEOUT)
+ {
+ /* send a key release */
+ m_lastKeypress = {};
+ bSendRelease = true;
+ m_lastChange = VOLUME_CHANGE_NONE;
+ }
+ }
+
+ switch (pendingVolumeChange)
+ {
+ case VOLUME_CHANGE_UP:
+ m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_UP, false);
+ break;
+ case VOLUME_CHANGE_DOWN:
+ m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_DOWN, false);
+ break;
+ case VOLUME_CHANGE_MUTE:
+ m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_MUTE, false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bIsMuted = !m_bIsMuted;
+ }
+ break;
+ case VOLUME_CHANGE_NONE:
+ if (bSendRelease)
+ m_cecAdapter->SendKeyRelease(CECDEVICE_AUDIOSYSTEM, false);
+ break;
+ }
+}
+
+void CPeripheralCecAdapter::VolumeUp(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_volumeChangeQueue.push(VOLUME_CHANGE_UP);
+ }
+}
+
+void CPeripheralCecAdapter::VolumeDown(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_volumeChangeQueue.push(VOLUME_CHANGE_DOWN);
+ }
+}
+
+void CPeripheralCecAdapter::ToggleMute(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_volumeChangeQueue.push(VOLUME_CHANGE_MUTE);
+ }
+}
+
+bool CPeripheralCecAdapter::IsMuted(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bIsMuted;
+ }
+ return false;
+}
+
+void CPeripheralCecAdapter::SetMenuLanguage(const char* strLanguage)
+{
+ if (StringUtils::EqualsNoCase(m_strMenuLanguage, strLanguage))
+ return;
+
+ std::string strGuiLanguage;
+
+ if (!strcmp(strLanguage, "bul"))
+ strGuiLanguage = "bg_bg";
+ else if (!strcmp(strLanguage, "hrv"))
+ strGuiLanguage = "hr_hr";
+ else if (!strcmp(strLanguage, "cze"))
+ strGuiLanguage = "cs_cz";
+ else if (!strcmp(strLanguage, "dan"))
+ strGuiLanguage = "da_dk";
+ else if (!strcmp(strLanguage, "deu"))
+ strGuiLanguage = "de_de";
+ else if (!strcmp(strLanguage, "dut"))
+ strGuiLanguage = "nl_nl";
+ else if (!strcmp(strLanguage, "eng"))
+ strGuiLanguage = "en_gb";
+ else if (!strcmp(strLanguage, "fin"))
+ strGuiLanguage = "fi_fi";
+ else if (!strcmp(strLanguage, "fre"))
+ strGuiLanguage = "fr_fr";
+ else if (!strcmp(strLanguage, "ger"))
+ strGuiLanguage = "de_de";
+ else if (!strcmp(strLanguage, "gre"))
+ strGuiLanguage = "el_gr";
+ else if (!strcmp(strLanguage, "hun"))
+ strGuiLanguage = "hu_hu";
+ else if (!strcmp(strLanguage, "ita"))
+ strGuiLanguage = "it_it";
+ else if (!strcmp(strLanguage, "nor"))
+ strGuiLanguage = "nb_no";
+ else if (!strcmp(strLanguage, "pol"))
+ strGuiLanguage = "pl_pl";
+ else if (!strcmp(strLanguage, "por"))
+ strGuiLanguage = "pt_pt";
+ else if (!strcmp(strLanguage, "rum"))
+ strGuiLanguage = "ro_ro";
+ else if (!strcmp(strLanguage, "rus"))
+ strGuiLanguage = "ru_ru";
+ else if (!strcmp(strLanguage, "srp"))
+ strGuiLanguage = "sr_rs@latin";
+ else if (!strcmp(strLanguage, "slo"))
+ strGuiLanguage = "sk_sk";
+ else if (!strcmp(strLanguage, "slv"))
+ strGuiLanguage = "sl_si";
+ else if (!strcmp(strLanguage, "spa"))
+ strGuiLanguage = "es_es";
+ else if (!strcmp(strLanguage, "swe"))
+ strGuiLanguage = "sv_se";
+ else if (!strcmp(strLanguage, "tur"))
+ strGuiLanguage = "tr_tr";
+
+ if (!strGuiLanguage.empty())
+ {
+ strGuiLanguage = "resource.language." + strGuiLanguage;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SETLANGUAGE, -1, -1, nullptr, strGuiLanguage);
+ CLog::Log(LOGDEBUG, "{} - language set to '{}'", __FUNCTION__, strGuiLanguage);
+ }
+ else
+ CLog::Log(LOGWARNING, "{} - TV menu language set to unknown value '{}'", __FUNCTION__,
+ strLanguage);
+}
+
+void CPeripheralCecAdapter::OnTvStandby(void)
+{
+ int iActionOnTvStandby = GetSettingInt("standby_pc_on_tv_standby");
+ switch (iActionOnTvStandby)
+ {
+ case LOCALISED_ID_POWEROFF:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_SHUTDOWN);
+ break;
+ case LOCALISED_ID_SUSPEND:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_SUSPEND);
+ break;
+ case LOCALISED_ID_HIBERNATE:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_HIBERNATE);
+ break;
+ case LOCALISED_ID_QUIT:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ break;
+ case LOCALISED_ID_PAUSE:
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PAUSE_IF_PLAYING);
+ break;
+ case LOCALISED_ID_STOP:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlaying())
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_STOP);
+ break;
+ }
+ case LOCALISED_ID_IGNORE:
+ break;
+ default:
+ CLog::Log(LOGERROR, "{} - Unexpected [standby_pc_on_tv_standby] setting value", __FUNCTION__);
+ break;
+ }
+}
+
+void CPeripheralCecAdapter::CecCommand(void* cbParam, const cec_command* command)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ if (adapter->m_bIsReady)
+ {
+ switch (command->opcode)
+ {
+ case CEC_OPCODE_STANDBY:
+ if (command->initiator == CECDEVICE_TV &&
+ (!adapter->m_standbySent.IsValid() ||
+ CDateTime::GetCurrentDateTime() - adapter->m_standbySent >
+ CDateTimeSpan(0, 0, 0, SCREENSAVER_TIMEOUT)))
+ {
+ adapter->OnTvStandby();
+ }
+ break;
+ case CEC_OPCODE_SET_MENU_LANGUAGE:
+ if (adapter->m_bUseTVMenuLanguage == 1 && command->initiator == CECDEVICE_TV &&
+ command->parameters.size == 3)
+ {
+ char strNewLanguage[4];
+ for (int iPtr = 0; iPtr < 3; iPtr++)
+ strNewLanguage[iPtr] = command->parameters[iPtr];
+ strNewLanguage[3] = 0;
+ adapter->SetMenuLanguage(strNewLanguage);
+ }
+ break;
+ case CEC_OPCODE_DECK_CONTROL:
+ if (command->initiator == CECDEVICE_TV && command->parameters.size == 1 &&
+ command->parameters[0] == CEC_DECK_CONTROL_MODE_STOP)
+ {
+ cec_keypress key;
+ key.duration = 500;
+ key.keycode = CEC_USER_CONTROL_CODE_STOP;
+ adapter->PushCecKeypress(key);
+ }
+ break;
+ case CEC_OPCODE_PLAY:
+ if (command->initiator == CECDEVICE_TV && command->parameters.size == 1)
+ {
+ if (command->parameters[0] == CEC_PLAY_MODE_PLAY_FORWARD)
+ {
+ cec_keypress key;
+ key.duration = 500;
+ key.keycode = CEC_USER_CONTROL_CODE_PLAY;
+ adapter->PushCecKeypress(key);
+ }
+ else if (command->parameters[0] == CEC_PLAY_MODE_PLAY_STILL)
+ {
+ cec_keypress key;
+ key.duration = 500;
+ key.keycode = CEC_USER_CONTROL_CODE_PAUSE;
+ adapter->PushCecKeypress(key);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void CPeripheralCecAdapter::CecConfiguration(void* cbParam, const libcec_configuration* config)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(adapter->m_critSection);
+ adapter->SetConfigurationFromLibCEC(*config);
+}
+
+void CPeripheralCecAdapter::CecAlert(void* cbParam,
+ const libcec_alert alert,
+ const libcec_parameter data)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ bool bReopenConnection(false);
+ int iAlertString(0);
+ switch (alert)
+ {
+ case CEC_ALERT_SERVICE_DEVICE:
+ iAlertString = 36027;
+ break;
+ case CEC_ALERT_CONNECTION_LOST:
+ bReopenConnection = true;
+ iAlertString = 36030;
+ break;
+#if defined(CEC_ALERT_PERMISSION_ERROR)
+ case CEC_ALERT_PERMISSION_ERROR:
+ bReopenConnection = true;
+ iAlertString = 36031;
+ break;
+ case CEC_ALERT_PORT_BUSY:
+ bReopenConnection = true;
+ iAlertString = 36032;
+ break;
+#endif
+ default:
+ break;
+ }
+
+ // display the alert
+ if (iAlertString)
+ {
+ std::string strLog(g_localizeStrings.Get(iAlertString));
+ if (data.paramType == CEC_PARAMETER_TYPE_STRING && data.paramData)
+ strLog += StringUtils::Format(" - {}", (const char*)data.paramData);
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ strLog);
+ }
+
+ if (bReopenConnection)
+ {
+ // Reopen the connection asynchronously. Otherwise a deadlock may occur.
+ // Reconnect means destruction and recreation of our libcec instance, but libcec
+ // calls this callback function synchronously and must not be destroyed meanwhile.
+ adapter->ReopenConnection(true);
+ }
+}
+
+void CPeripheralCecAdapter::CecKeyPress(void* cbParam, const cec_keypress* key)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!!adapter)
+ adapter->PushCecKeypress(*key);
+}
+
+void CPeripheralCecAdapter::GetNextKey(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bHasButton = false;
+ if (m_bIsReady)
+ {
+ std::vector<CecButtonPress>::iterator it = m_buttonQueue.begin();
+ if (it != m_buttonQueue.end())
+ {
+ m_currentButton = (*it);
+ m_buttonQueue.erase(it);
+ m_bHasButton = true;
+ }
+ }
+}
+
+void CPeripheralCecAdapter::PushCecKeypress(const CecButtonPress& key)
+{
+ CLog::Log(LOGDEBUG, "{} - received key {:2x} duration {}", __FUNCTION__, key.iButton,
+ key.iDuration);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // avoid the queue getting too long
+ if (m_configuration.iButtonRepeatRateMs && m_buttonQueue.size() > 5)
+ return;
+ if (m_configuration.iButtonRepeatRateMs == 0 && key.iDuration > 0)
+ {
+ if (m_currentButton.iButton == key.iButton && m_currentButton.iDuration == 0)
+ {
+ // update the duration
+ if (m_bHasButton)
+ m_currentButton.iDuration = key.iDuration;
+ // ignore this one, since it's already been handled by xbmc
+ return;
+ }
+ // if we received a keypress with a duration set, try to find the same one without a duration
+ // set, and replace it
+ for (std::vector<CecButtonPress>::reverse_iterator it = m_buttonQueue.rbegin();
+ it != m_buttonQueue.rend(); ++it)
+ {
+ if ((*it).iButton == key.iButton)
+ {
+ if ((*it).iDuration == 0)
+ {
+ // replace this entry
+ (*it).iDuration = key.iDuration;
+ return;
+ }
+ // add a new entry
+ break;
+ }
+ }
+ }
+
+ m_buttonQueue.push_back(key);
+}
+
+void CPeripheralCecAdapter::PushCecKeypress(const cec_keypress& key)
+{
+ CecButtonPress xbmcKey;
+ xbmcKey.iDuration = key.duration;
+
+ switch (key.keycode)
+ {
+ case CEC_USER_CONTROL_CODE_SELECT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SELECT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_LEFT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_LEFT_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_LEFT_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RIGHT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RIGHT_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RIGHT_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_SETUP_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CONTENTS_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CONTENTS_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_ROOT_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_ROOT_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_TOP_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TOP_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DVD_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DVD_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_FAVORITE_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_EXIT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_BACK;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_ENTER:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_ENTER;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CHANNEL_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CHANNEL_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PREVIOUS_CHANNEL:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_SOUND_SELECT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LANGUAGE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_POWER:
+ case CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION:
+ case CEC_USER_CONTROL_CODE_POWER_OFF_FUNCTION:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_POWER;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_VOLUME_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_VOLUME_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_MUTE:
+ case CEC_USER_CONTROL_CODE_MUTE_FUNCTION:
+ case CEC_USER_CONTROL_CODE_RESTORE_VOLUME_FUNCTION:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_MUTE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PLAY:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_PLAY;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_STOP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_STOP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PAUSE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_PAUSE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_REWIND:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_REVERSE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_FAST_FORWARD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_FORWARD;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER0:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_0;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER1:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_1;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER2:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_2;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER3:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_3;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER4:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_4;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER5:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_5;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER6:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_6;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER7:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_7;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER8:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_8;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER9:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_9;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RECORD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RECORD;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CLEAR:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CLEAR;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_INFO;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PAGE_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PAGE_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_FORWARD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_BACKWARD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F1_BLUE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_BLUE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F2_RED:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RED;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F3_GREEN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_GREEN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F4_YELLOW:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_YELLOW;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_ELECTRONIC_PROGRAM_GUIDE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_GUIDE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LIVE_TV;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NEXT_FAVORITE:
+ case CEC_USER_CONTROL_CODE_DOT:
+ case CEC_USER_CONTROL_CODE_AN_RETURN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE; // context menu
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DATA:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_SUB_PICTURE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SUBTITLE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_EJECT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_EJECT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION:
+ case CEC_USER_CONTROL_CODE_INPUT_SELECT:
+ case CEC_USER_CONTROL_CODE_INITIAL_CONFIGURATION:
+ case CEC_USER_CONTROL_CODE_HELP:
+ case CEC_USER_CONTROL_CODE_STOP_RECORD:
+ case CEC_USER_CONTROL_CODE_PAUSE_RECORD:
+ case CEC_USER_CONTROL_CODE_ANGLE:
+ case CEC_USER_CONTROL_CODE_VIDEO_ON_DEMAND:
+ case CEC_USER_CONTROL_CODE_TIMER_PROGRAMMING:
+ case CEC_USER_CONTROL_CODE_PLAY_FUNCTION:
+ case CEC_USER_CONTROL_CODE_PAUSE_PLAY_FUNCTION:
+ case CEC_USER_CONTROL_CODE_RECORD_FUNCTION:
+ case CEC_USER_CONTROL_CODE_PAUSE_RECORD_FUNCTION:
+ case CEC_USER_CONTROL_CODE_STOP_FUNCTION:
+ case CEC_USER_CONTROL_CODE_TUNE_FUNCTION:
+ case CEC_USER_CONTROL_CODE_SELECT_MEDIA_FUNCTION:
+ case CEC_USER_CONTROL_CODE_SELECT_AV_INPUT_FUNCTION:
+ case CEC_USER_CONTROL_CODE_SELECT_AUDIO_INPUT_FUNCTION:
+ case CEC_USER_CONTROL_CODE_F5:
+ case CEC_USER_CONTROL_CODE_NUMBER_ENTRY_MODE:
+ case CEC_USER_CONTROL_CODE_NUMBER11:
+ case CEC_USER_CONTROL_CODE_NUMBER12:
+ case CEC_USER_CONTROL_CODE_SELECT_BROADCAST_TYPE:
+ case CEC_USER_CONTROL_CODE_SELECT_SOUND_PRESENTATION:
+ case CEC_USER_CONTROL_CODE_UNKNOWN:
+ default:
+ break;
+ }
+}
+
+int CPeripheralCecAdapter::GetButton(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_bHasButton)
+ GetNextKey();
+
+ return m_bHasButton ? m_currentButton.iButton : 0;
+}
+
+unsigned int CPeripheralCecAdapter::GetHoldTime(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_bHasButton)
+ GetNextKey();
+
+ return m_bHasButton ? m_currentButton.iDuration : 0;
+}
+
+void CPeripheralCecAdapter::ResetButton(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bHasButton = false;
+
+ // wait for the key release if the duration isn't 0
+ if (m_currentButton.iDuration > 0)
+ {
+ m_currentButton.iButton = 0;
+ m_currentButton.iDuration = 0;
+ }
+}
+
+void CPeripheralCecAdapter::OnSettingChanged(const std::string& strChangedSetting)
+{
+ if (StringUtils::EqualsNoCase(strChangedSetting, "enabled"))
+ {
+ bool bEnabled(GetSettingBool("enabled"));
+ if (!bEnabled && IsRunning())
+ {
+ CLog::Log(LOGDEBUG, "{} - closing the CEC connection", __FUNCTION__);
+ StopThread(true);
+ }
+ else if (bEnabled && !IsRunning())
+ {
+ CLog::Log(LOGDEBUG, "{} - starting the CEC connection", __FUNCTION__);
+ SetConfigurationFromSettings();
+ InitialiseFeature(FEATURE_CEC);
+ }
+ }
+ else if (IsRunning())
+ {
+ if (m_queryThread->IsRunning())
+ {
+ CLog::Log(LOGDEBUG, "{} - sending the updated configuration to libCEC", __FUNCTION__);
+ SetConfigurationFromSettings();
+ m_queryThread->UpdateConfiguration(&m_configuration);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - restarting the CEC connection", __FUNCTION__);
+ SetConfigurationFromSettings();
+ InitialiseFeature(FEATURE_CEC);
+ }
+}
+
+void CPeripheralCecAdapter::CecSourceActivated(void* cbParam,
+ const CEC::cec_logical_address address,
+ const uint8_t activated)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ // wake up the screensaver, so the user doesn't switch to a black screen
+ if (activated == 1)
+ {
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ appPower->WakeUpScreenSaverAndDPMS();
+ }
+
+ if (adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") != LOCALISED_ID_NONE)
+ {
+ bool bShowingSlideshow =
+ (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW);
+ CGUIWindowSlideShow* pSlideShow =
+ bShowingSlideshow
+ ? CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(
+ WINDOW_SLIDESHOW)
+ : NULL;
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+
+ bool bPlayingAndDeactivated = activated == 0 && ((pSlideShow && pSlideShow->IsPlaying()) ||
+ !appPlayer->IsPausedPlayback());
+ bool bPausedAndActivated =
+ activated == 1 && adapter->m_bPlaybackPaused &&
+ ((pSlideShow && pSlideShow->IsPaused()) || (appPlayer && appPlayer->IsPausedPlayback()));
+ if (bPlayingAndDeactivated)
+ adapter->m_bPlaybackPaused = true;
+ else if (bPausedAndActivated)
+ adapter->m_bPlaybackPaused = false;
+
+ if ((bPlayingAndDeactivated || bPausedAndActivated) &&
+ adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") == LOCALISED_ID_PAUSE)
+ {
+ if (pSlideShow)
+ // pause/resume slideshow
+ pSlideShow->OnAction(CAction(ACTION_PAUSE));
+ else
+ // pause/resume player
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE);
+ }
+ else if (bPlayingAndDeactivated &&
+ adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") == LOCALISED_ID_STOP)
+ {
+ if (pSlideShow)
+ pSlideShow->OnAction(CAction(ACTION_STOP));
+ else
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_STOP);
+ }
+ }
+}
+
+void CPeripheralCecAdapter::CecLogMessage(void* cbParam, const cec_log_message* message)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ int iLevel = -1;
+ switch (message->level)
+ {
+ case CEC_LOG_ERROR:
+ iLevel = LOGERROR;
+ break;
+ case CEC_LOG_WARNING:
+ iLevel = LOGWARNING;
+ break;
+ case CEC_LOG_NOTICE:
+ iLevel = LOGDEBUG;
+ break;
+ case CEC_LOG_TRAFFIC:
+ case CEC_LOG_DEBUG:
+ iLevel = LOGDEBUG;
+ break;
+ default:
+ break;
+ }
+
+ if (iLevel >= CEC_LOG_NOTICE ||
+ (iLevel >= 0 && CServiceBroker::GetLogging().IsLogLevelLogged(LOGDEBUG)))
+ CLog::Log(iLevel, LOGCEC, "{} - {}", __FUNCTION__, message->message);
+}
+
+void CPeripheralCecAdapter::SetConfigurationFromLibCEC(const CEC::libcec_configuration& config)
+{
+ bool bChanged(false);
+
+ // set the primary device type
+ m_configuration.deviceTypes.Clear();
+ m_configuration.deviceTypes.Add(config.deviceTypes[0]);
+
+ // hide the "connected device" and "hdmi port number" settings when the PA was autodetected
+ bool bPAAutoDetected(config.bAutodetectAddress == 1);
+
+ SetSettingVisible("connected_device", !bPAAutoDetected);
+ SetSettingVisible("cec_hdmi_port", !bPAAutoDetected);
+
+ // set the connected device
+ m_configuration.baseDevice = config.baseDevice;
+ bChanged |=
+ SetSetting("connected_device",
+ config.baseDevice == CECDEVICE_AUDIOSYSTEM ? LOCALISED_ID_AVR : LOCALISED_ID_TV);
+
+ // set the HDMI port number
+ m_configuration.iHDMIPort = config.iHDMIPort;
+ bChanged |= SetSetting("cec_hdmi_port", config.iHDMIPort);
+
+ // set the physical address, when baseDevice or iHDMIPort are not set
+ std::string strPhysicalAddress("0");
+ if (!bPAAutoDetected && (m_configuration.baseDevice == CECDEVICE_UNKNOWN ||
+ m_configuration.iHDMIPort < CEC_MIN_HDMI_PORTNUMBER ||
+ m_configuration.iHDMIPort > CEC_MAX_HDMI_PORTNUMBER))
+ {
+ m_configuration.iPhysicalAddress = config.iPhysicalAddress;
+ strPhysicalAddress = StringUtils::Format("{:x}", config.iPhysicalAddress);
+ }
+ bChanged |= SetSetting("physical_address", strPhysicalAddress);
+
+ // set the devices to wake when starting
+ m_configuration.wakeDevices = config.wakeDevices;
+ bChanged |= WriteLogicalAddresses(config.wakeDevices, "wake_devices", "wake_devices_advanced");
+
+ // set the devices to power off when stopping
+ m_configuration.powerOffDevices = config.powerOffDevices;
+ bChanged |=
+ WriteLogicalAddresses(config.powerOffDevices, "standby_devices", "standby_devices_advanced");
+
+ // set the boolean settings
+ m_configuration.bActivateSource = config.bActivateSource;
+ bChanged |= SetSetting("activate_source", m_configuration.bActivateSource == 1);
+
+ m_configuration.iDoubleTapTimeoutMs = config.iDoubleTapTimeoutMs;
+ bChanged |= SetSetting("double_tap_timeout_ms", (int)m_configuration.iDoubleTapTimeoutMs);
+
+ m_configuration.iButtonRepeatRateMs = config.iButtonRepeatRateMs;
+ bChanged |= SetSetting("button_repeat_rate_ms", (int)m_configuration.iButtonRepeatRateMs);
+
+ m_configuration.iButtonReleaseDelayMs = config.iButtonReleaseDelayMs;
+ bChanged |= SetSetting("button_release_delay_ms", (int)m_configuration.iButtonReleaseDelayMs);
+
+ m_configuration.bPowerOffOnStandby = config.bPowerOffOnStandby;
+
+ m_configuration.iFirmwareVersion = config.iFirmwareVersion;
+
+ memcpy(m_configuration.strDeviceLanguage, config.strDeviceLanguage, 3);
+ m_configuration.iFirmwareBuildDate = config.iFirmwareBuildDate;
+
+ SetVersionInfo(m_configuration);
+
+ if (bChanged)
+ CLog::Log(LOGDEBUG, "SetConfigurationFromLibCEC - settings updated by libCEC");
+}
+
+void CPeripheralCecAdapter::SetConfigurationFromSettings(void)
+{
+ // client version matches the version of libCEC that we originally used the API from
+ m_configuration.clientVersion = LIBCEC_VERSION_TO_UINT(4, 0, 0);
+
+ // device name 'XBMC'
+ snprintf(m_configuration.strDeviceName, 13, "%s", GetSettingString("device_name").c_str());
+
+ // set the primary device type
+ m_configuration.deviceTypes.Clear();
+ switch (GetSettingInt("device_type"))
+ {
+ case LOCALISED_ID_PLAYBACK_DEVICE:
+ m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+ break;
+ case LOCALISED_ID_TUNER_DEVICE:
+ m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_TUNER);
+ break;
+ case LOCALISED_ID_RECORDING_DEVICE:
+ default:
+ m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE);
+ break;
+ }
+
+ // always try to autodetect the address.
+ // when the firmware supports this, it will override the physical address, connected device and
+ // hdmi port settings
+ m_configuration.bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS;
+
+ // set the physical address
+ // when set, it will override the connected device and hdmi port settings
+ std::string strPhysicalAddress = GetSettingString("physical_address");
+ int iPhysicalAddress;
+ if (sscanf(strPhysicalAddress.c_str(), "%x", &iPhysicalAddress) &&
+ iPhysicalAddress >= CEC_PHYSICAL_ADDRESS_TV && iPhysicalAddress <= CEC_MAX_PHYSICAL_ADDRESS)
+ m_configuration.iPhysicalAddress = iPhysicalAddress;
+ else
+ m_configuration.iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV;
+
+ // set the connected device
+ int iConnectedDevice = GetSettingInt("connected_device");
+ if (iConnectedDevice == LOCALISED_ID_AVR)
+ m_configuration.baseDevice = CECDEVICE_AUDIOSYSTEM;
+ else if (iConnectedDevice == LOCALISED_ID_TV)
+ m_configuration.baseDevice = CECDEVICE_TV;
+
+ // set the HDMI port number
+ int iHDMIPort = GetSettingInt("cec_hdmi_port");
+ if (iHDMIPort >= CEC_MIN_HDMI_PORTNUMBER && iHDMIPort <= CEC_MAX_HDMI_PORTNUMBER)
+ m_configuration.iHDMIPort = iHDMIPort;
+
+ // set the tv vendor override
+ int iVendor = GetSettingInt("tv_vendor");
+ if (iVendor >= CEC_MIN_VENDORID && iVendor <= CEC_MAX_VENDORID)
+ m_configuration.tvVendor = iVendor;
+
+ // read the devices to wake when starting
+ std::string strWakeDevices = GetSettingString("wake_devices_advanced");
+ StringUtils::Trim(strWakeDevices);
+ m_configuration.wakeDevices.Clear();
+ if (!strWakeDevices.empty())
+ ReadLogicalAddresses(strWakeDevices, m_configuration.wakeDevices);
+ else
+ ReadLogicalAddresses(GetSettingInt("wake_devices"), m_configuration.wakeDevices);
+
+ // read the devices to power off when stopping
+ std::string strStandbyDevices = GetSettingString("standby_devices_advanced");
+ StringUtils::Trim(strStandbyDevices);
+ m_configuration.powerOffDevices.Clear();
+ if (!strStandbyDevices.empty())
+ ReadLogicalAddresses(strStandbyDevices, m_configuration.powerOffDevices);
+ else
+ ReadLogicalAddresses(GetSettingInt("standby_devices"), m_configuration.powerOffDevices);
+
+ // read the boolean settings
+ m_bUseTVMenuLanguage = GetSettingBool("use_tv_menu_language");
+ m_configuration.bActivateSource = GetSettingBool("activate_source") ? 1 : 0;
+ m_bPowerOffScreensaver = GetSettingBool("cec_standby_screensaver");
+ m_bPowerOnScreensaver = GetSettingBool("cec_wake_screensaver");
+ m_bSendInactiveSource = GetSettingBool("send_inactive_source");
+ m_configuration.bAutoWakeAVR = GetSettingBool("power_avr_on_as") ? 1 : 0;
+
+ // read the mutually exclusive boolean settings
+ int iStandbyAction(GetSettingInt("standby_pc_on_tv_standby"));
+ m_configuration.bPowerOffOnStandby =
+ (iStandbyAction == LOCALISED_ID_SUSPEND || iStandbyAction == LOCALISED_ID_HIBERNATE) ? 1 : 0;
+ m_bShutdownOnStandby = iStandbyAction == LOCALISED_ID_POWEROFF;
+
+ // double tap prevention timeout in ms
+ m_configuration.iDoubleTapTimeoutMs = GetSettingInt("double_tap_timeout_ms");
+ m_configuration.iButtonRepeatRateMs = GetSettingInt("button_repeat_rate_ms");
+ m_configuration.iButtonReleaseDelayMs = GetSettingInt("button_release_delay_ms");
+
+ if (GetSettingBool("pause_playback_on_deactivate"))
+ {
+ SetSetting("pause_or_stop_playback_on_deactivate", LOCALISED_ID_PAUSE);
+ SetSetting("pause_playback_on_deactivate", false);
+ }
+}
+
+void CPeripheralCecAdapter::ReadLogicalAddresses(const std::string& strString,
+ cec_logical_addresses& addresses)
+{
+ for (size_t iPtr = 0; iPtr < strString.size(); iPtr++)
+ {
+ std::string strDevice = strString.substr(iPtr, 1);
+ StringUtils::Trim(strDevice);
+ if (!strDevice.empty())
+ {
+ int iDevice(0);
+ if (sscanf(strDevice.c_str(), "%x", &iDevice) == 1 && iDevice >= 0 && iDevice <= 0xF)
+ addresses.Set((cec_logical_address)iDevice);
+ }
+ }
+}
+
+void CPeripheralCecAdapter::ReadLogicalAddresses(int iLocalisedId, cec_logical_addresses& addresses)
+{
+ addresses.Clear();
+ switch (iLocalisedId)
+ {
+ case LOCALISED_ID_TV:
+ addresses.Set(CECDEVICE_TV);
+ break;
+ case LOCALISED_ID_AVR:
+ addresses.Set(CECDEVICE_AUDIOSYSTEM);
+ break;
+ case LOCALISED_ID_TV_AVR:
+ addresses.Set(CECDEVICE_TV);
+ addresses.Set(CECDEVICE_AUDIOSYSTEM);
+ break;
+ case LOCALISED_ID_NONE:
+ default:
+ break;
+ }
+}
+
+bool CPeripheralCecAdapter::WriteLogicalAddresses(const cec_logical_addresses& addresses,
+ const std::string& strSettingName,
+ const std::string& strAdvancedSettingName)
+{
+ bool bChanged(false);
+
+ // only update the advanced setting if it was set by the user
+ if (!GetSettingString(strAdvancedSettingName).empty())
+ {
+ std::string strPowerOffDevices;
+ for (unsigned int iPtr = CECDEVICE_TV; iPtr <= CECDEVICE_BROADCAST; iPtr++)
+ if (addresses[iPtr])
+ strPowerOffDevices += StringUtils::Format(" {:X}", iPtr);
+ StringUtils::Trim(strPowerOffDevices);
+ bChanged = SetSetting(strAdvancedSettingName, strPowerOffDevices);
+ }
+
+ int iSettingPowerOffDevices = LOCALISED_ID_NONE;
+ if (addresses[CECDEVICE_TV] && addresses[CECDEVICE_AUDIOSYSTEM])
+ iSettingPowerOffDevices = LOCALISED_ID_TV_AVR;
+ else if (addresses[CECDEVICE_TV])
+ iSettingPowerOffDevices = LOCALISED_ID_TV;
+ else if (addresses[CECDEVICE_AUDIOSYSTEM])
+ iSettingPowerOffDevices = LOCALISED_ID_AVR;
+ return SetSetting(strSettingName, iSettingPowerOffDevices) || bChanged;
+}
+
+CPeripheralCecAdapterUpdateThread::CPeripheralCecAdapterUpdateThread(
+ CPeripheralCecAdapter* adapter, libcec_configuration* configuration)
+ : CThread("CECAdapterUpdate"),
+ m_adapter(adapter),
+ m_configuration(*configuration),
+ m_bNextConfigurationScheduled(false),
+ m_bIsUpdating(true)
+{
+ m_nextConfiguration.Clear();
+ m_event.Reset();
+}
+
+CPeripheralCecAdapterUpdateThread::~CPeripheralCecAdapterUpdateThread(void)
+{
+ StopThread(false);
+ m_event.Set();
+ StopThread(true);
+}
+
+void CPeripheralCecAdapterUpdateThread::Signal(void)
+{
+ m_event.Set();
+}
+
+bool CPeripheralCecAdapterUpdateThread::UpdateConfiguration(libcec_configuration* configuration)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!configuration)
+ return false;
+
+ if (m_bIsUpdating)
+ {
+ m_bNextConfigurationScheduled = true;
+ m_nextConfiguration = *configuration;
+ }
+ else
+ {
+ m_configuration = *configuration;
+ m_event.Set();
+ }
+ return true;
+}
+
+bool CPeripheralCecAdapterUpdateThread::WaitReady(void)
+{
+ // don't wait if we're not powering up anything
+ if (m_configuration.wakeDevices.IsEmpty() && m_configuration.bActivateSource == 0)
+ return true;
+
+ // wait for the TV if we're configured to become the active source.
+ // wait for the first device in the wake list otherwise.
+ cec_logical_address waitFor =
+ (m_configuration.bActivateSource == 1) ? CECDEVICE_TV : m_configuration.wakeDevices.primary;
+
+ cec_power_status powerStatus(CEC_POWER_STATUS_UNKNOWN);
+ bool bContinue(true);
+ while (bContinue && !m_adapter->m_bStop && !m_bStop && powerStatus != CEC_POWER_STATUS_ON)
+ {
+ powerStatus = m_adapter->m_cecAdapter->GetDevicePowerStatus(waitFor);
+ if (powerStatus != CEC_POWER_STATUS_ON)
+ bContinue = !m_event.Wait(1000ms);
+ }
+
+ return powerStatus == CEC_POWER_STATUS_ON;
+}
+
+void CPeripheralCecAdapterUpdateThread::UpdateMenuLanguage(void)
+{
+ // request the menu language of the TV
+ if (m_adapter->m_bUseTVMenuLanguage == 1)
+ {
+ CLog::Log(LOGDEBUG, "{} - requesting the menu language of the TV", __FUNCTION__);
+ std::string language(m_adapter->m_cecAdapter->GetDeviceMenuLanguage(CECDEVICE_TV));
+ m_adapter->SetMenuLanguage(language.c_str());
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - using TV menu language is disabled", __FUNCTION__);
+ }
+}
+
+std::string CPeripheralCecAdapterUpdateThread::UpdateAudioSystemStatus(void)
+{
+ std::string strAmpName;
+
+ /* disable the mute setting when an amp is found, because the amp handles the mute setting and
+ set PCM output to 100% */
+ if (m_adapter->m_cecAdapter->IsActiveDeviceType(CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+ {
+ // request the OSD name of the amp
+ std::string ampName(m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_AUDIOSYSTEM));
+ CLog::Log(LOGDEBUG,
+ "{} - CEC capable amplifier found ({}). volume will be controlled on the amp",
+ __FUNCTION__, ampName);
+ strAmpName += ampName;
+
+ // set amp present
+ m_adapter->SetAudioSystemConnected(true);
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ appVolume->SetMute(false);
+ appVolume->SetVolume(CApplicationVolumeHandling::VOLUME_MAXIMUM, false);
+ }
+ else
+ {
+ // set amp present
+ CLog::Log(LOGDEBUG, "{} - no CEC capable amplifier found", __FUNCTION__);
+ m_adapter->SetAudioSystemConnected(false);
+ }
+
+ return strAmpName;
+}
+
+bool CPeripheralCecAdapterUpdateThread::SetInitialConfiguration(void)
+{
+ // the option to make XBMC the active source is set
+ if (m_configuration.bActivateSource == 1)
+ m_adapter->m_cecAdapter->SetActiveSource();
+
+ // devices to wake are set
+ cec_logical_addresses tvOnly;
+ tvOnly.Clear();
+ tvOnly.Set(CECDEVICE_TV);
+ if (!m_configuration.wakeDevices.IsEmpty() &&
+ (m_configuration.wakeDevices != tvOnly || m_configuration.bActivateSource == 0))
+ m_adapter->m_cecAdapter->PowerOnDevices(CECDEVICE_BROADCAST);
+
+ // wait until devices are powered up
+ if (!WaitReady())
+ return false;
+
+ UpdateMenuLanguage();
+
+ // request the OSD name of the TV
+ std::string strNotification;
+ std::string tvName(m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_TV));
+ strNotification = StringUtils::Format("{}: {}", g_localizeStrings.Get(36016), tvName);
+
+ std::string strAmpName = UpdateAudioSystemStatus();
+ if (!strAmpName.empty())
+ strNotification += StringUtils::Format("- {}", strAmpName);
+
+ m_adapter->m_bIsReady = true;
+
+ // and let the gui know that we're done
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ strNotification);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bIsUpdating = false;
+ return true;
+}
+
+bool CPeripheralCecAdapter::IsRunning(void) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bIsRunning;
+}
+
+void CPeripheralCecAdapterUpdateThread::Process(void)
+{
+ // set the initial configuration
+ if (!SetInitialConfiguration())
+ return;
+
+ // and wait for updates
+ bool bUpdate(false);
+ while (!m_bStop)
+ {
+ // update received
+ if (bUpdate || m_event.Wait(500ms))
+ {
+ if (m_bStop)
+ return;
+ // set the new configuration
+ libcec_configuration configuration;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ configuration = m_configuration;
+ m_bIsUpdating = false;
+ }
+
+ CLog::Log(LOGDEBUG, "{} - updating the configuration", __FUNCTION__);
+ bool bConfigSet(m_adapter->m_cecAdapter->SetConfiguration(&configuration));
+ // display message: config updated / failed to update
+ if (!bConfigSet)
+ CLog::Log(LOGERROR, "{} - libCEC couldn't set the new configuration", __FUNCTION__);
+ else
+ {
+ UpdateMenuLanguage();
+ UpdateAudioSystemStatus();
+ }
+
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ g_localizeStrings.Get(bConfigSet ? 36023 : 36024));
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if ((bUpdate = m_bNextConfigurationScheduled) == true)
+ {
+ // another update is scheduled
+ m_bNextConfigurationScheduled = false;
+ m_configuration = m_nextConfiguration;
+ }
+ else
+ {
+ // nothing left to do, wait for updates
+ m_bIsUpdating = false;
+ m_event.Reset();
+ }
+ }
+ }
+ }
+}
+
+void CPeripheralCecAdapter::OnDeviceRemoved(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bDeviceRemoved = true;
+}
+
+namespace PERIPHERALS
+{
+
+class CPeripheralCecAdapterReopenJob : public CJob
+{
+public:
+ CPeripheralCecAdapterReopenJob(CPeripheralCecAdapter* adapter) : m_adapter(adapter) {}
+ ~CPeripheralCecAdapterReopenJob() override = default;
+
+ bool DoWork(void) override { return m_adapter->ReopenConnection(false); }
+
+private:
+ CPeripheralCecAdapter* m_adapter;
+};
+
+}; // namespace PERIPHERALS
+
+bool CPeripheralCecAdapter::ReopenConnection(bool bAsync /* = false */)
+{
+ if (bAsync)
+ {
+ CServiceBroker::GetJobManager()->AddJob(new CPeripheralCecAdapterReopenJob(this), nullptr,
+ CJob::PRIORITY_NORMAL);
+ return true;
+ }
+
+ // stop running thread
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iExitCode = EXITCODE_RESTARTAPP;
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+ StopThread(false);
+ }
+ StopThread();
+
+ // reset all members to their defaults
+ ResetMembers();
+
+ // reopen the connection
+ return InitialiseFeature(FEATURE_CEC);
+}
+
+void CPeripheralCecAdapter::ActivateSource(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bActiveSourcePending = true;
+}
+
+void CPeripheralCecAdapter::ProcessActivateSource(void)
+{
+ bool bActivate(false);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bActivate = m_bActiveSourcePending;
+ m_bActiveSourcePending = false;
+ }
+
+ if (bActivate)
+ m_cecAdapter->SetActiveSource();
+}
+
+void CPeripheralCecAdapter::StandbyDevices(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bStandbyPending = true;
+}
+
+void CPeripheralCecAdapter::ProcessStandbyDevices(void)
+{
+ bool bStandby(false);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bStandby = m_bStandbyPending;
+ m_bStandbyPending = false;
+ if (bStandby)
+ m_bGoingToStandby = true;
+ }
+
+ if (bStandby)
+ {
+ if (!m_configuration.powerOffDevices.IsEmpty())
+ {
+ m_standbySent = CDateTime::GetCurrentDateTime();
+ m_cecAdapter->StandbyDevices(CECDEVICE_BROADCAST);
+ }
+ else if (m_bSendInactiveSource == 1)
+ {
+ CLog::Log(LOGDEBUG, "{} - sending inactive source commands", __FUNCTION__);
+ m_cecAdapter->SetInactiveView();
+ }
+ }
+}
+
+bool CPeripheralCecAdapter::ToggleDeviceState(CecStateChange mode /*= STATE_SWITCH_TOGGLE */,
+ bool forceType /*= false */)
+{
+ if (!IsRunning())
+ return false;
+ if (m_cecAdapter->IsLibCECActiveSource() &&
+ (mode == STATE_SWITCH_TOGGLE || mode == STATE_STANDBY))
+ {
+ CLog::Log(LOGDEBUG, "{} - putting CEC device on standby...", __FUNCTION__);
+ StandbyDevices();
+ return false;
+ }
+ else if (mode == STATE_SWITCH_TOGGLE || mode == STATE_ACTIVATE_SOURCE)
+ {
+ CLog::Log(LOGDEBUG, "{} - waking up CEC device...", __FUNCTION__);
+ ActivateSource();
+ return true;
+ }
+
+ return false;
+}
diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.h b/xbmc/peripherals/devices/PeripheralCecAdapter.h
new file mode 100644
index 0000000..b3755b3
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralCecAdapter.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#if !defined(HAVE_LIBCEC)
+#include "Peripheral.h"
+
+// an empty implementation, so CPeripherals can be compiled without a bunch of #ifdef's when libCEC
+// is not available
+namespace PERIPHERALS
+{
+class CPeripheralCecAdapter : public CPeripheral
+{
+public:
+ bool HasAudioControl(void) { return false; }
+ void VolumeUp(void) {}
+ void VolumeDown(void) {}
+ bool IsMuted(void) { return false; }
+ void ToggleMute(void) {}
+ bool ToggleDeviceState(CecStateChange mode = STATE_SWITCH_TOGGLE, bool forceType = false)
+ {
+ return false;
+ }
+
+ int GetButton(void) { return 0; }
+ unsigned int GetHoldTime(void) { return 0; }
+ void ResetButton(void) {}
+};
+} // namespace PERIPHERALS
+
+#else
+
+#include "PeripheralHID.h"
+#include "XBDateTime.h"
+#include "interfaces/AnnouncementManager.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <chrono>
+#include <queue>
+#include <vector>
+
+// undefine macro isset, it collides with function in cectypes.h
+#ifdef isset
+#undef isset
+#endif
+#include <libcec/cectypes.h>
+
+class CVariant;
+
+namespace CEC
+{
+class ICECAdapter;
+};
+
+namespace PERIPHERALS
+{
+class CPeripheralCecAdapterUpdateThread;
+class CPeripheralCecAdapterReopenJob;
+
+typedef struct
+{
+ int iButton;
+ unsigned int iDuration;
+} CecButtonPress;
+
+typedef enum
+{
+ VOLUME_CHANGE_NONE,
+ VOLUME_CHANGE_UP,
+ VOLUME_CHANGE_DOWN,
+ VOLUME_CHANGE_MUTE
+} CecVolumeChange;
+
+class CPeripheralCecAdapter : public CPeripheralHID,
+ public ANNOUNCEMENT::IAnnouncer,
+ private CThread
+{
+ friend class CPeripheralCecAdapterUpdateThread;
+ friend class CPeripheralCecAdapterReopenJob;
+
+public:
+ CPeripheralCecAdapter(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralCecAdapter(void) override;
+
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
+ // audio control
+ bool HasAudioControl(void);
+ void VolumeUp(void);
+ void VolumeDown(void);
+ void ToggleMute(void);
+ bool IsMuted(void);
+
+ // CPeripheral callbacks
+ void OnSettingChanged(const std::string& strChangedSetting) override;
+ void OnDeviceRemoved(void) override;
+
+ // input
+ int GetButton(void);
+ unsigned int GetHoldTime(void);
+ void ResetButton(void);
+
+ // public CEC methods
+ void ActivateSource(void);
+ void StandbyDevices(void);
+ bool ToggleDeviceState(CecStateChange mode = STATE_SWITCH_TOGGLE, bool forceType = false);
+
+private:
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void ResetMembers(void);
+ void Process(void) override;
+ bool IsRunning(void) const;
+
+ bool OpenConnection(void);
+ bool ReopenConnection(bool bAsync = false);
+
+ void SetConfigurationFromSettings(void);
+ void SetConfigurationFromLibCEC(const CEC::libcec_configuration& config);
+ void SetVersionInfo(const CEC::libcec_configuration& configuration);
+
+ static void ReadLogicalAddresses(const std::string& strString,
+ CEC::cec_logical_addresses& addresses);
+ static void ReadLogicalAddresses(int iLocalisedId, CEC::cec_logical_addresses& addresses);
+ bool WriteLogicalAddresses(const CEC::cec_logical_addresses& addresses,
+ const std::string& strSettingName,
+ const std::string& strAdvancedSettingName);
+
+ void ProcessActivateSource(void);
+ void ProcessStandbyDevices(void);
+ void ProcessVolumeChange(void);
+
+ void PushCecKeypress(const CEC::cec_keypress& key);
+ void PushCecKeypress(const CecButtonPress& key);
+ void GetNextKey(void);
+
+ void SetAudioSystemConnected(bool bSetTo);
+ void SetMenuLanguage(const char* strLanguage);
+ void OnTvStandby(void);
+
+ // callbacks from libCEC
+ static void CecLogMessage(void* cbParam, const CEC::cec_log_message* message);
+ static void CecCommand(void* cbParam, const CEC::cec_command* command);
+ static void CecConfiguration(void* cbParam, const CEC::libcec_configuration* config);
+ static void CecAlert(void* cbParam,
+ const CEC::libcec_alert alert,
+ const CEC::libcec_parameter data);
+ static void CecSourceActivated(void* param,
+ const CEC::cec_logical_address address,
+ const uint8_t activated);
+ static void CecKeyPress(void* cbParam, const CEC::cec_keypress* key);
+
+ CEC::ICECAdapter* m_cecAdapter;
+ bool m_bStarted;
+ bool m_bHasButton;
+ bool m_bIsReady;
+ bool m_bHasConnectedAudioSystem;
+ std::string m_strMenuLanguage;
+ CDateTime m_standbySent;
+ std::vector<CecButtonPress> m_buttonQueue;
+ CecButtonPress m_currentButton;
+ std::queue<CecVolumeChange> m_volumeChangeQueue;
+ std::chrono::time_point<std::chrono::steady_clock> m_lastKeypress;
+ CecVolumeChange m_lastChange;
+ int m_iExitCode;
+ bool m_bIsMuted;
+ bool m_bGoingToStandby;
+ bool m_bIsRunning;
+ bool m_bDeviceRemoved;
+ CPeripheralCecAdapterUpdateThread* m_queryThread;
+ CEC::ICECCallbacks m_callbacks;
+ mutable CCriticalSection m_critSection;
+ CEC::libcec_configuration m_configuration;
+ bool m_bActiveSourcePending;
+ bool m_bStandbyPending;
+ CDateTime m_preventActivateSourceOnPlay;
+ bool m_bActiveSourceBeforeStandby;
+ bool m_bOnPlayReceived;
+ bool m_bPlaybackPaused;
+ std::string m_strComPort;
+ bool m_bPowerOnScreensaver;
+ bool m_bUseTVMenuLanguage;
+ bool m_bSendInactiveSource;
+ bool m_bPowerOffScreensaver;
+ bool m_bShutdownOnStandby;
+};
+
+class CPeripheralCecAdapterUpdateThread : public CThread
+{
+public:
+ CPeripheralCecAdapterUpdateThread(CPeripheralCecAdapter* adapter,
+ CEC::libcec_configuration* configuration);
+ ~CPeripheralCecAdapterUpdateThread(void) override;
+
+ void Signal(void);
+ bool UpdateConfiguration(CEC::libcec_configuration* configuration);
+
+protected:
+ void UpdateMenuLanguage(void);
+ std::string UpdateAudioSystemStatus(void);
+ bool WaitReady(void);
+ bool SetInitialConfiguration(void);
+ void Process(void) override;
+
+ CPeripheralCecAdapter* m_adapter;
+ CEvent m_event;
+ CCriticalSection m_critSection;
+ CEC::libcec_configuration m_configuration;
+ CEC::libcec_configuration m_nextConfiguration;
+ bool m_bNextConfigurationScheduled;
+ bool m_bIsUpdating;
+};
+} // namespace PERIPHERALS
+
+#endif
diff --git a/xbmc/peripherals/devices/PeripheralDisk.cpp b/xbmc/peripherals/devices/PeripheralDisk.cpp
new file mode 100644
index 0000000..9b0682c
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralDisk.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralDisk.h"
+
+#include "guilib/LocalizeStrings.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralDisk::CPeripheralDisk(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35003)
+ : scanResult.m_strDeviceName;
+ m_features.push_back(FEATURE_DISK);
+}
diff --git a/xbmc/peripherals/devices/PeripheralDisk.h b/xbmc/peripherals/devices/PeripheralDisk.h
new file mode 100644
index 0000000..f81b31e
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralDisk.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralDisk : public CPeripheral
+{
+public:
+ CPeripheralDisk(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralDisk(void) override = default;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralHID.cpp b/xbmc/peripherals/devices/PeripheralHID.cpp
new file mode 100644
index 0000000..e3d061b
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralHID.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralHID.h"
+
+#include "guilib/LocalizeStrings.h"
+#include "input/InputManager.h"
+#include "peripherals/Peripherals.h"
+#include "utils/log.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralHID::CPeripheralHID(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35001)
+ : scanResult.m_strDeviceName;
+ m_features.push_back(FEATURE_HID);
+}
+
+CPeripheralHID::~CPeripheralHID(void)
+{
+ if (!m_strKeymap.empty() && !GetSettingBool("do_not_use_custom_keymap"))
+ {
+ CLog::Log(LOGDEBUG, "{} - switching active keymapping to: default", __FUNCTION__);
+ m_manager.GetInputManager().RemoveKeymap(m_strKeymap);
+ }
+}
+
+bool CPeripheralHID::InitialiseFeature(const PeripheralFeature feature)
+{
+ if (feature == FEATURE_HID && !m_bInitialised)
+ {
+ m_bInitialised = true;
+
+ if (HasSetting("keymap"))
+ m_strKeymap = GetSettingString("keymap");
+
+ if (m_strKeymap.empty())
+ {
+ m_strKeymap = StringUtils::Format("v{}p{}", VendorIdAsString(), ProductIdAsString());
+ SetSetting("keymap", m_strKeymap);
+ }
+
+ if (!IsSettingVisible("keymap"))
+ SetSettingVisible("do_not_use_custom_keymap", false);
+
+ if (!m_strKeymap.empty())
+ {
+ bool bKeymapEnabled(!GetSettingBool("do_not_use_custom_keymap"));
+ if (bKeymapEnabled)
+ {
+ CLog::Log(LOGDEBUG, "{} - adding keymapping for: {}", __FUNCTION__, m_strKeymap);
+ m_manager.GetInputManager().AddKeymap(m_strKeymap);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - removing keymapping for: {}", __FUNCTION__, m_strKeymap);
+ m_manager.GetInputManager().RemoveKeymap(m_strKeymap);
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "{} - initialised HID device ({}:{})", __FUNCTION__, m_strVendorId,
+ m_strProductId);
+ }
+
+ return CPeripheral::InitialiseFeature(feature);
+}
+
+void CPeripheralHID::OnSettingChanged(const std::string& strChangedSetting)
+{
+ if (m_bInitialised && ((StringUtils::EqualsNoCase(strChangedSetting, "keymap") &&
+ !GetSettingBool("do_not_use_custom_keymap")) ||
+ StringUtils::EqualsNoCase(strChangedSetting, "keymap_enabled")))
+ {
+ m_bInitialised = false;
+ InitialiseFeature(FEATURE_HID);
+ }
+}
diff --git a/xbmc/peripherals/devices/PeripheralHID.h b/xbmc/peripherals/devices/PeripheralHID.h
new file mode 100644
index 0000000..3b35dfd
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralHID.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "input/XBMC_keyboard.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralHID : public CPeripheral
+{
+public:
+ CPeripheralHID(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralHID(void) override;
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ virtual bool LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode)
+ {
+ return false;
+ }
+ void OnSettingChanged(const std::string& strChangedSetting) override;
+
+protected:
+ std::string m_strKeymap;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralImon.cpp b/xbmc/peripherals/devices/PeripheralImon.cpp
new file mode 100644
index 0000000..6683f8b
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralImon.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralImon.h"
+
+#include "input/InputManager.h"
+#include "settings/Settings.h"
+#include "utils/log.h"
+
+using namespace PERIPHERALS;
+
+std::atomic<long> CPeripheralImon::m_lCountOfImonsConflictWithDInput(0L);
+
+CPeripheralImon::CPeripheralImon(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheralHID(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_IMON);
+ m_bImonConflictsWithDInput = false;
+}
+
+void CPeripheralImon::OnDeviceRemoved()
+{
+ if (m_bImonConflictsWithDInput)
+ {
+ if (--m_lCountOfImonsConflictWithDInput == 0)
+ ActionOnImonConflict(false);
+ }
+}
+
+bool CPeripheralImon::InitialiseFeature(const PeripheralFeature feature)
+{
+ if (feature == FEATURE_IMON)
+ {
+#if defined(TARGET_WINDOWS)
+ if (HasSetting("disable_winjoystick") && GetSettingBool("disable_winjoystick"))
+ m_bImonConflictsWithDInput = true;
+ else
+#endif // TARGET_WINDOWS
+ m_bImonConflictsWithDInput = false;
+
+ if (m_bImonConflictsWithDInput)
+ {
+ ++m_lCountOfImonsConflictWithDInput;
+ ActionOnImonConflict(true);
+ }
+ return CPeripheral::InitialiseFeature(feature);
+ }
+
+ return CPeripheralHID::InitialiseFeature(feature);
+}
+
+void CPeripheralImon::AddSetting(const std::string& strKey,
+ const std::shared_ptr<const CSetting>& setting,
+ int order)
+{
+#if !defined(TARGET_WINDOWS)
+ if (strKey.compare("disable_winjoystick") != 0)
+#endif // !TARGET_WINDOWS
+ CPeripheralHID::AddSetting(strKey, setting, order);
+}
+
+void CPeripheralImon::OnSettingChanged(const std::string& strChangedSetting)
+{
+ if (strChangedSetting.compare("disable_winjoystick") == 0)
+ {
+ if (m_bImonConflictsWithDInput && !GetSettingBool("disable_winjoystick"))
+ {
+ m_bImonConflictsWithDInput = false;
+ if (--m_lCountOfImonsConflictWithDInput == 0)
+ ActionOnImonConflict(false);
+ }
+ else if (!m_bImonConflictsWithDInput && GetSettingBool("disable_winjoystick"))
+ {
+ m_bImonConflictsWithDInput = true;
+ ++m_lCountOfImonsConflictWithDInput;
+ ActionOnImonConflict(true);
+ }
+ }
+}
+
+void CPeripheralImon::ActionOnImonConflict(bool deviceInserted /*= true*/)
+{
+}
diff --git a/xbmc/peripherals/devices/PeripheralImon.h b/xbmc/peripherals/devices/PeripheralImon.h
new file mode 100644
index 0000000..612d059
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralImon.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PeripheralHID.h"
+
+#include <atomic>
+
+class CSetting;
+
+namespace PERIPHERALS
+{
+class CPeripheralImon : public CPeripheralHID
+{
+public:
+ CPeripheralImon(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralImon(void) override = default;
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void OnSettingChanged(const std::string& strChangedSetting) override;
+ void OnDeviceRemoved() override;
+ void AddSetting(const std::string& strKey,
+ const std::shared_ptr<const CSetting>& setting,
+ int order) override;
+ inline bool IsImonConflictsWithDInput() { return m_bImonConflictsWithDInput; }
+ static inline long GetCountOfImonsConflictWithDInput()
+ {
+ return m_lCountOfImonsConflictWithDInput;
+ }
+ static void ActionOnImonConflict(bool deviceInserted = true);
+
+private:
+ bool m_bImonConflictsWithDInput;
+ static std::atomic<long> m_lCountOfImonsConflictWithDInput;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralJoystick.cpp b/xbmc/peripherals/devices/PeripheralJoystick.cpp
new file mode 100644
index 0000000..bccad91
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralJoystick.cpp
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralJoystick.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonInstaller.h"
+#include "addons/AddonManager.h"
+#include "application/Application.h"
+#include "games/GameServices.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerIDs.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/InputManager.h"
+#include "input/joysticks/DeadzoneFilter.h"
+#include "input/joysticks/JoystickMonitor.h"
+#include "input/joysticks/JoystickTranslator.h"
+#include "input/joysticks/RumbleGenerator.h"
+#include "input/joysticks/interfaces/IDriverHandler.h"
+#include "input/joysticks/keymaps/KeymapHandling.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/addons/AddonButtonMap.h"
+#include "peripherals/bus/virtual/PeripheralBusAddon.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+CPeripheralJoystick::CPeripheralJoystick(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus),
+ m_requestedPort(JOYSTICK_PORT_UNKNOWN),
+ m_buttonCount(0),
+ m_hatCount(0),
+ m_axisCount(0),
+ m_motorCount(0),
+ m_supportsPowerOff(false),
+ m_rumbleGenerator(new CRumbleGenerator)
+{
+ m_features.push_back(FEATURE_JOYSTICK);
+ // FEATURE_RUMBLE conditionally added via SetMotorCount()
+}
+
+CPeripheralJoystick::~CPeripheralJoystick(void)
+{
+ if (m_rumbleGenerator)
+ {
+ m_rumbleGenerator->AbortRumble();
+ m_rumbleGenerator.reset();
+ }
+
+ if (m_joystickMonitor)
+ {
+ UnregisterInputHandler(m_joystickMonitor.get());
+ m_joystickMonitor.reset();
+ }
+
+ m_appInput.reset();
+ m_deadzoneFilter.reset();
+ m_buttonMap.reset();
+
+ // Wait for remaining install tasks
+ for (std::future<void>& task : m_installTasks)
+ task.wait();
+}
+
+bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ if (CPeripheral::InitialiseFeature(feature))
+ {
+ if (feature == FEATURE_JOYSTICK)
+ {
+ // Ensure an add-on is present to translate input
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (!addon)
+ {
+ CLog::Log(LOGERROR, "CPeripheralJoystick: No button mapping add-on for {}", m_strLocation);
+ }
+ else
+ {
+ if (m_bus->InitializeProperties(*this))
+ bSuccess = true;
+ else
+ CLog::Log(LOGERROR, "CPeripheralJoystick: Invalid location ({})", m_strLocation);
+ }
+
+ if (bSuccess)
+ {
+ m_buttonMap =
+ std::make_unique<CAddonButtonMap>(this, addon, DEFAULT_CONTROLLER_ID, m_manager);
+ if (m_buttonMap->Load())
+ {
+ InitializeDeadzoneFiltering(*m_buttonMap);
+ InitializeControllerProfile(*m_buttonMap);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "CPeripheralJoystick: Failed to load button map for {}",
+ m_strLocation);
+ m_buttonMap.reset();
+ }
+
+ // Give joystick monitor priority over default controller
+ m_appInput.reset(
+ new CKeymapHandling(this, false, m_manager.GetInputManager().KeymapEnvironment()));
+ m_joystickMonitor.reset(new CJoystickMonitor);
+ RegisterInputHandler(m_joystickMonitor.get(), false);
+ }
+ }
+ else if (feature == FEATURE_RUMBLE)
+ {
+ bSuccess = true; // Nothing to do
+ }
+ else if (feature == FEATURE_POWER_OFF)
+ {
+ bSuccess = true; // Nothing to do
+ }
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralJoystick::InitializeDeadzoneFiltering(IButtonMap& buttonMap)
+{
+ m_deadzoneFilter.reset(new CDeadzoneFilter(&buttonMap, this));
+}
+
+void CPeripheralJoystick::InitializeControllerProfile(IButtonMap& buttonMap)
+{
+ const std::string controllerId = buttonMap.GetAppearance();
+ if (controllerId.empty())
+ return;
+
+ auto controller = m_manager.GetControllerProfiles().GetController(controllerId);
+ if (controller)
+ CPeripheral::SetControllerProfile(controller);
+ else
+ {
+ std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex);
+
+ // Deposit controller into queue
+ m_controllersToInstall.emplace(controllerId);
+
+ // Clean up finished install tasks
+ m_installTasks.erase(std::remove_if(m_installTasks.begin(), m_installTasks.end(),
+ [](std::future<void>& task) {
+ return task.wait_for(std::chrono::seconds(0)) ==
+ std::future_status::ready;
+ }),
+ m_installTasks.end());
+
+ // Install controller off-thread
+ std::future<void> installTask = std::async(std::launch::async, [this]() {
+ // Withdraw controller from queue
+ std::string controllerToInstall;
+ {
+ std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex);
+ if (!m_controllersToInstall.empty())
+ {
+ controllerToInstall = m_controllersToInstall.front();
+ m_controllersToInstall.pop();
+ }
+ }
+
+ // Do the install
+ GAME::ControllerPtr controller = InstallAsync(controllerToInstall);
+ if (controller)
+ CPeripheral::SetControllerProfile(controller);
+ });
+
+ // Hold the task to prevent the destructor from completing during an install
+ m_installTasks.emplace_back(std::move(installTask));
+ }
+}
+
+void CPeripheralJoystick::OnUserNotification()
+{
+ IInputReceiver* inputReceiver = m_appInput->GetInputReceiver(m_rumbleGenerator->ControllerID());
+ m_rumbleGenerator->NotifyUser(inputReceiver);
+}
+
+bool CPeripheralJoystick::TestFeature(PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ switch (feature)
+ {
+ case FEATURE_RUMBLE:
+ {
+ IInputReceiver* inputReceiver =
+ m_appInput->GetInputReceiver(m_rumbleGenerator->ControllerID());
+ bSuccess = m_rumbleGenerator->DoTest(inputReceiver);
+ break;
+ }
+ case FEATURE_POWER_OFF:
+ if (m_supportsPowerOff)
+ {
+ PowerOff();
+ bSuccess = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralJoystick::PowerOff()
+{
+ m_bus->PowerOff(m_strLocation);
+}
+
+void CPeripheralJoystick::RegisterJoystickDriverHandler(IDriverHandler* handler, bool bPromiscuous)
+{
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ DriverHandler driverHandler = {handler, bPromiscuous};
+ m_driverHandlers.insert(m_driverHandlers.begin(), driverHandler);
+}
+
+void CPeripheralJoystick::UnregisterJoystickDriverHandler(IDriverHandler* handler)
+{
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ m_driverHandlers.erase(std::remove_if(m_driverHandlers.begin(), m_driverHandlers.end(),
+ [handler](const DriverHandler& driverHandler) {
+ return driverHandler.handler == handler;
+ }),
+ m_driverHandlers.end());
+}
+
+IKeymap* CPeripheralJoystick::GetKeymap(const std::string& controllerId)
+{
+ return m_appInput->GetKeymap(controllerId);
+}
+
+GAME::ControllerPtr CPeripheralJoystick::ControllerProfile() const
+{
+ // Button map has the freshest state
+ if (m_buttonMap)
+ {
+ const std::string controllerId = m_buttonMap->GetAppearance();
+ if (!controllerId.empty())
+ {
+ auto controller = m_manager.GetControllerProfiles().GetController(controllerId);
+ if (controller)
+ return controller;
+ }
+ }
+
+ // Fall back to last set controller profile
+ if (m_controllerProfile)
+ return m_controllerProfile;
+
+ // Fall back to default controller
+ return m_manager.GetControllerProfiles().GetDefaultController();
+}
+
+void CPeripheralJoystick::SetControllerProfile(const KODI::GAME::ControllerPtr& controller)
+{
+ CPeripheral::SetControllerProfile(controller);
+
+ // Save preference to buttonmap
+ if (m_buttonMap)
+ {
+ if (m_buttonMap->SetAppearance(controller->ID()))
+ m_buttonMap->SaveButtonMap();
+ }
+}
+
+bool CPeripheralJoystick::OnButtonMotion(unsigned int buttonIndex, bool bPressed)
+{
+ // Silence debug log if controllers are not enabled
+ if (m_manager.GetInputManager().IsControllerEnabled())
+ {
+ CLog::Log(LOGDEBUG, "BUTTON [ {} ] on \"{}\" {}", buttonIndex, DeviceName(),
+ bPressed ? "pressed" : "released");
+ }
+
+ // Avoid sending activated input if the app is in the background
+ if (bPressed && !g_application.IsAppFocused())
+ return false;
+
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ // Check GUI setting and send button release if controllers are disabled
+ if (!m_manager.GetInputManager().IsControllerEnabled())
+ {
+ for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin();
+ it != m_driverHandlers.end(); ++it)
+ it->handler->OnButtonMotion(buttonIndex, false);
+ return true;
+ }
+
+ // Process promiscuous handlers
+ for (auto& it : m_driverHandlers)
+ {
+ if (it.bPromiscuous)
+ it.handler->OnButtonMotion(buttonIndex, bPressed);
+ }
+
+ bool bHandled = false;
+
+ // Process regular handlers until one is handled
+ for (auto& it : m_driverHandlers)
+ {
+ if (!it.bPromiscuous)
+ {
+ bHandled |= it.handler->OnButtonMotion(buttonIndex, bPressed);
+
+ // If button is released, force bHandled to false to notify all handlers.
+ // This avoids "sticking".
+ if (!bPressed)
+ bHandled = false;
+
+ // Once a button is handled, we're done
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+bool CPeripheralJoystick::OnHatMotion(unsigned int hatIndex, HAT_STATE state)
+{
+ // Silence debug log if controllers are not enabled
+ if (m_manager.GetInputManager().IsControllerEnabled())
+ {
+ CLog::Log(LOGDEBUG, "HAT [ {} ] on \"{}\" {}", hatIndex, DeviceName(),
+ CJoystickTranslator::HatStateToString(state));
+ }
+
+ // Avoid sending activated input if the app is in the background
+ if (state != HAT_STATE::NONE && !g_application.IsAppFocused())
+ return false;
+
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ // Check GUI setting and send hat unpressed if controllers are disabled
+ if (!m_manager.GetInputManager().IsControllerEnabled())
+ {
+ for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin();
+ it != m_driverHandlers.end(); ++it)
+ it->handler->OnHatMotion(hatIndex, HAT_STATE::NONE);
+ return true;
+ }
+
+ // Process promiscuous handlers
+ for (auto& it : m_driverHandlers)
+ {
+ if (it.bPromiscuous)
+ it.handler->OnHatMotion(hatIndex, state);
+ }
+
+ bool bHandled = false;
+
+ // Process regular handlers until one is handled
+ for (auto& it : m_driverHandlers)
+ {
+ if (!it.bPromiscuous)
+ {
+ bHandled |= it.handler->OnHatMotion(hatIndex, state);
+
+ // If hat is centered, force bHandled to false to notify all handlers.
+ // This avoids "sticking".
+ if (state == HAT_STATE::NONE)
+ bHandled = false;
+
+ // Once a hat is handled, we're done
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+bool CPeripheralJoystick::OnAxisMotion(unsigned int axisIndex, float position)
+{
+ // Get axis properties
+ int center = 0;
+ unsigned int range = 1;
+ if (m_buttonMap)
+ m_buttonMap->GetAxisProperties(axisIndex, center, range);
+
+ // Apply deadzone filtering
+ if (center == 0 && m_deadzoneFilter)
+ position = m_deadzoneFilter->FilterAxis(axisIndex, position);
+
+ // Avoid sending activated input if the app is in the background
+ if (position != static_cast<float>(center) && !g_application.IsAppFocused())
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ // Check GUI setting and send analog axis centered if controllers are disabled
+ if (!m_manager.GetInputManager().IsControllerEnabled())
+ {
+ for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin();
+ it != m_driverHandlers.end(); ++it)
+ it->handler->OnAxisMotion(axisIndex, static_cast<float>(center), center, range);
+ return true;
+ }
+
+ // Process promiscuous handlers
+ for (auto& it : m_driverHandlers)
+ {
+ if (it.bPromiscuous)
+ it.handler->OnAxisMotion(axisIndex, position, center, range);
+ }
+
+ bool bHandled = false;
+
+ // Process regular handlers until one is handled
+ for (auto& it : m_driverHandlers)
+ {
+ if (!it.bPromiscuous)
+ {
+ bHandled |= it.handler->OnAxisMotion(axisIndex, position, center, range);
+
+ // If axis is centered, force bHandled to false to notify all handlers.
+ // This avoids "sticking".
+ if (position == static_cast<float>(center))
+ bHandled = false;
+
+ // Once an axis is handled, we're done
+ if (bHandled)
+ break;
+ }
+ }
+
+ if (bHandled)
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ return bHandled;
+}
+
+void CPeripheralJoystick::OnInputFrame(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ for (auto& it : m_driverHandlers)
+ it.handler->OnInputFrame();
+}
+
+bool CPeripheralJoystick::SetMotorState(unsigned int motorIndex, float magnitude)
+{
+ bool bHandled = false;
+
+ if (m_mappedBusType == PERIPHERAL_BUS_ADDON)
+ {
+ CPeripheralBusAddon* addonBus = static_cast<CPeripheralBusAddon*>(m_bus);
+ if (addonBus)
+ {
+ bHandled = addonBus->SendRumbleEvent(m_strLocation, motorIndex, magnitude);
+ }
+ }
+ return bHandled;
+}
+
+void CPeripheralJoystick::SetMotorCount(unsigned int motorCount)
+{
+ m_motorCount = motorCount;
+
+ if (m_motorCount == 0)
+ m_features.erase(std::remove(m_features.begin(), m_features.end(), FEATURE_RUMBLE),
+ m_features.end());
+ else if (std::find(m_features.begin(), m_features.end(), FEATURE_RUMBLE) == m_features.end())
+ m_features.push_back(FEATURE_RUMBLE);
+}
+
+void CPeripheralJoystick::SetSupportsPowerOff(bool bSupportsPowerOff)
+{
+ m_supportsPowerOff = bSupportsPowerOff;
+
+ if (!m_supportsPowerOff)
+ m_features.erase(std::remove(m_features.begin(), m_features.end(), FEATURE_POWER_OFF),
+ m_features.end());
+ else if (std::find(m_features.begin(), m_features.end(), FEATURE_POWER_OFF) == m_features.end())
+ m_features.push_back(FEATURE_POWER_OFF);
+}
+
+GAME::ControllerPtr CPeripheralJoystick::InstallAsync(const std::string& controllerId)
+{
+ GAME::ControllerPtr controller;
+
+ // Only 1 install at a time. Remaining installs will wake when this one
+ // is done.
+ std::unique_lock<CCriticalSection> lockInstall(m_manager.GetAddonInstallMutex());
+
+ CLog::LogF(LOGDEBUG, "Installing {}", controllerId);
+
+ if (InstallSync(controllerId))
+ controller = m_manager.GetControllerProfiles().GetController(controllerId);
+ else
+ CLog::LogF(LOGERROR, "Failed to install {}", controllerId);
+
+ return controller;
+}
+
+bool CPeripheralJoystick::InstallSync(const std::string& controllerId)
+{
+ // If the addon isn't installed we need to install it
+ bool installed = CServiceBroker::GetAddonMgr().IsAddonInstalled(controllerId);
+ if (!installed)
+ {
+ ADDON::AddonPtr installedAddon;
+ installed = ADDON::CAddonInstaller::GetInstance().InstallModal(
+ controllerId, installedAddon, ADDON::InstallModalPrompt::CHOICE_NO);
+ }
+
+ if (installed)
+ {
+ // Make sure add-on is enabled
+ if (CServiceBroker::GetAddonMgr().IsAddonDisabled(controllerId))
+ CServiceBroker::GetAddonMgr().EnableAddon(controllerId);
+ }
+
+ return installed;
+}
diff --git a/xbmc/peripherals/devices/PeripheralJoystick.h b/xbmc/peripherals/devices/PeripheralJoystick.h
new file mode 100644
index 0000000..aa55ad7
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralJoystick.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "XBDateTime.h"
+#include "games/controllers/ControllerTypes.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "input/joysticks/interfaces/IDriverReceiver.h"
+#include "threads/CriticalSection.h"
+
+#include <future>
+#include <memory>
+#include <queue>
+#include <string>
+#include <vector>
+
+#define JOYSTICK_PORT_UNKNOWN (-1)
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CDeadzoneFilter;
+class CKeymapHandling;
+class CRumbleGenerator;
+class IButtonMap;
+class IDriverHandler;
+class IInputHandler;
+} // namespace JOYSTICK
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CPeripherals;
+
+class CPeripheralJoystick : public CPeripheral, //! @todo extend CPeripheralHID
+ public KODI::JOYSTICK::IDriverReceiver
+{
+public:
+ CPeripheralJoystick(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+
+ ~CPeripheralJoystick(void) override;
+
+ // implementation of CPeripheral
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void OnUserNotification() override;
+ bool TestFeature(PeripheralFeature feature) override;
+ void RegisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler) override;
+ KODI::JOYSTICK::IDriverReceiver* GetDriverReceiver() override { return this; }
+ IKeymap* GetKeymap(const std::string& controllerId) override;
+ CDateTime LastActive() override { return m_lastActive; }
+ KODI::GAME::ControllerPtr ControllerProfile() const override;
+ void SetControllerProfile(const KODI::GAME::ControllerPtr& controller) override;
+
+ bool OnButtonMotion(unsigned int buttonIndex, bool bPressed);
+ bool OnHatMotion(unsigned int hatIndex, KODI::JOYSTICK::HAT_STATE state);
+ bool OnAxisMotion(unsigned int axisIndex, float position);
+ void OnInputFrame(void);
+
+ // implementation of IDriverReceiver
+ bool SetMotorState(unsigned int motorIndex, float magnitude) override;
+
+ /*!
+ * \brief Get the name of the driver or API providing this joystick
+ */
+ const std::string& Provider(void) const { return m_strProvider; }
+
+ /*!
+ * \brief Get the specific port number requested by this joystick
+ *
+ * This could indicate that the joystick is connected to a hardware port
+ * with a number label; some controllers, such as the Xbox 360 controller,
+ * also have LEDs that indicate the controller is on a specific port.
+ *
+ * \return The 0-indexed port number, or JOYSTICK_PORT_UNKNOWN if no port is requested
+ */
+ int RequestedPort(void) const { return m_requestedPort; }
+
+ /*!
+ * \brief Get the number of elements reported by the driver
+ */
+ unsigned int ButtonCount(void) const { return m_buttonCount; }
+ unsigned int HatCount(void) const { return m_hatCount; }
+ unsigned int AxisCount(void) const { return m_axisCount; }
+ unsigned int MotorCount(void) const { return m_motorCount; }
+ bool SupportsPowerOff(void) const { return m_supportsPowerOff; }
+
+ /*!
+ * \brief Set joystick properties
+ */
+ void SetProvider(const std::string& provider) { m_strProvider = provider; }
+ void SetRequestedPort(int port) { m_requestedPort = port; }
+ void SetButtonCount(unsigned int buttonCount) { m_buttonCount = buttonCount; }
+ void SetHatCount(unsigned int hatCount) { m_hatCount = hatCount; }
+ void SetAxisCount(unsigned int axisCount) { m_axisCount = axisCount; }
+ void SetMotorCount(unsigned int motorCount); // specialized to update m_features
+ void SetSupportsPowerOff(bool bSupportsPowerOff); // specialized to update m_features
+
+protected:
+ void InitializeDeadzoneFiltering(KODI::JOYSTICK::IButtonMap& buttonMap);
+ void InitializeControllerProfile(KODI::JOYSTICK::IButtonMap& buttonMap);
+
+ void PowerOff();
+
+ // Helper functions
+ KODI::GAME::ControllerPtr InstallAsync(const std::string& controllerId);
+ static bool InstallSync(const std::string& controllerId);
+
+ struct DriverHandler
+ {
+ KODI::JOYSTICK::IDriverHandler* handler;
+ bool bPromiscuous;
+ };
+
+ // State parameters
+ std::string m_strProvider;
+ int m_requestedPort;
+ unsigned int m_buttonCount;
+ unsigned int m_hatCount;
+ unsigned int m_axisCount;
+ unsigned int m_motorCount;
+ bool m_supportsPowerOff;
+ CDateTime m_lastActive;
+ std::queue<std::string> m_controllersToInstall;
+ std::vector<std::future<void>> m_installTasks;
+
+ // Input clients
+ std::unique_ptr<KODI::JOYSTICK::CKeymapHandling> m_appInput;
+ std::unique_ptr<KODI::JOYSTICK::CRumbleGenerator> m_rumbleGenerator;
+ std::unique_ptr<KODI::JOYSTICK::IInputHandler> m_joystickMonitor;
+ std::unique_ptr<KODI::JOYSTICK::IButtonMap> m_buttonMap;
+ std::unique_ptr<KODI::JOYSTICK::CDeadzoneFilter> m_deadzoneFilter;
+ std::vector<DriverHandler> m_driverHandlers;
+
+ // Synchronization parameters
+ CCriticalSection m_handlerMutex;
+ CCriticalSection m_controllerInstallMutex;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralKeyboard.cpp b/xbmc/peripherals/devices/PeripheralKeyboard.cpp
new file mode 100644
index 0000000..271d8b1
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralKeyboard.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralKeyboard.h"
+
+#include "games/GameServices.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/InputManager.h"
+#include "peripherals/Peripherals.h"
+
+#include <mutex>
+#include <sstream>
+
+using namespace KODI;
+using namespace PERIPHERALS;
+
+CPeripheralKeyboard::CPeripheralKeyboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ // Initialize CPeripheral
+ m_features.push_back(FEATURE_KEYBOARD);
+}
+
+CPeripheralKeyboard::~CPeripheralKeyboard(void)
+{
+ m_manager.GetInputManager().UnregisterKeyboardDriverHandler(this);
+}
+
+bool CPeripheralKeyboard::InitialiseFeature(const PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ if (CPeripheral::InitialiseFeature(feature))
+ {
+ switch (feature)
+ {
+ case FEATURE_KEYBOARD:
+ {
+ m_manager.GetInputManager().RegisterKeyboardDriverHandler(this);
+ break;
+ }
+ default:
+ break;
+ }
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralKeyboard::RegisterKeyboardDriverHandler(
+ KODI::KEYBOARD::IKeyboardDriverHandler* handler, bool bPromiscuous)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ KeyboardHandle handle{handler, bPromiscuous};
+ m_keyboardHandlers.insert(m_keyboardHandlers.begin(), handle);
+}
+
+void CPeripheralKeyboard::UnregisterKeyboardDriverHandler(
+ KODI::KEYBOARD::IKeyboardDriverHandler* handler)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ auto it =
+ std::find_if(m_keyboardHandlers.begin(), m_keyboardHandlers.end(),
+ [handler](const KeyboardHandle& handle) { return handle.handler == handler; });
+
+ if (it != m_keyboardHandlers.end())
+ m_keyboardHandlers.erase(it);
+}
+
+GAME::ControllerPtr CPeripheralKeyboard::ControllerProfile() const
+{
+ if (m_controllerProfile)
+ return m_controllerProfile;
+
+ return m_manager.GetControllerProfiles().GetDefaultKeyboard();
+}
+
+bool CPeripheralKeyboard::OnKeyPress(const CKey& key)
+{
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ bool bHandled = false;
+
+ // Process promiscuous handlers
+ for (const KeyboardHandle& handle : m_keyboardHandlers)
+ {
+ if (handle.bPromiscuous)
+ handle.handler->OnKeyPress(key);
+ }
+
+ // Process handlers until one is handled
+ for (const KeyboardHandle& handle : m_keyboardHandlers)
+ {
+ if (!handle.bPromiscuous)
+ {
+ bHandled = handle.handler->OnKeyPress(key);
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+void CPeripheralKeyboard::OnKeyRelease(const CKey& key)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ for (const KeyboardHandle& handle : m_keyboardHandlers)
+ handle.handler->OnKeyRelease(key);
+}
diff --git a/xbmc/peripherals/devices/PeripheralKeyboard.h b/xbmc/peripherals/devices/PeripheralKeyboard.h
new file mode 100644
index 0000000..2cf2934
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralKeyboard.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "XBDateTime.h"
+#include "input/keyboard/interfaces/IKeyboardDriverHandler.h"
+#include "threads/CriticalSection.h"
+
+#include <vector>
+
+namespace PERIPHERALS
+{
+class CPeripheralKeyboard : public CPeripheral, public KODI::KEYBOARD::IKeyboardDriverHandler
+{
+public:
+ CPeripheralKeyboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+
+ ~CPeripheralKeyboard(void) override;
+
+ // implementation of CPeripheral
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void RegisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler) override;
+ CDateTime LastActive() override { return m_lastActive; }
+ KODI::GAME::ControllerPtr ControllerProfile() const override;
+
+ // implementation of IKeyboardDriverHandler
+ bool OnKeyPress(const CKey& key) override;
+ void OnKeyRelease(const CKey& key) override;
+
+private:
+ struct KeyboardHandle
+ {
+ KODI::KEYBOARD::IKeyboardDriverHandler* handler;
+ bool bPromiscuous;
+ };
+
+ std::vector<KeyboardHandle> m_keyboardHandlers;
+ CCriticalSection m_mutex;
+ CDateTime m_lastActive;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralMouse.cpp b/xbmc/peripherals/devices/PeripheralMouse.cpp
new file mode 100644
index 0000000..4f26236
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralMouse.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralMouse.h"
+
+#include "games/GameServices.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/InputManager.h"
+#include "peripherals/Peripherals.h"
+
+#include <mutex>
+#include <sstream>
+
+using namespace KODI;
+using namespace PERIPHERALS;
+
+CPeripheralMouse::CPeripheralMouse(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ // Initialize CPeripheral
+ m_features.push_back(FEATURE_MOUSE);
+}
+
+CPeripheralMouse::~CPeripheralMouse(void)
+{
+ m_manager.GetInputManager().UnregisterMouseDriverHandler(this);
+}
+
+bool CPeripheralMouse::InitialiseFeature(const PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ if (CPeripheral::InitialiseFeature(feature))
+ {
+ if (feature == FEATURE_MOUSE)
+ {
+ m_manager.GetInputManager().RegisterMouseDriverHandler(this);
+ }
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralMouse::RegisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler,
+ bool bPromiscuous)
+{
+ using namespace KEYBOARD;
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ MouseHandle handle{handler, bPromiscuous};
+ m_mouseHandlers.insert(m_mouseHandlers.begin(), handle);
+}
+
+void CPeripheralMouse::UnregisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ auto it =
+ std::find_if(m_mouseHandlers.begin(), m_mouseHandlers.end(),
+ [handler](const MouseHandle& handle) { return handle.handler == handler; });
+
+ if (it != m_mouseHandlers.end())
+ m_mouseHandlers.erase(it);
+}
+
+GAME::ControllerPtr CPeripheralMouse::ControllerProfile() const
+{
+ if (m_controllerProfile)
+ return m_controllerProfile;
+
+ return m_manager.GetControllerProfiles().GetDefaultMouse();
+}
+
+bool CPeripheralMouse::OnPosition(int x, int y)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ bool bHandled = false;
+
+ // Process promiscuous handlers
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (handle.bPromiscuous)
+ handle.handler->OnPosition(x, y);
+ }
+
+ // Process handlers until one is handled
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (!handle.bPromiscuous)
+ {
+ bHandled = handle.handler->OnPosition(x, y);
+ if (bHandled)
+ break;
+ }
+ }
+
+ if (bHandled)
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ return bHandled;
+}
+
+bool CPeripheralMouse::OnButtonPress(MOUSE::BUTTON_ID button)
+{
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ bool bHandled = false;
+
+ // Process promiscuous handlers
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (handle.bPromiscuous)
+ handle.handler->OnButtonPress(button);
+ }
+
+ // Process handlers until one is handled
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (!handle.bPromiscuous)
+ {
+ bHandled = handle.handler->OnButtonPress(button);
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+void CPeripheralMouse::OnButtonRelease(MOUSE::BUTTON_ID button)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ for (const MouseHandle& handle : m_mouseHandlers)
+ handle.handler->OnButtonRelease(button);
+}
diff --git a/xbmc/peripherals/devices/PeripheralMouse.h b/xbmc/peripherals/devices/PeripheralMouse.h
new file mode 100644
index 0000000..b46776d
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralMouse.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "XBDateTime.h"
+#include "input/mouse/interfaces/IMouseDriverHandler.h"
+#include "threads/CriticalSection.h"
+
+#include <vector>
+
+namespace PERIPHERALS
+{
+class CPeripheralMouse : public CPeripheral, public KODI::MOUSE::IMouseDriverHandler
+{
+public:
+ CPeripheralMouse(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+
+ ~CPeripheralMouse(void) override;
+
+ // implementation of CPeripheral
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void RegisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler) override;
+ CDateTime LastActive() override { return m_lastActive; }
+ KODI::GAME::ControllerPtr ControllerProfile() const override;
+
+ // implementation of IMouseDriverHandler
+ bool OnPosition(int x, int y) override;
+ bool OnButtonPress(KODI::MOUSE::BUTTON_ID button) override;
+ void OnButtonRelease(KODI::MOUSE::BUTTON_ID button) override;
+
+private:
+ struct MouseHandle
+ {
+ KODI::MOUSE::IMouseDriverHandler* handler;
+ bool bPromiscuous;
+ };
+
+ std::vector<MouseHandle> m_mouseHandlers;
+ CCriticalSection m_mutex;
+ CDateTime m_lastActive;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralNIC.cpp b/xbmc/peripherals/devices/PeripheralNIC.cpp
new file mode 100644
index 0000000..a5b5c32
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNIC.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralNIC.h"
+
+#include "guilib/LocalizeStrings.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralNIC::CPeripheralNIC(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35002)
+ : scanResult.m_strDeviceName;
+ m_features.push_back(FEATURE_NIC);
+}
diff --git a/xbmc/peripherals/devices/PeripheralNIC.h b/xbmc/peripherals/devices/PeripheralNIC.h
new file mode 100644
index 0000000..01fe5f3
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNIC.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralNIC : public CPeripheral
+{
+public:
+ CPeripheralNIC(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralNIC(void) override = default;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralNyxboard.cpp b/xbmc/peripherals/devices/PeripheralNyxboard.cpp
new file mode 100644
index 0000000..548d8cf
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNyxboard.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralNyxboard.h"
+
+#include "PeripheralHID.h"
+#include "application/Application.h"
+#include "utils/log.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralNyxboard::CPeripheralNyxboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheralHID(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_NYXBOARD);
+}
+
+bool CPeripheralNyxboard::LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode)
+{
+ std::string strCommand;
+ if (keysym.sym == XBMCK_F7 && keysym.mod == XBMCKMOD_NONE &&
+ GetSettingBool("enable_flip_commands"))
+ {
+ /* switched to keyboard side */
+ CLog::Log(LOGDEBUG, "{} - switched to keyboard side", __FUNCTION__);
+ strCommand = GetSettingString("flip_keyboard");
+ }
+ else if (keysym.sym == XBMCK_F7 && keysym.mod == XBMCKMOD_LCTRL &&
+ GetSettingBool("enable_flip_commands"))
+ {
+ /* switched to remote side */
+ CLog::Log(LOGDEBUG, "{} - switched to remote side", __FUNCTION__);
+ strCommand = GetSettingString("flip_remote");
+ }
+
+ if (!strCommand.empty())
+ {
+ CLog::Log(LOGDEBUG, "{} - executing command '{}'", __FUNCTION__, strCommand);
+ if (g_application.ExecuteXBMCAction(strCommand))
+ {
+ *key = 0;
+ *unicode = (char)0;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/peripherals/devices/PeripheralNyxboard.h b/xbmc/peripherals/devices/PeripheralNyxboard.h
new file mode 100644
index 0000000..f81c3ef
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNyxboard.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PeripheralHID.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralNyxboard : public CPeripheralHID
+{
+public:
+ CPeripheralNyxboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralNyxboard(void) override = default;
+ bool LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode) override;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralTuner.cpp b/xbmc/peripherals/devices/PeripheralTuner.cpp
new file mode 100644
index 0000000..770656d
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralTuner.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralTuner.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralTuner::CPeripheralTuner(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_TUNER);
+}
diff --git a/xbmc/peripherals/devices/PeripheralTuner.h b/xbmc/peripherals/devices/PeripheralTuner.h
new file mode 100644
index 0000000..aedfc12
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralTuner.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralTuner : public CPeripheral
+{
+public:
+ CPeripheralTuner(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralTuner(void) override = default;
+};
+} // namespace PERIPHERALS