diff options
Diffstat (limited to 'src/peripherals')
42 files changed, 6879 insertions, 0 deletions
diff --git a/src/peripherals/Makefile b/src/peripherals/Makefile new file mode 100644 index 0000000000..754a2c5de3 --- /dev/null +++ b/src/peripherals/Makefile @@ -0,0 +1,6 @@ +SRCS=Peripherals.cpp + +LIB=peripherals.a + +include ../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/src/peripherals/PeripheralTypes.h b/src/peripherals/PeripheralTypes.h new file mode 100644 index 0000000000..e92fd41140 --- /dev/null +++ b/src/peripherals/PeripheralTypes.h @@ -0,0 +1,264 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <stdio.h> +#ifdef TARGET_WINDOWS +#include "PlatformDefs.h" +#endif +#include "utils/StdString.h" +#include "utils/StringUtils.h" + +class CSetting; + +namespace PERIPHERALS +{ + enum PeripheralBusType + { + PERIPHERAL_BUS_UNKNOWN = 0, + PERIPHERAL_BUS_USB, + PERIPHERAL_BUS_PCI, + PERIPHERAL_BUS_RPI, + PERIPHERAL_BUS_CEC + }; + + enum PeripheralFeature + { + FEATURE_UNKNOWN = 0, + FEATURE_HID, + FEATURE_NIC, + FEATURE_DISK, + FEATURE_NYXBOARD, + FEATURE_CEC, + FEATURE_BLUETOOTH, + FEATURE_TUNER, + FEATURE_IMON + }; + + enum PeripheralType + { + PERIPHERAL_UNKNOWN = 0, + PERIPHERAL_HID, + PERIPHERAL_NIC, + PERIPHERAL_DISK, + PERIPHERAL_NYXBOARD, + PERIPHERAL_CEC, + PERIPHERAL_BLUETOOTH, + PERIPHERAL_TUNER, + PERIPHERAL_IMON + }; + + struct PeripheralID + { + int m_iVendorId; + int m_iProductId; + }; + + struct PeripheralDeviceSetting + { + CSetting* m_setting; + int m_order; + }; + + struct PeripheralDeviceMapping + { + std::vector<PeripheralID> m_PeripheralID; + PeripheralBusType m_busType; + PeripheralType m_class; + CStdString m_strDeviceName; + PeripheralType m_mappedTo; + std::map<CStdString, PeripheralDeviceSetting> m_settings; + }; + + class PeripheralTypeTranslator + { + public: + static const char *TypeToString(const PeripheralType type) + { + switch (type) + { + case PERIPHERAL_BLUETOOTH: + return "bluetooth"; + case PERIPHERAL_CEC: + return "cec"; + case PERIPHERAL_DISK: + return "disk"; + case PERIPHERAL_HID: + return "hid"; + case PERIPHERAL_NIC: + return "nic"; + case PERIPHERAL_NYXBOARD: + return "nyxboard"; + case PERIPHERAL_TUNER: + return "tuner"; + case PERIPHERAL_IMON: + return "imon"; + default: + return "unknown"; + } + }; + + static PeripheralType GetTypeFromString(const CStdString &strType) + { + CStdString strTypeLowerCase(strType); + StringUtils::ToLower(strTypeLowerCase); + + if (strTypeLowerCase.Equals("bluetooth")) + return PERIPHERAL_BLUETOOTH; + else if (strTypeLowerCase.Equals("cec")) + return PERIPHERAL_CEC; + else if (strTypeLowerCase.Equals("disk")) + return PERIPHERAL_DISK; + else if (strTypeLowerCase.Equals("hid")) + return PERIPHERAL_HID; + else if (strTypeLowerCase.Equals("nic")) + return PERIPHERAL_NIC; + else if (strTypeLowerCase.Equals("nyxboard")) + return PERIPHERAL_NYXBOARD; + else if (strTypeLowerCase.Equals("tuner")) + return PERIPHERAL_TUNER; + else if (strTypeLowerCase.Equals("imon")) + return PERIPHERAL_IMON; + + return PERIPHERAL_UNKNOWN; + }; + + static const char *BusTypeToString(const PeripheralBusType type) + { + switch(type) + { + case PERIPHERAL_BUS_USB: + return "usb"; + case PERIPHERAL_BUS_PCI: + return "pci"; + case PERIPHERAL_BUS_RPI: + return "rpi"; + case PERIPHERAL_BUS_CEC: + return "cec"; + default: + return "unknown"; + } + }; + + static PeripheralBusType GetBusTypeFromString(const CStdString &strType) + { + CStdString strTypeLowerCase(strType); + StringUtils::ToLower(strTypeLowerCase); + + if (strTypeLowerCase.Equals("usb")) + return PERIPHERAL_BUS_USB; + else if (strTypeLowerCase.Equals("pci")) + return PERIPHERAL_BUS_PCI; + else if (strTypeLowerCase.Equals("rpi")) + return PERIPHERAL_BUS_RPI; + else if (strTypeLowerCase.Equals("cec")) + return PERIPHERAL_BUS_CEC; + + return PERIPHERAL_BUS_UNKNOWN; + }; + + static int HexStringToInt(const char *strHex) + { + int iVal; + sscanf(strHex, "%x", &iVal); + return iVal; + }; + + static void FormatHexString(int iVal, CStdString &strHexString) + { + if (iVal < 0) + iVal = 0; + if (iVal > 65536) + iVal = 65536; + + strHexString = StringUtils::Format("%04X", iVal); + }; + }; + + class PeripheralScanResult + { + public: + PeripheralScanResult(const PeripheralBusType busType) : + m_type(PERIPHERAL_UNKNOWN), + m_iVendorId(0), + m_iProductId(0), + m_mappedType(PERIPHERAL_UNKNOWN), + m_busType(busType), + m_mappedBusType(busType), + m_iSequence(0) {} + + PeripheralScanResult(void) : + m_type(PERIPHERAL_UNKNOWN), + m_iVendorId(0), + m_iProductId(0), + m_mappedType(PERIPHERAL_UNKNOWN), + m_busType(PERIPHERAL_BUS_UNKNOWN), + m_mappedBusType(PERIPHERAL_BUS_UNKNOWN), + m_iSequence(0) {} + + bool operator ==(const PeripheralScanResult& right) const + { + return m_iVendorId == right.m_iVendorId && + m_iProductId == right.m_iProductId && + m_type == right.m_type && + m_busType == right.m_busType && + m_strLocation.Equals(right.m_strLocation); + } + + bool operator !=(const PeripheralScanResult& right) const + { + return !(*this == right); + } + + PeripheralType m_type; + CStdString m_strLocation; + int m_iVendorId; + int m_iProductId; + PeripheralType m_mappedType; + CStdString m_strDeviceName; + PeripheralBusType m_busType; + PeripheralBusType m_mappedBusType; + unsigned int m_iSequence; // when more than one adapter of the same type is found + }; + + struct PeripheralScanResults + { + bool GetDeviceOnLocation(const CStdString& strLocation, PeripheralScanResult* result) const + { + for (std::vector<PeripheralScanResult>::const_iterator it = m_results.begin(); it != m_results.end(); it++) + { + if ((*it).m_strLocation == strLocation) + { + *result = *it; + return true; + } + } + return false; + } + + bool ContainsResult(const PeripheralScanResult& result) const + { + return std::find(m_results.begin(), m_results.end(), result) != m_results.end(); + } + + std::vector<PeripheralScanResult> m_results; + }; +} diff --git a/src/peripherals/Peripherals.cpp b/src/peripherals/Peripherals.cpp new file mode 100644 index 0000000000..557ee351f4 --- /dev/null +++ b/src/peripherals/Peripherals.cpp @@ -0,0 +1,727 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Peripherals.h" +#include "bus/PeripheralBus.h" +#include "devices/PeripheralBluetooth.h" +#include "devices/PeripheralDisk.h" +#include "devices/PeripheralHID.h" +#include "devices/PeripheralNIC.h" +#include "devices/PeripheralNyxboard.h" +#include "devices/PeripheralTuner.h" +#include "devices/PeripheralCecAdapter.h" +#include "devices/PeripheralImon.h" +#include "bus/PeripheralBusUSB.h" +#include "dialogs/GUIDialogPeripheralManager.h" + +#if defined(HAVE_LIBCEC) +#include "bus/virtual/PeripheralBusCEC.h" +#endif + +#include "threads/SingleLock.h" +#include "utils/log.h" +#include "utils/XMLUtils.h" +#include "utils/XBMCTinyXML.h" +#include "filesystem/Directory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "GUIUserMessages.h" +#include "utils/StringUtils.h" +#include "Util.h" +#include "guilib/Key.h" +#include "settings/lib/Setting.h" + +using namespace PERIPHERALS; +using namespace XFILE; +using namespace std; + +CPeripherals::CPeripherals(void) +{ + Clear(); +} + +CPeripherals::~CPeripherals(void) +{ + Clear(); +} + +CPeripherals &CPeripherals::Get(void) +{ + static CPeripherals peripheralsInstance; + return peripheralsInstance; +} + +void CPeripherals::Initialise(void) +{ + CSingleLock lock(m_critSection); + if (!m_bIsStarted) + { + m_bIsStarted = true; + + CDirectory::Create("special://profile/peripheral_data"); + + /* load mappings from peripherals.xml */ + LoadMappings(); + +#if defined(HAVE_PERIPHERAL_BUS_USB) + m_busses.push_back(new CPeripheralBusUSB(this)); +#endif +#if defined(HAVE_LIBCEC) + m_busses.push_back(new CPeripheralBusCEC(this)); +#endif + + /* initialise all known busses */ + for (int iBusPtr = (int)m_busses.size() - 1; iBusPtr >= 0; iBusPtr--) + { + if (!m_busses.at(iBusPtr)->Initialise()) + { + CLog::Log(LOGERROR, "%s - failed to initialise bus %s", __FUNCTION__, PeripheralTypeTranslator::BusTypeToString(m_busses.at(iBusPtr)->Type())); + delete m_busses.at(iBusPtr); + m_busses.erase(m_busses.begin() + iBusPtr); + } + } + + m_bInitialised = true; + } +} + +void CPeripherals::Clear(void) +{ + CSingleLock lock(m_critSection); + /* delete busses and devices */ + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + delete m_busses.at(iBusPtr); + m_busses.clear(); + + /* delete mappings */ + for (unsigned int iMappingPtr = 0; iMappingPtr < m_mappings.size(); iMappingPtr++) + { + map<CStdString, PeripheralDeviceSetting> settings = m_mappings.at(iMappingPtr).m_settings; + for (map<CStdString, PeripheralDeviceSetting>::iterator itr = settings.begin(); itr != settings.end(); ++itr) + delete itr->second.m_setting; + m_mappings.at(iMappingPtr).m_settings.clear(); + } + m_mappings.clear(); + + /* reset class state */ + m_bIsStarted = false; + m_bInitialised = false; +#if !defined(HAVE_LIBCEC) + m_bMissingLibCecWarningDisplayed = false; +#endif +} + +void CPeripherals::TriggerDeviceScan(const PeripheralBusType type /* = PERIPHERAL_BUS_UNKNOWN */) +{ + CSingleLock lock(m_critSection); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + if (type == PERIPHERAL_BUS_UNKNOWN || m_busses.at(iBusPtr)->Type() == type) + { + m_busses.at(iBusPtr)->TriggerDeviceScan(); + if (type != PERIPHERAL_BUS_UNKNOWN) + break; + } + } +} + +CPeripheralBus *CPeripherals::GetBusByType(const PeripheralBusType type) const +{ + CSingleLock lock(m_critSection); + CPeripheralBus *bus(NULL); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + if (m_busses.at(iBusPtr)->Type() == type) + { + bus = m_busses.at(iBusPtr); + break; + } + } + + return bus; +} + +CPeripheral *CPeripherals::GetPeripheralAtLocation(const CStdString &strLocation, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + CSingleLock lock(m_critSection); + CPeripheral *peripheral(NULL); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + /* check whether the bus matches if a bus type other than unknown was passed */ + if (busType != PERIPHERAL_BUS_UNKNOWN && m_busses.at(iBusPtr)->Type() != busType) + continue; + + /* return the first device that matches */ + if ((peripheral = m_busses.at(iBusPtr)->GetPeripheral(strLocation)) != NULL) + break; + } + + return peripheral; +} + +bool CPeripherals::HasPeripheralAtLocation(const CStdString &strLocation, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + return (GetPeripheralAtLocation(strLocation, busType) != NULL); +} + +CPeripheralBus *CPeripherals::GetBusWithDevice(const CStdString &strLocation) const +{ + CSingleLock lock(m_critSection); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + /* return the first bus that matches */ + if (m_busses.at(iBusPtr)->HasPeripheral(strLocation)) + return m_busses.at(iBusPtr); + } + + return NULL; +} + +int CPeripherals::GetPeripheralsWithFeature(vector<CPeripheral *> &results, const PeripheralFeature feature, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + CSingleLock lock(m_critSection); + int iReturn(0); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + /* check whether the bus matches if a bus type other than unknown was passed */ + if (busType != PERIPHERAL_BUS_UNKNOWN && m_busses.at(iBusPtr)->Type() != busType) + continue; + + iReturn += m_busses.at(iBusPtr)->GetPeripheralsWithFeature(results, feature); + } + + return iReturn; +} + +size_t CPeripherals::GetNumberOfPeripherals() const +{ + size_t iReturn(0); + CSingleLock lock(m_critSection); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + iReturn += m_busses.at(iBusPtr)->GetNumberOfPeripherals(); + } + + return iReturn; +} + +bool CPeripherals::HasPeripheralWithFeature(const PeripheralFeature feature, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const +{ + vector<CPeripheral *> dummy; + return (GetPeripheralsWithFeature(dummy, feature, busType) > 0); +} + +CPeripheral *CPeripherals::CreatePeripheral(CPeripheralBus &bus, const PeripheralScanResult& result) +{ + CPeripheral *peripheral = NULL; + PeripheralScanResult mappedResult = result; + if (mappedResult.m_busType == PERIPHERAL_BUS_UNKNOWN) + mappedResult.m_busType = bus.Type(); + + /* check whether there's something mapped in peripherals.xml */ + if (!GetMappingForDevice(bus, mappedResult)) + { + /* don't create instances for devices that aren't mapped in peripherals.xml */ + return NULL; + } + + switch(mappedResult.m_mappedType) + { + case PERIPHERAL_HID: + peripheral = new CPeripheralHID(mappedResult); + break; + + case PERIPHERAL_NIC: + peripheral = new CPeripheralNIC(mappedResult); + break; + + case PERIPHERAL_DISK: + peripheral = new CPeripheralDisk(mappedResult); + break; + + case PERIPHERAL_NYXBOARD: + peripheral = new CPeripheralNyxboard(mappedResult); + break; + + case PERIPHERAL_TUNER: + peripheral = new CPeripheralTuner(mappedResult); + break; + + case PERIPHERAL_BLUETOOTH: + peripheral = new CPeripheralBluetooth(mappedResult); + break; + + case PERIPHERAL_CEC: +#if defined(HAVE_LIBCEC) + if (bus.Type() == PERIPHERAL_BUS_CEC) + peripheral = new CPeripheralCecAdapter(mappedResult); +#else + if (!m_bMissingLibCecWarningDisplayed) + { + m_bMissingLibCecWarningDisplayed = true; + CLog::Log(LOGWARNING, "%s - libCEC support has not been compiled in, so the CEC adapter cannot be used.", __FUNCTION__); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(36000), g_localizeStrings.Get(36017)); + } +#endif + break; + + case PERIPHERAL_IMON: + peripheral = new CPeripheralImon(mappedResult); + break; + + default: + break; + } + + if (peripheral) + { + /* try to initialise the new peripheral + * Initialise() will make sure that each device is only initialised once */ + if (peripheral->Initialise()) + { + bus.Register(peripheral); + } + else + { + CLog::Log(LOGDEBUG, "%s - failed to initialise peripheral on '%s'", __FUNCTION__, mappedResult.m_strLocation.c_str()); + delete peripheral; + peripheral = NULL; + } + } + + return peripheral; +} + +void CPeripherals::OnDeviceAdded(const CPeripheralBus &bus, const CPeripheral &peripheral) +{ + CGUIDialogPeripheralManager *dialog = (CGUIDialogPeripheralManager *)g_windowManager.GetWindow(WINDOW_DIALOG_PERIPHERAL_MANAGER); + if (dialog && dialog->IsActive()) + dialog->Update(); + + // refresh settings (peripherals manager could be enabled now) + CGUIMessage msg(GUI_MSG_UPDATE, WINDOW_SETTINGS_SYSTEM, 0); + g_windowManager.SendThreadMessage(msg, WINDOW_SETTINGS_SYSTEM); + + SetChanged(); + + // don't show a notification for devices detected during the initial scan + if (bus.IsInitialised()) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(35005), peripheral.DeviceName()); +} + +void CPeripherals::OnDeviceDeleted(const CPeripheralBus &bus, const CPeripheral &peripheral) +{ + CGUIDialogPeripheralManager *dialog = (CGUIDialogPeripheralManager *)g_windowManager.GetWindow(WINDOW_DIALOG_PERIPHERAL_MANAGER); + if (dialog && dialog->IsActive()) + dialog->Update(); + + // refresh settings (peripherals manager could be disabled now) + CGUIMessage msg(GUI_MSG_UPDATE, WINDOW_SETTINGS_SYSTEM, 0); + g_windowManager.SendThreadMessage(msg, WINDOW_SETTINGS_SYSTEM); + + SetChanged(); + + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(35006), peripheral.DeviceName()); +} + +bool CPeripherals::GetMappingForDevice(const CPeripheralBus &bus, PeripheralScanResult& result) const +{ + /* check all mappings in the order in which they are defined in peripherals.xml */ + for (unsigned int iMappingPtr = 0; iMappingPtr < m_mappings.size(); iMappingPtr++) + { + PeripheralDeviceMapping mapping = m_mappings.at(iMappingPtr); + + bool bProductMatch = false; + if (mapping.m_PeripheralID.size() == 0) + { + bProductMatch = true; + } + else + { + for (unsigned int i = 0; i < mapping.m_PeripheralID.size(); i++) + if (mapping.m_PeripheralID[i].m_iVendorId == result.m_iVendorId && mapping.m_PeripheralID[i].m_iProductId == result.m_iProductId) + bProductMatch = true; + } + + bool bBusMatch = (mapping.m_busType == PERIPHERAL_BUS_UNKNOWN || mapping.m_busType == bus.Type()); + bool bClassMatch = (mapping.m_class == PERIPHERAL_UNKNOWN || mapping.m_class == result.m_type); + + if (bProductMatch && bBusMatch && bClassMatch) + { + CStdString strVendorId, strProductId; + PeripheralTypeTranslator::FormatHexString(result.m_iVendorId, strVendorId); + PeripheralTypeTranslator::FormatHexString(result.m_iProductId, strProductId); + CLog::Log(LOGDEBUG, "%s - device (%s:%s) mapped to %s (type = %s)", __FUNCTION__, strVendorId.c_str(), strProductId.c_str(), mapping.m_strDeviceName.c_str(), PeripheralTypeTranslator::TypeToString(mapping.m_mappedTo)); + result.m_mappedType = m_mappings[iMappingPtr].m_mappedTo; + result.m_strDeviceName = m_mappings[iMappingPtr].m_strDeviceName; + return true; + } + } + + return false; +} + +void CPeripherals::GetSettingsFromMapping(CPeripheral &peripheral) const +{ + /* check all mappings in the order in which they are defined in peripherals.xml */ + for (unsigned int iMappingPtr = 0; iMappingPtr < m_mappings.size(); iMappingPtr++) + { + const PeripheralDeviceMapping *mapping = &m_mappings.at(iMappingPtr); + + bool bProductMatch = false; + if (mapping->m_PeripheralID.size() == 0) + { + bProductMatch = true; + } + else + { + for (unsigned int i = 0; i < mapping->m_PeripheralID.size(); i++) + if (mapping->m_PeripheralID[i].m_iVendorId == peripheral.VendorId() && mapping->m_PeripheralID[i].m_iProductId == peripheral.ProductId()) + bProductMatch = true; + } + + bool bBusMatch = (mapping->m_busType == PERIPHERAL_BUS_UNKNOWN || mapping->m_busType == peripheral.GetBusType()); + bool bClassMatch = (mapping->m_class == PERIPHERAL_UNKNOWN || mapping->m_class == peripheral.Type()); + + if (bBusMatch && bProductMatch && bClassMatch) + { + for (map<CStdString, PeripheralDeviceSetting>::const_iterator itr = mapping->m_settings.begin(); itr != mapping->m_settings.end(); ++itr) + peripheral.AddSetting((*itr).first, (*itr).second.m_setting, (*itr).second.m_order); + } + } +} + +bool CPeripherals::LoadMappings(void) +{ + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile("special://xbmc/system/peripherals.xml")) + { + CLog::Log(LOGWARNING, "%s - peripherals.xml does not exist", __FUNCTION__); + return true; + } + + TiXmlElement *pRootElement = xmlDoc.RootElement(); + if (strcmpi(pRootElement->Value(), "peripherals") != 0) + { + CLog::Log(LOGERROR, "%s - peripherals.xml does not contain <peripherals>", __FUNCTION__); + return false; + } + + for (TiXmlElement *currentNode = pRootElement->FirstChildElement("peripheral"); currentNode; currentNode = currentNode->NextSiblingElement("peripheral")) + { + PeripheralID id; + PeripheralDeviceMapping mapping; + + mapping.m_strDeviceName = XMLUtils::GetAttribute(currentNode, "name"); + + // If there is no vendor_product attribute ignore this entry + if (currentNode->Attribute("vendor_product")) + { + // The vendor_product attribute is a list of comma separated vendor:product pairs + vector<string> vpArray = StringUtils::Split(currentNode->Attribute("vendor_product"), ","); + for (vector<string>::const_iterator i = vpArray.begin(); i != vpArray.end(); ++i) + { + vector<string> idArray = StringUtils::Split(*i, ":"); + if (idArray.size() != 2) + { + CLog::Log(LOGERROR, "%s - ignoring node \"%s\" with invalid vendor_product attribute", __FUNCTION__, mapping.m_strDeviceName.c_str()); + continue; + } + + id.m_iVendorId = PeripheralTypeTranslator::HexStringToInt(idArray[0].c_str()); + id.m_iProductId = PeripheralTypeTranslator::HexStringToInt(idArray[1].c_str()); + mapping.m_PeripheralID.push_back(id); + } + } + + mapping.m_busType = PeripheralTypeTranslator::GetBusTypeFromString(XMLUtils::GetAttribute(currentNode, "bus")); + mapping.m_class = PeripheralTypeTranslator::GetTypeFromString(XMLUtils::GetAttribute(currentNode, "class")); + mapping.m_mappedTo = PeripheralTypeTranslator::GetTypeFromString(XMLUtils::GetAttribute(currentNode, "mapTo")); + GetSettingsFromMappingsFile(currentNode, mapping.m_settings); + + m_mappings.push_back(mapping); + CLog::Log(LOGDEBUG, "%s - loaded node \"%s\"", __FUNCTION__, mapping.m_strDeviceName.c_str()); + } + + return true; +} + +void CPeripherals::GetSettingsFromMappingsFile(TiXmlElement *xmlNode, map<CStdString, PeripheralDeviceSetting> &settings) +{ + TiXmlElement *currentNode = xmlNode->FirstChildElement("setting"); + int iMaxOrder = 0; + + while (currentNode) + { + CSetting *setting = NULL; + CStdString strKey = XMLUtils::GetAttribute(currentNode, "key"); + if (strKey.empty()) + continue; + + CStdString strSettingsType = XMLUtils::GetAttribute(currentNode, "type"); + int iLabelId = currentNode->Attribute("label") ? atoi(currentNode->Attribute("label")) : -1; + const std::string config = XMLUtils::GetAttribute(currentNode, "configurable"); + bool bConfigurable = (config.empty() || (config != "no" && config != "false" && config != "0")); + if (strSettingsType.Equals("bool")) + { + const std::string value = XMLUtils::GetAttribute(currentNode, "value"); + bool bValue = (value != "no" && value != "false" && value != "0"); + setting = new CSettingBool(strKey, iLabelId, bValue); + } + else if (strSettingsType.Equals("int")) + { + int iValue = currentNode->Attribute("value") ? atoi(currentNode->Attribute("value")) : 0; + int iMin = currentNode->Attribute("min") ? atoi(currentNode->Attribute("min")) : 0; + int iStep = currentNode->Attribute("step") ? atoi(currentNode->Attribute("step")) : 1; + int iMax = currentNode->Attribute("max") ? atoi(currentNode->Attribute("max")) : 255; + setting = new CSettingInt(strKey, iLabelId, iValue, iMin, iStep, iMax); + } + else if (strSettingsType.Equals("float")) + { + float fValue = currentNode->Attribute("value") ? (float) atof(currentNode->Attribute("value")) : 0; + float fMin = currentNode->Attribute("min") ? (float) atof(currentNode->Attribute("min")) : 0; + float fStep = currentNode->Attribute("step") ? (float) atof(currentNode->Attribute("step")) : 0; + float fMax = currentNode->Attribute("max") ? (float) atof(currentNode->Attribute("max")) : 0; + setting = new CSettingNumber(strKey, iLabelId, fValue, fMin, fStep, fMax); + } + else if (strSettingsType.Equals("enum")) + { + CStdString strEnums = XMLUtils::GetAttribute(currentNode, "lvalues"); + if (!strEnums.empty()) + { + vector< pair<int,int> > enums; + vector<std::string> valuesVec; + StringUtils::Tokenize(strEnums, valuesVec, "|"); + for (unsigned int i = 0; i < valuesVec.size(); i++) + enums.push_back(make_pair(atoi(valuesVec[i].c_str()), atoi(valuesVec[i].c_str()))); + int iValue = currentNode->Attribute("value") ? atoi(currentNode->Attribute("value")) : 0; + setting = new CSettingInt(strKey, iLabelId, iValue, enums); + } + } + else + { + CStdString strValue = XMLUtils::GetAttribute(currentNode, "value"); + setting = new CSettingString(strKey, iLabelId, strValue); + } + + if (setting) + { + //TODO add more types if needed + + /* set the visibility */ + setting->SetVisible(bConfigurable); + + /* set the order */ + int iOrder = 0; + currentNode->Attribute("order", &iOrder); + /* if the order attribute is invalid or 0, then the setting will be added at the end */ + if (iOrder < 0) + iOrder = 0; + if (iOrder > iMaxOrder) + iMaxOrder = iOrder; + + /* and add this new setting */ + PeripheralDeviceSetting deviceSetting = { setting, iOrder }; + settings[strKey] = deviceSetting; + } + + currentNode = currentNode->NextSiblingElement("setting"); + } + + /* add the settings without an order attribute or an invalid order attribute set at the end */ + for (map<CStdString, PeripheralDeviceSetting>::iterator it = settings.begin(); it != settings.end(); ++it) + { + if (it->second.m_order == 0) + it->second.m_order = ++iMaxOrder; + } +} + +void CPeripherals::GetDirectory(const CStdString &strPath, CFileItemList &items) const +{ + if (!StringUtils::StartsWithNoCase(strPath, "peripherals://")) + return; + + CStdString strPathCut = strPath.substr(14); + CStdString strBus = strPathCut.substr(0, strPathCut.find('/')); + + CSingleLock lock(m_critSection); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + if (strBus.Equals("all") || strBus.Equals(PeripheralTypeTranslator::BusTypeToString(m_busses.at(iBusPtr)->Type()))) + m_busses.at(iBusPtr)->GetDirectory(strPath, items); + } +} + +CPeripheral *CPeripherals::GetByPath(const CStdString &strPath) const +{ + if (!StringUtils::StartsWithNoCase(strPath, "peripherals://")) + return NULL; + + CStdString strPathCut = strPath.substr(14); + CStdString strBus = strPathCut.substr(0, strPathCut.find('/')); + + CSingleLock lock(m_critSection); + for (unsigned int iBusPtr = 0; iBusPtr < m_busses.size(); iBusPtr++) + { + if (strBus.Equals(PeripheralTypeTranslator::BusTypeToString(m_busses.at(iBusPtr)->Type()))) + return m_busses.at(iBusPtr)->GetByPath(strPath); + } + + return NULL; +} + +bool CPeripherals::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_MUTE) + { + return ToggleMute(); + } + + if (SupportsCEC() && action.GetAmount() && (action.GetID() == ACTION_VOLUME_UP || action.GetID() == ACTION_VOLUME_DOWN)) + { + vector<CPeripheral *> peripherals; + if (GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < peripherals.size(); iPeripheralPtr++) + { + CPeripheralCecAdapter *cecDevice = (CPeripheralCecAdapter *) peripherals.at(iPeripheralPtr); + if (cecDevice && cecDevice->HasAudioControl()) + { + if (action.GetID() == ACTION_VOLUME_UP) + cecDevice->VolumeUp(); + else + cecDevice->VolumeDown(); + return true; + } + } + } + } + + return false; +} + +bool CPeripherals::IsMuted(void) +{ + vector<CPeripheral *> peripherals; + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < peripherals.size(); iPeripheralPtr++) + { + CPeripheralCecAdapter *cecDevice = (CPeripheralCecAdapter *) peripherals.at(iPeripheralPtr); + if (cecDevice && cecDevice->IsMuted()) + return true; + } + } + + return false; +} + +bool CPeripherals::ToggleMute(void) +{ + vector<CPeripheral *> peripherals; + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < peripherals.size(); iPeripheralPtr++) + { + CPeripheralCecAdapter *cecDevice = (CPeripheralCecAdapter *) peripherals.at(iPeripheralPtr); + if (cecDevice && cecDevice->HasAudioControl()) + { + cecDevice->ToggleMute(); + return true; + } + } + } + + return false; +} + +bool CPeripherals::ToggleDeviceState(CecStateChange mode /*= STATE_SWITCH_TOGGLE */, unsigned int iPeripheral /*= 0 */) +{ + bool ret(false); + vector<CPeripheral *> peripherals; + + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (unsigned int iPeripheralPtr = iPeripheral; iPeripheralPtr < peripherals.size(); iPeripheralPtr++) + { + CPeripheralCecAdapter *cecDevice = (CPeripheralCecAdapter *) peripherals.at(iPeripheralPtr); + if (cecDevice) + ret = cecDevice->ToggleDeviceState(mode); + if (iPeripheral) + break; + } + } + + return ret; +} + +bool CPeripherals::GetNextKeypress(float frameTime, CKey &key) +{ + vector<CPeripheral *> peripherals; + if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC)) + { + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < peripherals.size(); iPeripheralPtr++) + { + CPeripheralCecAdapter *cecDevice = (CPeripheralCecAdapter *) peripherals.at(iPeripheralPtr); + if (cecDevice && cecDevice->GetButton()) + { + CKey newKey(cecDevice->GetButton(), cecDevice->GetHoldTime()); + cecDevice->ResetButton(); + key = newKey; + return true; + } + } + } + + return false; +} + +void CPeripherals::OnSettingChanged(const CSetting *setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == "locale.language") + { + // user set language, no longer use the TV's language + vector<CPeripheral *> cecDevices; + if (g_peripherals.GetPeripheralsWithFeature(cecDevices, FEATURE_CEC) > 0) + { + for (vector<CPeripheral *>::iterator it = cecDevices.begin(); it != cecDevices.end(); ++it) + (*it)->SetSetting("use_tv_menu_language", false); + } + } +} + +void CPeripherals::OnSettingAction(const CSetting *setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == "input.peripherals") + { + CGUIDialogPeripheralManager *dialog = (CGUIDialogPeripheralManager *)g_windowManager.GetWindow(WINDOW_DIALOG_PERIPHERAL_MANAGER); + if (dialog != NULL) + dialog->DoModal(); + } +} diff --git a/src/peripherals/Peripherals.h b/src/peripherals/Peripherals.h new file mode 100644 index 0000000000..fcef6cad11 --- /dev/null +++ b/src/peripherals/Peripherals.h @@ -0,0 +1,229 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "bus/PeripheralBus.h" +#include "devices/Peripheral.h" +#include "settings/lib/ISettingCallback.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" +#include "utils/Observer.h" + +class CFileItemList; +class CSetting; +class CSettingsCategory; +class TiXmlElement; +class CAction; +class CKey; + +namespace PERIPHERALS +{ + #define g_peripherals CPeripherals::Get() + + class CPeripherals : public ISettingCallback, + public Observable + { + public: + static CPeripherals &Get(void); + virtual ~CPeripherals(void); + + /*! + * @brief Initialise the peripherals manager. + */ + virtual void Initialise(void); + + /*! + * @brief Clear all data known by the peripherals manager. + */ + virtual void Clear(void); + + /*! + * @brief Get the instance of the peripheral at the given location. + * @param strLocation The location. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return The peripheral or NULL if it wasn't found. + */ + virtual CPeripheral *GetPeripheralAtLocation(const CStdString &strLocation, PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + /*! + * @brief Check whether a peripheral is present at the given location. + * @param strLocation The location. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return True when a peripheral was found, false otherwise. + */ + virtual bool HasPeripheralAtLocation(const CStdString &strLocation, PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + /*! + * @brief Get the bus that holds the device with the given location. + * @param strLocation The location. + * @return The bus or NULL if no device was found. + */ + virtual CPeripheralBus *GetBusWithDevice(const CStdString &strLocation) const; + + /*! + * @brief Get all peripheral instances that have the given feature. + * @param results The list of results. + * @param feature The feature to search for. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return The number of devices that have been found. + */ + virtual int GetPeripheralsWithFeature(std::vector<CPeripheral *> &results, const PeripheralFeature feature, PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + size_t GetNumberOfPeripherals() const; + + /*! + * @brief Check whether there is at least one device present with the given feature. + * @param feature The feature to check for. + * @param busType The bus to query. Default (PERIPHERAL_BUS_UNKNOWN) searches all busses. + * @return True when at least one device was found with this feature, false otherwise. + */ + virtual bool HasPeripheralWithFeature(const PeripheralFeature feature, PeripheralBusType busType = PERIPHERAL_BUS_UNKNOWN) const; + + /*! + * @brief Called when a device has been added to a bus. + * @param bus The bus the device was added to. + * @param peripheral The peripheral that has been added. + */ + virtual void OnDeviceAdded(const CPeripheralBus &bus, const CPeripheral &peripheral); + + /*! + * @brief Called when a device has been deleted from a bus. + * @param bus The bus from which the device removed. + * @param peripheral The peripheral that has been removed. + */ + virtual void OnDeviceDeleted(const CPeripheralBus &bus, const CPeripheral &peripheral); + + /*! + * @brief Creates a new instance of a peripheral. + * @param bus The bus on which this peripheral is present. + * @param result The scan result from the device scanning code. + * @return The new peripheral or NULL if it could not be created. + */ + CPeripheral *CreatePeripheral(CPeripheralBus &bus, const PeripheralScanResult& result); + + /*! + * @brief Add the settings that are defined in the mappings file to the peripheral (if there is anything defined). + * @param peripheral The peripheral to get the settings for. + */ + void GetSettingsFromMapping(CPeripheral &peripheral) const; + + /*! + * @brief Trigger a device scan on all known busses + */ + virtual void TriggerDeviceScan(const PeripheralBusType type = PERIPHERAL_BUS_UNKNOWN); + + /*! + * @brief Get the instance of a bus given it's type. + * @param type The bus type. + * @return The bus or NULL if it wasn't found. + */ + virtual CPeripheralBus *GetBusByType(const PeripheralBusType type) const; + + /*! + * @brief Get all fileitems for a path. + * @param strPath The path to the directory to get the items from. + * @param items The item list. + */ + virtual void GetDirectory(const CStdString &strPath, CFileItemList &items) const; + + /*! + * @brief Get the instance of a peripheral given it's path. + * @param strPath The path to the peripheral. + * @return The peripheral or NULL if it wasn't found. + */ + virtual CPeripheral *GetByPath(const CStdString &strPath) const; + + /*! + * @brief Try to let one of the peripherals handle an action. + * @param action The change to handle. + * @return True when this change was handled by a peripheral (and should not be handled by anything else), false otherwise. + */ + virtual bool OnAction(const CAction &action); + + /*! + * @brief Check whether there's a peripheral that reports to be muted. + * @return True when at least one peripheral reports to be muted, false otherwise. + */ + virtual bool IsMuted(void); + + /*! + * @brief Try to toggle the mute status via a peripheral. + * @return True when this change was handled by a peripheral (and should not be handled by anything else), false otherwise. + */ + virtual bool ToggleMute(void); + + /*! + * @brief Try to toggle the playing device state via a peripheral. + * @param mode Whether to activate, put on standby or toggle the source. + * @param iPeripheral Optional CPeripheralCecAdapter pointer to a specific device, instead of iterating through all of them. + * @return True when the playing device has been switched on, false otherwise. + */ + virtual bool ToggleDeviceState(const CecStateChange mode = STATE_SWITCH_TOGGLE, const unsigned int iPeripheral = 0); + + /*! + * @brief Try to mute the audio via a peripheral. + * @return True when this change was handled by a peripheral (and should not be handled by anything else), false otherwise. + */ + virtual bool Mute(void) { return ToggleMute(); } // TODO CEC only supports toggling the mute status at this time + + /*! + * @brief Try to unmute the audio via a peripheral. + * @return True when this change was handled by a peripheral (and should not be handled by anything else), false otherwise. + */ + virtual bool UnMute(void) { return ToggleMute(); } // TODO CEC only supports toggling the mute status at this time + + /*! + * @brief Try to get a keypress from a peripheral. + * @param frameTime The current frametime. + * @param key The fetched key. + * @return True when a keypress was fetched, false otherwise. + */ + virtual bool GetNextKeypress(float frameTime, CKey &key); + + bool SupportsCEC(void) const + { +#if defined(HAVE_LIBCEC) + return true; +#else + return false; +#endif + } + + virtual void OnSettingChanged(const CSetting *setting); + virtual void OnSettingAction(const CSetting *setting); + + private: + CPeripherals(void); + bool LoadMappings(void); + bool GetMappingForDevice(const CPeripheralBus &bus, PeripheralScanResult& result) const; + static void GetSettingsFromMappingsFile(TiXmlElement *xmlNode, std::map<CStdString, PeripheralDeviceSetting> &m_settings); + + bool m_bInitialised; + bool m_bIsStarted; +#if !defined(HAVE_LIBCEC) + bool m_bMissingLibCecWarningDisplayed; +#endif + std::vector<CPeripheralBus *> m_busses; + std::vector<PeripheralDeviceMapping> m_mappings; + CSettingsCategory * m_settings; + CCriticalSection m_critSection; + }; +} diff --git a/src/peripherals/bus/Makefile.in b/src/peripherals/bus/Makefile.in new file mode 100644 index 0000000000..2262e4a40a --- /dev/null +++ b/src/peripherals/bus/Makefile.in @@ -0,0 +1,22 @@ +SRCS = PeripheralBus.cpp + +ifeq (@USE_LIBUDEV@,1) +SRCS += linux/PeripheralBusUSBLibUdev.cpp +endif + +ifeq (@USE_LIBUSB@,1) +SRCS += linux/PeripheralBusUSBLibUSB.cpp +endif + +ifeq ($(findstring osx,@ARCH@),osx) +SRCS += osx/PeripheralBusUSB.cpp +endif + +ifeq (@USE_LIBCEC@,1) +SRCS += virtual/PeripheralBusCEC.cpp +endif + +LIB = peripheral-bus.a + +include @abs_top_srcdir@/Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/src/peripherals/bus/PeripheralBus.cpp b/src/peripherals/bus/PeripheralBus.cpp new file mode 100644 index 0000000000..7f1191b845 --- /dev/null +++ b/src/peripherals/bus/PeripheralBus.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralBus.h" +#include "peripherals/Peripherals.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "FileItem.h" + +using namespace std; +using namespace PERIPHERALS; + +#define PERIPHERAL_DEFAULT_RESCAN_INTERVAL 5000 + +CPeripheralBus::CPeripheralBus(const CStdString &threadname, CPeripherals *manager, PeripheralBusType type) : + CThread(threadname), + m_iRescanTime(PERIPHERAL_DEFAULT_RESCAN_INTERVAL), + m_bInitialised(false), + m_bIsStarted(false), + m_bNeedsPolling(true), + m_manager(manager), + m_type(type), + m_triggerEvent(true) +{ +} + +void CPeripheralBus::OnDeviceAdded(const CStdString &strLocation) +{ + ScanForDevices(); +} + +void CPeripheralBus::OnDeviceChanged(const CStdString &strLocation) +{ + ScanForDevices(); +} + +void CPeripheralBus::OnDeviceRemoved(const CStdString &strLocation) +{ + ScanForDevices(); +} + +void CPeripheralBus::Clear(void) +{ + if (m_bNeedsPolling) + { + m_bStop = true; + m_triggerEvent.Set(); + StopThread(true); + } + + CSingleLock lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + delete m_peripherals.at(iPeripheralPtr); + m_peripherals.clear(); +} + +void CPeripheralBus::UnregisterRemovedDevices(const PeripheralScanResults &results) +{ + CSingleLock lock(m_critSection); + vector<CPeripheral *> removedPeripherals; + for (int iDevicePtr = (int) m_peripherals.size() - 1; iDevicePtr >= 0; iDevicePtr--) + { + CPeripheral *peripheral = m_peripherals.at(iDevicePtr); + PeripheralScanResult updatedDevice(m_type); + if (!results.GetDeviceOnLocation(peripheral->Location(), &updatedDevice) || + *peripheral != updatedDevice) + { + /* device removed */ + removedPeripherals.push_back(peripheral); + m_peripherals.erase(m_peripherals.begin() + iDevicePtr); + } + } + lock.Leave(); + + for (unsigned int iDevicePtr = 0; iDevicePtr < removedPeripherals.size(); iDevicePtr++) + { + CPeripheral *peripheral = removedPeripherals.at(iDevicePtr); + vector<PeripheralFeature> features; + peripheral->GetFeatures(features); + bool peripheralHasFeatures = features.size() > 1 || (features.size() == 1 && features.at(0) != FEATURE_UNKNOWN); + if (peripheral->Type() != PERIPHERAL_UNKNOWN || peripheralHasFeatures) + { + CLog::Log(LOGNOTICE, "%s - device removed from %s/%s: %s (%s:%s)", __FUNCTION__, PeripheralTypeTranslator::TypeToString(peripheral->Type()), peripheral->Location().c_str(), peripheral->DeviceName().c_str(), peripheral->VendorIdAsString(), peripheral->ProductIdAsString()); + peripheral->OnDeviceRemoved(); + } + + m_manager->OnDeviceDeleted(*this, *peripheral); + delete peripheral; + } +} + +void CPeripheralBus::RegisterNewDevices(const PeripheralScanResults &results) +{ + CSingleLock lock(m_critSection); + for (unsigned int iResultPtr = 0; iResultPtr < results.m_results.size(); iResultPtr++) + { + const PeripheralScanResult& result = results.m_results.at(iResultPtr); + if (!HasPeripheral(result.m_strLocation)) + g_peripherals.CreatePeripheral(*this, result); + } +} + +bool CPeripheralBus::ScanForDevices(void) +{ + bool bReturn(false); + + PeripheralScanResults results; + if (PerformDeviceScan(results)) + { + UnregisterRemovedDevices(results); + RegisterNewDevices(results); + + CPeripherals::Get().NotifyObservers(ObservableMessagePeripheralsChanged); + + bReturn = true; + } + + m_bInitialised = true; + return bReturn; +} + +bool CPeripheralBus::HasFeature(const PeripheralFeature feature) const +{ + bool bReturn(false); + CSingleLock lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + { + if (m_peripherals.at(iPeripheralPtr)->HasFeature(feature)) + { + bReturn = true; + break; + } + } + return bReturn; +} + +void CPeripheralBus::GetFeatures(std::vector<PeripheralFeature> &features) const +{ + CSingleLock lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + m_peripherals.at(iPeripheralPtr)->GetFeatures(features); +} + +CPeripheral *CPeripheralBus::GetPeripheral(const CStdString &strLocation) const +{ + CPeripheral *peripheral(NULL); + CSingleLock lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + { + if (m_peripherals.at(iPeripheralPtr)->Location() == strLocation) + { + peripheral = m_peripherals.at(iPeripheralPtr); + break; + } + } + return peripheral; +} + +int CPeripheralBus::GetPeripheralsWithFeature(vector<CPeripheral *> &results, const PeripheralFeature feature) const +{ + int iReturn(0); + CSingleLock lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + { + if (m_peripherals.at(iPeripheralPtr)->HasFeature(feature)) + { + results.push_back(m_peripherals.at(iPeripheralPtr)); + ++iReturn; + } + } + + return iReturn; +} + +size_t CPeripheralBus::GetNumberOfPeripheralsWithId(const int iVendorId, const int iProductId) const +{ + int iReturn(0); + CSingleLock lock(m_critSection); + for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++) + { + if (m_peripherals.at(iPeripheralPtr)->VendorId() == iVendorId && + m_peripherals.at(iPeripheralPtr)->ProductId() == iProductId) + iReturn++; + } + + return iReturn; +} + +void CPeripheralBus::Process(void) +{ + while (!m_bStop) + { + m_triggerEvent.Reset(); + + if (!ScanForDevices()) + break; + + if (!m_bStop) + m_triggerEvent.WaitMSec(m_iRescanTime); + } + + m_bIsStarted = false; +} + +bool CPeripheralBus::Initialise(void) +{ + CSingleLock lock(m_critSection); + if (!m_bIsStarted) + { + /* do an initial scan of the bus */ + m_bIsStarted = ScanForDevices(); + + if (m_bIsStarted && m_bNeedsPolling) + { + lock.Leave(); + m_triggerEvent.Reset(); + Create(); + SetPriority(-1); + } + } + + return m_bIsStarted; +} + +void CPeripheralBus::Register(CPeripheral *peripheral) +{ + if (!peripheral) + return; + + CSingleLock lock(m_critSection); + if (!HasPeripheral(peripheral->Location())) + { + m_peripherals.push_back(peripheral); + CLog::Log(LOGNOTICE, "%s - new %s device registered on %s->%s: %s (%s:%s)", __FUNCTION__, PeripheralTypeTranslator::TypeToString(peripheral->Type()), PeripheralTypeTranslator::BusTypeToString(m_type), peripheral->Location().c_str(), peripheral->DeviceName().c_str(), peripheral->VendorIdAsString(), peripheral->ProductIdAsString()); + lock.Leave(); + + m_manager->OnDeviceAdded(*this, *peripheral); + } +} + +void CPeripheralBus::TriggerDeviceScan(void) +{ + CSingleLock lock(m_critSection); + if (m_bNeedsPolling) + { + lock.Leave(); + m_triggerEvent.Set(); + } + else + { + lock.Leave(); + ScanForDevices(); + } +} + +bool CPeripheralBus::HasPeripheral(const CStdString &strLocation) const +{ + return (GetPeripheral(strLocation) != NULL); +} + +void CPeripheralBus::GetDirectory(const CStdString &strPath, CFileItemList &items) const +{ + CStdString strDevPath; + CSingleLock lock(m_critSection); + for (unsigned int iDevicePtr = 0; iDevicePtr < m_peripherals.size(); iDevicePtr++) + { + const CPeripheral *peripheral = m_peripherals.at(iDevicePtr); + if (peripheral->IsHidden()) + continue; + + CFileItemPtr peripheralFile(new CFileItem(peripheral->DeviceName())); + peripheralFile->SetPath(peripheral->FileLocation()); + peripheralFile->SetProperty("vendor", peripheral->VendorIdAsString()); + peripheralFile->SetProperty("product", peripheral->ProductIdAsString()); + peripheralFile->SetProperty("bus", PeripheralTypeTranslator::BusTypeToString(peripheral->GetBusType())); + peripheralFile->SetProperty("location", peripheral->Location()); + peripheralFile->SetProperty("class", PeripheralTypeTranslator::TypeToString(peripheral->Type())); + peripheralFile->SetProperty("version", peripheral->GetVersionInfo()); + items.Add(peripheralFile); + } +} + +CPeripheral *CPeripheralBus::GetByPath(const CStdString &strPath) const +{ + CStdString strDevPath; + CSingleLock lock(m_critSection); + for (unsigned int iDevicePtr = 0; iDevicePtr < m_peripherals.size(); iDevicePtr++) + { + if (strPath.Equals(m_peripherals.at(iDevicePtr)->FileLocation())) + return m_peripherals.at(iDevicePtr); + } + + return NULL; +} + +size_t CPeripheralBus::GetNumberOfPeripherals() const +{ + return m_peripherals.size(); +} diff --git a/src/peripherals/bus/PeripheralBus.h b/src/peripherals/bus/PeripheralBus.h new file mode 100644 index 0000000000..795f88b6a4 --- /dev/null +++ b/src/peripherals/bus/PeripheralBus.h @@ -0,0 +1,176 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <vector> +#include "utils/StdString.h" +#include "threads/Thread.h" +#include "peripherals/PeripheralTypes.h" +#include "peripherals/devices/Peripheral.h" + +class CFileItemList; + +namespace PERIPHERALS +{ + class CPeripherals; + + /*! + * @class CPeripheralBus + * This represents a bus on the system. By default, this bus instance will scan for changes every 5 seconds. + * If this bus only has to be updated after a notification sent by the system, set m_bNeedsPolling to false + * in the constructor, and implement the OnDeviceAdded(), OnDeviceChanged() and OnDeviceRemoved() methods. + * + * The PerformDeviceScan() method has to be implemented by each specific bus implementation. + */ + class CPeripheralBus : protected CThread + { + public: + CPeripheralBus(const CStdString &threadname, CPeripherals *manager, PeripheralBusType type); + virtual ~CPeripheralBus(void) { Clear(); } + + /*! + * @return The bus type + */ + const PeripheralBusType Type(void) const { return m_type; } + + /*! + * @return True if this bus needs to be polled for changes, false if this bus performs updates via callbacks + */ + bool NeedsPolling(void) const { return m_bNeedsPolling; } + + /*! + * @brief Get the instance of the peripheral at the given location. + * @param strLocation The location. + * @return The peripheral or NULL if it wasn't found. + */ + virtual CPeripheral *GetPeripheral(const CStdString &strLocation) const; + + /*! + * @brief Check whether a peripheral is present at the given location. + * @param strLocation The location. + * @return True when a peripheral was found, false otherwise. + */ + virtual bool HasPeripheral(const CStdString &strLocation) const; + + /*! + * @brief Get all peripheral instances that have the given feature. + * @param results The list of results. + * @param feature The feature to search for. + * @return The number of devices that have been found. + */ + virtual int GetPeripheralsWithFeature(std::vector<CPeripheral *> &results, const PeripheralFeature feature) const; + + virtual size_t GetNumberOfPeripherals() const; + virtual size_t GetNumberOfPeripheralsWithId(const int iVendorId, const int iProductId) const; + + /*! + * @brief Get all features that are supported by devices on this bus. + * @param features All features. + */ + virtual void GetFeatures(std::vector<PeripheralFeature> &features) const; + + /*! + * @brief Check whether there is at least one device present with the given feature. + * @param feature The feature to check for. + * @return True when at least one device was found with this feature, false otherwise. + */ + virtual bool HasFeature(const PeripheralFeature feature) const; + + /*! + * @brief Callback method for when a device has been added. Will perform a device scan. + * @param strLocation The location of the device that has been added. + */ + virtual void OnDeviceAdded(const CStdString &strLocation); + + /*! + * @brief Callback method for when a device has been changed. Will perform a device scan. + * @param strLocation The location of the device that has been changed. + */ + virtual void OnDeviceChanged(const CStdString &strLocation); + + /*! + * @brief Callback method for when a device has been removed. Will perform a device scan. + * @param strLocation The location of the device that has been removed. + */ + virtual void OnDeviceRemoved(const CStdString &strLocation); + + /*! + * @brief Initialise this bus and start a polling thread if this bus needs polling. + */ + virtual bool Initialise(void); + + /*! + * @brief Stop the polling thread and clear all known devices on this bus. + */ + virtual void Clear(void); + + /*! + * @brief Scan for devices. + */ + virtual void TriggerDeviceScan(void); + + /*! + * @brief Get all fileitems for a path. + * @param strPath The path to the directory to get the items from. + * @param items The item list. + */ + virtual void GetDirectory(const CStdString &strPath, CFileItemList &items) const; + + /*! + * @brief Get the instance of a peripheral given it's path. + * @param strPath The path to the peripheral. + * @return The peripheral or NULL if it wasn't found. + */ + virtual CPeripheral *GetByPath(const CStdString &strPath) const; + + /*! + * @brief Register a new peripheral on this bus. + * @param peripheral The peripheral to register. + */ + virtual void Register(CPeripheral *peripheral); + + virtual bool FindComPort(CStdString &strLocation) { return false; } + + virtual bool IsInitialised(void) const { return m_bInitialised; } + + protected: + virtual void Process(void); + virtual bool ScanForDevices(void); + virtual void UnregisterRemovedDevices(const PeripheralScanResults &results); + virtual void RegisterNewDevices(const PeripheralScanResults &results); + + /*! + * @brief Scan for devices on this bus and add them to the results list. This will have to be implemented for each bus. + * @param results The result list. + * @return True when the scan was successful, false otherwise. + */ + virtual bool PerformDeviceScan(PeripheralScanResults &results) = 0; + + std::vector<CPeripheral *> m_peripherals; + int m_iRescanTime; + bool m_bInitialised; + bool m_bIsStarted; + bool m_bNeedsPolling; /*!< true when this bus needs to be polled for new devices, false when it uses callbacks to notify this bus of changed */ + CPeripherals * m_manager; + PeripheralBusType m_type; + CCriticalSection m_critSection; + CEvent m_triggerEvent; + }; +} diff --git a/src/peripherals/bus/PeripheralBusUSB.h b/src/peripherals/bus/PeripheralBusUSB.h new file mode 100644 index 0000000000..a39f8791b3 --- /dev/null +++ b/src/peripherals/bus/PeripheralBusUSB.h @@ -0,0 +1,40 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#if defined(TARGET_WINDOWS) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "win32/PeripheralBusUSB.h" +#elif defined(TARGET_LINUX) && defined(HAVE_LIBUDEV) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "linux/PeripheralBusUSBLibUdev.h" +#elif defined(TARGET_LINUX) && defined(HAVE_LIBUSB) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "linux/PeripheralBusUSBLibUSB.h" +#elif defined(TARGET_FREEBSD) && defined(HAVE_LIBUSB) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "linux/PeripheralBusUSBLibUSB.h" +#elif defined(TARGET_DARWIN) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "osx/PeripheralBusUSB.h" +#elif defined(TARGET_ANDROID) +#define HAVE_PERIPHERAL_BUS_USB 1 +#include "linux/PeripheralBusUSBLibUSB.h" +#endif diff --git a/src/peripherals/bus/linux/PeripheralBusUSBLibUSB.cpp b/src/peripherals/bus/linux/PeripheralBusUSBLibUSB.cpp new file mode 100644 index 0000000000..2658218b63 --- /dev/null +++ b/src/peripherals/bus/linux/PeripheralBusUSBLibUSB.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralBusUSBLibUSB.h" +#include "peripherals/Peripherals.h" +#include <usb.h> +#include "utils/log.h" +#include "utils/StringUtils.h" + +using namespace PERIPHERALS; + +CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals *manager) : + CPeripheralBus("PeripBusUSB", manager, PERIPHERAL_BUS_USB) +{ + usb_init(); + usb_find_busses(); + m_busses = usb_get_busses(); + CLog::Log(LOGDEBUG, "%s - using libusb peripheral scanning", __FUNCTION__); +} + +bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results) +{ + struct usb_bus *bus; + usb_find_devices(); + for (bus = m_busses; bus; bus = bus->next) + { + struct usb_device *dev; + for (dev = bus->devices; dev; dev = dev->next) + { + PeripheralScanResult result(m_type); + result.m_iVendorId = dev->descriptor.idVendor; + result.m_iProductId = dev->descriptor.idProduct; + result.m_type = (dev->descriptor.bDeviceClass == USB_CLASS_PER_INTERFACE && dev->descriptor.bNumConfigurations > 0 && + dev->config[0].bNumInterfaces > 0 && dev->config[0].interface[0].num_altsetting > 0) ? + GetType(dev->config[0].interface[0].altsetting[0].bInterfaceClass) : + GetType(dev->descriptor.bDeviceClass); +#ifdef TARGET_FREEBSD + result.m_strLocation = StringUtils::Format("%s", dev->filename); +#else + result.m_strLocation = StringUtils::Format("/bus%s/dev%s", bus->dirname, dev->filename); +#endif + result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId); + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + } + + return true; +} + +const PeripheralType CPeripheralBusUSB::GetType(int iDeviceClass) +{ + switch (iDeviceClass) + { + case USB_CLASS_HID: + return PERIPHERAL_HID; + case USB_CLASS_COMM: + return PERIPHERAL_NIC; + case USB_CLASS_MASS_STORAGE: + return PERIPHERAL_DISK; + case USB_CLASS_PER_INTERFACE: + case USB_CLASS_AUDIO: + case USB_CLASS_PRINTER: + case USB_CLASS_PTP: + case USB_CLASS_HUB: + case USB_CLASS_DATA: + case USB_CLASS_VENDOR_SPEC: + default: + return PERIPHERAL_UNKNOWN; + } +} diff --git a/src/peripherals/bus/linux/PeripheralBusUSBLibUSB.h b/src/peripherals/bus/linux/PeripheralBusUSBLibUSB.h new file mode 100644 index 0000000000..461d29673d --- /dev/null +++ b/src/peripherals/bus/linux/PeripheralBusUSBLibUSB.h @@ -0,0 +1,45 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/devices/Peripheral.h" + +struct usb_bus; + +namespace PERIPHERALS +{ + class CPeripherals; + + class CPeripheralBusUSB : public CPeripheralBus + { + public: + CPeripheralBusUSB(CPeripherals *manager); + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults &results); + + protected: + static const PeripheralType GetType(int iDeviceClass); + struct usb_bus *m_busses; + }; +} diff --git a/src/peripherals/bus/linux/PeripheralBusUSBLibUdev.cpp b/src/peripherals/bus/linux/PeripheralBusUSBLibUdev.cpp new file mode 100644 index 0000000000..461a4fc7f6 --- /dev/null +++ b/src/peripherals/bus/linux/PeripheralBusUSBLibUdev.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralBusUSBLibUdev.h" +#include "peripherals/Peripherals.h" +extern "C" { +#include <libudev.h> +} +#include <poll.h> +#include "utils/log.h" + +#ifndef USB_CLASS_PER_INTERFACE +#define USB_CLASS_PER_INTERFACE 0 +#endif + +#ifndef USB_CLASS_AUDIO +#define USB_CLASS_AUDIO 1 +#endif + +#ifndef USB_CLASS_COMM +#define USB_CLASS_COMM 2 +#endif + +#ifndef USB_CLASS_HID +#define USB_CLASS_HID 3 +#endif + +#ifndef USB_CLASS_PHYSICAL +#define USB_CLASS_PHYSICAL 5 +#endif + +#ifndef USB_CLASS_PTP +#define USB_CLASS_PTP 6 +#endif + +#ifndef USB_CLASS_PRINTER +#define USB_CLASS_PRINTER 7 +#endif + +#ifndef USB_CLASS_MASS_STORAGE +#define USB_CLASS_MASS_STORAGE 8 +#endif + +#ifndef USB_CLASS_HUB +#define USB_CLASS_HUB 9 +#endif + +#ifndef USB_CLASS_DATA +#define USB_CLASS_DATA 10 +#endif + +#ifndef USB_CLASS_APP_SPEC +#define USB_CLASS_APP_SPEC 0xfe +#endif + +#ifndef USB_CLASS_VENDOR_SPEC +#define USB_CLASS_VENDOR_SPEC 0xff +#endif + +using namespace PERIPHERALS; + +CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals *manager) : + CPeripheralBus("PeripBusUSBUdev", manager, PERIPHERAL_BUS_USB) +{ + /* the Process() method in this class overrides the one in CPeripheralBus, so leave this set to true */ + m_bNeedsPolling = true; + + m_udev = NULL; + m_udevMon = NULL; + + if (!(m_udev = udev_new())) + { + CLog::Log(LOGERROR, "%s - failed to allocate udev context", __FUNCTION__); + return; + } + + /* set up a devices monitor that listen for any device change */ + m_udevMon = udev_monitor_new_from_netlink(m_udev, "udev"); + udev_monitor_enable_receiving(m_udevMon); + + CLog::Log(LOGDEBUG, "%s - initialised udev monitor", __FUNCTION__); +} + +CPeripheralBusUSB::~CPeripheralBusUSB(void) +{ + StopThread(true); + udev_monitor_unref(m_udevMon); + udev_unref(m_udev); +} + +bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results) +{ + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + struct udev_device *dev(NULL), *parent(NULL); + enumerate = udev_enumerate_new(m_udev); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + + bool bContinue(true); + CStdString strPath, strClass; + udev_list_entry_foreach(dev_list_entry, devices) + { + strPath = udev_list_entry_get_name(dev_list_entry); + if (strPath.empty()) + bContinue = false; + + if (bContinue) + { + if (!(parent = udev_device_new_from_syspath(m_udev, strPath))) + bContinue = false; + } + + if (bContinue) + { + dev = udev_device_get_parent(udev_device_get_parent(parent)); + if (!dev || !udev_device_get_sysattr_value(dev,"idVendor") || !udev_device_get_sysattr_value(dev, "idProduct")) + bContinue = false; + } + + if (bContinue) + { + strClass = udev_device_get_sysattr_value(dev, "bDeviceClass"); + if (strClass.empty()) + bContinue = false; + } + + if (bContinue) + { + int iClass = PeripheralTypeTranslator::HexStringToInt(strClass.c_str()); + if (iClass == USB_CLASS_PER_INTERFACE) + { + //TODO just assume this is a HID device for now, since the only devices that we're currently + // interested in are HID devices + iClass = USB_CLASS_HID; + } + + PeripheralScanResult result(m_type); + result.m_iVendorId = PeripheralTypeTranslator::HexStringToInt(udev_device_get_sysattr_value(dev, "idVendor")); + result.m_iProductId = PeripheralTypeTranslator::HexStringToInt(udev_device_get_sysattr_value(dev, "idProduct")); + result.m_type = GetType(iClass); + result.m_strLocation = udev_device_get_syspath(dev); + result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId); + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + + bContinue = true; + if (parent) + { + /* unref the _parent_ device */ + udev_device_unref(parent); + parent = NULL; + } + } + /* Free the enumerator object */ + udev_enumerate_unref(enumerate); + + return true; +} + +const PeripheralType CPeripheralBusUSB::GetType(int iDeviceClass) +{ + switch (iDeviceClass) + { + case USB_CLASS_HID: + return PERIPHERAL_HID; + case USB_CLASS_COMM: + return PERIPHERAL_NIC; + case USB_CLASS_MASS_STORAGE: + return PERIPHERAL_DISK; + case USB_CLASS_PER_INTERFACE: + case USB_CLASS_AUDIO: + case USB_CLASS_PRINTER: + case USB_CLASS_PTP: + case USB_CLASS_HUB: + case USB_CLASS_DATA: + case USB_CLASS_VENDOR_SPEC: + default: + return PERIPHERAL_UNKNOWN; + } +} + +void CPeripheralBusUSB::Process(void) +{ + bool bUpdated(false); + ScanForDevices(); + while (!m_bStop) + { + bUpdated = WaitForUpdate(); + if (bUpdated && !m_bStop) + ScanForDevices(); + } + + m_bIsStarted = false; +} + +void CPeripheralBusUSB::Clear(void) +{ + StopThread(false); + + CPeripheralBus::Clear(); +} + +bool CPeripheralBusUSB::WaitForUpdate() +{ + int udevFd = udev_monitor_get_fd(m_udevMon); + + if (udevFd < 0) + { + CLog::Log(LOGERROR, "%s - get udev monitor", __FUNCTION__); + return false; + } + + /* poll for udev changes */ + struct pollfd pollFd; + pollFd.fd = udevFd; + pollFd.events = POLLIN; + int iPollResult; + while (!m_bStop && ((iPollResult = poll(&pollFd, 1, 100)) <= 0)) + if (errno != EINTR && iPollResult != 0) + break; + + /* the thread is being stopped, so just return false */ + if (m_bStop) + return false; + + /* we have to read the message from the queue, even though we're not actually using it */ + struct udev_device *dev = udev_monitor_receive_device(m_udevMon); + if (dev) + udev_device_unref(dev); + else + { + CLog::Log(LOGERROR, "%s - failed to get device from udev_monitor_receive_device()", __FUNCTION__); + Clear(); + return false; + } + + return true; +} diff --git a/src/peripherals/bus/linux/PeripheralBusUSBLibUdev.h b/src/peripherals/bus/linux/PeripheralBusUSBLibUdev.h new file mode 100644 index 0000000000..b7715ce2e0 --- /dev/null +++ b/src/peripherals/bus/linux/PeripheralBusUSBLibUdev.h @@ -0,0 +1,54 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/devices/Peripheral.h" + +struct udev; +struct udev_monitor; + +namespace PERIPHERALS +{ + class CPeripherals; + + class CPeripheralBusUSB : public CPeripheralBus + { + public: + CPeripheralBusUSB(CPeripherals *manager); + virtual ~CPeripheralBusUSB(void); + + virtual void Clear(void); + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults &results); + + protected: + static const PeripheralType GetType(int iDeviceClass); + + virtual void Process(void); + bool WaitForUpdate(void); + + struct udev * m_udev; + struct udev_monitor *m_udevMon; + }; +} diff --git a/src/peripherals/bus/osx/PeripheralBusUSB.cpp b/src/peripherals/bus/osx/PeripheralBusUSB.cpp new file mode 100644 index 0000000000..641ed65ef7 --- /dev/null +++ b/src/peripherals/bus/osx/PeripheralBusUSB.cpp @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralBusUSB.h" +#include "peripherals/Peripherals.h" +#include "utils/log.h" +#include "osx/DarwinUtils.h" + +#include <sys/param.h> + +using namespace PERIPHERALS; + +#ifdef TARGET_DARWIN_OSX +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/hid/IOHIDLib.h> +#include <IOKit/hid/IOHIDKeys.h> +#include <IOKit/serial/IOSerialKeys.h> + +typedef struct USBDevicePrivateData { + CPeripheralBusUSB *refCon; + CStdString deviceName; + io_object_t notification; + PeripheralScanResult result; +} USBDevicePrivateData; +#endif + +CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals *manager) : + CPeripheralBus("PeripBusUSB", manager, PERIPHERAL_BUS_USB) +{ + m_bNeedsPolling = false; + +#ifdef TARGET_DARWIN_OSX + //set up the matching criteria for the devices we're interested in + //interested in instances of class IOUSBDevice and its subclasses + // match any usb device by not creating matching values in the dict + CFMutableDictionaryRef matching_dict = IOServiceMatching(kIOUSBDeviceClassName); + + m_notify_port = IONotificationPortCreate(kIOMasterPortDefault); + CFRunLoopSourceRef run_loop_source = IONotificationPortGetRunLoopSource(m_notify_port); + CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, kCFRunLoopCommonModes); + + //add a notification callback for attach event + IOReturn result = IOServiceAddMatchingNotification(m_notify_port, + kIOFirstMatchNotification, matching_dict, + (IOServiceMatchingCallback)DeviceAttachCallback, this, &m_attach_iterator); + if (result == kIOReturnSuccess) + { + //call the callback to 'arm' the notification + DeviceAttachCallback(this, m_attach_iterator); + } +#endif +} + +CPeripheralBusUSB::~CPeripheralBusUSB() +{ +#ifdef TARGET_DARWIN_OSX + if (m_notify_port) + { + // remove the sleep notification port from the application runloop + CFRunLoopRemoveSource( CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(m_notify_port), kCFRunLoopDefaultMode ); + IONotificationPortDestroy(m_notify_port); + m_notify_port = 0; + } + if (m_attach_iterator) + { + IOObjectRelease(m_attach_iterator); + m_attach_iterator = 0; + } +#endif +} + +bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results) +{ + results = m_scan_results; + return true; +} + +#ifdef TARGET_DARWIN_OSX +const PeripheralType CPeripheralBusUSB::GetType(int iDeviceClass) +{ + switch (iDeviceClass) + { + case kUSBHIDClass: + return PERIPHERAL_HID; + case kUSBCommClass: + return PERIPHERAL_NIC; + case kUSBMassStorageClass: + return PERIPHERAL_DISK; + //case USB_CLASS_PER_INTERFACE: + case kUSBAudioClass: + case kUSBPrintingClass: + //case USB_CLASS_PTP: + case kUSBHubClass: + case kUSBDataClass: + case kUSBVendorSpecificClass: + default: + return PERIPHERAL_UNKNOWN; + } +} + +void CPeripheralBusUSB::DeviceDetachCallback(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) +{ + if (messageType == kIOMessageServiceIsTerminated) + { + IOReturn result; + + USBDevicePrivateData *privateDataRef = (USBDevicePrivateData*)refCon; + + std::vector<PeripheralScanResult>::iterator it = privateDataRef->refCon->m_scan_results.m_results.begin(); + while(it != privateDataRef->refCon->m_scan_results.m_results.end()) + { + if (privateDataRef->result.m_strLocation == it->m_strLocation) + it = privateDataRef->refCon->m_scan_results.m_results.erase(it); + else + ++it; + } + privateDataRef->refCon->ScanForDevices(); + + CLog::Log(LOGDEBUG, "USB Device Detach:%s, %s\n", + privateDataRef->deviceName.c_str(), privateDataRef->result.m_strLocation.c_str()); + result = IOObjectRelease(privateDataRef->notification); + delete privateDataRef; + //release the service + result = IOObjectRelease(service); + } +} + +void CPeripheralBusUSB::DeviceAttachCallback(CPeripheralBusUSB* refCon, io_iterator_t iterator) +{ + io_service_t usbDevice; + while ((usbDevice = IOIteratorNext(iterator))) + { + IOReturn result; + + io_name_t deviceName; + result = IORegistryEntryGetName(usbDevice, deviceName); + if (result != KERN_SUCCESS) + deviceName[0] = '\0'; + + SInt32 deviceScore; + IOCFPlugInInterface **devicePlugin; + result = IOCreatePlugInInterfaceForService(usbDevice, + kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &devicePlugin, &deviceScore); + if (result != kIOReturnSuccess) + { + IOObjectRelease(usbDevice); + continue; + } + + IOUSBDeviceInterface **deviceInterface; + // Use the plugin interface to retrieve the device interface. + result = (*devicePlugin)->QueryInterface(devicePlugin, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*)&deviceInterface); + if (result != kIOReturnSuccess) + { + IODestroyPlugInInterface(devicePlugin); + IOObjectRelease(usbDevice); + continue; + } + + // get vendor/product ids + UInt16 vendorId; + UInt16 productId; + UInt32 locationId; + UInt8 bDeviceClass; + + result = (*deviceInterface)->GetDeviceVendor( deviceInterface, &vendorId); + result = (*deviceInterface)->GetDeviceProduct(deviceInterface, &productId); + result = (*deviceInterface)->GetLocationID( deviceInterface, &locationId); + result = (*deviceInterface)->GetDeviceClass( deviceInterface, &bDeviceClass); + + io_service_t usbInterface; + io_iterator_t interface_iterator; + IOUSBFindInterfaceRequest request; + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + result = (*deviceInterface)->CreateInterfaceIterator(deviceInterface, &request, &interface_iterator); + while ((usbInterface = IOIteratorNext(interface_iterator))) + { + SInt32 interfaceScore; + IOCFPlugInInterface **interfacePlugin; + //create intermediate plugin for interface access + result = IOCreatePlugInInterfaceForService(usbInterface, + kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &interfacePlugin, &interfaceScore); + if (result != kIOReturnSuccess) + { + IOObjectRelease(usbInterface); + continue; + } + IOUSBInterfaceInterface** interfaceInterface; + result = (*interfacePlugin)->QueryInterface(interfacePlugin, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (void**)&interfaceInterface); + if (result != kIOReturnSuccess) + { + IODestroyPlugInInterface(interfacePlugin); + IOObjectRelease(usbInterface); + continue; + } + + // finally we can get to the bInterfaceClass + // we should also check for kHIDKeyboardInterfaceProtocol but + // some IR remotes that emulate an HID keyboard do not report this. + UInt8 bInterfaceClass; + result = (*interfaceInterface)->GetInterfaceClass(interfaceInterface, &bInterfaceClass); + if (bInterfaceClass == kUSBHIDInterfaceClass || bInterfaceClass == kUSBCommunicationDataInterfaceClass) + { + std::string ttlDeviceFilePath; + CFStringRef deviceFilePathAsCFString; + USBDevicePrivateData *privateDataRef; + privateDataRef = new USBDevicePrivateData; + // save the device info to our private data. + privateDataRef->refCon = refCon; + privateDataRef->deviceName = deviceName; + privateDataRef->result.m_iVendorId = vendorId; + privateDataRef->result.m_iProductId = productId; + + if (bInterfaceClass == kUSBCommunicationDataInterfaceClass) + { + // fetch the bds device path if this is USB serial device. + // to do this we have to switch from the kIOUSBPlane to + // kIOServicePlane, then we can search down for the path. + io_registry_entry_t parent; + kern_return_t kresult; + kresult = IORegistryEntryGetParentEntry(usbInterface, kIOServicePlane, &parent); + if (kresult == KERN_SUCCESS) + { + deviceFilePathAsCFString = (CFStringRef)IORegistryEntrySearchCFProperty(parent, + kIOServicePlane, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, kIORegistryIterateRecursively); + if (deviceFilePathAsCFString) + { + // Convert the path from a CFString to a std::string + if (!CDarwinUtils::CFStringRefToUTF8String(deviceFilePathAsCFString, ttlDeviceFilePath)) + CLog::Log(LOGWARNING, "CPeripheralBusUSB::DeviceAttachCallback failed to convert CFStringRef"); + CFRelease(deviceFilePathAsCFString); + } + IOObjectRelease(parent); + } + } + if (!ttlDeviceFilePath.empty()) + privateDataRef->result.m_strLocation = StringUtils::Format("%s", ttlDeviceFilePath.c_str()); + else + privateDataRef->result.m_strLocation = StringUtils::Format("%d", locationId); + + if (bDeviceClass == kUSBCompositeClass) + privateDataRef->result.m_type = refCon->GetType(bInterfaceClass); + else + privateDataRef->result.m_type = refCon->GetType(bDeviceClass); + + privateDataRef->result.m_iSequence = refCon->GetNumberOfPeripheralsWithId(privateDataRef->result.m_iVendorId, privateDataRef->result.m_iProductId); + if (!refCon->m_scan_results.ContainsResult(privateDataRef->result)) + { + // register this usb device for an interest notification callback. + result = IOServiceAddInterestNotification(refCon->m_notify_port, + usbDevice, // service + kIOGeneralInterest, // interestType + (IOServiceInterestCallback)DeviceDetachCallback, // callback + privateDataRef, // refCon + &privateDataRef->notification); // notification + + if (result == kIOReturnSuccess) + { + refCon->m_scan_results.m_results.push_back(privateDataRef->result); + CLog::Log(LOGDEBUG, "USB Device Attach:%s, %s\n", + deviceName, privateDataRef->result.m_strLocation.c_str()); + } + else + { + delete privateDataRef; + } + } + else + { + delete privateDataRef; + } + // done with this device, only need one notification per device. + IODestroyPlugInInterface(interfacePlugin); + IOObjectRelease(usbInterface); + break; + } + IODestroyPlugInInterface(interfacePlugin); + IOObjectRelease(usbInterface); + } + IODestroyPlugInInterface(devicePlugin); + IOObjectRelease(usbDevice); + } + refCon->ScanForDevices(); +} +#endif diff --git a/src/peripherals/bus/osx/PeripheralBusUSB.h b/src/peripherals/bus/osx/PeripheralBusUSB.h new file mode 100644 index 0000000000..46ca534899 --- /dev/null +++ b/src/peripherals/bus/osx/PeripheralBusUSB.h @@ -0,0 +1,57 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/devices/Peripheral.h" + +#ifdef TARGET_DARWIN_OSX +#include <IOKit/IOKitLib.h> +#include <IOKit/IOMessage.h> +#endif + +namespace PERIPHERALS +{ + class CPeripherals; + + class CPeripheralBusUSB : public CPeripheralBus + { + public: + CPeripheralBusUSB(CPeripherals *manager); + virtual ~CPeripheralBusUSB(); + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults &results); + + protected: + PeripheralScanResults m_scan_results; + #ifdef TARGET_DARWIN_OSX + static const PeripheralType GetType(int iDeviceClass); + static void DeviceDetachCallback(void *refCon, io_service_t service, natural_t messageType, void *messageArgument); + static void DeviceAttachCallback(CPeripheralBusUSB* refCon, io_iterator_t iterator); + + IONotificationPortRef m_notify_port; + io_iterator_t m_attach_iterator; + #endif + }; + +} diff --git a/src/peripherals/bus/virtual/PeripheralBusCEC.cpp b/src/peripherals/bus/virtual/PeripheralBusCEC.cpp new file mode 100644 index 0000000000..4941ae644a --- /dev/null +++ b/src/peripherals/bus/virtual/PeripheralBusCEC.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#if defined(HAVE_LIBCEC) +#include "PeripheralBusCEC.h" +#include "peripherals/Peripherals.h" +#include "utils/log.h" +#include "DynamicDll.h" + +#include <libcec/cec.h> + +using namespace PERIPHERALS; +using namespace CEC; +using namespace std; + +class DllLibCECInterface +{ +public: + virtual ~DllLibCECInterface() {} + virtual ICECAdapter* CECInitialise(libcec_configuration *configuration)=0; + virtual void* CECDestroy(ICECAdapter *adapter)=0; +}; + +class PERIPHERALS::DllLibCEC : public DllDynamic, DllLibCECInterface +{ + DECLARE_DLL_WRAPPER(DllLibCEC, DLL_PATH_LIBCEC) + + DEFINE_METHOD1(ICECAdapter*, CECInitialise, (libcec_configuration *p1)) + DEFINE_METHOD1(void* , CECDestroy, (ICECAdapter *p1)) + + BEGIN_METHOD_RESOLVE() + RESOLVE_METHOD_RENAME(CECInitialise, CECInitialise) + RESOLVE_METHOD_RENAME(CECDestroy, CECDestroy) + END_METHOD_RESOLVE() +}; + +CPeripheralBusCEC::CPeripheralBusCEC(CPeripherals *manager) : + CPeripheralBus("PeripBusCEC", manager, PERIPHERAL_BUS_CEC), + m_dll(new DllLibCEC), + m_cecAdapter(NULL) +{ + m_iRescanTime = 5000; + if (!m_dll->Load() || !m_dll->IsLoaded()) + { + delete m_dll; + m_dll = NULL; + } + else + { + m_cecAdapter = m_dll->CECInitialise(&m_configuration); + } +} + +CPeripheralBusCEC::~CPeripheralBusCEC(void) +{ + if (m_dll && m_cecAdapter) + m_dll->CECDestroy(m_cecAdapter); + delete m_dll; +} + +bool CPeripheralBusCEC::PerformDeviceScan(PeripheralScanResults &results) +{ + if (!m_dll || !m_cecAdapter) + return false; + + cec_adapter_descriptor deviceList[10]; + int8_t iFound = m_cecAdapter->DetectAdapters(deviceList, 10, NULL, true); + + for (uint8_t iDevicePtr = 0; iDevicePtr < iFound; iDevicePtr++) + { + PeripheralScanResult result(m_type); + result.m_iVendorId = deviceList[iDevicePtr].iVendorId; + result.m_iProductId = deviceList[iDevicePtr].iProductId; + result.m_strLocation = deviceList[iDevicePtr].strComName; + result.m_type = PERIPHERAL_CEC; + + // override the bus type, so users don't have to reconfigure their adapters + switch(deviceList[iDevicePtr].adapterType) + { + case ADAPTERTYPE_P8_EXTERNAL: + case ADAPTERTYPE_P8_DAUGHTERBOARD: + result.m_mappedBusType = PERIPHERAL_BUS_USB; + break; + case ADAPTERTYPE_RPI: + result.m_mappedBusType = PERIPHERAL_BUS_RPI; + /** the Pi's adapter cannot be removed, no need to rescan */ + m_bNeedsPolling = false; + break; + default: + break; + } + + result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId); + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + return true; +} + +#endif diff --git a/src/peripherals/bus/virtual/PeripheralBusCEC.h b/src/peripherals/bus/virtual/PeripheralBusCEC.h new file mode 100644 index 0000000000..acba01a856 --- /dev/null +++ b/src/peripherals/bus/virtual/PeripheralBusCEC.h @@ -0,0 +1,57 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/devices/Peripheral.h" + +// undefine macro isset, it collides with function in cectypes.h +#ifdef isset +#undef isset +#endif +#include <libcec/cectypes.h> + +namespace CEC +{ + class ICECAdapter; +} + +namespace PERIPHERALS +{ + class CPeripherals; + class DllLibCEC; + + class CPeripheralBusCEC : public CPeripheralBus + { + public: + CPeripheralBusCEC(CPeripherals *manager); + virtual ~CPeripheralBusCEC(void); + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults &results); + + private: + DllLibCEC* m_dll; + CEC::ICECAdapter* m_cecAdapter; + CEC::libcec_configuration m_configuration; + }; +} diff --git a/src/peripherals/bus/win32/PeripheralBusUSB.cpp b/src/peripherals/bus/win32/PeripheralBusUSB.cpp new file mode 100644 index 0000000000..e53feede0a --- /dev/null +++ b/src/peripherals/bus/win32/PeripheralBusUSB.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralBusUSB.h" +#include "peripherals/Peripherals.h" +#include "utils/log.h" +#include "utils/StringUtils.h" + +const static GUID USB_RAW_GUID = { 0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } }; +const static GUID USB_HID_GUID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } }; +const static GUID USB_DISK_GUID = { 0x53F56307, 0xB6BF, 0x11D0, { 0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B } }; +const static GUID USB_NIC_GUID = { 0xAD498944, 0x762F, 0x11D0, { 0x8D, 0xCB, 0x00, 0xC0, 0x4F, 0xC3, 0x35, 0x8C } }; + +using namespace PERIPHERALS; + +// Only to avoid endless loops while scanning for devices +#define MAX_BUS_DEVICES 2000 + +CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals *manager) : + CPeripheralBus("PeripBusUSB", manager, PERIPHERAL_BUS_USB) +{ + /* device removals aren't always triggering OnDeviceRemoved events, so poll for changes every 5 seconds to be sure we don't miss anything */ + m_iRescanTime = 5000; +} + +bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results) +{ + /* XXX we'll just scan the RAW guid and find all devices. they'll show up as type 'unknown' in the UI, + but the other option is that they're detected more than once, because RAW will return all devices. + we have to scan the RAW guid here, because not every device is found by it's GUID correctly, e.g. CDC adapters. */ + return PerformDeviceScan(&USB_RAW_GUID, PERIPHERAL_UNKNOWN, results); +} + +bool CPeripheralBusUSB::PerformDeviceScan(const GUID *guid, const PeripheralType defaultType, PeripheralScanResults &results) +{ + bool bReturn(false); + DWORD required = 0, iMemberIndex = 0; + int nBufferSize = 200; // Just initial guess, will be increased if required + + SP_DEVICE_INTERFACE_DATA deviceInterfaceData; + deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + SP_DEVINFO_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + + HDEVINFO const hDevHandle = SetupDiGetClassDevs(guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (hDevHandle == INVALID_HANDLE_VALUE) + { + CLog::Log(LOGWARNING, "%s - cannot query USB devices: invalid handle", __FUNCTION__); + return bReturn; + } + + PSP_DEVICE_INTERFACE_DETAIL_DATA devicedetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(nBufferSize); + int nPropertyBufferSize = 100; // Just initial guess, will be increased if required + char* deviceProperty = (char*)malloc(nPropertyBufferSize); + if (!devicedetailData || !deviceProperty) + { + free(devicedetailData); + free(deviceProperty); + CLog::Log(LOGSEVERE, "%s: memory allocation failed", __FUNCTION__); + return false; + } + + SP_DEVINFO_DATA deviceInfo; + bReturn = true; + for (iMemberIndex = 0; bReturn && iMemberIndex < MAX_BUS_DEVICES; iMemberIndex++) + { + bReturn = SetupDiEnumDeviceInfo(hDevHandle, iMemberIndex, &devInfoData) == TRUE; + + if (bReturn) + bReturn = SetupDiEnumDeviceInterfaces(hDevHandle, 0, guid, iMemberIndex, &deviceInterfaceData) == TRUE; + else + { + bReturn = true; + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; // no more USB devices, nothing more to scan + else + continue; // try to get other USB devices + } + + if (bReturn) + { + devicedetailData->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); + deviceInfo.cbSize = sizeof(SP_DEVINFO_DATA); + + BOOL bDetailResult = SetupDiGetDeviceInterfaceDetail(hDevHandle, &deviceInterfaceData, devicedetailData, nBufferSize , &required, &deviceInfo); + if (!bDetailResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + free(devicedetailData); + devicedetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(required * sizeof(TCHAR)); + if (!devicedetailData) + { + free(deviceProperty); + CLog::Log(LOGSEVERE, "%s: memory allocation failed", __FUNCTION__); + return false; + } + devicedetailData->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); + nBufferSize = required; + bDetailResult = SetupDiGetDeviceInterfaceDetail(hDevHandle, &deviceInterfaceData, devicedetailData, nBufferSize , &required, &deviceInfo); + } + + if (bDetailResult) + { + bDetailResult = SetupDiGetDeviceRegistryProperty(hDevHandle, &deviceInfo, SPDRP_HARDWAREID, NULL, (PBYTE)deviceProperty, nPropertyBufferSize, &required); + if (!bDetailResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + free(deviceProperty); + deviceProperty = (char*)malloc(required); + if (!deviceProperty) + { + free(devicedetailData); + CLog::Log(LOGSEVERE, "%s: memory allocation failed", __FUNCTION__); + return false; + } + nPropertyBufferSize = required; + bDetailResult = SetupDiGetDeviceRegistryProperty(hDevHandle, &deviceInfo, SPDRP_HARDWAREID, NULL, (PBYTE)deviceProperty, nPropertyBufferSize, &required); + } + } + + if (bDetailResult) + { + std::string strTmp(deviceProperty); + + StringUtils::ToLower(strTmp); + size_t posVid, posPid; + if (((posVid=strTmp.find("\\vid_")) != std::string::npos || (posVid=strTmp.find("&vid_")) != std::string::npos) && + ((posPid=strTmp.find("\\pid_")) != std::string::npos || (posPid=strTmp.find("&pid_")) != std::string::npos)) + { + std::string strVendorId(strTmp, posVid + 5, 4); + std::string strProductId(strTmp, posPid + 5, 4); + PeripheralScanResult prevDevice(m_type); + if (!results.GetDeviceOnLocation(devicedetailData->DevicePath, &prevDevice)) + { + PeripheralScanResult result(m_type); + result.m_strLocation = devicedetailData->DevicePath; + result.m_iVendorId = PeripheralTypeTranslator::HexStringToInt(strVendorId.c_str()); + result.m_iProductId = PeripheralTypeTranslator::HexStringToInt(strProductId.c_str()); + result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId); + + // Assume that buffer is more then enough (we need only 8 chars, initial allocation is 100 chars). If not - just skip type detection. + if (SetupDiGetDeviceRegistryProperty(hDevHandle, &devInfoData, SPDRP_CLASS, NULL, (PBYTE)deviceProperty, nPropertyBufferSize, &required) && + strcmp("HIDClass", deviceProperty) == 0) + result.m_type = PERIPHERAL_HID; + else + result.m_type = defaultType; + + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + } + } + } + } + + SetupDiDestroyDeviceInfoList(hDevHandle); + if (devicedetailData) + free(devicedetailData); + if (deviceProperty) + free(deviceProperty); + + return bReturn; +} diff --git a/src/peripherals/bus/win32/PeripheralBusUSB.h b/src/peripherals/bus/win32/PeripheralBusUSB.h new file mode 100644 index 0000000000..92c51d3a08 --- /dev/null +++ b/src/peripherals/bus/win32/PeripheralBusUSB.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/devices/Peripheral.h" +#include <setupapi.h> //needed for GUID + +namespace PERIPHERALS +{ + class CPeripherals; + + class CPeripheralBusUSB : public CPeripheralBus + { + public: + CPeripheralBusUSB(CPeripherals *manager); + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults &results); + + private: + bool PerformDeviceScan(const GUID *guid, const PeripheralType defaultType, PeripheralScanResults &results); + bool GetProductAndVendorId(const PeripheralType type, const CStdString &strDeviceLocation, int *iVendorId, int *iProductId); + }; +} diff --git a/src/peripherals/devices/Makefile.in b/src/peripherals/devices/Makefile.in new file mode 100644 index 0000000000..23989f28b2 --- /dev/null +++ b/src/peripherals/devices/Makefile.in @@ -0,0 +1,17 @@ +SRCS = Peripheral.cpp +SRCS += PeripheralBluetooth.cpp +SRCS += PeripheralDisk.cpp +SRCS += PeripheralHID.cpp +SRCS += PeripheralImon.cpp +SRCS += PeripheralNIC.cpp +SRCS += PeripheralNyxboard.cpp +SRCS += PeripheralTuner.cpp + +ifeq (@USE_LIBCEC@,1) +SRCS += PeripheralCecAdapter.cpp +endif + +LIB = peripheral-devices.a + +include @abs_top_srcdir@/Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/src/peripherals/devices/Peripheral.cpp b/src/peripherals/devices/Peripheral.cpp new file mode 100644 index 0000000000..b1eaa1fa27 --- /dev/null +++ b/src/peripherals/devices/Peripheral.cpp @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Peripheral.h" +#include "peripherals/Peripherals.h" +#include "utils/log.h" +#include "utils/StringUtils.h" +#include "settings/lib/Setting.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/URIUtils.h" +#include "guilib/LocalizeStrings.h" + +using namespace PERIPHERALS; +using namespace std; + +struct SortBySettingsOrder +{ + bool operator()(const PeripheralDeviceSetting &left, const PeripheralDeviceSetting& right) + { + return left.m_order < right.m_order; + } +}; + +CPeripheral::CPeripheral(const PeripheralScanResult& scanResult) : + 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_strFileLocation(StringUtils::EmptyString), + 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) +{ + PeripheralTypeTranslator::FormatHexString(scanResult.m_iVendorId, m_strVendorId); + PeripheralTypeTranslator::FormatHexString(scanResult.m_iProductId, m_strProductId); + m_strFileLocation = StringUtils::Format(scanResult.m_iSequence > 0 ? "peripherals://%s/%s_%d.dev" : "peripherals://%s/%s.dev", + PeripheralTypeTranslator::BusTypeToString(scanResult.m_busType), + scanResult.m_strLocation.c_str(), + scanResult.m_iSequence); +} + +CPeripheral::~CPeripheral(void) +{ + PersistSettings(true); + + for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++) + delete m_subDevices.at(iSubdevicePtr); + 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; + + g_peripherals.GetSettingsFromMapping(*this); + m_strSettingsFile = StringUtils::Format("special://profile/peripheral_data/%s_%s_%s.xml", + PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), + m_strVendorId.c_str(), + m_strProductId.c_str()); + 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, "%s - initialised peripheral on '%s' with %d features and %d sub devices", + __FUNCTION__, m_strLocation.c_str(), (int)m_features.size(), (int)m_subDevices.size()); + m_bInitialised = true; + } + + return bReturn; +} + +void CPeripheral::GetSubdevices(vector<CPeripheral *> &subDevices) const +{ + for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++) + subDevices.push_back(m_subDevices.at(iSubdevicePtr)); +} + +bool CPeripheral::IsMultiFunctional(void) const +{ + return m_subDevices.size() > 0; +} + +vector<CSetting *> CPeripheral::GetSettings(void) const +{ + vector<PeripheralDeviceSetting> tmpSettings; + for (map<CStdString, PeripheralDeviceSetting>::const_iterator it = m_settings.begin(); it != m_settings.end(); ++it) + tmpSettings.push_back(it->second); + sort(tmpSettings.begin(), tmpSettings.end(), SortBySettingsOrder()); + + vector<CSetting *> settings; + for (vector<PeripheralDeviceSetting>::const_iterator it = tmpSettings.begin(); it != tmpSettings.end(); ++it) + settings.push_back(it->m_setting); + return settings; +} + +void CPeripheral::AddSetting(const CStdString &strKey, const CSetting *setting, int order) +{ + if (!setting) + { + CLog::Log(LOGERROR, "%s - invalid setting", __FUNCTION__); + return; + } + + if (!HasSetting(strKey)) + { + PeripheralDeviceSetting deviceSetting = { NULL, order }; + switch(setting->GetType()) + { + case SettingTypeBool: + { + const CSettingBool *mappedSetting = (const CSettingBool *) setting; + CSettingBool *boolSetting = new CSettingBool(strKey, *mappedSetting); + if (boolSetting) + { + boolSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = boolSetting; + } + } + break; + case SettingTypeInteger: + { + const CSettingInt *mappedSetting = (const CSettingInt *) setting; + CSettingInt *intSetting = new CSettingInt(strKey, *mappedSetting); + if (intSetting) + { + intSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = intSetting; + } + } + break; + case SettingTypeNumber: + { + const CSettingNumber *mappedSetting = (const CSettingNumber *) setting; + CSettingNumber *floatSetting = new CSettingNumber(strKey, *mappedSetting); + if (floatSetting) + { + floatSetting->SetVisible(mappedSetting->IsVisible()); + deviceSetting.m_setting = floatSetting; + } + } + break; + case SettingTypeString: + { + const CSettingString *mappedSetting = (const CSettingString *) setting; + CSettingString *stringSetting = new CSettingString(strKey, *mappedSetting); + if (stringSetting) + { + 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 CStdString &strKey) const +{ + map<CStdString, 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); + map<CStdString, 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 CStdString &strKey) const +{ + map<CStdString, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingTypeBool) + { + CSettingBool *boolSetting = (CSettingBool *) (*it).second.m_setting; + if (boolSetting) + return boolSetting->GetValue(); + } + + return false; +} + +int CPeripheral::GetSettingInt(const CStdString &strKey) const +{ + map<CStdString, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingTypeInteger) + { + CSettingInt *intSetting = (CSettingInt *) (*it).second.m_setting; + if (intSetting) + return intSetting->GetValue(); + } + + return 0; +} + +float CPeripheral::GetSettingFloat(const CStdString &strKey) const +{ + map<CStdString, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingTypeNumber) + { + CSettingNumber *floatSetting = (CSettingNumber *) (*it).second.m_setting; + if (floatSetting) + return (float)floatSetting->GetValue(); + } + + return 0; +} + +const CStdString CPeripheral::GetSettingString(const CStdString &strKey) const +{ + map<CStdString, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingTypeString) + { + CSettingString *stringSetting = (CSettingString *) (*it).second.m_setting; + if (stringSetting) + return stringSetting->GetValue(); + } + + return StringUtils::EmptyString; +} + +bool CPeripheral::SetSetting(const CStdString &strKey, bool bValue) +{ + bool bChanged(false); + map<CStdString, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingTypeBool) + { + CSettingBool *boolSetting = (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 CStdString &strKey, int iValue) +{ + bool bChanged(false); + map<CStdString, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingTypeInteger) + { + CSettingInt *intSetting = (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 CStdString &strKey, float fValue) +{ + bool bChanged(false); + map<CStdString, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingTypeNumber) + { + CSettingNumber *floatSetting = (CSettingNumber *) (*it).second.m_setting; + if (floatSetting) + { + bChanged = floatSetting->GetValue() != fValue; + floatSetting->SetValue(fValue); + if (bChanged && m_bInitialised) + m_changedSettings.insert(strKey); + } + } + return bChanged; +} + +void CPeripheral::SetSettingVisible(const CStdString &strKey, bool bSetTo) +{ + map<CStdString, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end()) + (*it).second.m_setting->SetVisible(bSetTo); +} + +bool CPeripheral::IsSettingVisible(const CStdString &strKey) const +{ + map<CStdString, 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 CStdString &strKey, const CStdString &strValue) +{ + bool bChanged(false); + map<CStdString, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey); + if (it != m_settings.end()) + { + if ((*it).second.m_setting->GetType() == SettingTypeString) + { + CSettingString *stringSetting = (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() == SettingTypeInteger) + bChanged = SetSetting(strKey, (int) (strValue.empty() ? 0 : atoi(strValue.c_str()))); + else if ((*it).second.m_setting->GetType() == SettingTypeNumber) + bChanged = SetSetting(strKey, (float) (strValue.empty() ? 0 : atof(strValue.c_str()))); + else if ((*it).second.m_setting->GetType() == SettingTypeBool) + bChanged = SetSetting(strKey, strValue.Equals("1")); + } + return bChanged; +} + +void CPeripheral::PersistSettings(bool bExiting /* = false */) +{ + CXBMCTinyXML doc; + TiXmlElement node("settings"); + doc.InsertEndChild(node); + for (map<CStdString, PeripheralDeviceSetting>::const_iterator itr = m_settings.begin(); itr != m_settings.end(); ++itr) + { + TiXmlElement nodeSetting("setting"); + nodeSetting.SetAttribute("id", itr->first.c_str()); + CStdString strValue; + switch ((*itr).second.m_setting->GetType()) + { + case SettingTypeString: + { + CSettingString *stringSetting = (CSettingString *) (*itr).second.m_setting; + if (stringSetting) + strValue = stringSetting->GetValue(); + } + break; + case SettingTypeInteger: + { + CSettingInt *intSetting = (CSettingInt *) (*itr).second.m_setting; + if (intSetting) + strValue = StringUtils::Format("%d", intSetting->GetValue()); + } + break; + case SettingTypeNumber: + { + CSettingNumber *floatSetting = (CSettingNumber *) (*itr).second.m_setting; + if (floatSetting) + strValue = StringUtils::Format("%.2f", floatSetting->GetValue()); + } + break; + case SettingTypeBool: + { + CSettingBool *boolSetting = (CSettingBool *) (*itr).second.m_setting; + if (boolSetting) + strValue = StringUtils::Format("%d", boolSetting->GetValue() ? 1:0); + } + break; + default: + break; + } + nodeSetting.SetAttribute("value", strValue.c_str()); + doc.RootElement()->InsertEndChild(nodeSetting); + } + + doc.SaveFile(m_strSettingsFile); + + if (!bExiting) + { + for (set<CStdString>::const_iterator it = m_changedSettings.begin(); it != m_changedSettings.end(); ++it) + 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) + { + CStdString strId = XMLUtils::GetAttribute(setting, "id"); + CStdString strValue = XMLUtils::GetAttribute(setting, "value"); + SetSetting(strId, strValue); + + setting = setting->NextSiblingElement("setting"); + } + } +} + +void CPeripheral::ResetDefaultSettings(void) +{ + ClearSettings(); + g_peripherals.GetSettingsFromMapping(*this); + + map<CStdString, PeripheralDeviceSetting>::iterator it = m_settings.begin(); + while (it != m_settings.end()) + { + m_changedSettings.insert((*it).first); + ++it; + } + + PersistSettings(); +} + +void CPeripheral::ClearSettings(void) +{ + map<CStdString, PeripheralDeviceSetting>::iterator it = m_settings.begin(); + while (it != m_settings.end()) + { + delete (*it).second.m_setting; + ++it; + } + m_settings.clear(); +} + +bool CPeripheral::operator ==(const PeripheralScanResult& right) const +{ + return m_strLocation.Equals(right.m_strLocation); +} + +bool CPeripheral::operator !=(const PeripheralScanResult& right) const +{ + return !(*this == right); +} diff --git a/src/peripherals/devices/Peripheral.h b/src/peripherals/devices/Peripheral.h new file mode 100644 index 0000000000..10a7fb33cb --- /dev/null +++ b/src/peripherals/devices/Peripheral.h @@ -0,0 +1,189 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <set> +#include "utils/StdString.h" +#include "peripherals/PeripheralTypes.h" + +class TiXmlDocument; + +class CSetting; + +namespace PERIPHERALS +{ + class CGUIDialogPeripheralSettings; + + typedef enum + { + STATE_SWITCH_TOGGLE, + STATE_ACTIVATE_SOURCE, + STATE_STANDBY + } CecStateChange; + + class CPeripheral + { + friend class CGUIDialogPeripheralSettings; + + public: + CPeripheral(const PeripheralScanResult& scanResult); + virtual ~CPeripheral(void); + + 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 CStdString &FileLocation(void) const { return m_strFileLocation; } + const CStdString &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(); } + const PeripheralType Type(void) const { return m_type; } + const PeripheralBusType GetBusType(void) const { return m_busType; }; + const CStdString &DeviceName(void) const { return m_strDeviceName; } + bool IsHidden(void) const { return m_bHidden; } + void SetHidden(bool bSetTo = true) { m_bHidden = bSetTo; } + const CStdString &GetVersionInfo(void) const { return m_strVersionInfo; } + + /*! + * @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 succesfully, 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 succesfully, false otherwise. + */ + virtual bool InitialiseFeature(const PeripheralFeature feature) { return true; } + + /*! + * @brief Called when a setting changed. + * @param strChangedSetting The changed setting. + */ + virtual void OnSettingChanged(const CStdString &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(std::vector<CPeripheral *> &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 CStdString &strKey, 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 CStdString &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 CStdString GetSettingString(const CStdString &strKey) const; + virtual bool SetSetting(const CStdString &strKey, const CStdString &strValue); + virtual void SetSettingVisible(const CStdString &strKey, bool bSetTo); + virtual bool IsSettingVisible(const CStdString &strKey) const; + + virtual int GetSettingInt(const CStdString &strKey) const; + virtual bool SetSetting(const CStdString &strKey, int iValue); + + virtual bool GetSettingBool(const CStdString &strKey) const; + virtual bool SetSetting(const CStdString &strKey, bool bValue); + + virtual float GetSettingFloat(const CStdString &strKey) const; + virtual bool SetSetting(const CStdString &strKey, float fValue); + + virtual void PersistSettings(bool bExiting = false); + virtual void LoadPersistedSettings(void); + virtual void ResetDefaultSettings(void); + + virtual std::vector<CSetting *> GetSettings(void) const; + + virtual bool ErrorOccured(void) const { return m_bError; } + + protected: + virtual void ClearSettings(void); + + PeripheralType m_type; + PeripheralBusType m_busType; + PeripheralBusType m_mappedBusType; + CStdString m_strLocation; + CStdString m_strDeviceName; + CStdString m_strSettingsFile; + CStdString m_strFileLocation; + int m_iVendorId; + CStdString m_strVendorId; + int m_iProductId; + CStdString m_strProductId; + CStdString m_strVersionInfo; + bool m_bInitialised; + bool m_bHidden; + bool m_bError; + std::vector<PeripheralFeature> m_features; + std::vector<CPeripheral *> m_subDevices; + std::map<CStdString, PeripheralDeviceSetting> m_settings; + std::set<CStdString> m_changedSettings; + }; +} diff --git a/src/peripherals/devices/PeripheralBluetooth.cpp b/src/peripherals/devices/PeripheralBluetooth.cpp new file mode 100644 index 0000000000..c48aa91ffc --- /dev/null +++ b/src/peripherals/devices/PeripheralBluetooth.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralBluetooth.h" +#include "utils/log.h" + +using namespace PERIPHERALS; + +CPeripheralBluetooth::CPeripheralBluetooth(const PeripheralScanResult& scanResult) : + CPeripheral(scanResult) +{ + m_features.push_back(FEATURE_BLUETOOTH); +} diff --git a/src/peripherals/devices/PeripheralBluetooth.h b/src/peripherals/devices/PeripheralBluetooth.h new file mode 100644 index 0000000000..292df34eab --- /dev/null +++ b/src/peripherals/devices/PeripheralBluetooth.h @@ -0,0 +1,32 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Peripheral.h" + +namespace PERIPHERALS +{ + class CPeripheralBluetooth : public CPeripheral + { + public: + CPeripheralBluetooth(const PeripheralScanResult& scanResult); + virtual ~CPeripheralBluetooth(void) {}; + }; +} diff --git a/src/peripherals/devices/PeripheralCecAdapter.cpp b/src/peripherals/devices/PeripheralCecAdapter.cpp new file mode 100644 index 0000000000..e80feb16bb --- /dev/null +++ b/src/peripherals/devices/PeripheralCecAdapter.cpp @@ -0,0 +1,1746 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#if defined(HAVE_LIBCEC) +#include "PeripheralCecAdapter.h" +#include "input/XBIRRemote.h" +#include "Application.h" +#include "ApplicationMessenger.h" +#include "DynamicDll.h" +#include "threads/SingleLock.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/Key.h" +#include "guilib/LocalizeStrings.h" +#include "peripherals/Peripherals.h" +#include "peripherals/bus/PeripheralBus.h" +#include "pictures/GUIWindowSlideShow.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "utils/log.h" +#include "utils/Variant.h" + +#include <libcec/cec.h> + +using namespace PERIPHERALS; +using namespace ANNOUNCEMENT; +using namespace CEC; +using namespace std; + +#define CEC_LIB_SUPPORTED_VERSION 0x2100 + +/* 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_NONE 231 + +/* time in seconds to suppress source activation after receiving OnStop */ +#define CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP 2 + +class DllLibCECInterface +{ +public: + virtual ~DllLibCECInterface() {} + virtual ICECAdapter* CECInitialise(libcec_configuration *configuration)=0; + virtual void* CECDestroy(ICECAdapter *adapter)=0; +}; + +class DllLibCEC : public DllDynamic, DllLibCECInterface +{ + DECLARE_DLL_WRAPPER(DllLibCEC, DLL_PATH_LIBCEC) + + DEFINE_METHOD1(ICECAdapter*, CECInitialise, (libcec_configuration *p1)) + DEFINE_METHOD1(void* , CECDestroy, (ICECAdapter *p1)) + + BEGIN_METHOD_RESOLVE() + RESOLVE_METHOD_RENAME(CECInitialise, CECInitialise) + RESOLVE_METHOD_RENAME(CECDestroy, CECDestroy) + END_METHOD_RESOLVE() +}; + +CPeripheralCecAdapter::CPeripheralCecAdapter(const PeripheralScanResult& scanResult) : + CPeripheralHID(scanResult), + CThread("CECAdapter"), + m_dll(NULL), + m_cecAdapter(NULL) +{ + ResetMembers(); + m_features.push_back(FEATURE_CEC); + m_strComPort = scanResult.m_strLocation; +} + +CPeripheralCecAdapter::~CPeripheralCecAdapter(void) +{ + { + CSingleLock lock(m_critSection); + CAnnouncementManager::Get().RemoveAnnouncer(this); + m_bStop = true; + } + + StopThread(true); + delete m_queryThread; + + if (m_dll && m_cecAdapter) + { + m_dll->CECDestroy(m_cecAdapter); + m_cecAdapter = NULL; + delete m_dll; + m_dll = NULL; + } +} + +void CPeripheralCecAdapter::ResetMembers(void) +{ + if (m_cecAdapter && m_dll) + m_dll->CECDestroy(m_cecAdapter); + m_cecAdapter = NULL; + delete m_dll; + m_dll = NULL; + m_bStarted = false; + m_bHasButton = false; + m_bIsReady = false; + m_bHasConnectedAudioSystem = false; + m_strMenuLanguage = "???"; + m_lastKeypress = 0; + m_lastChange = VOLUME_CHANGE_NONE; + m_iExitCode = EXITCODE_QUIT; + m_bIsMuted = false; // TODO fetch the correct initial value when system audiostatus is implemented in libCEC + 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_currentButton.iButton = 0; + m_currentButton.iDuration = 0; + m_standbySent.SetValid(false); + m_configuration.Clear(); +} + +void CPeripheralCecAdapter::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data) +{ + if (flag == System && !strcmp(sender, "xbmc") && !strcmp(message, "OnQuit") && m_bIsReady) + { + CSingleLock lock(m_critSection); + m_iExitCode = static_cast<int>(data["exitcode"].asInteger(EXITCODE_QUIT)); + CAnnouncementManager::Get().RemoveAnnouncer(this); + StopThread(false); + } + else if (flag == GUI && !strcmp(sender, "xbmc") && !strcmp(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, "%s - ignoring OnScreensaverDeactivated for power action", __FUNCTION__); + } + if (m_configuration.bPowerOnScreensaver == 1 && !bIgnoreDeactivate && + m_configuration.bActivateSource == 1) + { + ActivateSource(); + } + } + else if (flag == GUI && !strcmp(sender, "xbmc") && !strcmp(message, "OnScreensaverActivated") && m_bIsReady) + { + // Don't put devices to standby if application is currently playing + if ((!g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused()) && m_configuration.bPowerOffScreensaver == 1) + { + // only power off when we're the active source + if (m_cecAdapter->IsLibCECActiveSource()) + StandbyDevices(); + } + } + else if (flag == System && !strcmp(sender, "xbmc") && !strcmp(message, "OnSleep")) + { + // this will also power off devices when we're the active source + { + CSingleLock lock(m_critSection); + m_bGoingToStandby = true; + } + StopThread(); + } + else if (flag == System && !strcmp(sender, "xbmc") && !strcmp(message, "OnWake")) + { + CLog::Log(LOGDEBUG, "%s - reconnecting to the CEC adapter after standby mode", __FUNCTION__); + if (ReopenConnection()) + { + bool bActivate(false); + { + CSingleLock lock(m_critSection); + bActivate = m_bActiveSourceBeforeStandby; + m_bActiveSourceBeforeStandby = false; + } + if (bActivate) + ActivateSource(); + } + } + else if (flag == Player && !strcmp(sender, "xbmc") && !strcmp(message, "OnStop")) + { + CSingleLock lock(m_critSection); + m_preventActivateSourceOnPlay = CDateTime::GetCurrentDateTime(); + m_bOnPlayReceived = false; + } + else if (flag == Player && !strcmp(sender, "xbmc") && !strcmp(message, "OnPlay")) + { + // activate the source when playback started, and the option is enabled + bool bActivateSource(false); + { + CSingleLock 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.CBCecLogMessage = &CecLogMessage; + m_callbacks.CBCecKeyPress = &CecKeyPress; + m_callbacks.CBCecCommand = &CecCommand; + m_callbacks.CBCecConfigurationChanged = &CecConfiguration; + m_callbacks.CBCecAlert = &CecAlert; + m_callbacks.CBCecSourceActivated = &CecSourceActivated; + m_configuration.callbackParam = this; + m_configuration.callbacks = &m_callbacks; + + m_dll = new DllLibCEC; + if (m_dll->Load() && m_dll->IsLoaded()) + m_cecAdapter = m_dll->CECInitialise(&m_configuration); + else + { + // display warning: libCEC could not be loaded + CLog::Log(LOGERROR, "%s", g_localizeStrings.Get(36017).c_str()); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36017)); + delete m_dll; + m_dll = NULL; + m_features.clear(); + return false; + } + + if (m_configuration.serverVersion < CEC_LIB_SUPPORTED_VERSION) + { + /* unsupported libcec version */ + CLog::Log(LOGERROR, g_localizeStrings.Get(36040).c_str(), m_cecAdapter ? m_configuration.serverVersion : -1, CEC_LIB_SUPPORTED_VERSION); + + // display warning: incompatible libCEC + CStdString strMessage = StringUtils::Format(g_localizeStrings.Get(36040).c_str(), 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) + m_dll->CECDestroy(m_cecAdapter); + m_cecAdapter = NULL; + + m_features.clear(); + return false; + } + else + { + CLog::Log(LOGDEBUG, "%s - using libCEC v%s", __FUNCTION__, m_cecAdapter->ToString((cec_server_version)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 %s - firmware v%d", m_cecAdapter->ToString((cec_server_version)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(" (%s)", dt.GetAsDBDate().c_str()); + } +} + +bool CPeripheralCecAdapter::OpenConnection(void) +{ + bool bIsOpen(false); + + if (!GetSettingBool("enabled")) + { + CLog::Log(LOGDEBUG, "%s - CEC adapter is disabled in peripheral settings", __FUNCTION__); + m_bStarted = false; + return bIsOpen; + } + + // open the CEC adapter + CLog::Log(LOGDEBUG, "%s - opening a connection to the CEC adapter: %s", __FUNCTION__, m_strComPort.c_str()); + + // scanning the CEC bus takes about 5 seconds, so display a notification to inform users that we're busy + CStdString strMessage = StringUtils::Format(g_localizeStrings.Get(21336).c_str(), g_localizeStrings.Get(36000).c_str()); + 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, "%s - 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; + + Sleep(10000); + } + } + + if (bIsOpen) + { + CLog::Log(LOGDEBUG, "%s - connection to the CEC adapter opened", __FUNCTION__); + + // read the configuration + libcec_configuration config; + if (m_cecAdapter->GetCurrentConfiguration(&config)) + { + // update the local configuration + CSingleLock lock(m_critSection); + SetConfigurationFromLibCEC(config); + } + } + + return bIsOpen; +} + +void CPeripheralCecAdapter::Process(void) +{ + if (!OpenConnection()) + return; + + { + CSingleLock lock(m_critSection); + m_iExitCode = EXITCODE_QUIT; + m_bGoingToStandby = false; + m_bIsRunning = true; + m_bActiveSourceBeforeStandby = false; + } + + CAnnouncementManager::Get().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) + Sleep(5); + } + + m_queryThread->StopThread(true); + + bool bSendStandbyCommands(false); + { + CSingleLock 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, "%s - sending standby commands", __FUNCTION__); + m_standbySent = CDateTime::GetCurrentDateTime(); + m_cecAdapter->StandbyDevices(); + } + else if (m_configuration.bSendInactiveSource == 1) + { + CLog::Log(LOGDEBUG, "%s - sending inactive source commands", __FUNCTION__); + m_cecAdapter->SetInactiveView(); + } + } + else + { + CLog::Log(LOGDEBUG, "%s - XBMC is not the active source, not sending any standby commands", __FUNCTION__); + } + } + + m_cecAdapter->Close(); + + CLog::Log(LOGDEBUG, "%s - CEC adapter processor thread ended", __FUNCTION__); + + { + CSingleLock lock(m_critSection); + m_bStarted = false; + m_bIsRunning = false; + } +} + +bool CPeripheralCecAdapter::HasAudioControl(void) +{ + CSingleLock lock(m_critSection); + return m_bHasConnectedAudioSystem; +} + +void CPeripheralCecAdapter::SetAudioSystemConnected(bool bSetTo) +{ + CSingleLock lock(m_critSection); + m_bHasConnectedAudioSystem = bSetTo; +} + +void CPeripheralCecAdapter::ProcessVolumeChange(void) +{ + bool bSendRelease(false); + CecVolumeChange pendingVolumeChange = VOLUME_CHANGE_NONE; + { + CSingleLock lock(m_critSection); + 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(XbmcThreads::SystemClockMillis() - m_lastKeypress > VOLUME_REFRESH_TIMEOUT); + + /* only send the keypress when it hasn't been sent yet */ + if (pendingVolumeChange != m_lastChange) + { + m_lastKeypress = XbmcThreads::SystemClockMillis(); + m_lastChange = pendingVolumeChange; + } + else if (bRefresh) + { + m_lastKeypress = XbmcThreads::SystemClockMillis(); + pendingVolumeChange = m_lastChange; + } + else + pendingVolumeChange = VOLUME_CHANGE_NONE; + } + else if (m_lastKeypress > 0 && XbmcThreads::SystemClockMillis() - m_lastKeypress > VOLUME_CHANGE_TIMEOUT) + { + /* send a key release */ + m_lastKeypress = 0; + 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); + { + CSingleLock 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()) + { + CSingleLock lock(m_critSection); + m_volumeChangeQueue.push(VOLUME_CHANGE_UP); + } +} + +void CPeripheralCecAdapter::VolumeDown(void) +{ + if (HasAudioControl()) + { + CSingleLock lock(m_critSection); + m_volumeChangeQueue.push(VOLUME_CHANGE_DOWN); + } +} + +void CPeripheralCecAdapter::ToggleMute(void) +{ + if (HasAudioControl()) + { + CSingleLock lock(m_critSection); + m_volumeChangeQueue.push(VOLUME_CHANGE_MUTE); + } +} + +bool CPeripheralCecAdapter::IsMuted(void) +{ + if (HasAudioControl()) + { + CSingleLock lock(m_critSection); + return m_bIsMuted; + } + return false; +} + +void CPeripheralCecAdapter::SetMenuLanguage(const char *strLanguage) +{ + if (m_strMenuLanguage.Equals(strLanguage)) + return; + + CStdString strGuiLanguage; + + if (!strcmp(strLanguage, "bul")) + strGuiLanguage = "Bulgarian"; + else if (!strcmp(strLanguage, "hrv")) + strGuiLanguage = "Croatian"; + else if (!strcmp(strLanguage, "cze")) + strGuiLanguage = "Czech"; + else if (!strcmp(strLanguage, "dan")) + strGuiLanguage = "Danish"; + else if (!strcmp(strLanguage, "dut")) + strGuiLanguage = "Dutch"; + else if (!strcmp(strLanguage, "eng")) + strGuiLanguage = "English"; + else if (!strcmp(strLanguage, "fin")) + strGuiLanguage = "Finnish"; + else if (!strcmp(strLanguage, "fre")) + strGuiLanguage = "French"; + else if (!strcmp(strLanguage, "ger")) + strGuiLanguage = "German"; + else if (!strcmp(strLanguage, "gre")) + strGuiLanguage = "Greek"; + else if (!strcmp(strLanguage, "hun")) + strGuiLanguage = "Hungarian"; + else if (!strcmp(strLanguage, "ita")) + strGuiLanguage = "Italian"; + else if (!strcmp(strLanguage, "nor")) + strGuiLanguage = "Norwegian"; + else if (!strcmp(strLanguage, "pol")) + strGuiLanguage = "Polish"; + else if (!strcmp(strLanguage, "por")) + strGuiLanguage = "Portuguese"; + else if (!strcmp(strLanguage, "rum")) + strGuiLanguage = "Romanian"; + else if (!strcmp(strLanguage, "rus")) + strGuiLanguage = "Russian"; + else if (!strcmp(strLanguage, "srp")) + strGuiLanguage = "Serbian"; + else if (!strcmp(strLanguage, "slo")) + strGuiLanguage = "Slovenian"; + else if (!strcmp(strLanguage, "spa")) + strGuiLanguage = "Spanish"; + else if (!strcmp(strLanguage, "swe")) + strGuiLanguage = "Swedish"; + else if (!strcmp(strLanguage, "tur")) + strGuiLanguage = "Turkish"; + + if (!strGuiLanguage.empty()) + { + CApplicationMessenger::Get().SetGUILanguage(strGuiLanguage); + CLog::Log(LOGDEBUG, "%s - language set to '%s'", __FUNCTION__, strGuiLanguage.c_str()); + } + else + CLog::Log(LOGWARNING, "%s - TV menu language set to unknown value '%s'", __FUNCTION__, strLanguage); +} + +int CPeripheralCecAdapter::CecCommand(void *cbParam, const cec_command command) +{ + CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam; + if (!adapter) + return 0; + + if (adapter->m_bIsReady) + { + switch (command.opcode) + { + case CEC_OPCODE_STANDBY: + /* a device was put in standby mode */ + if (command.initiator == CECDEVICE_TV && + (adapter->m_configuration.bPowerOffOnStandby == 1 || adapter->m_configuration.bShutdownOnStandby == 1) && + (!adapter->m_standbySent.IsValid() || CDateTime::GetCurrentDateTime() - adapter->m_standbySent > CDateTimeSpan(0, 0, 0, SCREENSAVER_TIMEOUT))) + { + adapter->m_bStarted = false; + if (adapter->m_configuration.bPowerOffOnStandby == 1) + CApplicationMessenger::Get().Suspend(); + else if (adapter->m_configuration.bShutdownOnStandby == 1) + CApplicationMessenger::Get().Shutdown(); + } + break; + case CEC_OPCODE_SET_MENU_LANGUAGE: + if (adapter->m_configuration.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; + } + } + return 1; +} + +int CPeripheralCecAdapter::CecConfiguration(void *cbParam, const libcec_configuration config) +{ + CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam; + if (!adapter) + return 0; + + CSingleLock lock(adapter->m_critSection); + adapter->SetConfigurationFromLibCEC(config); + return 1; +} + +int CPeripheralCecAdapter::CecAlert(void *cbParam, const libcec_alert alert, const libcec_parameter data) +{ + CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam; + if (!adapter) + return 0; + + 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) + { + CStdString strLog(g_localizeStrings.Get(iAlertString)); + if (data.paramType == CEC_PARAMETER_TYPE_STRING && data.paramData) + strLog += StringUtils::Format(" - %s", (const char *)data.paramData); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strLog); + } + + if (bReopenConnection) + adapter->ReopenConnection(); + + return 1; +} + +int CPeripheralCecAdapter::CecKeyPress(void *cbParam, const cec_keypress key) +{ + CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam; + if (!adapter) + return 0; + + adapter->PushCecKeypress(key); + return 1; +} + +void CPeripheralCecAdapter::GetNextKey(void) +{ + CSingleLock lock(m_critSection); + m_bHasButton = false; + if (m_bIsReady) + { + 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, "%s - received key %2x duration %d", __FUNCTION__, key.iButton, key.iDuration); + + CSingleLock lock(m_critSection); + if (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 (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) +{ + CSingleLock lock(m_critSection); + if (!m_bHasButton) + GetNextKey(); + + return m_bHasButton ? m_currentButton.iButton : 0; +} + +unsigned int CPeripheralCecAdapter::GetHoldTime(void) +{ + CSingleLock lock(m_critSection); + if (!m_bHasButton) + GetNextKey(); + + return m_bHasButton ? m_currentButton.iDuration : 0; +} + +void CPeripheralCecAdapter::ResetButton(void) +{ + CSingleLock 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 CStdString &strChangedSetting) +{ + if (strChangedSetting.Equals("enabled")) + { + bool bEnabled(GetSettingBool("enabled")); + if (!bEnabled && IsRunning()) + { + CLog::Log(LOGDEBUG, "%s - closing the CEC connection", __FUNCTION__); + StopThread(true); + } + else if (bEnabled && !IsRunning()) + { + CLog::Log(LOGDEBUG, "%s - starting the CEC connection", __FUNCTION__); + SetConfigurationFromSettings(); + InitialiseFeature(FEATURE_CEC); + } + } + else if (IsRunning()) + { + if (m_queryThread->IsRunning()) + { + CLog::Log(LOGDEBUG, "%s - sending the updated configuration to libCEC", __FUNCTION__); + SetConfigurationFromSettings(); + m_queryThread->UpdateConfiguration(&m_configuration); + } + } + else + { + CLog::Log(LOGDEBUG, "%s - 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 = (CPeripheralCecAdapter *)cbParam; + if (!adapter) + return; + + // wake up the screensaver, so the user doesn't switch to a black screen + if (activated == 1) + g_application.WakeUpScreenSaverAndDPMS(); + + if (adapter->GetSettingBool("pause_playback_on_deactivate")) + { + bool bShowingSlideshow = (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW); + CGUIWindowSlideShow *pSlideShow = bShowingSlideshow ? (CGUIWindowSlideShow *)g_windowManager.GetWindow(WINDOW_SLIDESHOW) : NULL; + bool bPlayingAndDeactivated = activated == 0 && ( + (pSlideShow && pSlideShow->IsPlaying()) || g_application.m_pPlayer->IsPlaying()); + bool bPausedAndActivated = activated == 1 && adapter->m_bPlaybackPaused && ( + (pSlideShow && pSlideShow->IsPaused()) || g_application.m_pPlayer->IsPausedPlayback()); + if (bPlayingAndDeactivated) + adapter->m_bPlaybackPaused = true; + else if (bPausedAndActivated) + adapter->m_bPlaybackPaused = false; + + if (bPlayingAndDeactivated || bPausedAndActivated) + { + if (pSlideShow) + // pause/resume slideshow + pSlideShow->OnAction(CAction(ACTION_PAUSE)); + else + // pause/resume player + CApplicationMessenger::Get().MediaPause(); + } + } +} + +int CPeripheralCecAdapter::CecLogMessage(void *cbParam, const cec_log_message message) +{ + CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam; + if (!adapter) + return 0; + + 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 && CLog::IsLogLevelLogged(LOGDEBUG) && g_advancedSettings.CanLogComponent(LOGCEC))) + CLog::Log(iLevel, "%s - %s", __FUNCTION__, message.message); + + return 1; +} + +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 + CStdString 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.bUseTVMenuLanguage = config.bUseTVMenuLanguage; + bChanged |= SetSetting("use_tv_menu_language", m_configuration.bUseTVMenuLanguage == 1); + + m_configuration.bActivateSource = config.bActivateSource; + bChanged |= SetSetting("activate_source", m_configuration.bActivateSource == 1); + + m_configuration.bPowerOffScreensaver = config.bPowerOffScreensaver; + bChanged |= SetSetting("cec_standby_screensaver", m_configuration.bPowerOffScreensaver == 1); + + m_configuration.bPowerOnScreensaver = config.bPowerOnScreensaver; + bChanged |= SetSetting("cec_wake_screensaver", m_configuration.bPowerOnScreensaver == 1); + + m_configuration.bPowerOffOnStandby = config.bPowerOffOnStandby; + + m_configuration.bSendInactiveSource = config.bSendInactiveSource; + bChanged |= SetSetting("send_inactive_source", m_configuration.bSendInactiveSource == 1); + + m_configuration.iFirmwareVersion = config.iFirmwareVersion; + m_configuration.bShutdownOnStandby = config.bShutdownOnStandby; + + memcpy(m_configuration.strDeviceLanguage, config.strDeviceLanguage, 3); + m_configuration.iFirmwareBuildDate = config.iFirmwareBuildDate; + + SetVersionInfo(m_configuration); + + bChanged |= SetSetting("standby_pc_on_tv_standby", + m_configuration.bPowerOffOnStandby == 1 ? 13011 : + m_configuration.bShutdownOnStandby == 1 ? 13005 : 36028); + + if (bChanged) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), g_localizeStrings.Get(36023)); +} + +void CPeripheralCecAdapter::SetConfigurationFromSettings(void) +{ + // client version matches the version of libCEC that we originally used the API from + m_configuration.clientVersion = CEC_CLIENT_VERSION_2_2_0; + + // device name 'XBMC' + snprintf(m_configuration.strDeviceName, 13, "%s", GetSettingString("device_name").c_str()); + + // set the primary device type + m_configuration.deviceTypes.Clear(); + int iDeviceType = GetSettingInt("device_type"); + if (iDeviceType != (int)CEC_DEVICE_TYPE_RECORDING_DEVICE && + iDeviceType != (int)CEC_DEVICE_TYPE_PLAYBACK_DEVICE && + iDeviceType != (int)CEC_DEVICE_TYPE_TUNER) + iDeviceType = (int)CEC_DEVICE_TYPE_RECORDING_DEVICE; + m_configuration.deviceTypes.Add((cec_device_type)iDeviceType); + + // 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 + CStdString 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_MAX_VENDORID && + iVendor <= CEC_MAX_VENDORID) + m_configuration.tvVendor = iVendor; + + // read the devices to wake when starting + CStdString 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 + CStdString 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_configuration.bUseTVMenuLanguage = GetSettingBool("use_tv_menu_language") ? 1 : 0; + m_configuration.bActivateSource = GetSettingBool("activate_source") ? 1 : 0; + m_configuration.bPowerOffScreensaver = GetSettingBool("cec_standby_screensaver") ? 1 : 0; + m_configuration.bPowerOnScreensaver = GetSettingBool("cec_wake_screensaver") ? 1 : 0; + m_configuration.bSendInactiveSource = GetSettingBool("send_inactive_source") ? 1 : 0; + + // read the mutually exclusive boolean settings + int iStandbyAction(GetSettingInt("standby_pc_on_tv_standby")); + m_configuration.bPowerOffOnStandby = iStandbyAction == 13011 ? 1 : 0; + m_configuration.bShutdownOnStandby = iStandbyAction == 13005 ? 1 : 0; + +#if defined(CEC_DOUBLE_TAP_TIMEOUT_MS_OLD) + // double tap prevention timeout in ms. libCEC uses 50ms units for this in 2.2.0, so divide by 50 + m_configuration.iDoubleTapTimeout50Ms = GetSettingInt("double_tap_timeout_ms") / 50; +#else + // backwards compatibility. will be removed once the next major release of libCEC is out + m_configuration.iDoubleTapTimeoutMs = GetSettingInt("double_tap_timeout_ms"); +#endif +} + +void CPeripheralCecAdapter::ReadLogicalAddresses(const CStdString &strString, cec_logical_addresses &addresses) +{ + for (size_t iPtr = 0; iPtr < strString.size(); iPtr++) + { + CStdString 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 string& strSettingName, const string& strAdvancedSettingName) +{ + bool bChanged(false); + + // only update the advanced setting if it was set by the user + if (!GetSettingString(strAdvancedSettingName).empty()) + { + CStdString 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) +{ + CSingleLock 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.WaitMSec(1000); + } + + return powerStatus == CEC_POWER_STATUS_ON; +} + +void CPeripheralCecAdapterUpdateThread::UpdateMenuLanguage(void) +{ + // request the menu language of the TV + if (m_configuration.bUseTVMenuLanguage == 1) + { + CLog::Log(LOGDEBUG, "%s - requesting the menu language of the TV", __FUNCTION__); + cec_menu_language language; + if (m_adapter->m_cecAdapter->GetDeviceMenuLanguage(CECDEVICE_TV, &language)) + m_adapter->SetMenuLanguage(language.language); + else + CLog::Log(LOGDEBUG, "%s - unknown menu language", __FUNCTION__); + } + else + { + CLog::Log(LOGDEBUG, "%s - using TV menu language is disabled", __FUNCTION__); + } +} + +CStdString CPeripheralCecAdapterUpdateThread::UpdateAudioSystemStatus(void) +{ + CStdString 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 + cec_osd_name ampName = m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_AUDIOSYSTEM); + CLog::Log(LOGDEBUG, "%s - CEC capable amplifier found (%s). volume will be controlled on the amp", __FUNCTION__, ampName.name); + strAmpName += StringUtils::Format("%s", ampName.name); + + // set amp present + m_adapter->SetAudioSystemConnected(true); + g_application.SetMute(false); + g_application.SetVolume(VOLUME_MAXIMUM, false); + } + else + { + // set amp present + CLog::Log(LOGDEBUG, "%s - 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 + CStdString strNotification; + cec_osd_name tvName = m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_TV); + strNotification = StringUtils::Format("%s: %s", g_localizeStrings.Get(36016).c_str(), tvName.name); + + CStdString strAmpName = UpdateAudioSystemStatus(); + if (!strAmpName.empty()) + strNotification += StringUtils::Format("- %s", strAmpName.c_str()); + + m_adapter->m_bIsReady = true; + + // and let the gui know that we're done + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strNotification); + + CSingleLock lock(m_critSection); + m_bIsUpdating = false; + return true; +} + +bool CPeripheralCecAdapter::IsRunning(void) const +{ + CSingleLock 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.WaitMSec(500)) + { + if (m_bStop) + return; + // set the new configuration + libcec_configuration configuration; + { + CSingleLock lock(m_critSection); + configuration = m_configuration; + m_bIsUpdating = false; + } + + CLog::Log(LOGDEBUG, "%s - updating the configuration", __FUNCTION__); + bool bConfigSet(m_adapter->m_cecAdapter->SetConfiguration(&configuration)); + // display message: config updated / failed to update + if (!bConfigSet) + CLog::Log(LOGERROR, "%s - 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)); + + { + CSingleLock 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) +{ + CSingleLock lock(m_critSection); + m_bDeviceRemoved = true; +} + +bool CPeripheralCecAdapter::ReopenConnection(void) +{ + // stop running thread + { + CSingleLock lock(m_critSection); + m_iExitCode = EXITCODE_RESTARTAPP; + CAnnouncementManager::Get().RemoveAnnouncer(this); + StopThread(false); + } + StopThread(); + + // reset all members to their defaults + ResetMembers(); + + // reopen the connection + return InitialiseFeature(FEATURE_CEC); +} + +void CPeripheralCecAdapter::ActivateSource(void) +{ + CSingleLock lock(m_critSection); + m_bActiveSourcePending = true; +} + +void CPeripheralCecAdapter::ProcessActivateSource(void) +{ + bool bActivate(false); + + { + CSingleLock lock(m_critSection); + bActivate = m_bActiveSourcePending; + m_bActiveSourcePending = false; + } + + if (bActivate) + m_cecAdapter->SetActiveSource(); +} + +void CPeripheralCecAdapter::StandbyDevices(void) +{ + CSingleLock lock(m_critSection); + m_bStandbyPending = true; +} + +void CPeripheralCecAdapter::ProcessStandbyDevices(void) +{ + bool bStandby(false); + + { + CSingleLock 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_configuration.bSendInactiveSource == 1) + { + CLog::Log(LOGDEBUG, "%s - 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, "%s - putting CEC device on standby...", __FUNCTION__); + StandbyDevices(); + return false; + } + else if (mode == STATE_SWITCH_TOGGLE || mode == STATE_ACTIVATE_SOURCE) + { + CLog::Log(LOGDEBUG, "%s - waking up CEC device...", __FUNCTION__); + ActivateSource(); + return true; + } + + return false; +} + +#endif diff --git a/src/peripherals/devices/PeripheralCecAdapter.h b/src/peripherals/devices/PeripheralCecAdapter.h new file mode 100644 index 0000000000..0809b038fd --- /dev/null +++ b/src/peripherals/devices/PeripheralCecAdapter.h @@ -0,0 +1,209 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" + +#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) {} + }; +} + +#else + +#include "PeripheralHID.h" +#include "interfaces/AnnouncementManager.h" +#include "threads/Thread.h" +#include "threads/CriticalSection.h" +#include <queue> + +// undefine macro isset, it collides with function in cectypes.h +#ifdef isset +#undef isset +#endif +#include <libcec/cectypes.h> + +class DllLibCEC; + +namespace CEC +{ + class ICECAdapter; +}; + +namespace PERIPHERALS +{ + class CPeripheralCecAdapterUpdateThread; + + 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; + + public: + CPeripheralCecAdapter(const PeripheralScanResult& scanResult); + virtual ~CPeripheralCecAdapter(void); + + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data); + + // audio control + bool HasAudioControl(void); + void VolumeUp(void); + void VolumeDown(void); + void ToggleMute(void); + bool IsMuted(void); + + // CPeripheral callbacks + void OnSettingChanged(const CStdString &strChangedSetting); + void OnDeviceRemoved(void); + + // 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); + void ResetMembers(void); + void Process(void); + bool IsRunning(void) const; + + bool OpenConnection(void); + bool ReopenConnection(void); + + void SetConfigurationFromSettings(void); + void SetConfigurationFromLibCEC(const CEC::libcec_configuration &config); + void SetVersionInfo(const CEC::libcec_configuration &configuration); + + static void ReadLogicalAddresses(const CStdString &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); + + // callbacks from libCEC + static int CecLogMessage(void *cbParam, const CEC::cec_log_message message); + static int CecCommand(void *cbParam, const CEC::cec_command command); + static int CecConfiguration(void *cbParam, const CEC::libcec_configuration config); + static int 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 int CecKeyPress(void *cbParam, const CEC::cec_keypress key); + + DllLibCEC* m_dll; + CEC::ICECAdapter* m_cecAdapter; + bool m_bStarted; + bool m_bHasButton; + bool m_bIsReady; + bool m_bHasConnectedAudioSystem; + CStdString m_strMenuLanguage; + CDateTime m_standbySent; + std::vector<CecButtonPress> m_buttonQueue; + CecButtonPress m_currentButton; + std::queue<CecVolumeChange> m_volumeChangeQueue; + unsigned int 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; + 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; + CStdString m_strComPort; + }; + + class CPeripheralCecAdapterUpdateThread : public CThread + { + public: + CPeripheralCecAdapterUpdateThread(CPeripheralCecAdapter *adapter, CEC::libcec_configuration *configuration); + virtual ~CPeripheralCecAdapterUpdateThread(void); + + void Signal(void); + bool UpdateConfiguration(CEC::libcec_configuration *configuration); + + protected: + void UpdateMenuLanguage(void); + CStdString UpdateAudioSystemStatus(void); + bool WaitReady(void); + bool SetInitialConfiguration(void); + void Process(void); + + 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; + }; +} + +#endif diff --git a/src/peripherals/devices/PeripheralDisk.cpp b/src/peripherals/devices/PeripheralDisk.cpp new file mode 100644 index 0000000000..f64668a366 --- /dev/null +++ b/src/peripherals/devices/PeripheralDisk.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralDisk.h" +#include "guilib/LocalizeStrings.h" + +using namespace PERIPHERALS; + +CPeripheralDisk::CPeripheralDisk(const PeripheralScanResult& scanResult) : + CPeripheral(scanResult) +{ + m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35003) : scanResult.m_strDeviceName; + m_features.push_back(FEATURE_DISK); +} diff --git a/src/peripherals/devices/PeripheralDisk.h b/src/peripherals/devices/PeripheralDisk.h new file mode 100644 index 0000000000..432b7f7d96 --- /dev/null +++ b/src/peripherals/devices/PeripheralDisk.h @@ -0,0 +1,32 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Peripheral.h" + +namespace PERIPHERALS +{ + class CPeripheralDisk : public CPeripheral + { + public: + CPeripheralDisk(const PeripheralScanResult& scanResult); + virtual ~CPeripheralDisk(void) {}; + }; +} diff --git a/src/peripherals/devices/PeripheralHID.cpp b/src/peripherals/devices/PeripheralHID.cpp new file mode 100644 index 0000000000..ed1976b48d --- /dev/null +++ b/src/peripherals/devices/PeripheralHID.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralHID.h" +#include "utils/log.h" +#include "guilib/LocalizeStrings.h" +#include "input/ButtonTranslator.h" + +using namespace PERIPHERALS; +using namespace std; + +CPeripheralHID::CPeripheralHID(const PeripheralScanResult& scanResult) : + CPeripheral(scanResult) +{ + 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, "%s - switching active keymapping to: default", __FUNCTION__); + CButtonTranslator::GetInstance().RemoveDevice(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%sp%s", 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, "%s - adding keymapping for: %s", __FUNCTION__, m_strKeymap.c_str()); + CButtonTranslator::GetInstance().AddDevice(m_strKeymap); + } + else if (!bKeymapEnabled) + { + CLog::Log(LOGDEBUG, "%s - removing keymapping for: %s", __FUNCTION__, m_strKeymap.c_str()); + CButtonTranslator::GetInstance().RemoveDevice(m_strKeymap); + } + } + + CLog::Log(LOGDEBUG, "%s - initialised HID device (%s:%s)", __FUNCTION__, m_strVendorId.c_str(), m_strProductId.c_str()); + } + + return CPeripheral::InitialiseFeature(feature); +} + +void CPeripheralHID::OnSettingChanged(const CStdString &strChangedSetting) +{ + if (m_bInitialised && ((strChangedSetting.Equals("keymap") && !GetSettingBool("do_not_use_custom_keymap")) || strChangedSetting.Equals("keymap_enabled"))) + { + m_bInitialised = false; + InitialiseFeature(FEATURE_HID); + } +} + diff --git a/src/peripherals/devices/PeripheralHID.h b/src/peripherals/devices/PeripheralHID.h new file mode 100644 index 0000000000..bc9ea69f56 --- /dev/null +++ b/src/peripherals/devices/PeripheralHID.h @@ -0,0 +1,39 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Peripheral.h" +#include "input/XBMC_keyboard.h" + +namespace PERIPHERALS +{ + class CPeripheralHID : public CPeripheral + { + public: + CPeripheralHID(const PeripheralScanResult& scanResult); + virtual ~CPeripheralHID(void); + virtual bool InitialiseFeature(const PeripheralFeature feature); + virtual bool LookupSymAndUnicode(XBMC_keysym &keysym, uint8_t *key, char *unicode) { return false; } + virtual void OnSettingChanged(const CStdString &strChangedSetting); + + protected: + CStdString m_strKeymap; + }; +} diff --git a/src/peripherals/devices/PeripheralImon.cpp b/src/peripherals/devices/PeripheralImon.cpp new file mode 100644 index 0000000000..d42d647589 --- /dev/null +++ b/src/peripherals/devices/PeripheralImon.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralImon.h" +#include "utils/log.h" +#include "guilib/LocalizeStrings.h" +#include "settings/Settings.h" +#include "threads/Atomics.h" +#if defined (TARGET_WINDOWS) +#include "system.h" // For HAS_SDL_JOYSTICK +#if defined (HAS_SDL_JOYSTICK) +#include "input/windows/WINJoystick.h" +#endif // HAS_SDL_JOYSTICK +#endif // TARGET_WINDOWS + + +using namespace PERIPHERALS; +using namespace std; + +volatile long CPeripheralImon::m_lCountOfImonsConflictWithDInput = 0; + + +CPeripheralImon::CPeripheralImon(const PeripheralScanResult& scanResult) : + CPeripheralHID(scanResult) +{ + m_features.push_back(FEATURE_IMON); + m_bImonConflictsWithDInput = false; +} + +void CPeripheralImon::OnDeviceRemoved() +{ + if (m_bImonConflictsWithDInput) + { + if (AtomicDecrement(&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) + { + AtomicIncrement(&m_lCountOfImonsConflictWithDInput); + ActionOnImonConflict(true); + } + return CPeripheral::InitialiseFeature(feature); + } + + return CPeripheralHID::InitialiseFeature(feature); +} + +void CPeripheralImon::AddSetting(const CStdString &strKey, 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 CStdString &strChangedSetting) +{ + if (strChangedSetting.compare("disable_winjoystick") == 0) + { + if (m_bImonConflictsWithDInput && !GetSettingBool("disable_winjoystick")) + { + m_bImonConflictsWithDInput = false; + if (AtomicDecrement(&m_lCountOfImonsConflictWithDInput) == 0) + ActionOnImonConflict(false); + } + else if(!m_bImonConflictsWithDInput && GetSettingBool("disable_winjoystick")) + { + m_bImonConflictsWithDInput = true; + AtomicIncrement(&m_lCountOfImonsConflictWithDInput); + ActionOnImonConflict(true); + } + } +} + +void CPeripheralImon::ActionOnImonConflict(bool deviceInserted /*= true*/) +{ + if (deviceInserted || m_lCountOfImonsConflictWithDInput == 0) + { +#if defined(TARGET_WINDOWS) && defined (HAS_SDL_JOYSTICK) + bool enableJoystickNow = !deviceInserted && CSettings::Get().GetBool("input.enablejoystick"); + CLog::Log(LOGNOTICE, "Problematic iMON hardware %s. Joystick usage: %s", (deviceInserted ? "detected" : "was removed"), + (enableJoystickNow) ? "enabled." : "disabled." ); + g_Joystick.SetEnabled(enableJoystickNow); +#endif + } +} + diff --git a/src/peripherals/devices/PeripheralImon.h b/src/peripherals/devices/PeripheralImon.h new file mode 100644 index 0000000000..d73ac8f4f5 --- /dev/null +++ b/src/peripherals/devices/PeripheralImon.h @@ -0,0 +1,47 @@ +#pragma once +/* + * Copyright (C) 2012-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralHID.h" + +class CSetting; + +namespace PERIPHERALS +{ + class CPeripheralImon : public CPeripheralHID + { + public: + CPeripheralImon(const PeripheralScanResult& scanResult); + virtual ~CPeripheralImon(void) {} + virtual bool InitialiseFeature(const PeripheralFeature feature); + virtual void OnSettingChanged(const CStdString &strChangedSetting); + virtual void OnDeviceRemoved(); + virtual void AddSetting(const CStdString &strKey, const CSetting *setting, int order); + inline bool IsImonConflictsWithDInput() + { return m_bImonConflictsWithDInput;} + static inline long GetCountOfImonsConflictWithDInput() + { return m_lCountOfImonsConflictWithDInput; } + static void ActionOnImonConflict(bool deviceInserted = true); + + private: + bool m_bImonConflictsWithDInput; + static volatile long m_lCountOfImonsConflictWithDInput; + }; +} diff --git a/src/peripherals/devices/PeripheralNIC.cpp b/src/peripherals/devices/PeripheralNIC.cpp new file mode 100644 index 0000000000..0e413aa8d5 --- /dev/null +++ b/src/peripherals/devices/PeripheralNIC.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralNIC.h" +#include "utils/log.h" +#include "guilib/LocalizeStrings.h" + +using namespace PERIPHERALS; +using namespace std; + +CPeripheralNIC::CPeripheralNIC(const PeripheralScanResult& scanResult) : + CPeripheral(scanResult) +{ + m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35002) : scanResult.m_strDeviceName; + m_features.push_back(FEATURE_NIC); +} diff --git a/src/peripherals/devices/PeripheralNIC.h b/src/peripherals/devices/PeripheralNIC.h new file mode 100644 index 0000000000..e31e7f1021 --- /dev/null +++ b/src/peripherals/devices/PeripheralNIC.h @@ -0,0 +1,32 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Peripheral.h" + +namespace PERIPHERALS +{ + class CPeripheralNIC : public CPeripheral + { + public: + CPeripheralNIC(const PeripheralScanResult& scanResult); + virtual ~CPeripheralNIC(void) {}; + }; +} diff --git a/src/peripherals/devices/PeripheralNyxboard.cpp b/src/peripherals/devices/PeripheralNyxboard.cpp new file mode 100644 index 0000000000..a66bd01241 --- /dev/null +++ b/src/peripherals/devices/PeripheralNyxboard.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralNyxboard.h" +#include "PeripheralHID.h" +#include "guilib/Key.h" +#include "utils/log.h" +#include "Application.h" + +using namespace PERIPHERALS; +using namespace std; + +CPeripheralNyxboard::CPeripheralNyxboard(const PeripheralScanResult& scanResult) : + CPeripheralHID(scanResult) +{ + m_features.push_back(FEATURE_NYXBOARD); +} + +bool CPeripheralNyxboard::LookupSymAndUnicode(XBMC_keysym &keysym, uint8_t *key, char *unicode) +{ + CStdString strCommand; + if (keysym.sym == XBMCK_F7 && keysym.mod == XBMCKMOD_NONE && GetSettingBool("enable_flip_commands")) + { + /* switched to keyboard side */ + CLog::Log(LOGDEBUG, "%s - 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, "%s - switched to remote side", __FUNCTION__); + strCommand = GetSettingString("flip_remote"); + } + + if (!strCommand.empty()) + { + CLog::Log(LOGDEBUG, "%s - executing command '%s'", __FUNCTION__, strCommand.c_str()); + if (g_application.ExecuteXBMCAction(strCommand)) + { + *key = 0; + *unicode = (char) 0; + return true; + } + } + + return false; +} diff --git a/src/peripherals/devices/PeripheralNyxboard.h b/src/peripherals/devices/PeripheralNyxboard.h new file mode 100644 index 0000000000..6648e42929 --- /dev/null +++ b/src/peripherals/devices/PeripheralNyxboard.h @@ -0,0 +1,33 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralHID.h" + +namespace PERIPHERALS +{ + class CPeripheralNyxboard : public CPeripheralHID + { + public: + CPeripheralNyxboard(const PeripheralScanResult& scanResult); + virtual ~CPeripheralNyxboard(void) {}; + virtual bool LookupSymAndUnicode(XBMC_keysym &keysym, uint8_t *key, char *unicode); + }; +} diff --git a/src/peripherals/devices/PeripheralTuner.cpp b/src/peripherals/devices/PeripheralTuner.cpp new file mode 100644 index 0000000000..65504b0646 --- /dev/null +++ b/src/peripherals/devices/PeripheralTuner.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PeripheralTuner.h" +#include "utils/log.h" + +using namespace PERIPHERALS; + +CPeripheralTuner::CPeripheralTuner(const PeripheralScanResult& scanResult) : + CPeripheral(scanResult) +{ + m_features.push_back(FEATURE_TUNER); +} diff --git a/src/peripherals/devices/PeripheralTuner.h b/src/peripherals/devices/PeripheralTuner.h new file mode 100644 index 0000000000..8749378de0 --- /dev/null +++ b/src/peripherals/devices/PeripheralTuner.h @@ -0,0 +1,32 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Peripheral.h" + +namespace PERIPHERALS +{ + class CPeripheralTuner : public CPeripheral + { + public: + CPeripheralTuner(const PeripheralScanResult& scanResult); + virtual ~CPeripheralTuner(void) {}; + }; +} diff --git a/src/peripherals/dialogs/GUIDialogPeripheralManager.cpp b/src/peripherals/dialogs/GUIDialogPeripheralManager.cpp new file mode 100644 index 0000000000..16b4aed84a --- /dev/null +++ b/src/peripherals/dialogs/GUIDialogPeripheralManager.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GUIDialogPeripheralManager.h" +#include "GUIDialogPeripheralSettings.h" +#include "guilib/GUIWindowManager.h" +#include "peripherals/Peripherals.h" +#include "FileItem.h" +#include "guilib/Key.h" +#include "utils/log.h" + +#define BUTTON_CLOSE 10 +#define BUTTON_SETTINGS 11 +#define CONTROL_LIST 20 + +using namespace std; +using namespace PERIPHERALS; + +CGUIDialogPeripheralManager::CGUIDialogPeripheralManager(void) : + CGUIDialog(WINDOW_DIALOG_PERIPHERAL_MANAGER, "DialogPeripheralManager.xml"), + m_iSelected(0), + m_peripheralItems(new CFileItemList) +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogPeripheralManager::~CGUIDialogPeripheralManager(void) +{ + delete m_peripheralItems; +} + +bool CGUIDialogPeripheralManager::OnAction(const CAction &action) +{ + int iActionId = action.GetID(); + if (GetFocusedControlID() == CONTROL_LIST && + (iActionId == ACTION_MOVE_DOWN || iActionId == ACTION_MOVE_UP || + iActionId == ACTION_PAGE_DOWN || iActionId == ACTION_PAGE_UP)) + { + CGUIDialog::OnAction(action); + int iSelected = m_viewControl.GetSelectedItem(); + if (iSelected != m_iSelected) + m_iSelected = iSelected; + UpdateButtons(); + return true; + } + + return CGUIDialog::OnAction(action); +} + +void CGUIDialogPeripheralManager::OnInitWindow() +{ + m_iSelected = 0; + Update(); + CGUIDialog::OnInitWindow(); +} + +bool CGUIDialogPeripheralManager::OnClickList(CGUIMessage &message) +{ + if (CurrentItemHasSettings()) + return OpenSettingsDialog(); + + return true; +} + +bool CGUIDialogPeripheralManager::OnClickButtonClose(CGUIMessage &message) +{ + Close(); + return true; +} + +bool CGUIDialogPeripheralManager::OnClickButtonSettings(CGUIMessage &message) +{ + return OpenSettingsDialog(); +} + +bool CGUIDialogPeripheralManager::OpenSettingsDialog(void) +{ + CGUIDialogPeripheralSettings *dialog = (CGUIDialogPeripheralSettings *)g_windowManager.GetWindow(WINDOW_DIALOG_PERIPHERAL_SETTINGS); + if (dialog) + { + dialog->SetFileItem(GetCurrentListItem().get()); + dialog->DoModal(); + return true; + } + + return false; +} + +bool CGUIDialogPeripheralManager::OnMessageClick(CGUIMessage &message) +{ + int iControl = message.GetSenderId(); + switch(iControl) + { + case CONTROL_LIST: + return OnClickList(message); + case BUTTON_CLOSE: + return OnClickButtonClose(message); + case BUTTON_SETTINGS: + return OnClickButtonSettings(message); + default: + return false; + } +} + +bool CGUIDialogPeripheralManager::OnMessage(CGUIMessage& message) +{ + unsigned int iMessage = message.GetMessage(); + + switch (iMessage) + { + case GUI_MSG_WINDOW_DEINIT: + Clear(); + break; + case GUI_MSG_ITEM_SELECT: + return true; + case GUI_MSG_CLICKED: + return OnMessageClick(message); + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPeripheralManager::OnWindowLoaded(void) +{ + CGUIDialog::OnWindowLoaded(); + + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + const CGUIControl *list = GetControl(CONTROL_LIST); + m_viewControl.AddView(list); +} + +void CGUIDialogPeripheralManager::OnWindowUnload(void) +{ + CGUIDialog::OnWindowUnload(); + m_viewControl.Reset(); +} + +CFileItemPtr CGUIDialogPeripheralManager::GetCurrentListItem(void) const +{ + return m_peripheralItems->Get(m_iSelected); +} + +void CGUIDialogPeripheralManager::Update() +{ + CSingleLock lock(g_graphicsContext); + + m_viewControl.SetCurrentView(CONTROL_LIST); + Clear(); + g_peripherals.GetDirectory("peripherals://all/", *m_peripheralItems); + m_viewControl.SetItems(*m_peripheralItems); + m_viewControl.SetSelectedItem(m_iSelected); + + UpdateButtons(); + CGUIControl *list = (CGUIControl *) GetControl(CONTROL_LIST); + if (list) + list->SetInvalid(); +} + +void CGUIDialogPeripheralManager::Clear(void) +{ + m_viewControl.Clear(); + m_peripheralItems->Clear(); +} + +bool CGUIDialogPeripheralManager::CurrentItemHasSettings(void) const +{ + CSingleLock lock(g_graphicsContext); + CFileItemPtr currentItem = GetCurrentListItem(); + if (!currentItem) + return false; + + CPeripheral *peripheral = g_peripherals.GetByPath(currentItem.get()->GetPath()); + return peripheral && peripheral->HasConfigurableSettings(); +} + +void CGUIDialogPeripheralManager::UpdateButtons(void) +{ + CONTROL_ENABLE_ON_CONDITION(BUTTON_SETTINGS, CurrentItemHasSettings()); +} diff --git a/src/peripherals/dialogs/GUIDialogPeripheralManager.h b/src/peripherals/dialogs/GUIDialogPeripheralManager.h new file mode 100644 index 0000000000..1ee107c099 --- /dev/null +++ b/src/peripherals/dialogs/GUIDialogPeripheralManager.h @@ -0,0 +1,58 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "guilib/GUIDialog.h" +#include "view/GUIViewControl.h" + +namespace PERIPHERALS +{ + class CGUIDialogPeripheralManager : public CGUIDialog + { + public: + CGUIDialogPeripheralManager(void); + virtual ~CGUIDialogPeripheralManager(void); + virtual bool OnMessage(CGUIMessage& message); + virtual bool OnAction(const CAction& action); + virtual void OnInitWindow(); + virtual void OnWindowLoaded(void); + virtual void OnWindowUnload(void); + virtual bool HasListItems() const { return true; }; + virtual CFileItemPtr GetCurrentListItem(void) const; + virtual void Update(void); + + protected: + virtual bool OnMessageClick(CGUIMessage &message); + + virtual bool OnClickList(CGUIMessage &message); + virtual bool OnClickButtonClose(CGUIMessage &message); + virtual bool OnClickButtonSettings(CGUIMessage &message); + virtual bool OpenSettingsDialog(void); + virtual bool CurrentItemHasSettings(void) const; + + private: + void Clear(void); + void UpdateButtons(void); + + int m_iSelected; + CFileItemList* m_peripheralItems; + CGUIViewControl m_viewControl; + }; +} diff --git a/src/peripherals/dialogs/GUIDialogPeripheralSettings.cpp b/src/peripherals/dialogs/GUIDialogPeripheralSettings.cpp new file mode 100644 index 0000000000..4e86cdd056 --- /dev/null +++ b/src/peripherals/dialogs/GUIDialogPeripheralSettings.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GUIDialogPeripheralSettings.h" +#include "FileItem.h" +#include "addons/Skin.h" +#include "dialogs/GUIDialogYesNo.h" +#include "peripherals/Peripherals.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingSection.h" +#include "utils/log.h" + +#define CONTROL_BUTTON_DEFAULTS 50 + +using namespace std; +using namespace PERIPHERALS; + +CGUIDialogPeripheralSettings::CGUIDialogPeripheralSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_PERIPHERAL_SETTINGS, "DialogPeripheralSettings.xml"), + m_item(NULL), + m_initialising(false) +{ } + +CGUIDialogPeripheralSettings::~CGUIDialogPeripheralSettings() +{ + if (m_item != NULL) + delete m_item; + + m_settingsMap.clear(); +} + +bool CGUIDialogPeripheralSettings::OnMessage(CGUIMessage &message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED && + message.GetSenderId() == CONTROL_BUTTON_DEFAULTS) + { + OnResetSettings(); + return true; + } + + return CGUIDialogSettingsManualBase::OnMessage(message); +} + +void CGUIDialogPeripheralSettings::SetFileItem(const CFileItem *item) +{ + if (item == NULL) + return; + + if (m_item != NULL) + delete m_item; + + m_item = new CFileItem(*item); +} + +void CGUIDialogPeripheralSettings::OnSettingChanged(const CSetting *setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + // we need to copy the new value of the setting from the copy to the + // original setting + std::map<std::string, CSetting*>::iterator itSetting = m_settingsMap.find(setting->GetId()); + if (itSetting == m_settingsMap.end()) + return; + + itSetting->second->FromString(setting->ToString()); +} + +void CGUIDialogPeripheralSettings::Save() +{ + if (m_item == NULL || m_initialising) + return; + + CPeripheral *peripheral = g_peripherals.GetByPath(m_item->GetPath()); + if (peripheral == NULL) + return; + + peripheral->PersistSettings(); +} + +void CGUIDialogPeripheralSettings::OnResetSettings() +{ + if (m_item == NULL) + return; + + CPeripheral *peripheral = g_peripherals.GetByPath(m_item->GetPath()); + if (peripheral == NULL) + return; + + if (!CGUIDialogYesNo::ShowAndGetInput(10041, 0, 10042, 0)) + return; + + // reset the settings in the peripheral + peripheral->ResetDefaultSettings(); + + // re-create all settings and their controls + SetupView(); +} + +void CGUIDialogPeripheralSettings::InitializeSettings() +{ + if (m_item == NULL) + { + m_initialising = false; + return; + } + + m_initialising = true; + bool usePopup = g_SkinInfo->HasSkinFile("DialogSlider.xml"); + + CPeripheral *peripheral = g_peripherals.GetByPath(m_item->GetPath()); + if (peripheral == NULL) + { + CLog::Log(LOGDEBUG, "%s - no peripheral", __FUNCTION__); + m_initialising = false; + return; + } + + m_settingsMap.clear(); + CGUIDialogSettingsManualBase::InitializeSettings(); + + CSettingCategory *category = AddCategory("peripheralsettings", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogPeripheralSettings: unable to setup settings"); + return; + } + + CSettingGroup *group = AddGroup(category); + if (group == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogPeripheralSettings: unable to setup settings"); + return; + } + + vector<CSetting*> settings = peripheral->GetSettings(); + for (vector<CSetting*>::iterator itSetting = settings.begin(); itSetting != settings.end(); ++itSetting) + { + CSetting *setting = *itSetting; + if (setting == NULL) + continue; + + if (!setting->IsVisible()) + { + CLog::Log(LOGDEBUG, "%s - invisible", __FUNCTION__); + continue; + } + + // we need to create a copy of the setting because the CSetting instances + // are destroyed when leaving the dialog + CSetting *settingCopy = NULL; + switch(setting->GetType()) + { + case SettingTypeBool: + { + CSettingBool *settingBool = new CSettingBool(setting->GetId(), *static_cast<CSettingBool*>(setting)); + settingBool->SetControl(GetCheckmarkControl()); + + settingCopy = static_cast<CSetting*>(settingBool); + break; + } + + case SettingTypeInteger: + { + CSettingInt *intSetting = static_cast<CSettingInt*>(setting); + if (intSetting == NULL) + break; + + CSettingInt *settingInt = new CSettingInt(setting->GetId(), *intSetting); + if (settingInt->GetOptions().empty()) + settingInt->SetControl(GetSliderControl("integer", false, -1, usePopup, -1, "%i")); + else + settingInt->SetControl(GetSpinnerControl("string")); + + settingCopy = static_cast<CSetting*>(settingInt); + break; + } + + case SettingTypeNumber: + { + CSettingNumber *settingNumber = new CSettingNumber(setting->GetId(), *static_cast<CSettingNumber*>(setting)); + settingNumber->SetControl(GetSliderControl("number", false, -1, usePopup, -1, "%2.2f")); + + settingCopy = static_cast<CSetting*>(settingNumber); + break; + } + + case SettingTypeString: + { + CSettingString *settingString = new CSettingString(setting->GetId(), *static_cast<CSettingString*>(setting)); + settingString->SetControl(GetEditControl("string")); + + settingCopy = static_cast<CSetting*>(settingString); + break; + } + + default: + // TODO: add more types if needed + CLog::Log(LOGDEBUG, "%s - unknown type", __FUNCTION__); + break; + } + + if (settingCopy != NULL && settingCopy->GetControl() != NULL) + { + settingCopy->SetLevel(SettingLevelBasic); + group->AddSetting(settingCopy); + m_settingsMap.insert(std::make_pair(setting->GetId(), setting)); + } + } + + m_initialising = false; +} diff --git a/src/peripherals/dialogs/GUIDialogPeripheralSettings.h b/src/peripherals/dialogs/GUIDialogPeripheralSettings.h new file mode 100644 index 0000000000..373bd5bfbc --- /dev/null +++ b/src/peripherals/dialogs/GUIDialogPeripheralSettings.h @@ -0,0 +1,55 @@ +#pragma once +/* + * Copyright (C) 2005-2014 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +class CFileItem; + +namespace PERIPHERALS +{ +class CGUIDialogPeripheralSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogPeripheralSettings(); + virtual ~CGUIDialogPeripheralSettings(); + + // specializations of CGUIControl + virtual bool OnMessage(CGUIMessage &message); + + virtual void SetFileItem(const CFileItem *item); + +protected: + // implementations of ISettingCallback + virtual void OnSettingChanged(const CSetting *setting); + + // specialization of CGUIDialogSettingsBase + virtual bool AllowResettingSettings() const { return false; } + virtual void Save(); + virtual void OnResetSettings(); + + // specialization of CGUIDialogSettingsManualBase + virtual void InitializeSettings(); + + CFileItem *m_item; + bool m_initialising; + std::map<std::string, CSetting*> m_settingsMap; +}; +} diff --git a/src/peripherals/dialogs/Makefile b/src/peripherals/dialogs/Makefile new file mode 100644 index 0000000000..4aa6331efd --- /dev/null +++ b/src/peripherals/dialogs/Makefile @@ -0,0 +1,7 @@ +SRCS=GUIDialogPeripheralSettings.cpp \ + GUIDialogPeripheralManager.cpp + +LIB=peripheral-dialogs.a + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) |