summaryrefslogtreecommitdiffstats
path: root/xbmc/peripherals
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/peripherals
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/peripherals')
-rw-r--r--xbmc/peripherals/CMakeLists.txt13
-rw-r--r--xbmc/peripherals/EventLockHandle.cpp20
-rw-r--r--xbmc/peripherals/EventLockHandle.h48
-rw-r--r--xbmc/peripherals/EventPollHandle.cpp35
-rw-r--r--xbmc/peripherals/EventPollHandle.h68
-rw-r--r--xbmc/peripherals/EventScanner.cpp174
-rw-r--r--xbmc/peripherals/EventScanner.h76
-rw-r--r--xbmc/peripherals/IEventScannerCallback.h20
-rw-r--r--xbmc/peripherals/PeripheralTypes.h370
-rw-r--r--xbmc/peripherals/Peripherals.cpp1030
-rw-r--r--xbmc/peripherals/Peripherals.h371
-rw-r--r--xbmc/peripherals/addons/AddonButtonMap.cpp734
-rw-r--r--xbmc/peripherals/addons/AddonButtonMap.h146
-rw-r--r--xbmc/peripherals/addons/AddonButtonMapping.cpp139
-rw-r--r--xbmc/peripherals/addons/AddonButtonMapping.h72
-rw-r--r--xbmc/peripherals/addons/AddonInputHandling.cpp192
-rw-r--r--xbmc/peripherals/addons/AddonInputHandling.h108
-rw-r--r--xbmc/peripherals/addons/CMakeLists.txt13
-rw-r--r--xbmc/peripherals/addons/PeripheralAddon.cpp990
-rw-r--r--xbmc/peripherals/addons/PeripheralAddon.h178
-rw-r--r--xbmc/peripherals/addons/PeripheralAddonTranslator.cpp449
-rw-r--r--xbmc/peripherals/addons/PeripheralAddonTranslator.h62
-rw-r--r--xbmc/peripherals/bus/CMakeLists.txt6
-rw-r--r--xbmc/peripherals/bus/PeripheralBus.cpp352
-rw-r--r--xbmc/peripherals/bus/PeripheralBus.h221
-rw-r--r--xbmc/peripherals/bus/PeripheralBusUSB.h29
-rw-r--r--xbmc/peripherals/bus/virtual/CMakeLists.txt12
-rw-r--r--xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp502
-rw-r--r--xbmc/peripherals/bus/virtual/PeripheralBusAddon.h94
-rw-r--r--xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp83
-rw-r--r--xbmc/peripherals/bus/virtual/PeripheralBusApplication.h40
-rw-r--r--xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp57
-rw-r--r--xbmc/peripherals/bus/virtual/PeripheralBusCEC.h43
-rw-r--r--xbmc/peripherals/devices/CMakeLists.txt30
-rw-r--r--xbmc/peripherals/devices/Peripheral.cpp771
-rw-r--r--xbmc/peripherals/devices/Peripheral.h306
-rw-r--r--xbmc/peripherals/devices/PeripheralBluetooth.cpp19
-rw-r--r--xbmc/peripherals/devices/PeripheralBluetooth.h23
-rw-r--r--xbmc/peripherals/devices/PeripheralCecAdapter.cpp1861
-rw-r--r--xbmc/peripherals/devices/PeripheralCecAdapter.h226
-rw-r--r--xbmc/peripherals/devices/PeripheralDisk.cpp23
-rw-r--r--xbmc/peripherals/devices/PeripheralDisk.h23
-rw-r--r--xbmc/peripherals/devices/PeripheralHID.cpp86
-rw-r--r--xbmc/peripherals/devices/PeripheralHID.h33
-rw-r--r--xbmc/peripherals/devices/PeripheralImon.cpp90
-rw-r--r--xbmc/peripherals/devices/PeripheralImon.h43
-rw-r--r--xbmc/peripherals/devices/PeripheralJoystick.cpp538
-rw-r--r--xbmc/peripherals/devices/PeripheralJoystick.h150
-rw-r--r--xbmc/peripherals/devices/PeripheralKeyboard.cpp125
-rw-r--r--xbmc/peripherals/devices/PeripheralKeyboard.h52
-rw-r--r--xbmc/peripherals/devices/PeripheralMouse.cpp150
-rw-r--r--xbmc/peripherals/devices/PeripheralMouse.h53
-rw-r--r--xbmc/peripherals/devices/PeripheralNIC.cpp23
-rw-r--r--xbmc/peripherals/devices/PeripheralNIC.h23
-rw-r--r--xbmc/peripherals/devices/PeripheralNyxboard.cpp55
-rw-r--r--xbmc/peripherals/devices/PeripheralNyxboard.h24
-rw-r--r--xbmc/peripherals/devices/PeripheralTuner.cpp19
-rw-r--r--xbmc/peripherals/devices/PeripheralTuner.h23
-rw-r--r--xbmc/peripherals/dialogs/CMakeLists.txt7
-rw-r--r--xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp308
-rw-r--r--xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.h52
-rw-r--r--xbmc/peripherals/dialogs/GUIDialogPeripherals.cpp186
-rw-r--r--xbmc/peripherals/dialogs/GUIDialogPeripherals.h51
63 files changed, 12120 insertions, 0 deletions
diff --git a/xbmc/peripherals/CMakeLists.txt b/xbmc/peripherals/CMakeLists.txt
new file mode 100644
index 0000000..9de62b9
--- /dev/null
+++ b/xbmc/peripherals/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES EventLockHandle.cpp
+ EventPollHandle.cpp
+ EventScanner.cpp
+ Peripherals.cpp)
+
+set(HEADERS EventLockHandle.h
+ EventPollHandle.h
+ EventScanner.h
+ IEventScannerCallback.h
+ Peripherals.h
+ PeripheralTypes.h)
+
+core_add_library(peripherals)
diff --git a/xbmc/peripherals/EventLockHandle.cpp b/xbmc/peripherals/EventLockHandle.cpp
new file mode 100644
index 0000000..b6d2a6d
--- /dev/null
+++ b/xbmc/peripherals/EventLockHandle.cpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "EventLockHandle.h"
+
+using namespace PERIPHERALS;
+
+CEventLockHandle::CEventLockHandle(IEventLockCallback& callback) : m_callback(callback)
+{
+}
+
+CEventLockHandle::~CEventLockHandle(void)
+{
+ m_callback.ReleaseLock(*this);
+}
diff --git a/xbmc/peripherals/EventLockHandle.h b/xbmc/peripherals/EventLockHandle.h
new file mode 100644
index 0000000..7012ea8
--- /dev/null
+++ b/xbmc/peripherals/EventLockHandle.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace PERIPHERALS
+{
+class CEventLockHandle;
+
+/*!
+ * \brief Callback implemented by event scanner
+ */
+class IEventLockCallback
+{
+public:
+ virtual ~IEventLockCallback(void) = default;
+
+ virtual void ReleaseLock(CEventLockHandle& handle) = 0;
+};
+
+/*!
+ * \brief Handle returned by the event scanner to disable event processing
+ *
+ * When held, this disables event processing.
+ */
+class CEventLockHandle
+{
+public:
+ /*!
+ * \brief Create an event lock handle
+ */
+ CEventLockHandle(IEventLockCallback& callback);
+
+ /*!
+ * \brief Handle is automatically released when this class is destructed
+ */
+ ~CEventLockHandle(void);
+
+private:
+ // Construction parameters
+ IEventLockCallback& m_callback;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/EventPollHandle.cpp b/xbmc/peripherals/EventPollHandle.cpp
new file mode 100644
index 0000000..39a2e1a
--- /dev/null
+++ b/xbmc/peripherals/EventPollHandle.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "EventPollHandle.h"
+
+using namespace PERIPHERALS;
+
+CEventPollHandle::CEventPollHandle(IEventPollCallback& callback) : m_callback(callback)
+{
+}
+
+CEventPollHandle::~CEventPollHandle(void)
+{
+ m_callback.Release(*this);
+}
+
+void CEventPollHandle::Activate()
+{
+ m_callback.Activate(*this);
+}
+
+void CEventPollHandle::Deactivate()
+{
+ m_callback.Deactivate(*this);
+}
+
+void CEventPollHandle::HandleEvents(bool bWait)
+{
+ m_callback.HandleEvents(bWait);
+}
diff --git a/xbmc/peripherals/EventPollHandle.h b/xbmc/peripherals/EventPollHandle.h
new file mode 100644
index 0000000..5d99e1e
--- /dev/null
+++ b/xbmc/peripherals/EventPollHandle.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace PERIPHERALS
+{
+class CEventPollHandle;
+
+/*!
+ * \brief Callback implemented by event scanner
+ */
+class IEventPollCallback
+{
+public:
+ virtual ~IEventPollCallback(void) = default;
+
+ virtual void Activate(CEventPollHandle& handle) = 0;
+ virtual void Deactivate(CEventPollHandle& handle) = 0;
+ virtual void HandleEvents(bool bWait) = 0;
+ virtual void Release(CEventPollHandle& handle) = 0;
+};
+
+/*!
+ * \brief Handle returned by the event scanner to control scan timing
+ *
+ * When held, this allows one to control the timing of when events are
+ * handled.
+ */
+class CEventPollHandle
+{
+public:
+ /*!
+ * \brief Create an active polling handle
+ */
+ CEventPollHandle(IEventPollCallback& callback);
+
+ /*!
+ * \brief Handle is automatically released when this class is destructed
+ */
+ ~CEventPollHandle();
+
+ /*!
+ * \brief Activate handle
+ */
+ void Activate();
+
+ /*!
+ * \brief Deactivate handle
+ */
+ void Deactivate();
+
+ /*!
+ * \brief Trigger a scan for events
+ *
+ * \param bWait If true, this blocks until all events are handled
+ */
+ void HandleEvents(bool bWait);
+
+private:
+ IEventPollCallback& m_callback;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/EventScanner.cpp b/xbmc/peripherals/EventScanner.cpp
new file mode 100644
index 0000000..0462a17
--- /dev/null
+++ b/xbmc/peripherals/EventScanner.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "EventScanner.h"
+
+#include "IEventScannerCallback.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace PERIPHERALS;
+
+// Default event scan rate when no polling handles are held
+#define DEFAULT_SCAN_RATE_HZ 60
+
+// Timeout when a polling handle is held but doesn't trigger scan. This reduces
+// input latency when the game is running at < 1/4 speed.
+#define WATCHDOG_TIMEOUT_MS 80
+
+CEventScanner::CEventScanner(IEventScannerCallback& callback)
+ : CThread("PeripEventScan"), m_callback(callback)
+{
+}
+
+void CEventScanner::Start()
+{
+ Create();
+}
+
+void CEventScanner::Stop()
+{
+ StopThread(false);
+ m_scanEvent.Set();
+ StopThread(true);
+}
+
+EventPollHandlePtr CEventScanner::RegisterPollHandle()
+{
+ EventPollHandlePtr handle(new CEventPollHandle(*this));
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_handleMutex);
+ m_activeHandles.insert(handle.get());
+ }
+
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle registered");
+
+ return handle;
+}
+
+void CEventScanner::Activate(CEventPollHandle& handle)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_handleMutex);
+ m_activeHandles.insert(&handle);
+ }
+
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle activated");
+}
+
+void CEventScanner::Deactivate(CEventPollHandle& handle)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_handleMutex);
+ m_activeHandles.erase(&handle);
+ }
+
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle deactivated");
+}
+
+void CEventScanner::HandleEvents(bool bWait)
+{
+ if (bWait)
+ {
+ std::unique_lock<CCriticalSection> lock(m_pollMutex);
+
+ m_scanFinishedEvent.Reset();
+ m_scanEvent.Set();
+ m_scanFinishedEvent.Wait();
+ }
+ else
+ {
+ m_scanEvent.Set();
+ }
+}
+
+void CEventScanner::Release(CEventPollHandle& handle)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_handleMutex);
+ m_activeHandles.erase(&handle);
+ }
+
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Event poll handle released");
+}
+
+EventLockHandlePtr CEventScanner::RegisterLock()
+{
+ EventLockHandlePtr handle(new CEventLockHandle(*this));
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_lockMutex);
+ m_activeLocks.insert(handle.get());
+ }
+
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Event lock handle registered");
+
+ return handle;
+}
+
+void CEventScanner::ReleaseLock(CEventLockHandle& handle)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_lockMutex);
+ m_activeLocks.erase(&handle);
+ }
+
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Event lock handle released");
+}
+
+void CEventScanner::Process()
+{
+ auto nextScan = std::chrono::steady_clock::now();
+
+ while (!m_bStop)
+ {
+ {
+ std::unique_lock<CCriticalSection> lock(m_lockMutex);
+ if (m_activeLocks.empty())
+ m_callback.ProcessEvents();
+ }
+
+ m_scanFinishedEvent.Set();
+
+ auto now = std::chrono::steady_clock::now();
+ auto scanIntervalMs = GetScanIntervalMs();
+
+ // Handle wrap-around
+ if (now < nextScan)
+ nextScan = now;
+
+ while (nextScan <= now)
+ nextScan += scanIntervalMs;
+
+ auto waitTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(nextScan - now);
+
+ if (!m_bStop && waitTimeMs.count() > 0)
+ m_scanEvent.Wait(waitTimeMs);
+ }
+}
+
+std::chrono::milliseconds CEventScanner::GetScanIntervalMs() const
+{
+ bool bHasActiveHandle;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_handleMutex);
+ bHasActiveHandle = !m_activeHandles.empty();
+ }
+
+ if (!bHasActiveHandle)
+ {
+ // this truncates to 16 (from 16.666) should it round up to 17 using std::nearbyint or should we use nanoseconds?
+ return std::chrono::milliseconds(static_cast<uint32_t>(1000.0 / DEFAULT_SCAN_RATE_HZ));
+ }
+ else
+ return std::chrono::milliseconds(WATCHDOG_TIMEOUT_MS);
+}
diff --git a/xbmc/peripherals/EventScanner.h b/xbmc/peripherals/EventScanner.h
new file mode 100644
index 0000000..0092278
--- /dev/null
+++ b/xbmc/peripherals/EventScanner.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "EventLockHandle.h"
+#include "EventPollHandle.h"
+#include "PeripheralTypes.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/Thread.h"
+
+#include <chrono>
+#include <set>
+
+namespace PERIPHERALS
+{
+class IEventScannerCallback;
+
+/*!
+ * \brief Class to scan for peripheral events
+ *
+ * By default, a rate of 60 Hz is used. A client can obtain control over when
+ * input is handled by registering for a polling handle.
+ */
+class CEventScanner : public IEventPollCallback, public IEventLockCallback, protected CThread
+{
+public:
+ explicit CEventScanner(IEventScannerCallback& callback);
+
+ ~CEventScanner() override = default;
+
+ void Start();
+ void Stop();
+
+ EventPollHandlePtr RegisterPollHandle();
+
+ /*!
+ * \brief Acquire a lock that prevents event processing while held
+ */
+ EventLockHandlePtr RegisterLock();
+
+ // implementation of IEventPollCallback
+ void Activate(CEventPollHandle& handle) override;
+ void Deactivate(CEventPollHandle& handle) override;
+ void HandleEvents(bool bWait) override;
+ void Release(CEventPollHandle& handle) override;
+
+ // implementation of IEventLockCallback
+ void ReleaseLock(CEventLockHandle& handle) override;
+
+protected:
+ // implementation of CThread
+ void Process() override;
+
+private:
+ std::chrono::milliseconds GetScanIntervalMs() const;
+
+ // Construction parameters
+ IEventScannerCallback& m_callback;
+
+ // Event parameters
+ std::set<void*> m_activeHandles;
+ std::set<void*> m_activeLocks;
+ CEvent m_scanEvent;
+ CEvent m_scanFinishedEvent;
+ mutable CCriticalSection m_handleMutex;
+ CCriticalSection m_lockMutex;
+ CCriticalSection m_pollMutex; // Prevent two poll handles from polling at once
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/IEventScannerCallback.h b/xbmc/peripherals/IEventScannerCallback.h
new file mode 100644
index 0000000..d017273
--- /dev/null
+++ b/xbmc/peripherals/IEventScannerCallback.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace PERIPHERALS
+{
+class IEventScannerCallback
+{
+public:
+ virtual ~IEventScannerCallback(void) = default;
+
+ virtual void ProcessEvents(void) = 0;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/PeripheralTypes.h b/xbmc/peripherals/PeripheralTypes.h
new file mode 100644
index 0000000..425ac0f
--- /dev/null
+++ b/xbmc/peripherals/PeripheralTypes.h
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <stdio.h>
+#include <string>
+#include <vector>
+#ifdef TARGET_WINDOWS
+#include "PlatformDefs.h"
+#endif
+#include "utils/StringUtils.h"
+
+class CSetting;
+
+namespace PERIPHERALS
+{
+enum PeripheralBusType
+{
+ PERIPHERAL_BUS_UNKNOWN = 0,
+ PERIPHERAL_BUS_USB,
+ PERIPHERAL_BUS_PCI,
+ PERIPHERAL_BUS_CEC,
+ PERIPHERAL_BUS_ADDON,
+#ifdef TARGET_ANDROID
+ PERIPHERAL_BUS_ANDROID,
+#endif
+#if defined(TARGET_DARWIN)
+ PERIPHERAL_BUS_GCCONTROLLER,
+#endif
+ PERIPHERAL_BUS_APPLICATION,
+};
+
+enum PeripheralFeature
+{
+ FEATURE_UNKNOWN = 0,
+ FEATURE_HID,
+ FEATURE_NIC,
+ FEATURE_DISK,
+ FEATURE_NYXBOARD,
+ FEATURE_CEC,
+ FEATURE_BLUETOOTH,
+ FEATURE_TUNER,
+ FEATURE_IMON,
+ FEATURE_JOYSTICK,
+ FEATURE_RUMBLE,
+ FEATURE_POWER_OFF,
+ FEATURE_KEYBOARD,
+ FEATURE_MOUSE,
+};
+
+enum PeripheralType
+{
+ PERIPHERAL_UNKNOWN = 0,
+ PERIPHERAL_HID,
+ PERIPHERAL_NIC,
+ PERIPHERAL_DISK,
+ PERIPHERAL_NYXBOARD,
+ PERIPHERAL_CEC,
+ PERIPHERAL_BLUETOOTH,
+ PERIPHERAL_TUNER,
+ PERIPHERAL_IMON,
+ PERIPHERAL_JOYSTICK,
+ PERIPHERAL_KEYBOARD,
+ PERIPHERAL_MOUSE,
+};
+
+class CPeripheral;
+using PeripheralPtr = std::shared_ptr<CPeripheral>;
+using PeripheralVector = std::vector<PeripheralPtr>;
+
+class CPeripheralAddon;
+using PeripheralAddonPtr = std::shared_ptr<CPeripheralAddon>;
+using PeripheralAddonVector = std::vector<PeripheralAddonPtr>;
+
+class CEventPollHandle;
+using EventPollHandlePtr = std::unique_ptr<CEventPollHandle>;
+
+class CEventLockHandle;
+using EventLockHandlePtr = std::unique_ptr<CEventLockHandle>;
+
+struct PeripheralID
+{
+ int m_iVendorId;
+ int m_iProductId;
+};
+
+struct PeripheralDeviceSetting
+{
+ std::shared_ptr<CSetting> m_setting;
+ int m_order;
+};
+
+struct PeripheralDeviceMapping
+{
+ std::vector<PeripheralID> m_PeripheralID;
+ PeripheralBusType m_busType;
+ PeripheralType m_class;
+ std::string m_strDeviceName;
+ PeripheralType m_mappedTo;
+ std::map<std::string, 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";
+ case PERIPHERAL_JOYSTICK:
+ return "joystick";
+ case PERIPHERAL_KEYBOARD:
+ return "keyboard";
+ case PERIPHERAL_MOUSE:
+ return "mouse";
+ default:
+ return "unknown";
+ }
+ };
+
+ static PeripheralType GetTypeFromString(const std::string& strType)
+ {
+ std::string strTypeLowerCase(strType);
+ StringUtils::ToLower(strTypeLowerCase);
+
+ if (strTypeLowerCase == "bluetooth")
+ return PERIPHERAL_BLUETOOTH;
+ else if (strTypeLowerCase == "cec")
+ return PERIPHERAL_CEC;
+ else if (strTypeLowerCase == "disk")
+ return PERIPHERAL_DISK;
+ else if (strTypeLowerCase == "hid")
+ return PERIPHERAL_HID;
+ else if (strTypeLowerCase == "nic")
+ return PERIPHERAL_NIC;
+ else if (strTypeLowerCase == "nyxboard")
+ return PERIPHERAL_NYXBOARD;
+ else if (strTypeLowerCase == "tuner")
+ return PERIPHERAL_TUNER;
+ else if (strTypeLowerCase == "imon")
+ return PERIPHERAL_IMON;
+ else if (strTypeLowerCase == "joystick")
+ return PERIPHERAL_JOYSTICK;
+ else if (strTypeLowerCase == "keyboard")
+ return PERIPHERAL_KEYBOARD;
+ else if (strTypeLowerCase == "mouse")
+ return PERIPHERAL_MOUSE;
+
+ 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_CEC:
+ return "cec";
+ case PERIPHERAL_BUS_ADDON:
+ return "addon";
+#ifdef TARGET_ANDROID
+ case PERIPHERAL_BUS_ANDROID:
+ return "android";
+#endif
+#if defined(TARGET_DARWIN)
+ case PERIPHERAL_BUS_GCCONTROLLER:
+ return "darwin_gccontroller";
+#endif
+ case PERIPHERAL_BUS_APPLICATION:
+ return "application";
+ default:
+ return "unknown";
+ }
+ };
+
+ static PeripheralBusType GetBusTypeFromString(const std::string& strType)
+ {
+ std::string strTypeLowerCase(strType);
+ StringUtils::ToLower(strTypeLowerCase);
+
+ if (strTypeLowerCase == "usb")
+ return PERIPHERAL_BUS_USB;
+ else if (strTypeLowerCase == "pci")
+ return PERIPHERAL_BUS_PCI;
+ else if (strTypeLowerCase == "cec")
+ return PERIPHERAL_BUS_CEC;
+ else if (strTypeLowerCase == "addon")
+ return PERIPHERAL_BUS_ADDON;
+#ifdef TARGET_ANDROID
+ else if (strTypeLowerCase == "android")
+ return PERIPHERAL_BUS_ANDROID;
+#endif
+#if defined(TARGET_DARWIN)
+ else if (strTypeLowerCase == "darwin_gccontroller")
+ return PERIPHERAL_BUS_GCCONTROLLER;
+#endif
+ else if (strTypeLowerCase == "application")
+ return PERIPHERAL_BUS_APPLICATION;
+
+ return PERIPHERAL_BUS_UNKNOWN;
+ };
+
+ static const char* FeatureToString(const PeripheralFeature type)
+ {
+ switch (type)
+ {
+ case FEATURE_HID:
+ return "HID";
+ case FEATURE_NIC:
+ return "NIC";
+ case FEATURE_DISK:
+ return "disk";
+ case FEATURE_NYXBOARD:
+ return "nyxboard";
+ case FEATURE_CEC:
+ return "CEC";
+ case FEATURE_BLUETOOTH:
+ return "bluetooth";
+ case FEATURE_TUNER:
+ return "tuner";
+ case FEATURE_IMON:
+ return "imon";
+ case FEATURE_JOYSTICK:
+ return "joystick";
+ case FEATURE_RUMBLE:
+ return "rumble";
+ case FEATURE_POWER_OFF:
+ return "poweroff";
+ case FEATURE_KEYBOARD:
+ return "keyboard";
+ case FEATURE_MOUSE:
+ return "mouse";
+ case FEATURE_UNKNOWN:
+ default:
+ return "unknown";
+ }
+ };
+
+ static PeripheralFeature GetFeatureTypeFromString(const std::string& strType)
+ {
+ std::string strTypeLowerCase(strType);
+ StringUtils::ToLower(strTypeLowerCase);
+
+ if (strTypeLowerCase == "hid")
+ return FEATURE_HID;
+ else if (strTypeLowerCase == "cec")
+ return FEATURE_CEC;
+ else if (strTypeLowerCase == "disk")
+ return FEATURE_DISK;
+ else if (strTypeLowerCase == "nyxboard")
+ return FEATURE_NYXBOARD;
+ else if (strTypeLowerCase == "bluetooth")
+ return FEATURE_BLUETOOTH;
+ else if (strTypeLowerCase == "tuner")
+ return FEATURE_TUNER;
+ else if (strTypeLowerCase == "imon")
+ return FEATURE_IMON;
+ else if (strTypeLowerCase == "joystick")
+ return FEATURE_JOYSTICK;
+ else if (strTypeLowerCase == "rumble")
+ return FEATURE_RUMBLE;
+ else if (strTypeLowerCase == "poweroff")
+ return FEATURE_POWER_OFF;
+ else if (strTypeLowerCase == "keyboard")
+ return FEATURE_KEYBOARD;
+ else if (strTypeLowerCase == "mouse")
+ return FEATURE_MOUSE;
+
+ return FEATURE_UNKNOWN;
+ };
+
+ static int HexStringToInt(const char* strHex)
+ {
+ int iVal;
+ sscanf(strHex, "%x", &iVal);
+ return iVal;
+ };
+
+ static void FormatHexString(int iVal, std::string& strHexString)
+ {
+ if (iVal < 0)
+ iVal = 0;
+ if (iVal > 65536)
+ iVal = 65536;
+
+ strHexString = StringUtils::Format("{:04X}", iVal);
+ };
+};
+
+class PeripheralScanResult
+{
+public:
+ explicit PeripheralScanResult(const PeripheralBusType busType)
+ : m_busType(busType), m_mappedBusType(busType)
+ {
+ }
+
+ PeripheralScanResult(void) = default;
+
+ 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 &&
+ StringUtils::EqualsNoCase(m_strLocation, right.m_strLocation);
+ }
+
+ bool operator!=(const PeripheralScanResult& right) const { return !(*this == right); }
+
+ PeripheralType m_type = PERIPHERAL_UNKNOWN;
+ std::string m_strLocation;
+ int m_iVendorId = 0;
+ int m_iProductId = 0;
+ PeripheralType m_mappedType = PERIPHERAL_UNKNOWN;
+ std::string m_strDeviceName;
+ PeripheralBusType m_busType = PERIPHERAL_BUS_UNKNOWN;
+ PeripheralBusType m_mappedBusType = PERIPHERAL_BUS_UNKNOWN;
+ unsigned int m_iSequence = 0; // when more than one adapter of the same type is found
+};
+
+struct PeripheralScanResults
+{
+ bool GetDeviceOnLocation(const std::string& strLocation, PeripheralScanResult* result) const
+ {
+ for (const auto& it : m_results)
+ {
+ 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;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/Peripherals.cpp b/xbmc/peripherals/Peripherals.cpp
new file mode 100644
index 0000000..c178158
--- /dev/null
+++ b/xbmc/peripherals/Peripherals.cpp
@@ -0,0 +1,1030 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Peripherals.h"
+
+#include "CompileInfo.h"
+#include "EventScanner.h"
+#include "addons/AddonButtonMap.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "addons/gui/GUIDialogAddonSettings.h"
+#include "addons/gui/GUIWindowAddonBrowser.h"
+#include "bus/PeripheralBus.h"
+#include "bus/PeripheralBusUSB.h"
+
+#include <mutex>
+#include <utility>
+#if defined(TARGET_ANDROID)
+#include "platform/android/peripherals/PeripheralBusAndroid.h"
+#elif defined(TARGET_DARWIN)
+#include "platform/darwin/peripherals/PeripheralBusGCController.h"
+#endif
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "bus/virtual/PeripheralBusAddon.h"
+#include "bus/virtual/PeripheralBusApplication.h"
+#include "devices/PeripheralBluetooth.h"
+#include "devices/PeripheralCecAdapter.h"
+#include "devices/PeripheralDisk.h"
+#include "devices/PeripheralHID.h"
+#include "devices/PeripheralImon.h"
+#include "devices/PeripheralJoystick.h"
+#include "devices/PeripheralKeyboard.h"
+#include "devices/PeripheralMouse.h"
+#include "devices/PeripheralNIC.h"
+#include "devices/PeripheralNyxboard.h"
+#include "devices/PeripheralTuner.h"
+#include "filesystem/Directory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+#include "input/Key.h"
+#include "input/joysticks/interfaces/IButtonMapper.h"
+#include "interfaces/AnnouncementManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/ThreadMessage.h"
+#include "peripherals/dialogs/GUIDialogPeripherals.h"
+#include "settings/SettingAddon.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#if defined(HAVE_LIBCEC)
+#include "bus/virtual/PeripheralBusCEC.h"
+#else
+#include "dialogs/GUIDialogKaiToast.h"
+#include "guilib/LocalizeStrings.h"
+#endif
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+using namespace XFILE;
+
+CPeripherals::CPeripherals(CInputManager& inputManager,
+ GAME::CControllerManager& controllerProfiles)
+ : m_inputManager(inputManager),
+ m_controllerProfiles(controllerProfiles),
+ m_eventScanner(new CEventScanner(*this))
+{
+ // Register settings
+ std::set<std::string> settingSet;
+ settingSet.insert(CSettings::SETTING_INPUT_PERIPHERALS);
+ settingSet.insert(CSettings::SETTING_INPUT_PERIPHERALLIBRARIES);
+ settingSet.insert(CSettings::SETTING_INPUT_CONTROLLERCONFIG);
+ settingSet.insert(CSettings::SETTING_INPUT_TESTRUMBLE);
+ settingSet.insert(CSettings::SETTING_LOCALE_LANGUAGE);
+ CServiceBroker::GetSettingsComponent()->GetSettings()->RegisterCallback(this, settingSet);
+}
+
+CPeripherals::~CPeripherals()
+{
+ // Unregister settings
+ CServiceBroker::GetSettingsComponent()->GetSettings()->UnregisterCallback(this);
+
+ Clear();
+}
+
+void CPeripherals::Initialise()
+{
+ Clear();
+
+ CDirectory::Create("special://profile/peripheral_data");
+
+ /* load mappings from peripherals.xml */
+ LoadMappings();
+
+ std::vector<PeripheralBusPtr> busses;
+
+#if defined(HAVE_PERIPHERAL_BUS_USB)
+ busses.push_back(std::make_shared<CPeripheralBusUSB>(*this));
+#endif
+#if defined(HAVE_LIBCEC)
+ busses.push_back(std::make_shared<CPeripheralBusCEC>(*this));
+#endif
+ busses.push_back(std::make_shared<CPeripheralBusAddon>(*this));
+#if defined(TARGET_ANDROID)
+ busses.push_back(std::make_shared<CPeripheralBusAndroid>(*this));
+#elif defined(TARGET_DARWIN)
+ busses.push_back(std::make_shared<CPeripheralBusGCController>(*this));
+#endif
+ busses.push_back(std::make_shared<CPeripheralBusApplication>(*this));
+
+ {
+ std::unique_lock<CCriticalSection> bussesLock(m_critSectionBusses);
+ m_busses = busses;
+ }
+
+ /* initialise all known busses and run an initial scan for devices */
+ for (auto& bus : busses)
+ bus->Initialise();
+
+ m_eventScanner->Start();
+
+ CServiceBroker::GetAppMessenger()->RegisterReceiver(this);
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+}
+
+void CPeripherals::Clear()
+{
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+
+ m_eventScanner->Stop();
+
+ // avoid deadlocks by copying all busses into a temporary variable and destroying them from there
+ std::vector<PeripheralBusPtr> busses;
+ {
+ std::unique_lock<CCriticalSection> bussesLock(m_critSectionBusses);
+ /* delete busses and devices */
+ busses = m_busses;
+ m_busses.clear();
+ }
+
+ for (const auto& bus : busses)
+ bus->Clear();
+ busses.clear();
+
+ {
+ std::unique_lock<CCriticalSection> mappingsLock(m_critSectionMappings);
+ /* delete mappings */
+ for (auto& mapping : m_mappings)
+ mapping.m_settings.clear();
+ m_mappings.clear();
+ }
+
+#if !defined(HAVE_LIBCEC)
+ m_bMissingLibCecWarningDisplayed = false;
+#endif
+}
+
+void CPeripherals::TriggerDeviceScan(const PeripheralBusType type /* = PERIPHERAL_BUS_UNKNOWN */)
+{
+ std::vector<PeripheralBusPtr> busses;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ busses = m_busses;
+ }
+
+ for (auto& bus : busses)
+ {
+ bool bScan = false;
+
+ if (type == PERIPHERAL_BUS_UNKNOWN)
+ bScan = true;
+ else if (bus->Type() == PERIPHERAL_BUS_ADDON)
+ bScan = true;
+ else if (bus->Type() == type)
+ bScan = true;
+
+ if (bScan)
+ bus->TriggerDeviceScan();
+ }
+}
+
+PeripheralBusPtr CPeripherals::GetBusByType(const PeripheralBusType type) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+
+ const auto& bus =
+ std::find_if(m_busses.cbegin(), m_busses.cend(),
+ [type](const PeripheralBusPtr& bus) { return bus->Type() == type; });
+ if (bus != m_busses.cend())
+ return *bus;
+
+ return nullptr;
+}
+
+PeripheralPtr CPeripherals::GetPeripheralAtLocation(
+ const std::string& strLocation, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const
+{
+ PeripheralPtr result;
+
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ for (const auto& bus : m_busses)
+ {
+ /* check whether the bus matches if a bus type other than unknown was passed */
+ if (busType != PERIPHERAL_BUS_UNKNOWN && bus->Type() != busType)
+ continue;
+
+ /* return the first device that matches */
+ PeripheralPtr peripheral = bus->GetPeripheral(strLocation);
+ if (peripheral)
+ {
+ result = peripheral;
+ break;
+ }
+ }
+
+ return result;
+}
+
+bool CPeripherals::HasPeripheralAtLocation(
+ const std::string& strLocation, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const
+{
+ return (GetPeripheralAtLocation(strLocation, busType) != nullptr);
+}
+
+PeripheralBusPtr CPeripherals::GetBusWithDevice(const std::string& strLocation) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+
+ const auto& bus =
+ std::find_if(m_busses.cbegin(), m_busses.cend(), [&strLocation](const PeripheralBusPtr& bus) {
+ return bus->HasPeripheral(strLocation);
+ });
+ if (bus != m_busses.cend())
+ return *bus;
+
+ return nullptr;
+}
+
+bool CPeripherals::SupportsFeature(PeripheralFeature feature) const
+{
+ bool bSupportsFeature = false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ for (const auto& bus : m_busses)
+ bSupportsFeature |= bus->SupportsFeature(feature);
+
+ return bSupportsFeature;
+}
+
+int CPeripherals::GetPeripheralsWithFeature(
+ PeripheralVector& results,
+ const PeripheralFeature feature,
+ PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ int iReturn(0);
+ for (const auto& bus : m_busses)
+ {
+ /* check whether the bus matches if a bus type other than unknown was passed */
+ if (busType != PERIPHERAL_BUS_UNKNOWN && bus->Type() != busType)
+ continue;
+
+ iReturn += bus->GetPeripheralsWithFeature(results, feature);
+ }
+
+ return iReturn;
+}
+
+size_t CPeripherals::GetNumberOfPeripherals() const
+{
+ size_t iReturn(0);
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ for (const auto& bus : m_busses)
+ iReturn += bus->GetNumberOfPeripherals();
+
+ return iReturn;
+}
+
+bool CPeripherals::HasPeripheralWithFeature(
+ const PeripheralFeature feature, PeripheralBusType busType /* = PERIPHERAL_BUS_UNKNOWN */) const
+{
+ PeripheralVector dummy;
+ return (GetPeripheralsWithFeature(dummy, feature, busType) > 0);
+}
+
+void CPeripherals::CreatePeripheral(CPeripheralBus& bus, const PeripheralScanResult& result)
+{
+ PeripheralPtr peripheral;
+ PeripheralScanResult mappedResult = result;
+ if (mappedResult.m_busType == PERIPHERAL_BUS_UNKNOWN)
+ mappedResult.m_busType = bus.Type();
+
+ /* check whether there's something mapped in peripherals.xml */
+ GetMappingForDevice(bus, mappedResult);
+
+ switch (mappedResult.m_mappedType)
+ {
+ case PERIPHERAL_HID:
+ peripheral = PeripheralPtr(new CPeripheralHID(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_NIC:
+ peripheral = PeripheralPtr(new CPeripheralNIC(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_DISK:
+ peripheral = PeripheralPtr(new CPeripheralDisk(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_NYXBOARD:
+ peripheral = PeripheralPtr(new CPeripheralNyxboard(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_TUNER:
+ peripheral = PeripheralPtr(new CPeripheralTuner(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_BLUETOOTH:
+ peripheral = PeripheralPtr(new CPeripheralBluetooth(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_CEC:
+#if defined(HAVE_LIBCEC)
+ if (bus.Type() == PERIPHERAL_BUS_CEC)
+ peripheral = PeripheralPtr(new CPeripheralCecAdapter(*this, mappedResult, &bus));
+#else
+ if (!m_bMissingLibCecWarningDisplayed)
+ {
+ m_bMissingLibCecWarningDisplayed = true;
+ CLog::Log(
+ LOGWARNING,
+ "{} - libCEC support has not been compiled in, so the CEC adapter cannot be used.",
+ __FUNCTION__);
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning,
+ g_localizeStrings.Get(36000),
+ g_localizeStrings.Get(36017));
+ }
+#endif
+ break;
+
+ case PERIPHERAL_IMON:
+ peripheral = PeripheralPtr(new CPeripheralImon(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_JOYSTICK:
+ peripheral = PeripheralPtr(new CPeripheralJoystick(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_KEYBOARD:
+ peripheral = PeripheralPtr(new CPeripheralKeyboard(*this, mappedResult, &bus));
+ break;
+
+ case PERIPHERAL_MOUSE:
+ peripheral = PeripheralPtr(new CPeripheralMouse(*this, mappedResult, &bus));
+ break;
+
+ default:
+ break;
+ }
+
+ if (peripheral)
+ {
+ /* try to initialise the new peripheral
+ * Initialise() will make sure that each device is only initialised once */
+ if (peripheral->Initialise())
+ bus.Register(peripheral);
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - failed to initialise peripheral on '{}'", __FUNCTION__,
+ mappedResult.m_strLocation);
+ }
+ }
+}
+
+void CPeripherals::OnDeviceAdded(const CPeripheralBus& bus, const CPeripheral& peripheral)
+{
+ OnDeviceChanged();
+
+ //! @todo Improve device notifications in v18
+#if 0
+ bool bNotify = true;
+
+ // don't show a notification for devices detected during the initial scan
+ if (!bus.IsInitialised())
+ bNotify = false;
+
+ if (bNotify)
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(35005), peripheral.DeviceName());
+#endif
+}
+
+void CPeripherals::OnDeviceDeleted(const CPeripheralBus& bus, const CPeripheral& peripheral)
+{
+ OnDeviceChanged();
+
+ //! @todo Improve device notifications in v18
+#if 0
+ bool bNotify = true;
+
+ if (bNotify)
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(35006), peripheral.DeviceName());
+#endif
+}
+
+void CPeripherals::OnDeviceChanged()
+{
+ // refresh settings (peripherals manager could be enabled/disabled now)
+ CGUIMessage msgSettings(GUI_MSG_UPDATE, WINDOW_SETTINGS_SYSTEM, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msgSettings,
+ WINDOW_SETTINGS_SYSTEM);
+
+ SetChanged();
+}
+
+bool CPeripherals::GetMappingForDevice(const CPeripheralBus& bus,
+ PeripheralScanResult& result) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSectionMappings);
+
+ /* check all mappings in the order in which they are defined in peripherals.xml */
+ for (const auto& mapping : m_mappings)
+ {
+ bool bProductMatch = false;
+ if (mapping.m_PeripheralID.empty())
+ bProductMatch = true;
+ else
+ {
+ for (const auto& peripheralID : mapping.m_PeripheralID)
+ if (peripheralID.m_iVendorId == result.m_iVendorId &&
+ peripheralID.m_iProductId == result.m_iProductId)
+ bProductMatch = true;
+ }
+
+ bool bBusMatch =
+ (mapping.m_busType == PERIPHERAL_BUS_UNKNOWN || mapping.m_busType == bus.Type());
+ bool bClassMatch = (mapping.m_class == PERIPHERAL_UNKNOWN || mapping.m_class == result.m_type);
+
+ if (bProductMatch && bBusMatch && bClassMatch)
+ {
+ std::string strVendorId, strProductId;
+ PeripheralTypeTranslator::FormatHexString(result.m_iVendorId, strVendorId);
+ PeripheralTypeTranslator::FormatHexString(result.m_iProductId, strProductId);
+ CLog::Log(LOGDEBUG, "{} - device ({}:{}) mapped to {} (type = {})", __FUNCTION__, strVendorId,
+ strProductId, mapping.m_strDeviceName,
+ PeripheralTypeTranslator::TypeToString(mapping.m_mappedTo));
+ result.m_mappedType = mapping.m_mappedTo;
+ if (result.m_strDeviceName.empty() && !mapping.m_strDeviceName.empty())
+ result.m_strDeviceName = mapping.m_strDeviceName;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CPeripherals::GetSettingsFromMapping(CPeripheral& peripheral) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSectionMappings);
+
+ /* check all mappings in the order in which they are defined in peripherals.xml */
+ for (const auto& mapping : m_mappings)
+ {
+ bool bProductMatch = false;
+ if (mapping.m_PeripheralID.empty())
+ bProductMatch = true;
+ else
+ {
+ for (const auto& peripheralID : mapping.m_PeripheralID)
+ if (peripheralID.m_iVendorId == peripheral.VendorId() &&
+ peripheralID.m_iProductId == peripheral.ProductId())
+ bProductMatch = true;
+ }
+
+ bool bBusMatch = (mapping.m_busType == PERIPHERAL_BUS_UNKNOWN ||
+ mapping.m_busType == peripheral.GetBusType());
+ bool bClassMatch =
+ (mapping.m_class == PERIPHERAL_UNKNOWN || mapping.m_class == peripheral.Type());
+
+ if (bBusMatch && bProductMatch && bClassMatch)
+ {
+ for (auto itr = mapping.m_settings.begin(); itr != mapping.m_settings.end(); ++itr)
+ peripheral.AddSetting((*itr).first, (*itr).second.m_setting, (*itr).second.m_order);
+ }
+ }
+}
+
+#define SS(x) ((x) ? x : "")
+bool CPeripherals::LoadMappings()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSectionMappings);
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile("special://xbmc/system/peripherals.xml"))
+ {
+ CLog::Log(LOGWARNING, "{} - peripherals.xml does not exist", __FUNCTION__);
+ return true;
+ }
+
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ if (!pRootElement || StringUtils::CompareNoCase(pRootElement->Value(), "peripherals") != 0)
+ {
+ CLog::Log(LOGERROR, "{} - peripherals.xml does not contain <peripherals>", __FUNCTION__);
+ return false;
+ }
+
+ for (TiXmlElement* currentNode = pRootElement->FirstChildElement("peripheral"); currentNode;
+ currentNode = currentNode->NextSiblingElement("peripheral"))
+ {
+ PeripheralID id;
+ PeripheralDeviceMapping mapping;
+
+ mapping.m_strDeviceName = XMLUtils::GetAttribute(currentNode, "name");
+
+ // If there is no vendor_product attribute ignore this entry
+ if (currentNode->Attribute("vendor_product"))
+ {
+ // The vendor_product attribute is a list of comma separated vendor:product pairs
+ std::vector<std::string> vpArray =
+ StringUtils::Split(currentNode->Attribute("vendor_product"), ",");
+ for (const auto& i : vpArray)
+ {
+ std::vector<std::string> idArray = StringUtils::Split(i, ":");
+ if (idArray.size() != 2)
+ {
+ CLog::Log(LOGERROR, "{} - ignoring node \"{}\" with invalid vendor_product attribute",
+ __FUNCTION__, mapping.m_strDeviceName);
+ continue;
+ }
+
+ id.m_iVendorId = PeripheralTypeTranslator::HexStringToInt(idArray[0].c_str());
+ id.m_iProductId = PeripheralTypeTranslator::HexStringToInt(idArray[1].c_str());
+ mapping.m_PeripheralID.push_back(id);
+ }
+ }
+
+ mapping.m_busType =
+ PeripheralTypeTranslator::GetBusTypeFromString(XMLUtils::GetAttribute(currentNode, "bus"));
+ mapping.m_class =
+ PeripheralTypeTranslator::GetTypeFromString(XMLUtils::GetAttribute(currentNode, "class"));
+ mapping.m_mappedTo =
+ PeripheralTypeTranslator::GetTypeFromString(XMLUtils::GetAttribute(currentNode, "mapTo"));
+ GetSettingsFromMappingsFile(currentNode, mapping.m_settings);
+
+ m_mappings.push_back(mapping);
+ CLog::Log(LOGDEBUG, "{} - loaded node \"{}\"", __FUNCTION__, mapping.m_strDeviceName);
+ }
+
+ return true;
+}
+
+void CPeripherals::GetSettingsFromMappingsFile(
+ TiXmlElement* xmlNode, std::map<std::string, PeripheralDeviceSetting>& settings)
+{
+ TiXmlElement* currentNode = xmlNode->FirstChildElement("setting");
+ int iMaxOrder = 0;
+
+ while (currentNode)
+ {
+ SettingPtr setting;
+ std::string strKey = XMLUtils::GetAttribute(currentNode, "key");
+ if (strKey.empty())
+ continue;
+
+ std::string strSettingsType = XMLUtils::GetAttribute(currentNode, "type");
+ int iLabelId = currentNode->Attribute("label") ? atoi(currentNode->Attribute("label")) : -1;
+ const std::string config = XMLUtils::GetAttribute(currentNode, "configurable");
+ bool bConfigurable = (config.empty() || (config != "no" && config != "false" && config != "0"));
+ if (strSettingsType == "bool")
+ {
+ const std::string value = XMLUtils::GetAttribute(currentNode, "value");
+ bool bValue = (value != "no" && value != "false" && value != "0");
+ setting = std::make_shared<CSettingBool>(strKey, iLabelId, bValue);
+ }
+ else if (strSettingsType == "int")
+ {
+ int iValue = currentNode->Attribute("value") ? atoi(currentNode->Attribute("value")) : 0;
+ int iMin = currentNode->Attribute("min") ? atoi(currentNode->Attribute("min")) : 0;
+ int iStep = currentNode->Attribute("step") ? atoi(currentNode->Attribute("step")) : 1;
+ int iMax = currentNode->Attribute("max") ? atoi(currentNode->Attribute("max")) : 255;
+ setting = std::make_shared<CSettingInt>(strKey, iLabelId, iValue, iMin, iStep, iMax);
+ }
+ else if (strSettingsType == "float")
+ {
+ float fValue =
+ currentNode->Attribute("value") ? (float)atof(currentNode->Attribute("value")) : 0;
+ float fMin = currentNode->Attribute("min") ? (float)atof(currentNode->Attribute("min")) : 0;
+ float fStep =
+ currentNode->Attribute("step") ? (float)atof(currentNode->Attribute("step")) : 0;
+ float fMax = currentNode->Attribute("max") ? (float)atof(currentNode->Attribute("max")) : 0;
+ setting = std::make_shared<CSettingNumber>(strKey, iLabelId, fValue, fMin, fStep, fMax);
+ }
+ else if (StringUtils::EqualsNoCase(strSettingsType, "enum"))
+ {
+ std::string strEnums = XMLUtils::GetAttribute(currentNode, "lvalues");
+ if (!strEnums.empty())
+ {
+ TranslatableIntegerSettingOptions enums;
+ std::vector<std::string> valuesVec;
+ StringUtils::Tokenize(strEnums, valuesVec, "|");
+ for (unsigned int i = 0; i < valuesVec.size(); i++)
+ enums.emplace_back(atoi(valuesVec[i].c_str()), atoi(valuesVec[i].c_str()));
+ int iValue = currentNode->Attribute("value") ? atoi(currentNode->Attribute("value")) : 0;
+ setting = std::make_shared<CSettingInt>(strKey, iLabelId, iValue, enums);
+ }
+ }
+ else if (StringUtils::EqualsNoCase(strSettingsType, "addon"))
+ {
+ std::string addonFilter = XMLUtils::GetAttribute(currentNode, "addontype");
+ ADDON::AddonType addonType = ADDON::CAddonInfo::TranslateType(addonFilter);
+ std::string strValue = XMLUtils::GetAttribute(currentNode, "value");
+ setting = std::make_shared<CSettingAddon>(strKey, iLabelId, strValue);
+ static_cast<CSettingAddon&>(*setting).SetAddonType(addonType);
+ }
+ else
+ {
+ std::string strValue = XMLUtils::GetAttribute(currentNode, "value");
+ setting = std::make_shared<CSettingString>(strKey, iLabelId, strValue);
+ }
+
+ if (setting)
+ {
+ //! @todo add more types if needed
+
+ /* set the visibility */
+ setting->SetVisible(bConfigurable);
+
+ /* set the order */
+ int iOrder = 0;
+ currentNode->Attribute("order", &iOrder);
+ /* if the order attribute is invalid or 0, then the setting will be added at the end */
+ if (iOrder < 0)
+ iOrder = 0;
+ if (iOrder > iMaxOrder)
+ iMaxOrder = iOrder;
+
+ /* and add this new setting */
+ PeripheralDeviceSetting deviceSetting = {setting, iOrder};
+ settings[strKey] = deviceSetting;
+ }
+
+ currentNode = currentNode->NextSiblingElement("setting");
+ }
+
+ /* add the settings without an order attribute or an invalid order attribute set at the end */
+ for (auto& it : settings)
+ {
+ if (it.second.m_order == 0)
+ it.second.m_order = ++iMaxOrder;
+ }
+}
+
+void CPeripherals::GetDirectory(const std::string& strPath, CFileItemList& items) const
+{
+ if (!StringUtils::StartsWithNoCase(strPath, "peripherals://"))
+ return;
+
+ std::string strPathCut = strPath.substr(14);
+ std::string strBus = strPathCut.substr(0, strPathCut.find('/'));
+
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ for (const auto& bus : m_busses)
+ {
+ if (StringUtils::EqualsNoCase(strBus, "all") ||
+ StringUtils::EqualsNoCase(strBus, PeripheralTypeTranslator::BusTypeToString(bus->Type())))
+ bus->GetDirectory(strPath, items);
+ }
+}
+
+PeripheralPtr CPeripherals::GetByPath(const std::string& strPath) const
+{
+ PeripheralPtr result;
+
+ if (!StringUtils::StartsWithNoCase(strPath, "peripherals://"))
+ return result;
+
+ std::string strPathCut = strPath.substr(14);
+ std::string strBus = strPathCut.substr(0, strPathCut.find('/'));
+
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ for (const auto& bus : m_busses)
+ {
+ if (StringUtils::EqualsNoCase(strBus, PeripheralTypeTranslator::BusTypeToString(bus->Type())))
+ {
+ result = bus->GetByPath(strPath);
+ break;
+ }
+ }
+
+ return result;
+}
+
+bool CPeripherals::OnAction(const CAction& action)
+{
+ if (action.GetID() == ACTION_MUTE)
+ {
+ return ToggleMute();
+ }
+
+ if (SupportsCEC() && action.GetAmount() &&
+ (action.GetID() == ACTION_VOLUME_UP || action.GetID() == ACTION_VOLUME_DOWN))
+ {
+ PeripheralVector peripherals;
+ if (GetPeripheralsWithFeature(peripherals, FEATURE_CEC))
+ {
+ for (auto& peripheral : peripherals)
+ {
+ std::shared_ptr<CPeripheralCecAdapter> cecDevice =
+ std::static_pointer_cast<CPeripheralCecAdapter>(peripheral);
+ if (cecDevice->HasAudioControl())
+ {
+ if (action.GetID() == ACTION_VOLUME_UP)
+ cecDevice->VolumeUp();
+ else
+ cecDevice->VolumeDown();
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CPeripherals::IsMuted()
+{
+ PeripheralVector peripherals;
+ if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC))
+ {
+ for (const auto& peripheral : peripherals)
+ {
+ std::shared_ptr<CPeripheralCecAdapter> cecDevice =
+ std::static_pointer_cast<CPeripheralCecAdapter>(peripheral);
+ if (cecDevice->IsMuted())
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CPeripherals::ToggleMute()
+{
+ PeripheralVector peripherals;
+ if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC))
+ {
+ for (auto& peripheral : peripherals)
+ {
+ std::shared_ptr<CPeripheralCecAdapter> cecDevice =
+ std::static_pointer_cast<CPeripheralCecAdapter>(peripheral);
+ if (cecDevice->HasAudioControl())
+ {
+ cecDevice->ToggleMute();
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CPeripherals::ToggleDeviceState(CecStateChange mode /*= STATE_SWITCH_TOGGLE */)
+{
+ bool ret(false);
+ PeripheralVector peripherals;
+
+ if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC))
+ {
+ for (auto& peripheral : peripherals)
+ {
+ std::shared_ptr<CPeripheralCecAdapter> cecDevice =
+ std::static_pointer_cast<CPeripheralCecAdapter>(peripheral);
+ ret |= cecDevice->ToggleDeviceState(mode);
+ }
+ }
+
+ return ret;
+}
+
+bool CPeripherals::GetNextKeypress(float frameTime, CKey& key)
+{
+ PeripheralVector peripherals;
+ if (SupportsCEC() && GetPeripheralsWithFeature(peripherals, FEATURE_CEC))
+ {
+ for (auto& peripheral : peripherals)
+ {
+ std::shared_ptr<CPeripheralCecAdapter> cecDevice =
+ std::static_pointer_cast<CPeripheralCecAdapter>(peripheral);
+ if (cecDevice->GetButton())
+ {
+ CKey newKey(cecDevice->GetButton(), cecDevice->GetHoldTime());
+ cecDevice->ResetButton();
+ key = newKey;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+EventPollHandlePtr CPeripherals::RegisterEventPoller()
+{
+ return m_eventScanner->RegisterPollHandle();
+}
+
+EventLockHandlePtr CPeripherals::RegisterEventLock()
+{
+ return m_eventScanner->RegisterLock();
+}
+
+void CPeripherals::OnUserNotification()
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_INPUT_RUMBLENOTIFY))
+ return;
+
+ PeripheralVector peripherals;
+ GetPeripheralsWithFeature(peripherals, FEATURE_RUMBLE);
+
+ for (auto& peripheral : peripherals)
+ peripheral->OnUserNotification();
+}
+
+void CPeripherals::TestFeature(PeripheralFeature feature)
+{
+ PeripheralVector peripherals;
+ GetPeripheralsWithFeature(peripherals, feature);
+
+ for (auto& peripheral : peripherals)
+ {
+ if (peripheral->TestFeature(feature))
+ {
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Device \"{}\" tested {} feature", peripheral->DeviceName(),
+ PeripheralTypeTranslator::FeatureToString(feature));
+ }
+ else
+ {
+ if (peripheral->HasFeature(feature))
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Device \"{}\" failed to test {} feature",
+ peripheral->DeviceName(), PeripheralTypeTranslator::FeatureToString(feature));
+ else
+ CLog::Log(LOGDEBUG, "PERIPHERALS: Device \"{}\" doesn't support {} feature",
+ peripheral->DeviceName(), PeripheralTypeTranslator::FeatureToString(feature));
+ }
+ }
+}
+
+void CPeripherals::PowerOffDevices()
+{
+ TestFeature(FEATURE_POWER_OFF);
+}
+
+void CPeripherals::ProcessEvents(void)
+{
+ std::vector<PeripheralBusPtr> busses;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ busses = m_busses;
+ }
+
+ for (PeripheralBusPtr& bus : busses)
+ bus->ProcessEvents();
+}
+
+void CPeripherals::EnableButtonMapping()
+{
+ std::vector<PeripheralBusPtr> busses;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSectionBusses);
+ busses = m_busses;
+ }
+
+ for (PeripheralBusPtr& bus : busses)
+ bus->EnableButtonMapping();
+}
+
+PeripheralAddonPtr CPeripherals::GetAddonWithButtonMap(const CPeripheral* device)
+{
+ PeripheralBusAddonPtr addonBus =
+ std::static_pointer_cast<CPeripheralBusAddon>(GetBusByType(PERIPHERAL_BUS_ADDON));
+
+ PeripheralAddonPtr addon;
+
+ PeripheralAddonPtr addonWithButtonMap;
+ if (addonBus && addonBus->GetAddonWithButtonMap(device, addonWithButtonMap))
+ addon = std::move(addonWithButtonMap);
+
+ return addon;
+}
+
+void CPeripherals::ResetButtonMaps(const std::string& controllerId)
+{
+ PeripheralBusAddonPtr addonBus =
+ std::static_pointer_cast<CPeripheralBusAddon>(GetBusByType(PERIPHERAL_BUS_ADDON));
+
+ PeripheralVector peripherals;
+ GetPeripheralsWithFeature(peripherals, FEATURE_JOYSTICK);
+
+ for (auto& peripheral : peripherals)
+ {
+ PeripheralAddonPtr addon;
+ if (addonBus->GetAddonWithButtonMap(peripheral.get(), addon))
+ {
+ CAddonButtonMap buttonMap(peripheral.get(), addon, controllerId, *this);
+ buttonMap.Reset();
+ }
+ }
+}
+
+void CPeripherals::RegisterJoystickButtonMapper(IButtonMapper* mapper)
+{
+ PeripheralVector peripherals;
+ GetPeripheralsWithFeature(peripherals, FEATURE_JOYSTICK);
+ GetPeripheralsWithFeature(peripherals, FEATURE_KEYBOARD);
+ GetPeripheralsWithFeature(peripherals, FEATURE_MOUSE);
+
+ for (auto& peripheral : peripherals)
+ peripheral->RegisterJoystickButtonMapper(mapper);
+}
+
+void CPeripherals::UnregisterJoystickButtonMapper(IButtonMapper* mapper)
+{
+ mapper->ResetButtonMapCallbacks();
+
+ PeripheralVector peripherals;
+ GetPeripheralsWithFeature(peripherals, FEATURE_JOYSTICK);
+ GetPeripheralsWithFeature(peripherals, FEATURE_KEYBOARD);
+ GetPeripheralsWithFeature(peripherals, FEATURE_MOUSE);
+
+ for (auto& peripheral : peripherals)
+ peripheral->UnregisterJoystickButtonMapper(mapper);
+}
+
+void CPeripherals::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == nullptr)
+ return;
+
+ const std::string& settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_LOCALE_LANGUAGE)
+ {
+ // user set language, no longer use the TV's language
+ PeripheralVector cecDevices;
+ if (GetPeripheralsWithFeature(cecDevices, FEATURE_CEC) > 0)
+ {
+ for (auto& cecDevice : cecDevices)
+ cecDevice->SetSetting("use_tv_menu_language", false);
+ }
+ }
+}
+
+void CPeripherals::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == nullptr)
+ return;
+
+ const std::string& settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_INPUT_PERIPHERALS)
+ CGUIDialogPeripherals::Show(*this);
+ else if (settingId == CSettings::SETTING_INPUT_CONTROLLERCONFIG)
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_GAME_CONTROLLERS);
+ else if (settingId == CSettings::SETTING_INPUT_TESTRUMBLE)
+ TestFeature(FEATURE_RUMBLE);
+ else if (settingId == CSettings::SETTING_INPUT_PERIPHERALLIBRARIES)
+ {
+ std::string strAddonId;
+ if (CGUIWindowAddonBrowser::SelectAddonID(ADDON::AddonType::PERIPHERALDLL, strAddonId, false,
+ true, true, false, true) == 1 &&
+ !strAddonId.empty())
+ {
+ ADDON::AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(strAddonId, addon, ADDON::OnlyEnabled::CHOICE_YES))
+ CGUIDialogAddonSettings::ShowForAddon(addon);
+ }
+ }
+}
+
+void CPeripherals::OnApplicationMessage(MESSAGING::ThreadMessage* pMsg)
+{
+ switch (pMsg->dwMessage)
+ {
+ case TMSG_CECTOGGLESTATE:
+ *static_cast<bool*>(pMsg->lpVoid) = ToggleDeviceState(STATE_SWITCH_TOGGLE);
+ break;
+
+ case TMSG_CECACTIVATESOURCE:
+ ToggleDeviceState(STATE_ACTIVATE_SOURCE);
+ break;
+
+ case TMSG_CECSTANDBY:
+ ToggleDeviceState(STATE_STANDBY);
+ break;
+ }
+}
+
+int CPeripherals::GetMessageMask()
+{
+ return TMSG_MASK_PERIPHERALS;
+}
+
+void CPeripherals::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag == ANNOUNCEMENT::Player &&
+ sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER)
+ {
+ if (message == "OnQuit")
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_INPUT_CONTROLLERPOWEROFF))
+ PowerOffDevices();
+ }
+ }
+}
diff --git a/xbmc/peripherals/Peripherals.h b/xbmc/peripherals/Peripherals.h
new file mode 100644
index 0000000..d9931d7
--- /dev/null
+++ b/xbmc/peripherals/Peripherals.h
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IEventScannerCallback.h"
+#include "bus/PeripheralBus.h"
+#include "devices/Peripheral.h"
+#include "interfaces/IAnnouncer.h"
+#include "messaging/IMessageTarget.h"
+#include "settings/lib/ISettingCallback.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+#include "utils/Observer.h"
+
+#include <memory>
+#include <vector>
+
+class CFileItemList;
+class CInputManager;
+class CSetting;
+class CSettingsCategory;
+class TiXmlElement;
+class CAction;
+class CKey;
+
+namespace KODI
+{
+namespace GAME
+{
+class CControllerManager;
+}
+
+namespace JOYSTICK
+{
+class IButtonMapper;
+}
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CEventScanner;
+
+class CPeripherals : public ISettingCallback,
+ public Observable,
+ public KODI::MESSAGING::IMessageTarget,
+ public IEventScannerCallback,
+ public ANNOUNCEMENT::IAnnouncer
+{
+public:
+ explicit CPeripherals(CInputManager& inputManager,
+ KODI::GAME::CControllerManager& controllerProfiles);
+
+ ~CPeripherals() override;
+
+ /*!
+ * @brief Initialise the peripherals manager.
+ */
+ void Initialise();
+
+ /*!
+ * @brief Clear all data known by the peripherals manager.
+ */
+ void Clear();
+
+ /*!
+ * @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.
+ */
+ PeripheralPtr GetPeripheralAtLocation(const std::string& 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.
+ */
+ bool HasPeripheralAtLocation(const std::string& 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.
+ */
+ PeripheralBusPtr GetBusWithDevice(const std::string& strLocation) const;
+
+ /*!
+ * @brief Check if any busses support the given feature
+ * @param feature The feature to check for
+ * @return True if a bus supports the feature, false otherwise
+ */
+ bool SupportsFeature(PeripheralFeature feature) 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.
+ */
+ int GetPeripheralsWithFeature(PeripheralVector& 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ void 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
+ */
+ 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.
+ */
+ PeripheralBusPtr 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.
+ */
+ void GetDirectory(const std::string& strPath, CFileItemList& items) const;
+
+ /*!
+ * @brief Get the instance of a peripheral given it's path.
+ * @param strPath The path to the peripheral.
+ * @return The peripheral or NULL if it wasn't found.
+ */
+ PeripheralPtr GetByPath(const std::string& 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.
+ */
+ 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.
+ */
+ bool IsMuted();
+
+ /*!
+ * @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.
+ */
+ bool ToggleMute();
+
+ /*!
+ * @brief Try to toggle the playing device state via a peripheral.
+ * @param mode Whether to activate, put on standby or toggle the source.
+ * @return True when the playing device has been switched on, false otherwise.
+ */
+ bool ToggleDeviceState(const CecStateChange mode = STATE_SWITCH_TOGGLE);
+
+ /*!
+ * @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.
+ */
+ bool Mute()
+ {
+ 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.
+ */
+ bool UnMute()
+ {
+ 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.
+ */
+ bool GetNextKeypress(float frameTime, CKey& key);
+
+ /*!
+ * @brief Register with the event scanner to control scan timing
+ * @return A handle that unregisters itself when expired
+ */
+ EventPollHandlePtr RegisterEventPoller();
+
+ /*!
+ * @brief Register with the event scanner to disable event processing
+ * @return A handle that unregisters itself when expired
+ */
+ EventLockHandlePtr RegisterEventLock();
+
+ /*!
+ *
+ */
+ void OnUserNotification();
+
+ /*!
+ * @brief Request peripherals with the specified feature to perform a quick test
+ * @return true if any peripherals support the feature, false otherwise
+ */
+ void TestFeature(PeripheralFeature feature);
+
+ /*!
+ * \brief Request all devices with power-off support to power down
+ */
+ void PowerOffDevices();
+
+ bool SupportsCEC() const
+ {
+#if defined(HAVE_LIBCEC)
+ return true;
+#else
+ return false;
+#endif
+ }
+
+ // implementation of IEventScannerCallback
+ void ProcessEvents(void) override;
+
+ /*!
+ * \brief Initialize button mapping
+ *
+ * This command enables button mapping on all busses. Button maps allow
+ * connect events from the driver to the higher-level features used by
+ * controller profiles.
+ *
+ * If user input is required, a blocking dialog may be shown.
+ */
+ void EnableButtonMapping();
+
+ /*!
+ * \brief Get an add-on that can provide button maps for a device
+ * \return An add-on that provides button maps, or empty if no add-on is found
+ */
+ PeripheralAddonPtr GetAddonWithButtonMap(const CPeripheral* device);
+
+ /*!
+ * \brief Reset all button maps to the defaults for all devices and the given controller
+ * \param controllerId The controller profile to reset
+ * @todo Add a device parameter to allow resetting button maps per-device
+ */
+ void ResetButtonMaps(const std::string& controllerId);
+
+ /*!
+ * \brief Register a button mapper interface
+ * \param mapper The button mapper
+ *
+ * Clients implementing the IButtonMapper interface call
+ * \ref CPeripherals::RegisterJoystickButtonMapper to register themselves
+ * as eligible for button mapping commands.
+ *
+ * When registering the mapper is forwarded to all peripherals. See
+ * \ref CPeripheral::RegisterJoystickButtonMapper for what is done to the
+ * mapper after being given to the peripheral.
+ */
+ void RegisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper);
+
+ /*!
+ * \brief Unregister a button mapper interface
+ * \param mapper The button mapper
+ */
+ void UnregisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper);
+
+ // implementation of ISettingCallback
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+ void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override;
+
+ // implementation of IMessageTarget
+ void OnApplicationMessage(KODI::MESSAGING::ThreadMessage* pMsg) override;
+ int GetMessageMask() override;
+
+ // implementation of IAnnouncer
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
+ /*!
+ * \brief Access the input manager passed to the constructor
+ */
+ CInputManager& GetInputManager() { return m_inputManager; }
+
+ /*!
+ * \brief Access controller profiles through the construction parameter
+ */
+ KODI::GAME::CControllerManager& GetControllerProfiles() { return m_controllerProfiles; }
+
+ /*!
+ * \brief Get a mutex that allows for add-on install tasks to block on each other
+ */
+ CCriticalSection& GetAddonInstallMutex() { return m_addonInstallMutex; }
+
+private:
+ bool LoadMappings();
+ bool GetMappingForDevice(const CPeripheralBus& bus, PeripheralScanResult& result) const;
+ static void GetSettingsFromMappingsFile(
+ TiXmlElement* xmlNode, std::map<std::string, PeripheralDeviceSetting>& m_settings);
+
+ void OnDeviceChanged();
+
+ // Construction parameters
+ CInputManager& m_inputManager;
+ KODI::GAME::CControllerManager& m_controllerProfiles;
+
+#if !defined(HAVE_LIBCEC)
+ bool m_bMissingLibCecWarningDisplayed = false;
+#endif
+ std::vector<PeripheralBusPtr> m_busses;
+ std::vector<PeripheralDeviceMapping> m_mappings;
+ std::unique_ptr<CEventScanner> m_eventScanner;
+ mutable CCriticalSection m_critSectionBusses;
+ mutable CCriticalSection m_critSectionMappings;
+ CCriticalSection m_addonInstallMutex;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/addons/AddonButtonMap.cpp b/xbmc/peripherals/addons/AddonButtonMap.cpp
new file mode 100644
index 0000000..0d76ec9
--- /dev/null
+++ b/xbmc/peripherals/addons/AddonButtonMap.cpp
@@ -0,0 +1,734 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AddonButtonMap.h"
+
+#include "PeripheralAddonTranslator.h"
+#include "input/joysticks/JoystickUtils.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/devices/Peripheral.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <mutex>
+#include <vector>
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+CAddonButtonMap::CAddonButtonMap(CPeripheral* device,
+ const std::weak_ptr<CPeripheralAddon>& addon,
+ const std::string& strControllerId,
+ CPeripherals& manager)
+ : m_device(device), m_addon(addon), m_strControllerId(strControllerId), m_manager(manager)
+{
+ auto peripheralAddon = m_addon.lock();
+ assert(peripheralAddon != nullptr);
+
+ peripheralAddon->RegisterButtonMap(device, this);
+}
+
+CAddonButtonMap::~CAddonButtonMap(void)
+{
+ if (auto addon = m_addon.lock())
+ addon->UnregisterButtonMap(this);
+}
+
+std::string CAddonButtonMap::Location(void) const
+{
+ return m_device->Location();
+}
+
+bool CAddonButtonMap::Load(void)
+{
+ std::string controllerAppearance;
+ FeatureMap features;
+ DriverMap driverMap;
+ PrimitiveVector ignoredPrimitives;
+
+ bool bSuccess = false;
+ if (auto addon = m_addon.lock())
+ {
+ bSuccess |= addon->GetFeatures(m_device, m_strControllerId, features);
+ bSuccess |= addon->GetIgnoredPrimitives(m_device, ignoredPrimitives);
+ }
+
+ if (features.empty())
+ {
+ // Check if we can initialize a buttonmap from the peripheral bus
+ PeripheralBusPtr peripheralBus = m_manager.GetBusByType(m_device->GetBusType());
+ if (peripheralBus)
+ {
+ CLog::Log(LOGDEBUG,
+ "Buttonmap not found for {}, attempting to initialize from peripheral bus",
+ m_device->Location());
+ if (peripheralBus->InitializeButtonMap(*m_device, *this))
+ {
+ bSuccess = true;
+
+ if (auto addon = m_addon.lock())
+ {
+ addon->GetFeatures(m_device, m_strControllerId, features);
+ addon->GetIgnoredPrimitives(m_device, ignoredPrimitives);
+ }
+ }
+ }
+ }
+
+ // GetFeatures() was changed to always return false if no features were
+ // retrieved. Check here, just in case its contract is changed or violated in
+ // the future.
+ if (bSuccess && features.empty())
+ bSuccess = false;
+
+ if (bSuccess)
+ driverMap = CreateLookupTable(features);
+ else
+ CLog::Log(LOGDEBUG, "Failed to load button map for \"{}\"", m_device->Location());
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ m_controllerAppearance = std::move(controllerAppearance);
+ m_features = std::move(features);
+ m_driverMap = std::move(driverMap);
+ m_ignoredPrimitives = CPeripheralAddonTranslator::TranslatePrimitives(ignoredPrimitives);
+ }
+
+ return true;
+}
+
+void CAddonButtonMap::Reset(void)
+{
+ if (auto addon = m_addon.lock())
+ addon->ResetButtonMap(m_device, m_strControllerId);
+}
+
+bool CAddonButtonMap::IsEmpty(void) const
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ return m_driverMap.empty();
+}
+
+std::string CAddonButtonMap::GetAppearance() const
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ return m_controllerAppearance;
+}
+
+bool CAddonButtonMap::SetAppearance(const std::string& controllerId) const
+{
+ return false;
+}
+
+bool CAddonButtonMap::GetFeature(const CDriverPrimitive& primitive, FeatureName& feature)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ DriverMap::const_iterator it = m_driverMap.find(primitive);
+ if (it != m_driverMap.end())
+ {
+ feature = it->second;
+ return true;
+ }
+
+ return false;
+}
+
+FEATURE_TYPE CAddonButtonMap::GetFeatureType(const FeatureName& feature)
+{
+ FEATURE_TYPE type = FEATURE_TYPE::UNKNOWN;
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ type = CPeripheralAddonTranslator::TranslateFeatureType(it->second.Type());
+
+ return type;
+}
+
+bool CAddonButtonMap::GetScalar(const FeatureName& feature, CDriverPrimitive& primitive)
+{
+ bool retVal(false);
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ {
+ const kodi::addon::JoystickFeature& addonFeature = it->second;
+
+ if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_SCALAR ||
+ addonFeature.Type() == JOYSTICK_FEATURE_TYPE_MOTOR)
+ {
+ primitive = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(JOYSTICK_SCALAR_PRIMITIVE));
+ retVal = true;
+ }
+ }
+
+ return retVal;
+}
+
+void CAddonButtonMap::AddScalar(const FeatureName& feature, const CDriverPrimitive& primitive)
+{
+ const bool bMotor = (primitive.Type() == PRIMITIVE_TYPE::MOTOR);
+
+ kodi::addon::JoystickFeature scalar(feature, bMotor ? JOYSTICK_FEATURE_TYPE_MOTOR
+ : JOYSTICK_FEATURE_TYPE_SCALAR);
+ scalar.SetPrimitive(JOYSTICK_SCALAR_PRIMITIVE,
+ CPeripheralAddonTranslator::TranslatePrimitive(primitive));
+
+ if (auto addon = m_addon.lock())
+ addon->MapFeature(m_device, m_strControllerId, scalar);
+}
+
+bool CAddonButtonMap::GetAnalogStick(const FeatureName& feature,
+ JOYSTICK::ANALOG_STICK_DIRECTION direction,
+ JOYSTICK::CDriverPrimitive& primitive)
+{
+ bool retVal(false);
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ {
+ const kodi::addon::JoystickFeature& addonFeature = it->second;
+
+ if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_ANALOG_STICK)
+ {
+ primitive = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(GetAnalogStickIndex(direction)));
+ retVal = primitive.IsValid();
+ }
+ }
+
+ return retVal;
+}
+
+void CAddonButtonMap::AddAnalogStick(const FeatureName& feature,
+ JOYSTICK::ANALOG_STICK_DIRECTION direction,
+ const JOYSTICK::CDriverPrimitive& primitive)
+{
+ using namespace JOYSTICK;
+
+ JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetAnalogStickIndex(direction);
+ kodi::addon::DriverPrimitive addonPrimitive =
+ CPeripheralAddonTranslator::TranslatePrimitive(primitive);
+
+ kodi::addon::JoystickFeature analogStick(feature, JOYSTICK_FEATURE_TYPE_ANALOG_STICK);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ auto it = m_features.find(feature);
+ if (it != m_features.end())
+ analogStick = it->second;
+ }
+
+ const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive(
+ analogStick.Primitive(primitiveIndex)));
+ if (bModified)
+ analogStick.SetPrimitive(primitiveIndex, addonPrimitive);
+
+ if (auto addon = m_addon.lock())
+ addon->MapFeature(m_device, m_strControllerId, analogStick);
+
+ // Because each direction is mapped individually, we need to refresh the
+ // feature each time a new direction is mapped.
+ if (bModified)
+ Load();
+}
+
+bool CAddonButtonMap::GetRelativePointer(const FeatureName& feature,
+ JOYSTICK::RELATIVE_POINTER_DIRECTION direction,
+ JOYSTICK::CDriverPrimitive& primitive)
+{
+ bool retVal(false);
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ {
+ const kodi::addon::JoystickFeature& addonFeature = it->second;
+
+ if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_RELPOINTER)
+ {
+ primitive = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(GetRelativePointerIndex(direction)));
+ retVal = primitive.IsValid();
+ }
+ }
+
+ return retVal;
+}
+
+void CAddonButtonMap::AddRelativePointer(const FeatureName& feature,
+ JOYSTICK::RELATIVE_POINTER_DIRECTION direction,
+ const JOYSTICK::CDriverPrimitive& primitive)
+{
+ using namespace JOYSTICK;
+
+ JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetRelativePointerIndex(direction);
+ kodi::addon::DriverPrimitive addonPrimitive =
+ CPeripheralAddonTranslator::TranslatePrimitive(primitive);
+
+ kodi::addon::JoystickFeature relPointer(feature, JOYSTICK_FEATURE_TYPE_RELPOINTER);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ auto it = m_features.find(feature);
+ if (it != m_features.end())
+ relPointer = it->second;
+ }
+
+ const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive(
+ relPointer.Primitive(primitiveIndex)));
+ if (bModified)
+ relPointer.SetPrimitive(primitiveIndex, addonPrimitive);
+
+ if (auto addon = m_addon.lock())
+ addon->MapFeature(m_device, m_strControllerId, relPointer);
+
+ // Because each direction is mapped individually, we need to refresh the
+ // feature each time a new direction is mapped.
+ if (bModified)
+ Load();
+}
+
+bool CAddonButtonMap::GetAccelerometer(const FeatureName& feature,
+ CDriverPrimitive& positiveX,
+ CDriverPrimitive& positiveY,
+ CDriverPrimitive& positiveZ)
+{
+ bool retVal(false);
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ {
+ const kodi::addon::JoystickFeature& addonFeature = it->second;
+
+ if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_ACCELEROMETER)
+ {
+ positiveX = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(JOYSTICK_ACCELEROMETER_POSITIVE_X));
+ positiveY = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(JOYSTICK_ACCELEROMETER_POSITIVE_Y));
+ positiveZ = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(JOYSTICK_ACCELEROMETER_POSITIVE_Z));
+ retVal = true;
+ }
+ }
+
+ return retVal;
+}
+
+void CAddonButtonMap::AddAccelerometer(const FeatureName& feature,
+ const CDriverPrimitive& positiveX,
+ const CDriverPrimitive& positiveY,
+ const CDriverPrimitive& positiveZ)
+{
+ using namespace JOYSTICK;
+
+ kodi::addon::JoystickFeature accelerometer(feature, JOYSTICK_FEATURE_TYPE_ACCELEROMETER);
+
+ accelerometer.SetPrimitive(JOYSTICK_ACCELEROMETER_POSITIVE_X,
+ CPeripheralAddonTranslator::TranslatePrimitive(positiveX));
+ accelerometer.SetPrimitive(JOYSTICK_ACCELEROMETER_POSITIVE_Y,
+ CPeripheralAddonTranslator::TranslatePrimitive(positiveY));
+ accelerometer.SetPrimitive(JOYSTICK_ACCELEROMETER_POSITIVE_Z,
+ CPeripheralAddonTranslator::TranslatePrimitive(positiveZ));
+
+ if (auto addon = m_addon.lock())
+ addon->MapFeature(m_device, m_strControllerId, accelerometer);
+}
+
+bool CAddonButtonMap::GetWheel(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::WHEEL_DIRECTION direction,
+ KODI::JOYSTICK::CDriverPrimitive& primitive)
+{
+ bool retVal(false);
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ {
+ const kodi::addon::JoystickFeature& addonFeature = it->second;
+
+ if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_WHEEL)
+ {
+ primitive = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(GetPrimitiveIndex(direction)));
+ retVal = primitive.IsValid();
+ }
+ }
+
+ return retVal;
+}
+
+void CAddonButtonMap::AddWheel(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::WHEEL_DIRECTION direction,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive)
+{
+ using namespace JOYSTICK;
+
+ JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetPrimitiveIndex(direction);
+ kodi::addon::DriverPrimitive addonPrimitive =
+ CPeripheralAddonTranslator::TranslatePrimitive(primitive);
+
+ kodi::addon::JoystickFeature joystickFeature(feature, JOYSTICK_FEATURE_TYPE_WHEEL);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ auto it = m_features.find(feature);
+ if (it != m_features.end())
+ joystickFeature = it->second;
+ }
+
+ const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive(
+ joystickFeature.Primitive(primitiveIndex)));
+ if (bModified)
+ joystickFeature.SetPrimitive(primitiveIndex, addonPrimitive);
+
+ if (auto addon = m_addon.lock())
+ addon->MapFeature(m_device, m_strControllerId, joystickFeature);
+
+ // Because each direction is mapped individually, we need to refresh the
+ // feature each time a new direction is mapped.
+ if (bModified)
+ Load();
+}
+
+bool CAddonButtonMap::GetThrottle(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::THROTTLE_DIRECTION direction,
+ KODI::JOYSTICK::CDriverPrimitive& primitive)
+{
+ bool retVal(false);
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ {
+ const kodi::addon::JoystickFeature& addonFeature = it->second;
+
+ if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_THROTTLE)
+ {
+ primitive = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(GetPrimitiveIndex(direction)));
+ retVal = primitive.IsValid();
+ }
+ }
+
+ return retVal;
+}
+
+void CAddonButtonMap::AddThrottle(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::THROTTLE_DIRECTION direction,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive)
+{
+ using namespace JOYSTICK;
+
+ JOYSTICK_FEATURE_PRIMITIVE primitiveIndex = GetPrimitiveIndex(direction);
+ kodi::addon::DriverPrimitive addonPrimitive =
+ CPeripheralAddonTranslator::TranslatePrimitive(primitive);
+
+ kodi::addon::JoystickFeature joystickFeature(feature, JOYSTICK_FEATURE_TYPE_THROTTLE);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ auto it = m_features.find(feature);
+ if (it != m_features.end())
+ joystickFeature = it->second;
+ }
+
+ const bool bModified = (primitive != CPeripheralAddonTranslator::TranslatePrimitive(
+ joystickFeature.Primitive(primitiveIndex)));
+ if (bModified)
+ joystickFeature.SetPrimitive(primitiveIndex, addonPrimitive);
+
+ if (auto addon = m_addon.lock())
+ addon->MapFeature(m_device, m_strControllerId, joystickFeature);
+
+ // Because each direction is mapped individually, we need to refresh the
+ // feature each time a new direction is mapped.
+ if (bModified)
+ Load();
+}
+
+bool CAddonButtonMap::GetKey(const FeatureName& feature, CDriverPrimitive& primitive)
+{
+ bool retVal(false);
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ FeatureMap::const_iterator it = m_features.find(feature);
+ if (it != m_features.end())
+ {
+ const kodi::addon::JoystickFeature& addonFeature = it->second;
+
+ if (addonFeature.Type() == JOYSTICK_FEATURE_TYPE_KEY)
+ {
+ primitive = CPeripheralAddonTranslator::TranslatePrimitive(
+ addonFeature.Primitive(JOYSTICK_SCALAR_PRIMITIVE));
+ retVal = true;
+ }
+ }
+
+ return retVal;
+}
+
+void CAddonButtonMap::AddKey(const FeatureName& feature, const CDriverPrimitive& primitive)
+{
+ kodi::addon::JoystickFeature scalar(feature, JOYSTICK_FEATURE_TYPE_KEY);
+ scalar.SetPrimitive(JOYSTICK_SCALAR_PRIMITIVE,
+ CPeripheralAddonTranslator::TranslatePrimitive(primitive));
+
+ if (auto addon = m_addon.lock())
+ addon->MapFeature(m_device, m_strControllerId, scalar);
+}
+
+void CAddonButtonMap::SetIgnoredPrimitives(
+ const std::vector<JOYSTICK::CDriverPrimitive>& primitives)
+{
+ if (auto addon = m_addon.lock())
+ addon->SetIgnoredPrimitives(m_device,
+ CPeripheralAddonTranslator::TranslatePrimitives(primitives));
+}
+
+bool CAddonButtonMap::IsIgnored(const JOYSTICK::CDriverPrimitive& primitive)
+{
+ return std::find(m_ignoredPrimitives.begin(), m_ignoredPrimitives.end(), primitive) !=
+ m_ignoredPrimitives.end();
+}
+
+bool CAddonButtonMap::GetAxisProperties(unsigned int axisIndex, int& center, unsigned int& range)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ for (const auto& it : m_driverMap)
+ {
+ const CDriverPrimitive& primitive = it.first;
+
+ if (primitive.Type() != PRIMITIVE_TYPE::SEMIAXIS)
+ continue;
+
+ if (primitive.Index() != axisIndex)
+ continue;
+
+ center = primitive.Center();
+ range = primitive.Range();
+ return true;
+ }
+
+ return false;
+}
+
+void CAddonButtonMap::SaveButtonMap()
+{
+ if (auto addon = m_addon.lock())
+ addon->SaveButtonMap(m_device);
+}
+
+void CAddonButtonMap::RevertButtonMap()
+{
+ if (auto addon = m_addon.lock())
+ addon->RevertButtonMap(m_device);
+}
+
+CAddonButtonMap::DriverMap CAddonButtonMap::CreateLookupTable(const FeatureMap& features)
+{
+ using namespace JOYSTICK;
+
+ DriverMap driverMap;
+
+ for (const auto& it : features)
+ {
+ const kodi::addon::JoystickFeature& feature = it.second;
+
+ switch (feature.Type())
+ {
+ case JOYSTICK_FEATURE_TYPE_SCALAR:
+ case JOYSTICK_FEATURE_TYPE_KEY:
+ {
+ driverMap[CPeripheralAddonTranslator::TranslatePrimitive(
+ feature.Primitive(JOYSTICK_SCALAR_PRIMITIVE))] = it.first;
+ break;
+ }
+
+ case JOYSTICK_FEATURE_TYPE_ANALOG_STICK:
+ {
+ std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = {
+ JOYSTICK_ANALOG_STICK_UP,
+ JOYSTICK_ANALOG_STICK_DOWN,
+ JOYSTICK_ANALOG_STICK_RIGHT,
+ JOYSTICK_ANALOG_STICK_LEFT,
+ };
+
+ for (auto primitive : primitives)
+ driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] =
+ it.first;
+ break;
+ }
+
+ case JOYSTICK_FEATURE_TYPE_ACCELEROMETER:
+ {
+ std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = {
+ JOYSTICK_ACCELEROMETER_POSITIVE_X,
+ JOYSTICK_ACCELEROMETER_POSITIVE_Y,
+ JOYSTICK_ACCELEROMETER_POSITIVE_Z,
+ };
+
+ for (auto primitive : primitives)
+ {
+ CDriverPrimitive translatedPrimitive =
+ CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive));
+ driverMap[translatedPrimitive] = it.first;
+
+ // Map opposite semiaxis
+ CDriverPrimitive oppositePrimitive = CDriverPrimitive(
+ translatedPrimitive.Index(), 0, translatedPrimitive.SemiAxisDirection() * -1, 1);
+ driverMap[oppositePrimitive] = it.first;
+ }
+ break;
+ }
+
+ case JOYSTICK_FEATURE_TYPE_WHEEL:
+ {
+ std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = {
+ JOYSTICK_WHEEL_LEFT,
+ JOYSTICK_WHEEL_RIGHT,
+ };
+
+ for (auto primitive : primitives)
+ driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] =
+ it.first;
+ break;
+ }
+
+ case JOYSTICK_FEATURE_TYPE_THROTTLE:
+ {
+ std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = {
+ JOYSTICK_THROTTLE_UP,
+ JOYSTICK_THROTTLE_DOWN,
+ };
+
+ for (auto primitive : primitives)
+ driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] =
+ it.first;
+ break;
+ }
+
+ case JOYSTICK_FEATURE_TYPE_RELPOINTER:
+ {
+ std::vector<JOYSTICK_FEATURE_PRIMITIVE> primitives = {
+ JOYSTICK_RELPOINTER_UP,
+ JOYSTICK_RELPOINTER_DOWN,
+ JOYSTICK_RELPOINTER_RIGHT,
+ JOYSTICK_RELPOINTER_LEFT,
+ };
+
+ for (auto primitive : primitives)
+ driverMap[CPeripheralAddonTranslator::TranslatePrimitive(feature.Primitive(primitive))] =
+ it.first;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ return driverMap;
+}
+
+JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetAnalogStickIndex(
+ JOYSTICK::ANALOG_STICK_DIRECTION dir)
+{
+ using namespace JOYSTICK;
+
+ switch (dir)
+ {
+ case ANALOG_STICK_DIRECTION::UP:
+ return JOYSTICK_ANALOG_STICK_UP;
+ case ANALOG_STICK_DIRECTION::DOWN:
+ return JOYSTICK_ANALOG_STICK_DOWN;
+ case ANALOG_STICK_DIRECTION::RIGHT:
+ return JOYSTICK_ANALOG_STICK_RIGHT;
+ case ANALOG_STICK_DIRECTION::LEFT:
+ return JOYSTICK_ANALOG_STICK_LEFT;
+ default:
+ break;
+ }
+
+ return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0);
+}
+
+JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetRelativePointerIndex(
+ JOYSTICK::RELATIVE_POINTER_DIRECTION dir)
+{
+ using namespace JOYSTICK;
+
+ switch (dir)
+ {
+ case RELATIVE_POINTER_DIRECTION::UP:
+ return JOYSTICK_RELPOINTER_UP;
+ case RELATIVE_POINTER_DIRECTION::DOWN:
+ return JOYSTICK_RELPOINTER_DOWN;
+ case RELATIVE_POINTER_DIRECTION::RIGHT:
+ return JOYSTICK_RELPOINTER_RIGHT;
+ case RELATIVE_POINTER_DIRECTION::LEFT:
+ return JOYSTICK_RELPOINTER_LEFT;
+ default:
+ break;
+ }
+
+ return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0);
+}
+
+JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetPrimitiveIndex(JOYSTICK::WHEEL_DIRECTION dir)
+{
+ using namespace JOYSTICK;
+
+ switch (dir)
+ {
+ case WHEEL_DIRECTION::RIGHT:
+ return JOYSTICK_WHEEL_RIGHT;
+ case WHEEL_DIRECTION::LEFT:
+ return JOYSTICK_WHEEL_LEFT;
+ default:
+ break;
+ }
+
+ return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0);
+}
+
+JOYSTICK_FEATURE_PRIMITIVE CAddonButtonMap::GetPrimitiveIndex(JOYSTICK::THROTTLE_DIRECTION dir)
+{
+ using namespace JOYSTICK;
+
+ switch (dir)
+ {
+ case THROTTLE_DIRECTION::UP:
+ return JOYSTICK_THROTTLE_UP;
+ case THROTTLE_DIRECTION::DOWN:
+ return JOYSTICK_THROTTLE_DOWN;
+ default:
+ break;
+ }
+
+ return static_cast<JOYSTICK_FEATURE_PRIMITIVE>(0);
+}
diff --git a/xbmc/peripherals/addons/AddonButtonMap.h b/xbmc/peripherals/addons/AddonButtonMap.h
new file mode 100644
index 0000000..cade54b
--- /dev/null
+++ b/xbmc/peripherals/addons/AddonButtonMap.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PeripheralAddon.h" // for FeatureMap
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "input/joysticks/interfaces/IButtonMap.h"
+#include "peripherals/PeripheralTypes.h"
+#include "threads/CriticalSection.h"
+
+namespace PERIPHERALS
+{
+class CPeripheral;
+class CPeripherals;
+
+class CAddonButtonMap : public KODI::JOYSTICK::IButtonMap
+{
+public:
+ CAddonButtonMap(CPeripheral* device,
+ const std::weak_ptr<CPeripheralAddon>& addon,
+ const std::string& strControllerId,
+ CPeripherals& manager);
+
+ ~CAddonButtonMap(void) override;
+
+ // Implementation of IButtonMap
+ std::string ControllerID(void) const override { return m_strControllerId; }
+
+ std::string Location(void) const override;
+
+ bool Load(void) override;
+
+ void Reset(void) override;
+
+ bool IsEmpty(void) const override;
+
+ std::string GetAppearance() const override;
+
+ bool SetAppearance(const std::string& controllerId) const override;
+
+ bool GetFeature(const KODI::JOYSTICK::CDriverPrimitive& primitive,
+ KODI::JOYSTICK::FeatureName& feature) override;
+
+ KODI::JOYSTICK::FEATURE_TYPE GetFeatureType(const KODI::JOYSTICK::FeatureName& feature) override;
+
+ bool GetScalar(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ void AddScalar(const KODI::JOYSTICK::FeatureName& feature,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ bool GetAnalogStick(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::ANALOG_STICK_DIRECTION direction,
+ KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ void AddAnalogStick(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::ANALOG_STICK_DIRECTION direction,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ bool GetRelativePointer(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION direction,
+ KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ void AddRelativePointer(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION direction,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ bool GetAccelerometer(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::CDriverPrimitive& positiveX,
+ KODI::JOYSTICK::CDriverPrimitive& positiveY,
+ KODI::JOYSTICK::CDriverPrimitive& positiveZ) override;
+
+ void AddAccelerometer(const KODI::JOYSTICK::FeatureName& feature,
+ const KODI::JOYSTICK::CDriverPrimitive& positiveX,
+ const KODI::JOYSTICK::CDriverPrimitive& positiveY,
+ const KODI::JOYSTICK::CDriverPrimitive& positiveZ) override;
+
+ bool GetWheel(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::WHEEL_DIRECTION direction,
+ KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ void AddWheel(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::WHEEL_DIRECTION direction,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ bool GetThrottle(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::THROTTLE_DIRECTION direction,
+ KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ void AddThrottle(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::THROTTLE_DIRECTION direction,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ bool GetKey(const KODI::JOYSTICK::FeatureName& feature,
+ KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ void AddKey(const KODI::JOYSTICK::FeatureName& feature,
+ const KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ void SetIgnoredPrimitives(
+ const std::vector<KODI::JOYSTICK::CDriverPrimitive>& primitives) override;
+
+ bool IsIgnored(const KODI::JOYSTICK::CDriverPrimitive& primitive) override;
+
+ bool GetAxisProperties(unsigned int axisIndex, int& center, unsigned int& range) override;
+
+ void SaveButtonMap() override;
+
+ void RevertButtonMap() override;
+
+private:
+ typedef std::map<KODI::JOYSTICK::CDriverPrimitive, KODI::JOYSTICK::FeatureName> DriverMap;
+ typedef std::vector<KODI::JOYSTICK::CDriverPrimitive> JoystickPrimitiveVector;
+
+ // Utility functions
+ static DriverMap CreateLookupTable(const FeatureMap& features);
+
+ static JOYSTICK_FEATURE_PRIMITIVE GetAnalogStickIndex(KODI::JOYSTICK::ANALOG_STICK_DIRECTION dir);
+ static JOYSTICK_FEATURE_PRIMITIVE GetRelativePointerIndex(
+ KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION dir);
+ static JOYSTICK_FEATURE_PRIMITIVE GetPrimitiveIndex(KODI::JOYSTICK::WHEEL_DIRECTION dir);
+ static JOYSTICK_FEATURE_PRIMITIVE GetPrimitiveIndex(KODI::JOYSTICK::THROTTLE_DIRECTION dir);
+
+ // Construction parameters
+ CPeripheral* const m_device;
+ const std::weak_ptr<CPeripheralAddon> m_addon;
+ const std::string m_strControllerId;
+ CPeripherals& m_manager;
+
+ // Button map state
+ std::string m_controllerAppearance;
+ FeatureMap m_features;
+ DriverMap m_driverMap;
+ JoystickPrimitiveVector m_ignoredPrimitives;
+
+ // Synchronization parameters
+ mutable CCriticalSection m_mutex;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/addons/AddonButtonMapping.cpp b/xbmc/peripherals/addons/AddonButtonMapping.cpp
new file mode 100644
index 0000000..abf981f
--- /dev/null
+++ b/xbmc/peripherals/addons/AddonButtonMapping.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AddonButtonMapping.h"
+
+#include "input/joysticks/generic/ButtonMapping.h"
+#include "input/joysticks/interfaces/IButtonMapper.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/addons/AddonButtonMap.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+CAddonButtonMapping::CAddonButtonMapping(CPeripherals& manager,
+ CPeripheral* peripheral,
+ IButtonMapper* mapper)
+{
+ PeripheralAddonPtr addon = manager.GetAddonWithButtonMap(peripheral);
+
+ if (!addon)
+ {
+ CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", peripheral->DeviceName());
+ }
+ else
+ {
+ const std::string controllerId = mapper->ControllerID();
+ m_buttonMap = std::make_unique<CAddonButtonMap>(peripheral, addon, controllerId, manager);
+ if (m_buttonMap->Load())
+ {
+ IKeymap* keymap = peripheral->GetKeymap(controllerId);
+ m_buttonMapping.reset(new CButtonMapping(mapper, m_buttonMap.get(), keymap));
+
+ // Allow the mapper to save our button map
+ mapper->SetButtonMapCallback(peripheral->Location(), this);
+ }
+ else
+ m_buttonMap.reset();
+ }
+}
+
+CAddonButtonMapping::~CAddonButtonMapping(void)
+{
+ m_buttonMapping.reset();
+ m_buttonMap.reset();
+}
+
+bool CAddonButtonMapping::OnButtonMotion(unsigned int buttonIndex, bool bPressed)
+{
+ if (m_buttonMapping)
+ return m_buttonMapping->OnButtonMotion(buttonIndex, bPressed);
+
+ return false;
+}
+
+bool CAddonButtonMapping::OnHatMotion(unsigned int hatIndex, HAT_STATE state)
+{
+ if (m_buttonMapping)
+ return m_buttonMapping->OnHatMotion(hatIndex, state);
+
+ return false;
+}
+
+bool CAddonButtonMapping::OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range)
+{
+ if (m_buttonMapping)
+ return m_buttonMapping->OnAxisMotion(axisIndex, position, center, range);
+
+ return false;
+}
+
+void CAddonButtonMapping::OnInputFrame(void)
+{
+ if (m_buttonMapping)
+ m_buttonMapping->OnInputFrame();
+}
+
+bool CAddonButtonMapping::OnKeyPress(const CKey& key)
+{
+ if (m_buttonMapping)
+ return m_buttonMapping->OnKeyPress(key);
+
+ return false;
+}
+
+void CAddonButtonMapping::OnKeyRelease(const CKey& key)
+{
+ if (m_buttonMapping)
+ m_buttonMapping->OnKeyRelease(key);
+}
+
+bool CAddonButtonMapping::OnPosition(int x, int y)
+{
+ if (m_buttonMapping)
+ return m_buttonMapping->OnPosition(x, y);
+
+ return false;
+}
+
+bool CAddonButtonMapping::OnButtonPress(MOUSE::BUTTON_ID button)
+{
+ if (m_buttonMapping)
+ return m_buttonMapping->OnButtonPress(button);
+
+ return false;
+}
+
+void CAddonButtonMapping::OnButtonRelease(MOUSE::BUTTON_ID button)
+{
+ if (m_buttonMapping)
+ m_buttonMapping->OnButtonRelease(button);
+}
+
+void CAddonButtonMapping::SaveButtonMap()
+{
+ if (m_buttonMapping)
+ m_buttonMapping->SaveButtonMap();
+}
+
+void CAddonButtonMapping::ResetIgnoredPrimitives()
+{
+ if (m_buttonMapping)
+ m_buttonMapping->ResetIgnoredPrimitives();
+}
+
+void CAddonButtonMapping::RevertButtonMap()
+{
+ if (m_buttonMapping)
+ m_buttonMapping->RevertButtonMap();
+}
diff --git a/xbmc/peripherals/addons/AddonButtonMapping.h b/xbmc/peripherals/addons/AddonButtonMapping.h
new file mode 100644
index 0000000..245a588
--- /dev/null
+++ b/xbmc/peripherals/addons/AddonButtonMapping.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "input/joysticks/interfaces/IButtonMapCallback.h"
+#include "input/joysticks/interfaces/IDriverHandler.h"
+#include "input/keyboard/interfaces/IKeyboardDriverHandler.h"
+#include "input/mouse/interfaces/IMouseDriverHandler.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CButtonMapping;
+class IButtonMap;
+class IButtonMapper;
+} // namespace JOYSTICK
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CPeripheral;
+class CPeripherals;
+
+class CAddonButtonMapping : public KODI::JOYSTICK::IDriverHandler,
+ public KODI::KEYBOARD::IKeyboardDriverHandler,
+ public KODI::MOUSE::IMouseDriverHandler,
+ public KODI::JOYSTICK::IButtonMapCallback
+{
+public:
+ CAddonButtonMapping(CPeripherals& manager,
+ CPeripheral* peripheral,
+ KODI::JOYSTICK::IButtonMapper* mapper);
+
+ ~CAddonButtonMapping(void) override;
+
+ // implementation of IDriverHandler
+ bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override;
+ bool OnHatMotion(unsigned int hatIndex, KODI::JOYSTICK::HAT_STATE state) override;
+ bool OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range) override;
+ void OnInputFrame(void) override;
+
+ // implementation of IKeyboardDriverHandler
+ bool OnKeyPress(const CKey& key) override;
+ void OnKeyRelease(const CKey& key) override;
+
+ // implementation of IMouseDriverHandler
+ bool OnPosition(int x, int y) override;
+ bool OnButtonPress(KODI::MOUSE::BUTTON_ID button) override;
+ void OnButtonRelease(KODI::MOUSE::BUTTON_ID button) override;
+
+ // implementation of IButtonMapCallback
+ void SaveButtonMap() override;
+ void ResetIgnoredPrimitives() override;
+ void RevertButtonMap() override;
+
+private:
+ std::unique_ptr<KODI::JOYSTICK::CButtonMapping> m_buttonMapping;
+ std::unique_ptr<KODI::JOYSTICK::IButtonMap> m_buttonMap;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/addons/AddonInputHandling.cpp b/xbmc/peripherals/addons/AddonInputHandling.cpp
new file mode 100644
index 0000000..7490b96
--- /dev/null
+++ b/xbmc/peripherals/addons/AddonInputHandling.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AddonInputHandling.h"
+
+#include "input/joysticks/generic/DriverReceiving.h"
+#include "input/joysticks/generic/InputHandling.h"
+#include "input/joysticks/interfaces/IDriverReceiver.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "input/keyboard/generic/KeyboardInputHandling.h"
+#include "input/keyboard/interfaces/IKeyboardInputHandler.h"
+#include "input/mouse/generic/MouseInputHandling.h"
+#include "input/mouse/interfaces/IMouseInputHandler.h"
+#include "peripherals/addons/AddonButtonMap.h"
+#include "peripherals/devices/Peripheral.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+CAddonInputHandling::CAddonInputHandling(CPeripherals& manager,
+ CPeripheral* peripheral,
+ std::shared_ptr<CPeripheralAddon> addon,
+ IInputHandler* handler,
+ IDriverReceiver* receiver)
+ : m_manager(manager),
+ m_peripheral(peripheral),
+ m_addon(std::move(addon)),
+ m_joystickInputHandler(handler),
+ m_joystickDriverReceiver(receiver)
+{
+}
+
+CAddonInputHandling::CAddonInputHandling(CPeripherals& manager,
+ CPeripheral* peripheral,
+ std::shared_ptr<CPeripheralAddon> addon,
+ KEYBOARD::IKeyboardInputHandler* handler)
+ : m_manager(manager),
+ m_peripheral(peripheral),
+ m_addon(std::move(addon)),
+ m_keyboardInputHandler(handler)
+{
+}
+
+CAddonInputHandling::CAddonInputHandling(CPeripherals& manager,
+ CPeripheral* peripheral,
+ std::shared_ptr<CPeripheralAddon> addon,
+ MOUSE::IMouseInputHandler* handler)
+ : m_manager(manager),
+ m_peripheral(peripheral),
+ m_addon(std::move(addon)),
+ m_mouseInputHandler(handler)
+{
+}
+
+CAddonInputHandling::~CAddonInputHandling(void)
+{
+ m_joystickDriverHandler.reset();
+ m_joystickInputReceiver.reset();
+ m_keyboardDriverHandler.reset();
+ m_mouseDriverHandler.reset();
+ m_buttonMap.reset();
+}
+
+bool CAddonInputHandling::Load()
+{
+ std::string controllerId;
+ if (m_joystickInputHandler != nullptr)
+ controllerId = m_joystickInputHandler->ControllerID();
+ else if (m_keyboardInputHandler != nullptr)
+ controllerId = m_keyboardInputHandler->ControllerID();
+ else if (m_mouseInputHandler != nullptr)
+ controllerId = m_mouseInputHandler->ControllerID();
+
+ if (!controllerId.empty())
+ m_buttonMap = std::make_unique<CAddonButtonMap>(m_peripheral, m_addon, controllerId, m_manager);
+
+ if (m_buttonMap && m_buttonMap->Load())
+ {
+ if (m_joystickInputHandler != nullptr)
+ {
+ m_joystickDriverHandler =
+ std::make_unique<CInputHandling>(m_joystickInputHandler, m_buttonMap.get());
+ if (m_joystickDriverReceiver != nullptr)
+ {
+ m_joystickInputReceiver =
+ std::make_unique<CDriverReceiving>(m_joystickDriverReceiver, m_buttonMap.get());
+
+ // Interfaces are connected here because they share button map as a common resource
+ m_joystickInputHandler->SetInputReceiver(m_joystickInputReceiver.get());
+ }
+ return true;
+ }
+ else if (m_keyboardInputHandler != nullptr)
+ {
+ m_keyboardDriverHandler = std::make_unique<KEYBOARD::CKeyboardInputHandling>(
+ m_keyboardInputHandler, m_buttonMap.get());
+ return true;
+ }
+ else if (m_mouseInputHandler != nullptr)
+ {
+ m_mouseDriverHandler =
+ std::make_unique<MOUSE::CMouseInputHandling>(m_mouseInputHandler, m_buttonMap.get());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CAddonInputHandling::OnButtonMotion(unsigned int buttonIndex, bool bPressed)
+{
+ if (m_joystickDriverHandler)
+ return m_joystickDriverHandler->OnButtonMotion(buttonIndex, bPressed);
+
+ return false;
+}
+
+bool CAddonInputHandling::OnHatMotion(unsigned int hatIndex, HAT_STATE state)
+{
+ if (m_joystickDriverHandler)
+ return m_joystickDriverHandler->OnHatMotion(hatIndex, state);
+
+ return false;
+}
+
+bool CAddonInputHandling::OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range)
+{
+ if (m_joystickDriverHandler)
+ return m_joystickDriverHandler->OnAxisMotion(axisIndex, position, center, range);
+
+ return false;
+}
+
+void CAddonInputHandling::OnInputFrame(void)
+{
+ if (m_joystickDriverHandler)
+ m_joystickDriverHandler->OnInputFrame();
+}
+
+bool CAddonInputHandling::OnKeyPress(const CKey& key)
+{
+ if (m_keyboardDriverHandler)
+ return m_keyboardDriverHandler->OnKeyPress(key);
+
+ return false;
+}
+
+void CAddonInputHandling::OnKeyRelease(const CKey& key)
+{
+ if (m_keyboardDriverHandler)
+ m_keyboardDriverHandler->OnKeyRelease(key);
+}
+
+bool CAddonInputHandling::OnPosition(int x, int y)
+{
+ if (m_mouseDriverHandler)
+ return m_mouseDriverHandler->OnPosition(x, y);
+
+ return false;
+}
+
+bool CAddonInputHandling::OnButtonPress(MOUSE::BUTTON_ID button)
+{
+ if (m_mouseDriverHandler)
+ return m_mouseDriverHandler->OnButtonPress(button);
+
+ return false;
+}
+
+void CAddonInputHandling::OnButtonRelease(MOUSE::BUTTON_ID button)
+{
+ if (m_mouseDriverHandler)
+ m_mouseDriverHandler->OnButtonRelease(button);
+}
+
+bool CAddonInputHandling::SetRumbleState(const JOYSTICK::FeatureName& feature, float magnitude)
+{
+ if (m_joystickInputReceiver)
+ return m_joystickInputReceiver->SetRumbleState(feature, magnitude);
+
+ return false;
+}
diff --git a/xbmc/peripherals/addons/AddonInputHandling.h b/xbmc/peripherals/addons/AddonInputHandling.h
new file mode 100644
index 0000000..295e6d4
--- /dev/null
+++ b/xbmc/peripherals/addons/AddonInputHandling.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "input/joysticks/interfaces/IDriverHandler.h"
+#include "input/joysticks/interfaces/IInputReceiver.h"
+#include "input/keyboard/interfaces/IKeyboardDriverHandler.h"
+#include "input/mouse/interfaces/IMouseDriverHandler.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IButtonMap;
+class IDriverReceiver;
+class IInputHandler;
+} // namespace JOYSTICK
+
+namespace KEYBOARD
+{
+class IKeyboardInputHandler;
+}
+
+namespace MOUSE
+{
+class IMouseInputHandler;
+}
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CPeripheral;
+class CPeripherals;
+class CPeripheralAddon;
+
+class CAddonInputHandling : public KODI::JOYSTICK::IDriverHandler,
+ public KODI::JOYSTICK::IInputReceiver,
+ public KODI::KEYBOARD::IKeyboardDriverHandler,
+ public KODI::MOUSE::IMouseDriverHandler
+{
+public:
+ CAddonInputHandling(CPeripherals& manager,
+ CPeripheral* peripheral,
+ std::shared_ptr<CPeripheralAddon> addon,
+ KODI::JOYSTICK::IInputHandler* handler,
+ KODI::JOYSTICK::IDriverReceiver* receiver);
+
+ CAddonInputHandling(CPeripherals& manager,
+ CPeripheral* peripheral,
+ std::shared_ptr<CPeripheralAddon> addon,
+ KODI::KEYBOARD::IKeyboardInputHandler* handler);
+
+ CAddonInputHandling(CPeripherals& manager,
+ CPeripheral* peripheral,
+ std::shared_ptr<CPeripheralAddon> addon,
+ KODI::MOUSE::IMouseInputHandler* handler);
+
+ ~CAddonInputHandling(void) override;
+
+ bool Load();
+
+ // implementation of IDriverHandler
+ bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override;
+ bool OnHatMotion(unsigned int hatIndex, KODI::JOYSTICK::HAT_STATE state) override;
+ bool OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range) override;
+ void OnInputFrame(void) override;
+
+ // implementation of IKeyboardDriverHandler
+ bool OnKeyPress(const CKey& key) override;
+ void OnKeyRelease(const CKey& key) override;
+
+ // implementation of IMouseDriverHandler
+ bool OnPosition(int x, int y) override;
+ bool OnButtonPress(KODI::MOUSE::BUTTON_ID button) override;
+ void OnButtonRelease(KODI::MOUSE::BUTTON_ID button) override;
+
+ // implementation of IInputReceiver
+ bool SetRumbleState(const KODI::JOYSTICK::FeatureName& feature, float magnitude) override;
+
+private:
+ // Construction parameters
+ CPeripherals& m_manager;
+ CPeripheral* const m_peripheral;
+ const std::shared_ptr<CPeripheralAddon> m_addon;
+ KODI::JOYSTICK::IInputHandler* const m_joystickInputHandler{nullptr};
+ KODI::JOYSTICK::IDriverReceiver* const m_joystickDriverReceiver{nullptr};
+ KODI::KEYBOARD::IKeyboardInputHandler* m_keyboardInputHandler{nullptr};
+ KODI::MOUSE::IMouseInputHandler* const m_mouseInputHandler{nullptr};
+
+ // Input parameters
+ std::unique_ptr<KODI::JOYSTICK::IDriverHandler> m_joystickDriverHandler;
+ std::unique_ptr<KODI::JOYSTICK::IInputReceiver> m_joystickInputReceiver;
+ std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler> m_keyboardDriverHandler;
+ std::unique_ptr<KODI::MOUSE::IMouseDriverHandler> m_mouseDriverHandler;
+ std::unique_ptr<KODI::JOYSTICK::IButtonMap> m_buttonMap;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/addons/CMakeLists.txt b/xbmc/peripherals/addons/CMakeLists.txt
new file mode 100644
index 0000000..9f31978
--- /dev/null
+++ b/xbmc/peripherals/addons/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES AddonButtonMap.cpp
+ AddonButtonMapping.cpp
+ AddonInputHandling.cpp
+ PeripheralAddon.cpp
+ PeripheralAddonTranslator.cpp)
+
+set(HEADERS AddonButtonMap.h
+ AddonButtonMapping.h
+ AddonInputHandling.h
+ PeripheralAddon.h
+ PeripheralAddonTranslator.h)
+
+core_add_library(peripherals_addons)
diff --git a/xbmc/peripherals/addons/PeripheralAddon.cpp b/xbmc/peripherals/addons/PeripheralAddon.cpp
new file mode 100644
index 0000000..7a55bb3
--- /dev/null
+++ b/xbmc/peripherals/addons/PeripheralAddon.cpp
@@ -0,0 +1,990 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralAddon.h"
+
+#include "FileItem.h"
+#include "PeripheralAddonTranslator.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "filesystem/Directory.h"
+#include "filesystem/SpecialProtocol.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/interfaces/IButtonMap.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/bus/virtual/PeripheralBusAddon.h"
+#include "peripherals/devices/PeripheralJoystick.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+#include <shared_mutex>
+#include <string.h>
+#include <utility>
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+using namespace XFILE;
+
+#define KEYBOARD_BUTTON_MAP_NAME "Keyboard"
+#define KEYBOARD_PROVIDER "application"
+
+#define MOUSE_BUTTON_MAP_NAME "Mouse"
+#define MOUSE_PROVIDER "application"
+
+#ifndef SAFE_DELETE
+#define SAFE_DELETE(p) \
+ do \
+ { \
+ delete (p); \
+ (p) = NULL; \
+ } while (0)
+#endif
+
+CPeripheralAddon::CPeripheralAddon(const ADDON::AddonInfoPtr& addonInfo, CPeripherals& manager)
+ : IAddonInstanceHandler(ADDON_INSTANCE_PERIPHERAL, addonInfo),
+ m_manager(manager),
+ m_bSupportsJoystickRumble(false),
+ m_bSupportsJoystickPowerOff(false)
+{
+ m_bProvidesJoysticks =
+ addonInfo->Type(ADDON::AddonType::PERIPHERALDLL)->GetValue("@provides_joysticks").asBoolean();
+ m_bProvidesButtonMaps = addonInfo->Type(ADDON::AddonType::PERIPHERALDLL)
+ ->GetValue("@provides_buttonmaps")
+ .asBoolean();
+
+ // Create "C" interface structures, used as own parts to prevent API problems on update
+ m_ifc.peripheral = new AddonInstance_Peripheral;
+ m_ifc.peripheral->props = new AddonProps_Peripheral();
+ m_ifc.peripheral->toAddon = new KodiToAddonFuncTable_Peripheral();
+ m_ifc.peripheral->toKodi = new AddonToKodiFuncTable_Peripheral();
+
+ ResetProperties();
+}
+
+CPeripheralAddon::~CPeripheralAddon(void)
+{
+ DestroyAddon();
+
+ if (m_ifc.peripheral)
+ {
+ delete m_ifc.peripheral->toAddon;
+ delete m_ifc.peripheral->toKodi;
+ delete m_ifc.peripheral->props;
+ }
+ delete m_ifc.peripheral;
+}
+
+void CPeripheralAddon::ResetProperties(void)
+{
+ // Initialise members
+ m_strUserPath = CSpecialProtocol::TranslatePath(Profile());
+ m_strClientPath = CSpecialProtocol::TranslatePath(Path());
+
+ m_ifc.peripheral->props->user_path = m_strUserPath.c_str();
+ m_ifc.peripheral->props->addon_path = m_strClientPath.c_str();
+
+ m_ifc.peripheral->toKodi->kodiInstance = this;
+ m_ifc.peripheral->toKodi->feature_count = cb_feature_count;
+ m_ifc.peripheral->toKodi->feature_type = cb_feature_type;
+ m_ifc.peripheral->toKodi->refresh_button_maps = cb_refresh_button_maps;
+ m_ifc.peripheral->toKodi->trigger_scan = cb_trigger_scan;
+
+ memset(m_ifc.peripheral->toAddon, 0, sizeof(KodiToAddonFuncTable_Peripheral));
+}
+
+bool CPeripheralAddon::CreateAddon(void)
+{
+ std::unique_lock<CSharedSection> lock(m_dllSection);
+
+ // Reset all properties to defaults
+ ResetProperties();
+
+ // Create directory for user data
+ if (!CDirectory::Exists(m_strUserPath))
+ CDirectory::Create(m_strUserPath);
+
+ // Initialise the add-on
+ CLog::Log(LOGDEBUG, "PERIPHERAL - {} - creating peripheral add-on instance '{}'", __FUNCTION__,
+ Name());
+
+ if (CreateInstance() != ADDON_STATUS_OK)
+ return false;
+
+ if (!GetAddonProperties())
+ {
+ DestroyInstance();
+ return false;
+ }
+
+ return true;
+}
+
+void CPeripheralAddon::DestroyAddon()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_peripherals.clear();
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_buttonMapMutex);
+ // only clear buttonMaps but don't delete them as they are owned by a
+ // CAddonJoystickInputHandling instance
+ m_buttonMaps.clear();
+ }
+
+ {
+ std::unique_lock<CSharedSection> lock(m_dllSection);
+ DestroyInstance();
+ }
+}
+
+bool CPeripheralAddon::GetAddonProperties(void)
+{
+ PERIPHERAL_CAPABILITIES addonCapabilities = {};
+
+ // Get the capabilities
+ m_ifc.peripheral->toAddon->get_capabilities(m_ifc.peripheral, &addonCapabilities);
+
+ // Verify capabilities against addon.xml
+ if (m_bProvidesJoysticks != addonCapabilities.provides_joysticks)
+ {
+ CLog::Log(
+ LOGERROR,
+ "PERIPHERAL - Add-on '{}': provides_joysticks'({}) in add-on DLL doesn't match "
+ "'provides_joysticks'({}) in addon.xml. Please contact the developer of this add-on: {}",
+ Name(), addonCapabilities.provides_joysticks ? "true" : "false",
+ m_bProvidesJoysticks ? "true" : "false", Author());
+ return false;
+ }
+ if (m_bProvidesButtonMaps != addonCapabilities.provides_buttonmaps)
+ {
+ CLog::Log(
+ LOGERROR,
+ "PERIPHERAL - Add-on '{}': provides_buttonmaps' ({}) in add-on DLL doesn't match "
+ "'provides_buttonmaps' ({}) in addon.xml. Please contact the developer of this add-on: {}",
+ Name(), addonCapabilities.provides_buttonmaps ? "true" : "false",
+ m_bProvidesButtonMaps ? "true" : "false", Author());
+ return false;
+ }
+
+ // Read properties that depend on underlying driver
+ m_bSupportsJoystickRumble = addonCapabilities.provides_joystick_rumble;
+ m_bSupportsJoystickPowerOff = addonCapabilities.provides_joystick_power_off;
+
+ return true;
+}
+
+bool CPeripheralAddon::Register(unsigned int peripheralIndex, const PeripheralPtr& peripheral)
+{
+ if (!peripheral)
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_peripherals.find(peripheralIndex) == m_peripherals.end())
+ {
+ if (peripheral->Type() == PERIPHERAL_JOYSTICK)
+ {
+ m_peripherals[peripheralIndex] = std::static_pointer_cast<CPeripheralJoystick>(peripheral);
+
+ CLog::Log(LOGINFO, "{} - new {} device registered on {}->{}: {}", __FUNCTION__,
+ PeripheralTypeTranslator::TypeToString(peripheral->Type()),
+ PeripheralTypeTranslator::BusTypeToString(PERIPHERAL_BUS_ADDON),
+ peripheral->Location(), peripheral->DeviceName());
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void CPeripheralAddon::UnregisterRemovedDevices(const PeripheralScanResults& results,
+ PeripheralVector& removedPeripherals)
+{
+ std::vector<unsigned int> removedIndexes;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (auto& it : m_peripherals)
+ {
+ const PeripheralPtr& peripheral = it.second;
+ PeripheralScanResult updatedDevice(PERIPHERAL_BUS_ADDON);
+ if (!results.GetDeviceOnLocation(peripheral->Location(), &updatedDevice) ||
+ *peripheral != updatedDevice)
+ {
+ // Device removed
+ removedIndexes.push_back(it.first);
+ }
+ }
+ }
+
+ for (auto index : removedIndexes)
+ {
+ auto it = m_peripherals.find(index);
+ const PeripheralPtr& peripheral = it->second;
+ CLog::Log(LOGINFO, "{} - device removed from {}/{}: {} ({}:{})", __FUNCTION__,
+ PeripheralTypeTranslator::TypeToString(peripheral->Type()), peripheral->Location(),
+ peripheral->DeviceName(), peripheral->VendorIdAsString(),
+ peripheral->ProductIdAsString());
+ UnregisterButtonMap(peripheral.get());
+ peripheral->OnDeviceRemoved();
+ removedPeripherals.push_back(peripheral);
+ m_peripherals.erase(it);
+ }
+}
+
+bool CPeripheralAddon::HasFeature(const PeripheralFeature feature) const
+{
+ if (feature == FEATURE_JOYSTICK)
+ return m_bProvidesJoysticks;
+
+ return false;
+}
+
+void CPeripheralAddon::GetFeatures(std::vector<PeripheralFeature>& features) const
+{
+ if (m_bProvidesJoysticks &&
+ std::find(features.begin(), features.end(), FEATURE_JOYSTICK) == features.end())
+ features.push_back(FEATURE_JOYSTICK);
+}
+
+PeripheralPtr CPeripheralAddon::GetPeripheral(unsigned int index) const
+{
+ PeripheralPtr peripheral;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto it = m_peripherals.find(index);
+ if (it != m_peripherals.end())
+ peripheral = it->second;
+ return peripheral;
+}
+
+PeripheralPtr CPeripheralAddon::GetByPath(const std::string& strPath) const
+{
+ PeripheralPtr result;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& it : m_peripherals)
+ {
+ if (StringUtils::EqualsNoCase(strPath, it.second->FileLocation()))
+ {
+ result = it.second;
+ break;
+ }
+ }
+
+ return result;
+}
+
+bool CPeripheralAddon::SupportsFeature(PeripheralFeature feature) const
+{
+ switch (feature)
+ {
+ case FEATURE_RUMBLE:
+ return m_bSupportsJoystickRumble;
+ case FEATURE_POWER_OFF:
+ return m_bSupportsJoystickPowerOff;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+unsigned int CPeripheralAddon::GetPeripheralsWithFeature(PeripheralVector& results,
+ const PeripheralFeature feature) const
+{
+ unsigned int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& it : m_peripherals)
+ {
+ if (it.second->HasFeature(feature))
+ {
+ results.push_back(it.second);
+ ++iReturn;
+ }
+ }
+ return iReturn;
+}
+
+unsigned int CPeripheralAddon::GetNumberOfPeripherals(void) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return static_cast<unsigned int>(m_peripherals.size());
+}
+
+unsigned int CPeripheralAddon::GetNumberOfPeripheralsWithId(const int iVendorId,
+ const int iProductId) const
+{
+ unsigned int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& it : m_peripherals)
+ {
+ if (it.second->VendorId() == iVendorId && it.second->ProductId() == iProductId)
+ iReturn++;
+ }
+
+ return iReturn;
+}
+
+void CPeripheralAddon::GetDirectory(const std::string& strPath, CFileItemList& items) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& it : m_peripherals)
+ {
+ const PeripheralPtr& peripheral = it.second;
+ 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());
+ peripheralFile->SetArt("icon", peripheral->GetIcon());
+ items.Add(peripheralFile);
+ }
+}
+
+bool CPeripheralAddon::PerformDeviceScan(PeripheralScanResults& results)
+{
+ unsigned int peripheralCount;
+ PERIPHERAL_INFO* pScanResults;
+ PERIPHERAL_ERROR retVal;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->perform_device_scan)
+ return false;
+
+ LogError(retVal = m_ifc.peripheral->toAddon->perform_device_scan(m_ifc.peripheral,
+ &peripheralCount, &pScanResults),
+ "PerformDeviceScan()");
+
+ if (retVal == PERIPHERAL_NO_ERROR)
+ {
+ for (unsigned int i = 0; i < peripheralCount; i++)
+ {
+ kodi::addon::Peripheral peripheral(pScanResults[i]);
+ PeripheralScanResult result(PERIPHERAL_BUS_ADDON);
+ switch (peripheral.Type())
+ {
+ case PERIPHERAL_TYPE_JOYSTICK:
+ result.m_type = PERIPHERAL_JOYSTICK;
+ break;
+ default:
+ continue;
+ }
+
+ result.m_strDeviceName = peripheral.Name();
+ result.m_strLocation = StringUtils::Format("{}/{}", ID(), peripheral.Index());
+ result.m_iVendorId = peripheral.VendorID();
+ result.m_iProductId = peripheral.ProductID();
+ result.m_mappedType = PERIPHERAL_JOYSTICK;
+ result.m_mappedBusType = PERIPHERAL_BUS_ADDON;
+ result.m_iSequence = 0;
+
+ if (!results.ContainsResult(result))
+ results.m_results.push_back(result);
+ }
+
+ m_ifc.peripheral->toAddon->free_scan_results(m_ifc.peripheral, peripheralCount, pScanResults);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CPeripheralAddon::ProcessEvents(void)
+{
+ if (!m_bProvidesJoysticks)
+ return false;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->get_events)
+ return false;
+
+ PERIPHERAL_ERROR retVal;
+
+ unsigned int eventCount = 0;
+ PERIPHERAL_EVENT* pEvents = nullptr;
+
+ LogError(retVal = m_ifc.peripheral->toAddon->get_events(m_ifc.peripheral, &eventCount, &pEvents),
+ "GetEvents()");
+ if (retVal == PERIPHERAL_NO_ERROR)
+ {
+ for (unsigned int i = 0; i < eventCount; i++)
+ {
+ kodi::addon::PeripheralEvent event(pEvents[i]);
+ PeripheralPtr device = GetPeripheral(event.PeripheralIndex());
+ if (!device)
+ continue;
+
+ switch (device->Type())
+ {
+ case PERIPHERAL_JOYSTICK:
+ {
+ std::shared_ptr<CPeripheralJoystick> joystickDevice =
+ std::static_pointer_cast<CPeripheralJoystick>(device);
+
+ switch (event.Type())
+ {
+ case PERIPHERAL_EVENT_TYPE_DRIVER_BUTTON:
+ {
+ const bool bPressed = (event.ButtonState() == JOYSTICK_STATE_BUTTON_PRESSED);
+ joystickDevice->OnButtonMotion(event.DriverIndex(), bPressed);
+ break;
+ }
+ case PERIPHERAL_EVENT_TYPE_DRIVER_HAT:
+ {
+ const HAT_STATE state =
+ CPeripheralAddonTranslator::TranslateHatState(event.HatState());
+ joystickDevice->OnHatMotion(event.DriverIndex(), state);
+ break;
+ }
+ case PERIPHERAL_EVENT_TYPE_DRIVER_AXIS:
+ {
+ joystickDevice->OnAxisMotion(event.DriverIndex(), event.AxisState());
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ for (const auto& it : m_peripherals)
+ {
+ if (it.second->Type() == PERIPHERAL_JOYSTICK)
+ std::static_pointer_cast<CPeripheralJoystick>(it.second)->OnInputFrame();
+ }
+
+ m_ifc.peripheral->toAddon->free_events(m_ifc.peripheral, eventCount, pEvents);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CPeripheralAddon::SendRumbleEvent(unsigned int peripheralIndex,
+ unsigned int driverIndex,
+ float magnitude)
+{
+ if (!m_bProvidesJoysticks)
+ return false;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->send_event)
+ return false;
+
+ PERIPHERAL_EVENT eventStruct = {};
+
+ eventStruct.peripheral_index = peripheralIndex;
+ eventStruct.type = PERIPHERAL_EVENT_TYPE_SET_MOTOR;
+ eventStruct.driver_index = driverIndex;
+ eventStruct.motor_state = magnitude;
+
+ return m_ifc.peripheral->toAddon->send_event(m_ifc.peripheral, &eventStruct);
+}
+
+bool CPeripheralAddon::GetJoystickProperties(unsigned int index, CPeripheralJoystick& joystick)
+{
+ if (!m_bProvidesJoysticks)
+ return false;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->get_joystick_info)
+ return false;
+
+ PERIPHERAL_ERROR retVal;
+
+ JOYSTICK_INFO joystickStruct;
+
+ LogError(retVal = m_ifc.peripheral->toAddon->get_joystick_info(m_ifc.peripheral, index,
+ &joystickStruct),
+ "GetJoystickInfo()");
+ if (retVal == PERIPHERAL_NO_ERROR)
+ {
+ kodi::addon::Joystick addonJoystick(joystickStruct);
+ SetJoystickInfo(joystick, addonJoystick);
+
+ m_ifc.peripheral->toAddon->free_joystick_info(m_ifc.peripheral, &joystickStruct);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CPeripheralAddon::GetFeatures(const CPeripheral* device,
+ const std::string& strControllerId,
+ FeatureMap& features)
+{
+ if (!m_bProvidesButtonMaps)
+ return false;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->get_features)
+ return false;
+
+ PERIPHERAL_ERROR retVal;
+
+ kodi::addon::Joystick joystickInfo;
+ GetJoystickInfo(device, joystickInfo);
+
+ JOYSTICK_INFO joystickStruct;
+ joystickInfo.ToStruct(joystickStruct);
+
+ unsigned int featureCount = 0;
+ JOYSTICK_FEATURE* pFeatures = nullptr;
+
+ LogError(retVal = m_ifc.peripheral->toAddon->get_features(m_ifc.peripheral, &joystickStruct,
+ strControllerId.c_str(), &featureCount,
+ &pFeatures),
+ "GetFeatures()");
+
+ kodi::addon::Joystick::FreeStruct(joystickStruct);
+
+ if (retVal == PERIPHERAL_NO_ERROR)
+ {
+ for (unsigned int i = 0; i < featureCount; i++)
+ {
+ kodi::addon::JoystickFeature feature(pFeatures[i]);
+ if (feature.Type() != JOYSTICK_FEATURE_TYPE_UNKNOWN)
+ features[feature.Name()] = feature;
+ }
+
+ m_ifc.peripheral->toAddon->free_features(m_ifc.peripheral, featureCount, pFeatures);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CPeripheralAddon::MapFeature(const CPeripheral* device,
+ const std::string& strControllerId,
+ const kodi::addon::JoystickFeature& feature)
+{
+ if (!m_bProvidesButtonMaps)
+ return false;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->map_features)
+ return false;
+
+ PERIPHERAL_ERROR retVal;
+
+ kodi::addon::Joystick joystickInfo;
+ GetJoystickInfo(device, joystickInfo);
+
+ JOYSTICK_INFO joystickStruct;
+ joystickInfo.ToStruct(joystickStruct);
+
+ JOYSTICK_FEATURE addonFeature;
+ feature.ToStruct(addonFeature);
+
+ LogError(retVal = m_ifc.peripheral->toAddon->map_features(
+ m_ifc.peripheral, &joystickStruct, strControllerId.c_str(), 1, &addonFeature),
+ "MapFeatures()");
+
+ kodi::addon::Joystick::FreeStruct(joystickStruct);
+ kodi::addon::JoystickFeature::FreeStruct(addonFeature);
+
+ return retVal == PERIPHERAL_NO_ERROR;
+}
+
+bool CPeripheralAddon::GetIgnoredPrimitives(const CPeripheral* device, PrimitiveVector& primitives)
+{
+ if (!m_bProvidesButtonMaps)
+ return false;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->get_ignored_primitives)
+ return false;
+
+ PERIPHERAL_ERROR retVal;
+
+ kodi::addon::Joystick joystickInfo;
+ GetJoystickInfo(device, joystickInfo);
+
+ JOYSTICK_INFO joystickStruct;
+ joystickInfo.ToStruct(joystickStruct);
+
+ unsigned int primitiveCount = 0;
+ JOYSTICK_DRIVER_PRIMITIVE* pPrimitives = nullptr;
+
+ LogError(retVal = m_ifc.peripheral->toAddon->get_ignored_primitives(
+ m_ifc.peripheral, &joystickStruct, &primitiveCount, &pPrimitives),
+ "GetIgnoredPrimitives()");
+
+ kodi::addon::Joystick::FreeStruct(joystickStruct);
+
+ if (retVal == PERIPHERAL_NO_ERROR)
+ {
+ for (unsigned int i = 0; i < primitiveCount; i++)
+ primitives.emplace_back(pPrimitives[i]);
+
+ m_ifc.peripheral->toAddon->free_primitives(m_ifc.peripheral, primitiveCount, pPrimitives);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CPeripheralAddon::SetIgnoredPrimitives(const CPeripheral* device,
+ const PrimitiveVector& primitives)
+{
+ if (!m_bProvidesButtonMaps)
+ return false;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->set_ignored_primitives)
+ return false;
+
+ PERIPHERAL_ERROR retVal;
+
+ kodi::addon::Joystick joystickInfo;
+ GetJoystickInfo(device, joystickInfo);
+
+ JOYSTICK_INFO joystickStruct;
+ joystickInfo.ToStruct(joystickStruct);
+
+ JOYSTICK_DRIVER_PRIMITIVE* addonPrimitives = nullptr;
+ kodi::addon::DriverPrimitives::ToStructs(primitives, &addonPrimitives);
+ const unsigned int primitiveCount = static_cast<unsigned int>(primitives.size());
+
+ LogError(retVal = m_ifc.peripheral->toAddon->set_ignored_primitives(
+ m_ifc.peripheral, &joystickStruct, primitiveCount, addonPrimitives),
+ "SetIgnoredPrimitives()");
+
+ kodi::addon::Joystick::FreeStruct(joystickStruct);
+ kodi::addon::DriverPrimitives::FreeStructs(primitiveCount, addonPrimitives);
+
+ return retVal == PERIPHERAL_NO_ERROR;
+}
+
+void CPeripheralAddon::SaveButtonMap(const CPeripheral* device)
+{
+ if (!m_bProvidesButtonMaps)
+ return;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->save_button_map)
+ return;
+
+ kodi::addon::Joystick joystickInfo;
+ GetJoystickInfo(device, joystickInfo);
+
+ JOYSTICK_INFO joystickStruct;
+ joystickInfo.ToStruct(joystickStruct);
+
+ m_ifc.peripheral->toAddon->save_button_map(m_ifc.peripheral, &joystickStruct);
+
+ kodi::addon::Joystick::FreeStruct(joystickStruct);
+
+ // Notify observing button maps
+ RefreshButtonMaps(device->DeviceName());
+}
+
+void CPeripheralAddon::RevertButtonMap(const CPeripheral* device)
+{
+ if (!m_bProvidesButtonMaps)
+ return;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->revert_button_map)
+ return;
+
+ kodi::addon::Joystick joystickInfo;
+ GetJoystickInfo(device, joystickInfo);
+
+ JOYSTICK_INFO joystickStruct;
+ joystickInfo.ToStruct(joystickStruct);
+
+ m_ifc.peripheral->toAddon->revert_button_map(m_ifc.peripheral, &joystickStruct);
+
+ kodi::addon::Joystick::FreeStruct(joystickStruct);
+}
+
+void CPeripheralAddon::ResetButtonMap(const CPeripheral* device, const std::string& strControllerId)
+{
+ if (!m_bProvidesButtonMaps)
+ return;
+
+ kodi::addon::Joystick joystickInfo;
+ GetJoystickInfo(device, joystickInfo);
+
+ JOYSTICK_INFO joystickStruct;
+ joystickInfo.ToStruct(joystickStruct);
+
+ m_ifc.peripheral->toAddon->reset_button_map(m_ifc.peripheral, &joystickStruct,
+ strControllerId.c_str());
+
+ kodi::addon::Joystick::FreeStruct(joystickStruct);
+
+ // Notify observing button maps
+ RefreshButtonMaps(device->DeviceName());
+}
+
+void CPeripheralAddon::PowerOffJoystick(unsigned int index)
+{
+ if (!HasFeature(FEATURE_JOYSTICK))
+ return;
+
+ if (!SupportsFeature(FEATURE_POWER_OFF))
+ return;
+
+ std::shared_lock<CSharedSection> lock(m_dllSection);
+
+ if (!m_ifc.peripheral->toAddon->power_off_joystick)
+ return;
+
+ m_ifc.peripheral->toAddon->power_off_joystick(m_ifc.peripheral, index);
+}
+
+void CPeripheralAddon::RegisterButtonMap(CPeripheral* device, IButtonMap* buttonMap)
+{
+ std::unique_lock<CCriticalSection> lock(m_buttonMapMutex);
+
+ UnregisterButtonMap(buttonMap);
+ m_buttonMaps.emplace_back(device, buttonMap);
+}
+
+void CPeripheralAddon::UnregisterButtonMap(IButtonMap* buttonMap)
+{
+ std::unique_lock<CCriticalSection> lock(m_buttonMapMutex);
+
+ for (auto it = m_buttonMaps.begin(); it != m_buttonMaps.end(); ++it)
+ {
+ if (it->second == buttonMap)
+ {
+ m_buttonMaps.erase(it);
+ break;
+ }
+ }
+}
+
+void CPeripheralAddon::UnregisterButtonMap(CPeripheral* device)
+{
+ std::unique_lock<CCriticalSection> lock(m_buttonMapMutex);
+
+ m_buttonMaps.erase(
+ std::remove_if(m_buttonMaps.begin(), m_buttonMaps.end(),
+ [device](const std::pair<CPeripheral*, JOYSTICK::IButtonMap*>& buttonMap) {
+ return buttonMap.first == device;
+ }),
+ m_buttonMaps.end());
+}
+
+void CPeripheralAddon::RefreshButtonMaps(const std::string& strDeviceName /* = "" */)
+{
+ std::unique_lock<CCriticalSection> lock(m_buttonMapMutex);
+
+ for (auto it = m_buttonMaps.begin(); it != m_buttonMaps.end(); ++it)
+ {
+ if (strDeviceName.empty() || strDeviceName == it->first->DeviceName())
+ it->second->Load();
+ }
+}
+
+bool CPeripheralAddon::ProvidesJoysticks(const ADDON::AddonInfoPtr& addonInfo)
+{
+ return addonInfo->Type(ADDON::AddonType::PERIPHERALDLL)
+ ->GetValue("@provides_joysticks")
+ .asBoolean();
+}
+
+bool CPeripheralAddon::ProvidesButtonMaps(const ADDON::AddonInfoPtr& addonInfo)
+{
+ return addonInfo->Type(ADDON::AddonType::PERIPHERALDLL)
+ ->GetValue("@provides_buttonmaps")
+ .asBoolean();
+}
+
+void CPeripheralAddon::TriggerDeviceScan()
+{
+ m_manager.TriggerDeviceScan(PERIPHERAL_BUS_ADDON);
+}
+
+unsigned int CPeripheralAddon::FeatureCount(const std::string& controllerId,
+ JOYSTICK_FEATURE_TYPE type) const
+{
+ using namespace GAME;
+
+ unsigned int count = 0;
+
+ CControllerManager& controllerProfiles = m_manager.GetControllerProfiles();
+ ControllerPtr controller = controllerProfiles.GetController(controllerId);
+ if (controller)
+ count = controller->FeatureCount(CPeripheralAddonTranslator::TranslateFeatureType(type));
+
+ return count;
+}
+
+JOYSTICK_FEATURE_TYPE CPeripheralAddon::FeatureType(const std::string& controllerId,
+ const std::string& featureName) const
+{
+ using namespace GAME;
+
+ JOYSTICK_FEATURE_TYPE type = JOYSTICK_FEATURE_TYPE_UNKNOWN;
+
+ CControllerManager& controllerProfiles = m_manager.GetControllerProfiles();
+ ControllerPtr controller = controllerProfiles.GetController(controllerId);
+ if (controller)
+ type = CPeripheralAddonTranslator::TranslateFeatureType(controller->FeatureType(featureName));
+
+ return type;
+}
+
+void CPeripheralAddon::GetPeripheralInfo(const CPeripheral* device,
+ kodi::addon::Peripheral& peripheralInfo)
+{
+ peripheralInfo.SetType(CPeripheralAddonTranslator::TranslateType(device->Type()));
+ peripheralInfo.SetName(device->DeviceName());
+ peripheralInfo.SetVendorID(device->VendorId());
+ peripheralInfo.SetProductID(device->ProductId());
+}
+
+void CPeripheralAddon::GetJoystickInfo(const CPeripheral* device,
+ kodi::addon::Joystick& joystickInfo)
+{
+ GetPeripheralInfo(device, joystickInfo);
+
+ if (device->Type() == PERIPHERAL_JOYSTICK)
+ {
+ const CPeripheralJoystick* joystick = static_cast<const CPeripheralJoystick*>(device);
+ joystickInfo.SetProvider(joystick->Provider());
+ joystickInfo.SetButtonCount(joystick->ButtonCount());
+ joystickInfo.SetHatCount(joystick->HatCount());
+ joystickInfo.SetAxisCount(joystick->AxisCount());
+ joystickInfo.SetMotorCount(joystick->MotorCount());
+ joystickInfo.SetSupportsPowerOff(joystick->SupportsPowerOff());
+ }
+ else if (device->Type() == PERIPHERAL_KEYBOARD || device->Type() == PERIPHERAL_MOUSE)
+ {
+ joystickInfo.SetName(GetDeviceName(device->Type())); // Override name with non-localized version
+ joystickInfo.SetProvider(GetProvider(device->Type()));
+ }
+}
+
+void CPeripheralAddon::SetJoystickInfo(CPeripheralJoystick& joystick,
+ const kodi::addon::Joystick& joystickInfo)
+{
+ joystick.SetProvider(joystickInfo.Provider());
+ joystick.SetRequestedPort(joystickInfo.RequestedPort());
+ joystick.SetButtonCount(joystickInfo.ButtonCount());
+ joystick.SetHatCount(joystickInfo.HatCount());
+ joystick.SetAxisCount(joystickInfo.AxisCount());
+ joystick.SetMotorCount(joystickInfo.MotorCount());
+ joystick.SetSupportsPowerOff(joystickInfo.SupportsPowerOff());
+}
+
+bool CPeripheralAddon::LogError(const PERIPHERAL_ERROR error, const char* strMethod) const
+{
+ if (error != PERIPHERAL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "PERIPHERAL - {} - addon '{}' returned an error: {}", strMethod, Name(),
+ CPeripheralAddonTranslator::TranslateError(error));
+ return false;
+ }
+ return true;
+}
+
+std::string CPeripheralAddon::GetDeviceName(PeripheralType type)
+{
+ switch (type)
+ {
+ case PERIPHERAL_KEYBOARD:
+ return KEYBOARD_BUTTON_MAP_NAME;
+ case PERIPHERAL_MOUSE:
+ return MOUSE_BUTTON_MAP_NAME;
+ default:
+ break;
+ }
+
+ return "";
+}
+
+std::string CPeripheralAddon::GetProvider(PeripheralType type)
+{
+ switch (type)
+ {
+ case PERIPHERAL_KEYBOARD:
+ return KEYBOARD_PROVIDER;
+ case PERIPHERAL_MOUSE:
+ return MOUSE_PROVIDER;
+ default:
+ break;
+ }
+
+ return "";
+}
+
+void CPeripheralAddon::cb_trigger_scan(void* kodiInstance)
+{
+ if (kodiInstance == nullptr)
+ return;
+
+ static_cast<CPeripheralAddon*>(kodiInstance)->TriggerDeviceScan();
+}
+
+void CPeripheralAddon::cb_refresh_button_maps(void* kodiInstance,
+ const char* deviceName,
+ const char* controllerId)
+{
+ if (!kodiInstance)
+ return;
+
+ static_cast<CPeripheralAddon*>(kodiInstance)->RefreshButtonMaps(deviceName ? deviceName : "");
+}
+
+unsigned int CPeripheralAddon::cb_feature_count(void* kodiInstance,
+ const char* controllerId,
+ JOYSTICK_FEATURE_TYPE type)
+{
+ if (kodiInstance == nullptr || controllerId == nullptr)
+ return 0;
+
+ return static_cast<CPeripheralAddon*>(kodiInstance)->FeatureCount(controllerId, type);
+}
+
+JOYSTICK_FEATURE_TYPE CPeripheralAddon::cb_feature_type(void* kodiInstance,
+ const char* controllerId,
+ const char* featureName)
+{
+ if (kodiInstance == nullptr || controllerId == nullptr || featureName == nullptr)
+ return JOYSTICK_FEATURE_TYPE_UNKNOWN;
+
+ return static_cast<CPeripheralAddon*>(kodiInstance)->FeatureType(controllerId, featureName);
+}
diff --git a/xbmc/peripherals/addons/PeripheralAddon.h b/xbmc/peripherals/addons/PeripheralAddon.h
new file mode 100644
index 0000000..2dd9d22
--- /dev/null
+++ b/xbmc/peripherals/addons/PeripheralAddon.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/binary-addons/AddonInstanceHandler.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Peripheral.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "peripherals/PeripheralTypes.h"
+#include "threads/CriticalSection.h"
+#include "threads/SharedSection.h"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+class CFileItemList;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IButtonMap;
+class IDriverHandler;
+} // namespace JOYSTICK
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CPeripheral;
+class CPeripheralJoystick;
+class CPeripherals;
+
+typedef std::vector<kodi::addon::DriverPrimitive> PrimitiveVector;
+typedef std::map<KODI::JOYSTICK::FeatureName, kodi::addon::JoystickFeature> FeatureMap;
+
+class CPeripheralAddon : public ADDON::IAddonInstanceHandler
+{
+public:
+ explicit CPeripheralAddon(const ADDON::AddonInfoPtr& addonInfo, CPeripherals& manager);
+ ~CPeripheralAddon(void) override;
+
+ /*!
+ * @brief Initialise the instance of this add-on
+ */
+ bool CreateAddon(void);
+
+ /*!
+ * \brief Deinitialize the instance of this add-on
+ */
+ void DestroyAddon();
+
+ bool Register(unsigned int peripheralIndex, const PeripheralPtr& peripheral);
+ void UnregisterRemovedDevices(const PeripheralScanResults& results,
+ PeripheralVector& removedPeripherals);
+ void GetFeatures(std::vector<PeripheralFeature>& features) const;
+ bool HasFeature(const PeripheralFeature feature) const;
+ PeripheralPtr GetPeripheral(unsigned int index) const;
+ PeripheralPtr GetByPath(const std::string& strPath) const;
+ bool SupportsFeature(PeripheralFeature feature) const;
+ unsigned int GetPeripheralsWithFeature(PeripheralVector& results,
+ const PeripheralFeature feature) const;
+ unsigned int GetNumberOfPeripherals(void) const;
+ unsigned int GetNumberOfPeripheralsWithId(const int iVendorId, const int iProductId) const;
+ void GetDirectory(const std::string& strPath, CFileItemList& items) const;
+
+ /** @name Peripheral add-on methods */
+ //@{
+ bool PerformDeviceScan(PeripheralScanResults& results);
+ bool ProcessEvents(void);
+ bool SendRumbleEvent(unsigned int index, unsigned int driverIndex, float magnitude);
+ //@}
+
+ /** @name Joystick methods */
+ //@{
+ bool GetJoystickProperties(unsigned int index, CPeripheralJoystick& joystick);
+ bool HasButtonMaps(void) const { return m_bProvidesButtonMaps; }
+ bool GetFeatures(const CPeripheral* device,
+ const std::string& strControllerId,
+ FeatureMap& features);
+ bool MapFeature(const CPeripheral* device,
+ const std::string& strControllerId,
+ const kodi::addon::JoystickFeature& feature);
+ bool GetIgnoredPrimitives(const CPeripheral* device, PrimitiveVector& primitives);
+ bool SetIgnoredPrimitives(const CPeripheral* device, const PrimitiveVector& primitives);
+ void SaveButtonMap(const CPeripheral* device);
+ void RevertButtonMap(const CPeripheral* device);
+ void ResetButtonMap(const CPeripheral* device, const std::string& strControllerId);
+ void PowerOffJoystick(unsigned int index);
+ //@}
+
+ void RegisterButtonMap(CPeripheral* device, KODI::JOYSTICK::IButtonMap* buttonMap);
+ void UnregisterButtonMap(KODI::JOYSTICK::IButtonMap* buttonMap);
+
+ static bool ProvidesJoysticks(const ADDON::AddonInfoPtr& addonInfo);
+ static bool ProvidesButtonMaps(const ADDON::AddonInfoPtr& addonInfo);
+
+private:
+ void UnregisterButtonMap(CPeripheral* device);
+
+ // Binary add-on callbacks
+ void TriggerDeviceScan();
+ void RefreshButtonMaps(const std::string& strDeviceName = "");
+ unsigned int FeatureCount(const std::string& controllerId, JOYSTICK_FEATURE_TYPE type) const;
+ JOYSTICK_FEATURE_TYPE FeatureType(const std::string& controllerId,
+ const std::string& featureName) const;
+
+ /*!
+ * @brief Helper functions
+ */
+ static void GetPeripheralInfo(const CPeripheral* device, kodi::addon::Peripheral& peripheralInfo);
+
+ static void GetJoystickInfo(const CPeripheral* device, kodi::addon::Joystick& joystickInfo);
+ static void SetJoystickInfo(CPeripheralJoystick& joystick,
+ const kodi::addon::Joystick& joystickInfo);
+
+ /*!
+ * @brief Reset all class members to their defaults. Called by the constructors
+ */
+ void ResetProperties(void);
+
+ /*!
+ * @brief Retrieve add-on properties from the add-on
+ */
+ bool GetAddonProperties(void);
+
+ bool LogError(const PERIPHERAL_ERROR error, const char* strMethod) const;
+
+ static std::string GetDeviceName(PeripheralType type);
+ static std::string GetProvider(PeripheralType type);
+
+ // Construction parameters
+ CPeripherals& m_manager;
+
+ /* @brief Cache for const char* members in PERIPHERAL_PROPERTIES */
+ std::string m_strUserPath; /*!< @brief translated path to the user profile */
+ std::string m_strClientPath; /*!< @brief translated path to this add-on */
+
+ /*!
+ * @brief Callback functions from addon to kodi
+ */
+ //@{
+ static void cb_trigger_scan(void* kodiInstance);
+ static void cb_refresh_button_maps(void* kodiInstance,
+ const char* deviceName,
+ const char* controllerId);
+ static unsigned int cb_feature_count(void* kodiInstance,
+ const char* controllerId,
+ JOYSTICK_FEATURE_TYPE type);
+ static JOYSTICK_FEATURE_TYPE cb_feature_type(void* kodiInstance,
+ const char* controllerId,
+ const char* featureName);
+ //@}
+
+ /* @brief Add-on properties */
+ bool m_bProvidesJoysticks;
+ bool m_bSupportsJoystickRumble;
+ bool m_bSupportsJoystickPowerOff;
+ bool m_bProvidesButtonMaps;
+
+ /* @brief Map of peripherals belonging to the add-on */
+ std::map<unsigned int, PeripheralPtr> m_peripherals;
+
+ /* @brief Button map observers */
+ std::vector<std::pair<CPeripheral*, KODI::JOYSTICK::IButtonMap*>> m_buttonMaps;
+ CCriticalSection m_buttonMapMutex;
+
+ /* @brief Thread synchronization */
+ mutable CCriticalSection m_critSection;
+
+ CSharedSection m_dllSection;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/addons/PeripheralAddonTranslator.cpp b/xbmc/peripherals/addons/PeripheralAddonTranslator.cpp
new file mode 100644
index 0000000..16dd529
--- /dev/null
+++ b/xbmc/peripherals/addons/PeripheralAddonTranslator.cpp
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralAddonTranslator.h"
+
+#include "games/controllers/ControllerTranslator.h"
+#include "input/joysticks/JoystickUtils.h"
+
+#include <algorithm>
+#include <iterator>
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+// --- Helper function ---------------------------------------------------------
+
+JOYSTICK_DRIVER_SEMIAXIS_DIRECTION operator*(JOYSTICK_DRIVER_SEMIAXIS_DIRECTION dir, int i)
+{
+ return static_cast<JOYSTICK_DRIVER_SEMIAXIS_DIRECTION>(static_cast<int>(dir) * i);
+}
+
+// --- CPeripheralAddonTranslator ----------------------------------------------
+
+const char* CPeripheralAddonTranslator::TranslateError(const PERIPHERAL_ERROR error)
+{
+ switch (error)
+ {
+ case PERIPHERAL_NO_ERROR:
+ return "no error";
+ case PERIPHERAL_ERROR_FAILED:
+ return "command failed";
+ case PERIPHERAL_ERROR_INVALID_PARAMETERS:
+ return "invalid parameters";
+ case PERIPHERAL_ERROR_NOT_IMPLEMENTED:
+ return "not implemented";
+ case PERIPHERAL_ERROR_NOT_CONNECTED:
+ return "not connected";
+ case PERIPHERAL_ERROR_CONNECTION_FAILED:
+ return "connection failed";
+ case PERIPHERAL_ERROR_UNKNOWN:
+ default:
+ return "unknown error";
+ }
+}
+
+PeripheralType CPeripheralAddonTranslator::TranslateType(PERIPHERAL_TYPE type)
+{
+ switch (type)
+ {
+ case PERIPHERAL_TYPE_JOYSTICK:
+ return PERIPHERAL_JOYSTICK;
+ default:
+ break;
+ }
+ return PERIPHERAL_UNKNOWN;
+}
+
+PERIPHERAL_TYPE CPeripheralAddonTranslator::TranslateType(PeripheralType type)
+{
+ switch (type)
+ {
+ case PERIPHERAL_JOYSTICK:
+ return PERIPHERAL_TYPE_JOYSTICK;
+ default:
+ break;
+ }
+ return PERIPHERAL_TYPE_UNKNOWN;
+}
+
+CDriverPrimitive CPeripheralAddonTranslator::TranslatePrimitive(
+ const kodi::addon::DriverPrimitive& primitive)
+{
+ CDriverPrimitive retVal;
+
+ switch (primitive.Type())
+ {
+ case JOYSTICK_DRIVER_PRIMITIVE_TYPE_BUTTON:
+ {
+ retVal = CDriverPrimitive(PRIMITIVE_TYPE::BUTTON, primitive.DriverIndex());
+ break;
+ }
+ case JOYSTICK_DRIVER_PRIMITIVE_TYPE_HAT_DIRECTION:
+ {
+ retVal = CDriverPrimitive(primitive.DriverIndex(),
+ TranslateHatDirection(primitive.HatDirection()));
+ break;
+ }
+ case JOYSTICK_DRIVER_PRIMITIVE_TYPE_SEMIAXIS:
+ {
+ retVal = CDriverPrimitive(primitive.DriverIndex(), primitive.Center(),
+ TranslateSemiAxisDirection(primitive.SemiAxisDirection()),
+ primitive.Range());
+ break;
+ }
+ case JOYSTICK_DRIVER_PRIMITIVE_TYPE_MOTOR:
+ {
+ retVal = CDriverPrimitive(PRIMITIVE_TYPE::MOTOR, primitive.DriverIndex());
+ break;
+ }
+ case JOYSTICK_DRIVER_PRIMITIVE_TYPE_KEY:
+ {
+ KEYBOARD::KeySymbol keycode =
+ GAME::CControllerTranslator::TranslateKeysym(primitive.Keycode());
+ retVal = CDriverPrimitive(keycode);
+ break;
+ }
+ case JOYSTICK_DRIVER_PRIMITIVE_TYPE_MOUSE_BUTTON:
+ {
+ retVal = CDriverPrimitive(TranslateMouseButton(primitive.MouseIndex()));
+ break;
+ }
+ case JOYSTICK_DRIVER_PRIMITIVE_TYPE_RELPOINTER_DIRECTION:
+ {
+ retVal = CDriverPrimitive(TranslateRelPointerDirection(primitive.RelPointerDirection()));
+ break;
+ }
+ default:
+ break;
+ }
+
+ return retVal;
+}
+
+kodi::addon::DriverPrimitive CPeripheralAddonTranslator::TranslatePrimitive(
+ const CDriverPrimitive& primitive)
+{
+ kodi::addon::DriverPrimitive retVal;
+
+ switch (primitive.Type())
+ {
+ case PRIMITIVE_TYPE::BUTTON:
+ {
+ retVal = kodi::addon::DriverPrimitive::CreateButton(primitive.Index());
+ break;
+ }
+ case PRIMITIVE_TYPE::HAT:
+ {
+ retVal = kodi::addon::DriverPrimitive(primitive.Index(),
+ TranslateHatDirection(primitive.HatDirection()));
+ break;
+ }
+ case PRIMITIVE_TYPE::SEMIAXIS:
+ {
+ retVal = kodi::addon::DriverPrimitive(
+ primitive.Index(), primitive.Center(),
+ TranslateSemiAxisDirection(primitive.SemiAxisDirection()), primitive.Range());
+ break;
+ }
+ case PRIMITIVE_TYPE::MOTOR:
+ {
+ retVal = kodi::addon::DriverPrimitive::CreateMotor(primitive.Index());
+ break;
+ }
+ case PRIMITIVE_TYPE::KEY:
+ {
+ std::string keysym = GAME::CControllerTranslator::TranslateKeycode(primitive.Keycode());
+ retVal = kodi::addon::DriverPrimitive(keysym);
+ break;
+ }
+ case PRIMITIVE_TYPE::MOUSE_BUTTON:
+ {
+ retVal = kodi::addon::DriverPrimitive::CreateMouseButton(
+ TranslateMouseButton(primitive.MouseButton()));
+ break;
+ }
+ case PRIMITIVE_TYPE::RELATIVE_POINTER:
+ {
+ retVal =
+ kodi::addon::DriverPrimitive(TranslateRelPointerDirection(primitive.PointerDirection()));
+ break;
+ }
+ default:
+ break;
+ }
+
+ return retVal;
+}
+
+std::vector<JOYSTICK::CDriverPrimitive> CPeripheralAddonTranslator::TranslatePrimitives(
+ const std::vector<kodi::addon::DriverPrimitive>& primitives)
+{
+ std::vector<JOYSTICK::CDriverPrimitive> ret;
+ std::transform(primitives.begin(), primitives.end(), std::back_inserter(ret),
+ [](const kodi::addon::DriverPrimitive& primitive) {
+ return CPeripheralAddonTranslator::TranslatePrimitive(primitive);
+ });
+ return ret;
+}
+
+std::vector<kodi::addon::DriverPrimitive> CPeripheralAddonTranslator::TranslatePrimitives(
+ const std::vector<JOYSTICK::CDriverPrimitive>& primitives)
+{
+ std::vector<kodi::addon::DriverPrimitive> ret;
+ std::transform(primitives.begin(), primitives.end(), std::back_inserter(ret),
+ [](const JOYSTICK::CDriverPrimitive& primitive) {
+ return CPeripheralAddonTranslator::TranslatePrimitive(primitive);
+ });
+ return ret;
+}
+
+HAT_DIRECTION CPeripheralAddonTranslator::TranslateHatDirection(JOYSTICK_DRIVER_HAT_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case JOYSTICK_DRIVER_HAT_LEFT:
+ return HAT_DIRECTION::LEFT;
+ case JOYSTICK_DRIVER_HAT_RIGHT:
+ return HAT_DIRECTION::RIGHT;
+ case JOYSTICK_DRIVER_HAT_UP:
+ return HAT_DIRECTION::UP;
+ case JOYSTICK_DRIVER_HAT_DOWN:
+ return HAT_DIRECTION::DOWN;
+ default:
+ break;
+ }
+ return HAT_DIRECTION::NONE;
+}
+
+JOYSTICK_DRIVER_HAT_DIRECTION CPeripheralAddonTranslator::TranslateHatDirection(HAT_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case HAT_DIRECTION::UP:
+ return JOYSTICK_DRIVER_HAT_UP;
+ case HAT_DIRECTION::DOWN:
+ return JOYSTICK_DRIVER_HAT_DOWN;
+ case HAT_DIRECTION::RIGHT:
+ return JOYSTICK_DRIVER_HAT_RIGHT;
+ case HAT_DIRECTION::LEFT:
+ return JOYSTICK_DRIVER_HAT_LEFT;
+ default:
+ break;
+ }
+ return JOYSTICK_DRIVER_HAT_UNKNOWN;
+}
+
+HAT_STATE CPeripheralAddonTranslator::TranslateHatState(JOYSTICK_STATE_HAT state)
+{
+ HAT_STATE translatedState = HAT_STATE::NONE;
+
+ if (state & JOYSTICK_STATE_HAT_UP)
+ translatedState |= HAT_STATE::UP;
+ if (state & JOYSTICK_STATE_HAT_DOWN)
+ translatedState |= HAT_STATE::DOWN;
+ if (state & JOYSTICK_STATE_HAT_RIGHT)
+ translatedState |= HAT_STATE::RIGHT;
+ if (state & JOYSTICK_STATE_HAT_LEFT)
+ translatedState |= HAT_STATE::LEFT;
+
+ return translatedState;
+}
+
+SEMIAXIS_DIRECTION CPeripheralAddonTranslator::TranslateSemiAxisDirection(
+ JOYSTICK_DRIVER_SEMIAXIS_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case JOYSTICK_DRIVER_SEMIAXIS_POSITIVE:
+ return SEMIAXIS_DIRECTION::POSITIVE;
+ case JOYSTICK_DRIVER_SEMIAXIS_NEGATIVE:
+ return SEMIAXIS_DIRECTION::NEGATIVE;
+ default:
+ break;
+ }
+ return SEMIAXIS_DIRECTION::ZERO;
+}
+
+JOYSTICK_DRIVER_SEMIAXIS_DIRECTION CPeripheralAddonTranslator::TranslateSemiAxisDirection(
+ SEMIAXIS_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case SEMIAXIS_DIRECTION::POSITIVE:
+ return JOYSTICK_DRIVER_SEMIAXIS_POSITIVE;
+ case SEMIAXIS_DIRECTION::NEGATIVE:
+ return JOYSTICK_DRIVER_SEMIAXIS_NEGATIVE;
+ default:
+ break;
+ }
+ return JOYSTICK_DRIVER_SEMIAXIS_UNKNOWN;
+}
+
+MOUSE::BUTTON_ID CPeripheralAddonTranslator::TranslateMouseButton(
+ JOYSTICK_DRIVER_MOUSE_INDEX button)
+{
+ switch (button)
+ {
+ case JOYSTICK_DRIVER_MOUSE_INDEX_LEFT:
+ return MOUSE::BUTTON_ID::LEFT;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_RIGHT:
+ return MOUSE::BUTTON_ID::RIGHT;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_MIDDLE:
+ return MOUSE::BUTTON_ID::MIDDLE;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON4:
+ return MOUSE::BUTTON_ID::BUTTON4;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON5:
+ return MOUSE::BUTTON_ID::BUTTON5;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_UP:
+ return MOUSE::BUTTON_ID::WHEEL_UP;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_DOWN:
+ return MOUSE::BUTTON_ID::WHEEL_DOWN;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_LEFT:
+ return MOUSE::BUTTON_ID::HORIZ_WHEEL_LEFT;
+ case JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_RIGHT:
+ return MOUSE::BUTTON_ID::HORIZ_WHEEL_RIGHT;
+ default:
+ break;
+ }
+
+ return MOUSE::BUTTON_ID::UNKNOWN;
+}
+
+JOYSTICK_DRIVER_MOUSE_INDEX CPeripheralAddonTranslator::TranslateMouseButton(
+ MOUSE::BUTTON_ID button)
+{
+ switch (button)
+ {
+ case MOUSE::BUTTON_ID::LEFT:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_LEFT;
+ case MOUSE::BUTTON_ID::RIGHT:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_RIGHT;
+ case MOUSE::BUTTON_ID::MIDDLE:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_MIDDLE;
+ case MOUSE::BUTTON_ID::BUTTON4:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON4;
+ case MOUSE::BUTTON_ID::BUTTON5:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_BUTTON5;
+ case MOUSE::BUTTON_ID::WHEEL_UP:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_UP;
+ case MOUSE::BUTTON_ID::WHEEL_DOWN:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_WHEEL_DOWN;
+ case MOUSE::BUTTON_ID::HORIZ_WHEEL_LEFT:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_LEFT;
+ case MOUSE::BUTTON_ID::HORIZ_WHEEL_RIGHT:
+ return JOYSTICK_DRIVER_MOUSE_INDEX_HORIZ_WHEEL_RIGHT;
+ default:
+ break;
+ }
+
+ return JOYSTICK_DRIVER_MOUSE_INDEX_UNKNOWN;
+}
+
+RELATIVE_POINTER_DIRECTION CPeripheralAddonTranslator::TranslateRelPointerDirection(
+ JOYSTICK_DRIVER_RELPOINTER_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case JOYSTICK_DRIVER_RELPOINTER_LEFT:
+ return RELATIVE_POINTER_DIRECTION::LEFT;
+ case JOYSTICK_DRIVER_RELPOINTER_RIGHT:
+ return RELATIVE_POINTER_DIRECTION::RIGHT;
+ case JOYSTICK_DRIVER_RELPOINTER_UP:
+ return RELATIVE_POINTER_DIRECTION::UP;
+ case JOYSTICK_DRIVER_RELPOINTER_DOWN:
+ return RELATIVE_POINTER_DIRECTION::DOWN;
+ default:
+ break;
+ }
+
+ return RELATIVE_POINTER_DIRECTION::NONE;
+}
+
+JOYSTICK_DRIVER_RELPOINTER_DIRECTION CPeripheralAddonTranslator::TranslateRelPointerDirection(
+ RELATIVE_POINTER_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case RELATIVE_POINTER_DIRECTION::UP:
+ return JOYSTICK_DRIVER_RELPOINTER_UP;
+ case RELATIVE_POINTER_DIRECTION::DOWN:
+ return JOYSTICK_DRIVER_RELPOINTER_DOWN;
+ case RELATIVE_POINTER_DIRECTION::RIGHT:
+ return JOYSTICK_DRIVER_RELPOINTER_RIGHT;
+ case RELATIVE_POINTER_DIRECTION::LEFT:
+ return JOYSTICK_DRIVER_RELPOINTER_LEFT;
+ default:
+ break;
+ }
+ return JOYSTICK_DRIVER_RELPOINTER_UNKNOWN;
+}
+
+JOYSTICK::FEATURE_TYPE CPeripheralAddonTranslator::TranslateFeatureType(JOYSTICK_FEATURE_TYPE type)
+{
+ switch (type)
+ {
+ case JOYSTICK_FEATURE_TYPE_SCALAR:
+ return JOYSTICK::FEATURE_TYPE::SCALAR;
+ case JOYSTICK_FEATURE_TYPE_ANALOG_STICK:
+ return JOYSTICK::FEATURE_TYPE::ANALOG_STICK;
+ case JOYSTICK_FEATURE_TYPE_ACCELEROMETER:
+ return JOYSTICK::FEATURE_TYPE::ACCELEROMETER;
+ case JOYSTICK_FEATURE_TYPE_MOTOR:
+ return JOYSTICK::FEATURE_TYPE::MOTOR;
+ case JOYSTICK_FEATURE_TYPE_RELPOINTER:
+ return JOYSTICK::FEATURE_TYPE::RELPOINTER;
+ case JOYSTICK_FEATURE_TYPE_ABSPOINTER:
+ return JOYSTICK::FEATURE_TYPE::ABSPOINTER;
+ case JOYSTICK_FEATURE_TYPE_WHEEL:
+ return JOYSTICK::FEATURE_TYPE::WHEEL;
+ case JOYSTICK_FEATURE_TYPE_THROTTLE:
+ return JOYSTICK::FEATURE_TYPE::THROTTLE;
+ case JOYSTICK_FEATURE_TYPE_KEY:
+ return JOYSTICK::FEATURE_TYPE::KEY;
+ default:
+ break;
+ }
+ return JOYSTICK::FEATURE_TYPE::UNKNOWN;
+}
+
+JOYSTICK_FEATURE_TYPE CPeripheralAddonTranslator::TranslateFeatureType(JOYSTICK::FEATURE_TYPE type)
+{
+ switch (type)
+ {
+ case JOYSTICK::FEATURE_TYPE::SCALAR:
+ return JOYSTICK_FEATURE_TYPE_SCALAR;
+ case JOYSTICK::FEATURE_TYPE::ANALOG_STICK:
+ return JOYSTICK_FEATURE_TYPE_ANALOG_STICK;
+ case JOYSTICK::FEATURE_TYPE::ACCELEROMETER:
+ return JOYSTICK_FEATURE_TYPE_ACCELEROMETER;
+ case JOYSTICK::FEATURE_TYPE::MOTOR:
+ return JOYSTICK_FEATURE_TYPE_MOTOR;
+ case JOYSTICK::FEATURE_TYPE::RELPOINTER:
+ return JOYSTICK_FEATURE_TYPE_RELPOINTER;
+ case JOYSTICK::FEATURE_TYPE::ABSPOINTER:
+ return JOYSTICK_FEATURE_TYPE_ABSPOINTER;
+ case JOYSTICK::FEATURE_TYPE::WHEEL:
+ return JOYSTICK_FEATURE_TYPE_WHEEL;
+ case JOYSTICK::FEATURE_TYPE::THROTTLE:
+ return JOYSTICK_FEATURE_TYPE_THROTTLE;
+ case JOYSTICK::FEATURE_TYPE::KEY:
+ return JOYSTICK_FEATURE_TYPE_KEY;
+ default:
+ break;
+ }
+ return JOYSTICK_FEATURE_TYPE_UNKNOWN;
+}
+
+kodi::addon::DriverPrimitive CPeripheralAddonTranslator::Opposite(
+ const kodi::addon::DriverPrimitive& primitive)
+{
+ return kodi::addon::DriverPrimitive(primitive.DriverIndex(), primitive.Center() * -1,
+ primitive.SemiAxisDirection() * -1, primitive.Range());
+}
diff --git a/xbmc/peripherals/addons/PeripheralAddonTranslator.h b/xbmc/peripherals/addons/PeripheralAddonTranslator.h
new file mode 100644
index 0000000..308d62c
--- /dev/null
+++ b/xbmc/peripherals/addons/PeripheralAddonTranslator.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Peripheral.h"
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "input/mouse/MouseTypes.h"
+#include "peripherals/PeripheralTypes.h"
+
+#include <vector>
+
+namespace PERIPHERALS
+{
+class CPeripheralAddonTranslator
+{
+public:
+ static const char* TranslateError(PERIPHERAL_ERROR error);
+
+ static PeripheralType TranslateType(PERIPHERAL_TYPE type);
+ static PERIPHERAL_TYPE TranslateType(PeripheralType type);
+
+ static KODI::JOYSTICK::CDriverPrimitive TranslatePrimitive(
+ const kodi::addon::DriverPrimitive& primitive);
+ static kodi::addon::DriverPrimitive TranslatePrimitive(
+ const KODI::JOYSTICK::CDriverPrimitive& primitive);
+
+ static std::vector<KODI::JOYSTICK::CDriverPrimitive> TranslatePrimitives(
+ const std::vector<kodi::addon::DriverPrimitive>& primitives);
+ static std::vector<kodi::addon::DriverPrimitive> TranslatePrimitives(
+ const std::vector<KODI::JOYSTICK::CDriverPrimitive>& primitives);
+
+ static KODI::JOYSTICK::HAT_DIRECTION TranslateHatDirection(JOYSTICK_DRIVER_HAT_DIRECTION dir);
+ static JOYSTICK_DRIVER_HAT_DIRECTION TranslateHatDirection(KODI::JOYSTICK::HAT_DIRECTION dir);
+
+ static KODI::JOYSTICK::HAT_STATE TranslateHatState(JOYSTICK_STATE_HAT state);
+
+ static KODI::JOYSTICK::SEMIAXIS_DIRECTION TranslateSemiAxisDirection(
+ JOYSTICK_DRIVER_SEMIAXIS_DIRECTION dir);
+ static JOYSTICK_DRIVER_SEMIAXIS_DIRECTION TranslateSemiAxisDirection(
+ KODI::JOYSTICK::SEMIAXIS_DIRECTION dir);
+
+ static KODI::MOUSE::BUTTON_ID TranslateMouseButton(JOYSTICK_DRIVER_MOUSE_INDEX button);
+ static JOYSTICK_DRIVER_MOUSE_INDEX TranslateMouseButton(KODI::MOUSE::BUTTON_ID button);
+
+ static KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION TranslateRelPointerDirection(
+ JOYSTICK_DRIVER_RELPOINTER_DIRECTION dir);
+ static JOYSTICK_DRIVER_RELPOINTER_DIRECTION TranslateRelPointerDirection(
+ KODI::JOYSTICK::RELATIVE_POINTER_DIRECTION dir);
+
+ static KODI::JOYSTICK::FEATURE_TYPE TranslateFeatureType(JOYSTICK_FEATURE_TYPE type);
+ static JOYSTICK_FEATURE_TYPE TranslateFeatureType(KODI::JOYSTICK::FEATURE_TYPE type);
+
+ static kodi::addon::DriverPrimitive Opposite(const kodi::addon::DriverPrimitive& semiaxis);
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/bus/CMakeLists.txt b/xbmc/peripherals/bus/CMakeLists.txt
new file mode 100644
index 0000000..81c80c5
--- /dev/null
+++ b/xbmc/peripherals/bus/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES PeripheralBus.cpp)
+
+set(HEADERS PeripheralBus.h
+ PeripheralBusUSB.h)
+
+core_add_library(peripherals_bus)
diff --git a/xbmc/peripherals/bus/PeripheralBus.cpp b/xbmc/peripherals/bus/PeripheralBus.cpp
new file mode 100644
index 0000000..59a4c61
--- /dev/null
+++ b/xbmc/peripherals/bus/PeripheralBus.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralBus.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/devices/Peripheral.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+using namespace PERIPHERALS;
+using namespace std::chrono_literals;
+
+namespace
+{
+constexpr auto PERIPHERAL_DEFAULT_RESCAN_INTERVAL = 5000ms;
+}
+
+CPeripheralBus::CPeripheralBus(const std::string& threadname,
+ CPeripherals& manager,
+ PeripheralBusType type)
+ : CThread(threadname.c_str()),
+ m_iRescanTime(PERIPHERAL_DEFAULT_RESCAN_INTERVAL),
+ m_bNeedsPolling(true),
+ m_manager(manager),
+ m_type(type),
+ m_triggerEvent(true)
+{
+}
+
+bool CPeripheralBus::InitializeProperties(CPeripheral& peripheral)
+{
+ return true;
+}
+
+void CPeripheralBus::OnDeviceAdded(const std::string& strLocation)
+{
+ ScanForDevices();
+}
+
+void CPeripheralBus::OnDeviceChanged(const std::string& strLocation)
+{
+ ScanForDevices();
+}
+
+void CPeripheralBus::OnDeviceRemoved(const std::string& strLocation)
+{
+ ScanForDevices();
+}
+
+void CPeripheralBus::Clear(void)
+{
+ if (m_bNeedsPolling)
+ {
+ StopThread(false);
+ m_triggerEvent.Set();
+ StopThread(true);
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_peripherals.clear();
+}
+
+void CPeripheralBus::UnregisterRemovedDevices(const PeripheralScanResults& results)
+{
+ PeripheralVector removedPeripherals;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (int iDevicePtr = (int)m_peripherals.size() - 1; iDevicePtr >= 0; iDevicePtr--)
+ {
+ const PeripheralPtr& peripheral = m_peripherals.at(iDevicePtr);
+ PeripheralScanResult updatedDevice(m_type);
+ if (!results.GetDeviceOnLocation(peripheral->Location(), &updatedDevice) ||
+ *peripheral != updatedDevice)
+ {
+ /* device removed */
+ removedPeripherals.push_back(peripheral);
+ m_peripherals.erase(m_peripherals.begin() + iDevicePtr);
+ }
+ }
+ }
+
+ for (auto& peripheral : removedPeripherals)
+ {
+ std::vector<PeripheralFeature> features;
+ peripheral->GetFeatures(features);
+ bool peripheralHasFeatures =
+ features.size() > 1 || (features.size() == 1 && features.at(0) != FEATURE_UNKNOWN);
+ if (peripheral->Type() != PERIPHERAL_UNKNOWN || peripheralHasFeatures)
+ {
+ CLog::Log(LOGINFO, "{} - device removed from {}/{}: {} ({}:{})", __FUNCTION__,
+ PeripheralTypeTranslator::TypeToString(peripheral->Type()), peripheral->Location(),
+ peripheral->DeviceName(), peripheral->VendorIdAsString(),
+ peripheral->ProductIdAsString());
+ peripheral->OnDeviceRemoved();
+ }
+
+ m_manager.OnDeviceDeleted(*this, *peripheral);
+ }
+}
+
+void CPeripheralBus::RegisterNewDevices(const PeripheralScanResults& results)
+{
+ for (unsigned int iResultPtr = 0; iResultPtr < results.m_results.size(); iResultPtr++)
+ {
+ const PeripheralScanResult& result = results.m_results.at(iResultPtr);
+ if (!HasPeripheral(result.m_strLocation))
+ m_manager.CreatePeripheral(*this, result);
+ }
+}
+
+bool CPeripheralBus::ScanForDevices(void)
+{
+ bool bReturn(false);
+
+ PeripheralScanResults results;
+ if (PerformDeviceScan(results))
+ {
+ UnregisterRemovedDevices(results);
+ RegisterNewDevices(results);
+
+ m_manager.NotifyObservers(ObservableMessagePeripheralsChanged);
+
+ bReturn = true;
+ }
+
+ return bReturn;
+}
+
+bool CPeripheralBus::HasFeature(const PeripheralFeature feature) const
+{
+ bool bReturn(false);
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++)
+ {
+ if (m_peripherals.at(iPeripheralPtr)->HasFeature(feature))
+ {
+ bReturn = true;
+ break;
+ }
+ }
+ return bReturn;
+}
+
+void CPeripheralBus::GetFeatures(std::vector<PeripheralFeature>& features) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (unsigned int iPeripheralPtr = 0; iPeripheralPtr < m_peripherals.size(); iPeripheralPtr++)
+ m_peripherals.at(iPeripheralPtr)->GetFeatures(features);
+}
+
+PeripheralPtr CPeripheralBus::GetPeripheral(const std::string& strLocation) const
+{
+ PeripheralPtr result;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (auto& peripheral : m_peripherals)
+ {
+ if (peripheral->Location() == strLocation)
+ {
+ result = peripheral;
+ break;
+ }
+ }
+ return result;
+}
+
+unsigned int CPeripheralBus::GetPeripheralsWithFeature(PeripheralVector& results,
+ const PeripheralFeature feature) const
+{
+ unsigned int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (auto& peripheral : m_peripherals)
+ {
+ if (peripheral->HasFeature(feature))
+ {
+ results.push_back(peripheral);
+ ++iReturn;
+ }
+ }
+
+ return iReturn;
+}
+
+unsigned int CPeripheralBus::GetNumberOfPeripheralsWithId(const int iVendorId,
+ const int iProductId) const
+{
+ unsigned int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& peripheral : m_peripherals)
+ {
+ if (peripheral->VendorId() == iVendorId && peripheral->ProductId() == iProductId)
+ iReturn++;
+ }
+
+ return iReturn;
+}
+
+void CPeripheralBus::Process(void)
+{
+ while (!m_bStop)
+ {
+ m_triggerEvent.Reset();
+
+ if (!ScanForDevices())
+ break;
+
+ // depending on bus implementation
+ // needsPolling can be set properly
+ // only after initial scan.
+ // if this is the case, bail out.
+ if (!m_bNeedsPolling)
+ break;
+
+ if (!m_bStop)
+ m_triggerEvent.Wait(m_iRescanTime);
+ }
+}
+
+void CPeripheralBus::Initialise(void)
+{
+ bool bNeedsPolling = false;
+
+ if (!IsRunning())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bNeedsPolling = m_bNeedsPolling;
+ }
+
+ if (bNeedsPolling)
+ {
+ m_triggerEvent.Reset();
+ Create();
+ SetPriority(ThreadPriority::BELOW_NORMAL);
+ }
+}
+
+void CPeripheralBus::Register(const PeripheralPtr& peripheral)
+{
+ if (!peripheral)
+ return;
+
+ bool bPeripheralAdded = false;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!HasPeripheral(peripheral->Location()))
+ {
+ m_peripherals.push_back(peripheral);
+ bPeripheralAdded = true;
+ }
+ }
+
+ if (bPeripheralAdded)
+ {
+ CLog::Log(LOGINFO, "{} - new {} device registered on {}->{}: {} ({}:{})", __FUNCTION__,
+ PeripheralTypeTranslator::TypeToString(peripheral->Type()),
+ PeripheralTypeTranslator::BusTypeToString(m_type), peripheral->Location(),
+ peripheral->DeviceName(), peripheral->VendorIdAsString(),
+ peripheral->ProductIdAsString());
+ m_manager.OnDeviceAdded(*this, *peripheral);
+ }
+}
+
+void CPeripheralBus::TriggerDeviceScan(void)
+{
+ bool bNeedsPolling;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bNeedsPolling = m_bNeedsPolling;
+ }
+
+ if (bNeedsPolling)
+ m_triggerEvent.Set();
+ else
+ ScanForDevices();
+}
+
+bool CPeripheralBus::HasPeripheral(const std::string& strLocation) const
+{
+ return (GetPeripheral(strLocation) != NULL);
+}
+
+void CPeripheralBus::GetDirectory(const std::string& strPath, CFileItemList& items) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& peripheral : m_peripherals)
+ {
+ if (peripheral->IsHidden())
+ continue;
+
+ CFileItemPtr peripheralFile(new CFileItem(peripheral->DeviceName()));
+ peripheralFile->SetPath(peripheral->FileLocation());
+ peripheralFile->SetProperty("vendor", peripheral->VendorIdAsString());
+ peripheralFile->SetProperty("product", peripheral->ProductIdAsString());
+ peripheralFile->SetProperty(
+ "bus", PeripheralTypeTranslator::BusTypeToString(peripheral->GetBusType()));
+ peripheralFile->SetProperty("location", peripheral->Location());
+ peripheralFile->SetProperty("class",
+ PeripheralTypeTranslator::TypeToString(peripheral->Type()));
+
+ std::string strVersion(peripheral->GetVersionInfo());
+ if (strVersion.empty())
+ strVersion = g_localizeStrings.Get(13205);
+
+ std::string strDetails = StringUtils::Format("{} {}", g_localizeStrings.Get(24051), strVersion);
+ if (peripheral->GetBusType() == PERIPHERAL_BUS_CEC && !peripheral->GetSettingBool("enabled"))
+ strDetails =
+ StringUtils::Format("{}: {}", g_localizeStrings.Get(126), g_localizeStrings.Get(13106));
+
+ peripheralFile->SetProperty("version", strVersion);
+ peripheralFile->SetLabel2(strDetails);
+ peripheralFile->SetArt("icon", peripheral->GetIcon());
+
+ items.Add(peripheralFile);
+ }
+}
+
+PeripheralPtr CPeripheralBus::GetByPath(const std::string& strPath) const
+{
+ PeripheralPtr result;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (auto& peripheral : m_peripherals)
+ {
+ if (StringUtils::EqualsNoCase(strPath, peripheral->FileLocation()))
+ {
+ result = peripheral;
+ break;
+ }
+ }
+
+ return result;
+}
+
+unsigned int CPeripheralBus::GetNumberOfPeripherals() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return static_cast<unsigned int>(m_peripherals.size());
+}
diff --git a/xbmc/peripherals/bus/PeripheralBus.h b/xbmc/peripherals/bus/PeripheralBus.h
new file mode 100644
index 0000000..6b67a7f
--- /dev/null
+++ b/xbmc/peripherals/bus/PeripheralBus.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "peripherals/PeripheralTypes.h"
+#include "threads/Thread.h"
+
+#include <memory>
+#include <mutex>
+#include <vector>
+
+class CFileItemList;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IButtonMap;
+} // namespace JOYSTICK
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CPeripheral;
+class CPeripherals;
+
+/*!
+ * @class CPeripheralBus
+ * This represents a bus on the system. By default, this bus instance will scan for changes every 5
+ * seconds. If this bus only has to be updated after a notification sent by the system, set
+ * m_bNeedsPolling to false in the constructor, and implement the OnDeviceAdded(), OnDeviceChanged()
+ * and OnDeviceRemoved() methods.
+ *
+ * The PerformDeviceScan() method has to be implemented by each specific bus implementation.
+ */
+class CPeripheralBus : protected CThread
+{
+public:
+ CPeripheralBus(const std::string& threadname, CPeripherals& manager, PeripheralBusType type);
+ ~CPeripheralBus(void) override { Clear(); }
+
+ /*!
+ * @return The bus type
+ */
+ PeripheralBusType Type(void) const { return m_type; }
+
+ /*!
+ * @return True if this bus needs to be polled for changes, false if this bus performs updates via
+ * callbacks
+ */
+ bool NeedsPolling(void) const
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bNeedsPolling;
+ }
+
+ /*!
+ * \brief Initialize the properties of a peripheral with a known location
+ */
+ virtual bool InitializeProperties(CPeripheral& peripheral);
+
+ /*!
+ * \brief Initialize a joystick buttonmap, if possible
+ */
+ virtual bool InitializeButtonMap(const CPeripheral& peripheral,
+ KODI::JOYSTICK::IButtonMap& buttonMap) const
+ {
+ return false;
+ }
+
+ /*!
+ * @brief Get the instance of the peripheral at the given location.
+ * @param strLocation The location.
+ * @return The peripheral or NULL if it wasn't found.
+ */
+ virtual PeripheralPtr GetPeripheral(const std::string& strLocation) const;
+
+ /*!
+ * @brief Check whether a peripheral is present at the given location.
+ * @param strLocation The location.
+ * @return True when a peripheral was found, false otherwise.
+ */
+ virtual bool HasPeripheral(const std::string& strLocation) const;
+
+ /*!
+ * @brief Check if the bus supports the given feature
+ * @param feature The feature to check for
+ * @return True if the bus supports the feature, false otherwise
+ */
+ virtual bool SupportsFeature(PeripheralFeature feature) const { return false; }
+
+ /*!
+ * @brief Get all peripheral instances that have the given feature.
+ * @param results The list of results.
+ * @param feature The feature to search for.
+ * @return The number of devices that have been found.
+ */
+ virtual unsigned int GetPeripheralsWithFeature(PeripheralVector& results,
+ const PeripheralFeature feature) const;
+
+ virtual unsigned int GetNumberOfPeripherals() const;
+ virtual unsigned int GetNumberOfPeripheralsWithId(const int iVendorId,
+ const int iProductId) const;
+
+ /*!
+ * @brief Get all features that are supported by devices on this bus.
+ * @param features All features.
+ */
+ virtual void GetFeatures(std::vector<PeripheralFeature>& features) const;
+
+ /*!
+ * @brief Check whether there is at least one device present with the given feature.
+ * @param feature The feature to check for.
+ * @return True when at least one device was found with this feature, false otherwise.
+ */
+ virtual bool HasFeature(const PeripheralFeature feature) const;
+
+ /*!
+ * @brief Callback method for when a device has been added. Will perform a device scan.
+ * @param strLocation The location of the device that has been added.
+ */
+ virtual void OnDeviceAdded(const std::string& strLocation);
+
+ /*!
+ * @brief Callback method for when a device has been changed. Will perform a device scan.
+ * @param strLocation The location of the device that has been changed.
+ */
+ virtual void OnDeviceChanged(const std::string& strLocation);
+
+ /*!
+ * @brief Callback method for when a device has been removed. Will perform a device scan.
+ * @param strLocation The location of the device that has been removed.
+ */
+ virtual void OnDeviceRemoved(const std::string& strLocation);
+
+ /*!
+ * @brief Initialise this bus and start a polling thread if this bus needs polling.
+ */
+ virtual void Initialise(void);
+
+ /*!
+ * @brief Stop the polling thread and clear all known devices on this bus.
+ */
+ virtual void Clear(void);
+
+ /*!
+ * @brief Scan for devices.
+ */
+ virtual void TriggerDeviceScan(void);
+
+ /*!
+ * @brief Get all fileitems for a path.
+ * @param strPath The path to the directory to get the items from.
+ * @param items The item list.
+ */
+ virtual void GetDirectory(const std::string& strPath, CFileItemList& items) const;
+
+ /*!
+ * @brief Get the instance of a peripheral given it's path.
+ * @param strPath The path to the peripheral.
+ * @return The peripheral or NULL if it wasn't found.
+ */
+ virtual PeripheralPtr GetByPath(const std::string& strPath) const;
+
+ /*!
+ * @brief Register a new peripheral on this bus.
+ * @param peripheral The peripheral to register.
+ */
+ virtual void Register(const PeripheralPtr& peripheral);
+
+ virtual bool FindComPort(std::string& strLocation) { return false; }
+
+ /*!
+ * \brief Poll for events
+ */
+ virtual void ProcessEvents(void) {}
+
+ /*!
+ * \brief Initialize button mapping
+ * \return True if button mapping is enabled for this bus
+ */
+ virtual void EnableButtonMapping() {}
+
+ /*!
+ * \brief Power off the specified device
+ * \param strLocation The device's location
+ */
+ virtual void PowerOff(const std::string& strLocation) {}
+
+protected:
+ void Process(void) override;
+ virtual bool ScanForDevices(void);
+ virtual void UnregisterRemovedDevices(const PeripheralScanResults& results);
+ virtual void RegisterNewDevices(const PeripheralScanResults& results);
+
+ /*!
+ * @brief Scan for devices on this bus and add them to the results list. This will have to be
+ * implemented for each bus.
+ * @param results The result list.
+ * @return True when the scan was successful, false otherwise.
+ */
+ virtual bool PerformDeviceScan(PeripheralScanResults& results) = 0;
+
+ PeripheralVector m_peripherals;
+ std::chrono::milliseconds m_iRescanTime;
+ bool m_bNeedsPolling; /*!< true when this bus needs to be polled for new devices, false when it
+ uses callbacks to notify this bus of changed */
+ CPeripherals& m_manager;
+ const PeripheralBusType m_type;
+ mutable CCriticalSection m_critSection;
+ CEvent m_triggerEvent;
+};
+using PeripheralBusPtr = std::shared_ptr<CPeripheralBus>;
+using PeripheralBusVector = std::vector<PeripheralBusPtr>;
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/bus/PeripheralBusUSB.h b/xbmc/peripherals/bus/PeripheralBusUSB.h
new file mode 100644
index 0000000..dd32ff6
--- /dev/null
+++ b/xbmc/peripherals/bus/PeripheralBusUSB.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+#define HAVE_PERIPHERAL_BUS_USB 1
+#include "platform/win32/peripherals/PeripheralBusUSB.h"
+#elif defined(TARGET_WINDOWS_STORE)
+#define HAVE_PERIPHERAL_BUS_USB 1
+#include "platform/win10/peripherals/PeripheralBusUSB.h"
+#elif defined(TARGET_LINUX) && defined(HAVE_LIBUDEV)
+#define HAVE_PERIPHERAL_BUS_USB 1
+#include "platform/linux/peripherals/PeripheralBusUSBLibUdev.h"
+#elif defined(TARGET_LINUX) && defined(HAVE_LIBUSB)
+#define HAVE_PERIPHERAL_BUS_USB 1
+#include "platform/linux/peripherals/PeripheralBusUSBLibUSB.h"
+#elif defined(TARGET_FREEBSD) && defined(HAVE_LIBUSB)
+#define HAVE_PERIPHERAL_BUS_USB 1
+#include "platform/linux/peripherals/PeripheralBusUSBLibUSB.h"
+#elif defined(TARGET_DARWIN_OSX)
+#define HAVE_PERIPHERAL_BUS_USB 1
+#include "platform/darwin/osx/peripherals/PeripheralBusUSB.h"
+#endif
diff --git a/xbmc/peripherals/bus/virtual/CMakeLists.txt b/xbmc/peripherals/bus/virtual/CMakeLists.txt
new file mode 100644
index 0000000..bf59695
--- /dev/null
+++ b/xbmc/peripherals/bus/virtual/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES PeripheralBusAddon.cpp
+ PeripheralBusApplication.cpp)
+
+set(HEADERS PeripheralBusAddon.h
+ PeripheralBusApplication.h)
+
+if(CEC_FOUND)
+ list(APPEND SOURCES PeripheralBusCEC.cpp)
+ list(APPEND HEADERS PeripheralBusCEC.h)
+endif()
+
+core_add_library(peripheral_bus_virtual)
diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp
new file mode 100644
index 0000000..2162c45
--- /dev/null
+++ b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.cpp
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralBusAddon.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonEvents.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/addons/PeripheralAddon.h"
+#include "peripherals/devices/PeripheralJoystick.h"
+#include "threads/SingleLock.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <memory>
+#include <mutex>
+
+using namespace KODI;
+using namespace PERIPHERALS;
+
+CPeripheralBusAddon::CPeripheralBusAddon(CPeripherals& manager)
+ : CPeripheralBus("PeripBusAddon", manager, PERIPHERAL_BUS_ADDON)
+{
+ using namespace ADDON;
+
+ CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CPeripheralBusAddon::OnEvent);
+
+ UpdateAddons();
+}
+
+CPeripheralBusAddon::~CPeripheralBusAddon()
+{
+ using namespace ADDON;
+
+ CServiceBroker::GetAddonMgr().Events().Unsubscribe(this);
+
+ // stop everything before destroying any (loaded) addons
+ Clear();
+
+ // destroy any (loaded) addons
+ for (const auto& addon : m_addons)
+ addon->DestroyAddon();
+
+ m_failedAddons.clear();
+ m_addons.clear();
+}
+
+bool CPeripheralBusAddon::GetAddonWithButtonMap(PeripheralAddonPtr& addon) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ auto it = std::find_if(m_addons.begin(), m_addons.end(),
+ [](const PeripheralAddonPtr& addon) { return addon->HasButtonMaps(); });
+
+ if (it != m_addons.end())
+ {
+ addon = *it;
+ return true;
+ }
+
+ return false;
+}
+
+bool CPeripheralBusAddon::GetAddonWithButtonMap(const CPeripheral* device,
+ PeripheralAddonPtr& addon) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // If device is from an add-on, try to use that add-on
+ if (device && device->GetBusType() == PERIPHERAL_BUS_ADDON)
+ {
+ PeripheralAddonPtr addonWithButtonMap;
+ unsigned int index;
+ if (SplitLocation(device->Location(), addonWithButtonMap, index))
+ {
+ if (addonWithButtonMap->HasButtonMaps())
+ addon = std::move(addonWithButtonMap);
+ else
+ CLog::Log(LOGDEBUG, "Add-on {} doesn't provide button maps for its controllers",
+ addonWithButtonMap->ID());
+ }
+ }
+
+ if (!addon)
+ {
+ auto it = std::find_if(m_addons.begin(), m_addons.end(),
+ [](const PeripheralAddonPtr& addon) { return addon->HasButtonMaps(); });
+
+ if (it != m_addons.end())
+ addon = *it;
+ }
+
+ return addon.get() != nullptr;
+}
+
+bool CPeripheralBusAddon::PerformDeviceScan(PeripheralScanResults& results)
+{
+ PeripheralAddonVector addons;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ addons = m_addons;
+ }
+
+ for (const auto& addon : addons)
+ addon->PerformDeviceScan(results);
+
+ // Scan during bus initialization must return true or bus gets deleted
+ return true;
+}
+
+bool CPeripheralBusAddon::InitializeProperties(CPeripheral& peripheral)
+{
+ if (!CPeripheralBus::InitializeProperties(peripheral))
+ return false;
+
+ bool bSuccess = false;
+
+ PeripheralAddonPtr addon;
+ unsigned int index;
+
+ if (SplitLocation(peripheral.Location(), addon, index))
+ {
+ switch (peripheral.Type())
+ {
+ case PERIPHERAL_JOYSTICK:
+ bSuccess =
+ addon->GetJoystickProperties(index, static_cast<CPeripheralJoystick&>(peripheral));
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return bSuccess;
+}
+
+bool CPeripheralBusAddon::SendRumbleEvent(const std::string& strLocation,
+ unsigned int motorIndex,
+ float magnitude)
+{
+ bool bHandled = false;
+
+ PeripheralAddonPtr addon;
+ unsigned int peripheralIndex;
+ if (SplitLocation(strLocation, addon, peripheralIndex))
+ bHandled = addon->SendRumbleEvent(peripheralIndex, motorIndex, magnitude);
+
+ return bHandled;
+}
+
+void CPeripheralBusAddon::ProcessEvents(void)
+{
+ PeripheralAddonVector addons;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ addons = m_addons;
+ }
+
+ for (const auto& addon : addons)
+ addon->ProcessEvents();
+}
+
+void CPeripheralBusAddon::EnableButtonMapping()
+{
+ using namespace ADDON;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ PeripheralAddonPtr dummy;
+
+ if (!GetAddonWithButtonMap(dummy))
+ {
+ std::vector<AddonInfoPtr> disabledAddons;
+ CServiceBroker::GetAddonMgr().GetDisabledAddonInfos(disabledAddons, AddonType::PERIPHERALDLL);
+ if (!disabledAddons.empty())
+ PromptEnableAddons(disabledAddons);
+ }
+}
+
+void CPeripheralBusAddon::PowerOff(const std::string& strLocation)
+{
+ PeripheralAddonPtr addon;
+ unsigned int peripheralIndex;
+ if (SplitLocation(strLocation, addon, peripheralIndex))
+ addon->PowerOffJoystick(peripheralIndex);
+}
+
+void CPeripheralBusAddon::UnregisterRemovedDevices(const PeripheralScanResults& results)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ PeripheralVector removedPeripherals;
+
+ for (const auto& addon : m_addons)
+ addon->UnregisterRemovedDevices(results, removedPeripherals);
+
+ for (const auto& peripheral : removedPeripherals)
+ m_manager.OnDeviceDeleted(*this, *peripheral);
+}
+
+void CPeripheralBusAddon::Register(const PeripheralPtr& peripheral)
+{
+ if (!peripheral)
+ return;
+
+ PeripheralAddonPtr addon;
+ unsigned int peripheralIndex;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (SplitLocation(peripheral->Location(), addon, peripheralIndex))
+ {
+ if (addon->Register(peripheralIndex, peripheral))
+ m_manager.OnDeviceAdded(*this, *peripheral);
+ }
+}
+
+void CPeripheralBusAddon::GetFeatures(std::vector<PeripheralFeature>& features) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& addon : m_addons)
+ addon->GetFeatures(features);
+}
+
+bool CPeripheralBusAddon::HasFeature(const PeripheralFeature feature) const
+{
+ bool bReturn(false);
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& addon : m_addons)
+ bReturn = bReturn || addon->HasFeature(feature);
+ return bReturn;
+}
+
+PeripheralPtr CPeripheralBusAddon::GetPeripheral(const std::string& strLocation) const
+{
+ PeripheralPtr peripheral;
+ PeripheralAddonPtr addon;
+ unsigned int peripheralIndex;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (SplitLocation(strLocation, addon, peripheralIndex))
+ peripheral = addon->GetPeripheral(peripheralIndex);
+
+ return peripheral;
+}
+
+PeripheralPtr CPeripheralBusAddon::GetByPath(const std::string& strPath) const
+{
+ PeripheralPtr result;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& addon : m_addons)
+ {
+ PeripheralPtr peripheral = addon->GetByPath(strPath);
+ if (peripheral)
+ {
+ result = peripheral;
+ break;
+ }
+ }
+
+ return result;
+}
+
+bool CPeripheralBusAddon::SupportsFeature(PeripheralFeature feature) const
+{
+ bool bSupportsFeature = false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& addon : m_addons)
+ bSupportsFeature |= addon->SupportsFeature(feature);
+
+ return bSupportsFeature;
+}
+
+unsigned int CPeripheralBusAddon::GetPeripheralsWithFeature(PeripheralVector& results,
+ const PeripheralFeature feature) const
+{
+ unsigned int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& addon : m_addons)
+ iReturn += addon->GetPeripheralsWithFeature(results, feature);
+ return iReturn;
+}
+
+unsigned int CPeripheralBusAddon::GetNumberOfPeripherals(void) const
+{
+ unsigned int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& addon : m_addons)
+ iReturn += addon->GetNumberOfPeripherals();
+ return iReturn;
+}
+
+unsigned int CPeripheralBusAddon::GetNumberOfPeripheralsWithId(const int iVendorId,
+ const int iProductId) const
+{
+ unsigned int iReturn = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& addon : m_addons)
+ iReturn += addon->GetNumberOfPeripheralsWithId(iVendorId, iProductId);
+ return iReturn;
+}
+
+void CPeripheralBusAddon::GetDirectory(const std::string& strPath, CFileItemList& items) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& addon : m_addons)
+ addon->GetDirectory(strPath, items);
+}
+
+void CPeripheralBusAddon::OnEvent(const ADDON::AddonEvent& event)
+{
+ if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) ||
+ typeid(event) == typeid(ADDON::AddonEvents::ReInstalled))
+ {
+ if (CServiceBroker::GetAddonMgr().HasType(event.addonId, ADDON::AddonType::PERIPHERALDLL))
+ UpdateAddons();
+ }
+ else if (typeid(event) == typeid(ADDON::AddonEvents::Disabled))
+ {
+ if (CServiceBroker::GetAddonMgr().HasType(event.addonId, ADDON::AddonType::PERIPHERALDLL))
+ UnRegisterAddon(event.addonId);
+ }
+ else if (typeid(event) == typeid(ADDON::AddonEvents::UnInstalled))
+ {
+ UnRegisterAddon(event.addonId);
+ }
+}
+
+bool CPeripheralBusAddon::SplitLocation(const std::string& strLocation,
+ PeripheralAddonPtr& addon,
+ unsigned int& peripheralIndex) const
+{
+ std::vector<std::string> parts = StringUtils::Split(strLocation, "/");
+ if (parts.size() == 2)
+ {
+ addon.reset();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ const std::string& strAddonId = parts[0];
+ for (const auto& addonIt : m_addons)
+ {
+ if (addonIt->ID() == strAddonId)
+ {
+ addon = addonIt;
+ break;
+ }
+ }
+
+ if (addon)
+ {
+ const char* strJoystickIndex = parts[1].c_str();
+ char* p = NULL;
+ peripheralIndex = strtol(strJoystickIndex, &p, 10);
+ if (strJoystickIndex != p)
+ return true;
+ }
+ }
+ return false;
+}
+
+void CPeripheralBusAddon::UpdateAddons(void)
+{
+ using namespace ADDON;
+
+ auto GetPeripheralAddonID = [](const PeripheralAddonPtr& addon) { return addon->ID(); };
+ auto GetAddonID = [](const AddonInfoPtr& addon) { return addon->ID(); };
+
+ std::set<std::string> currentIds;
+ std::set<std::string> newIds;
+
+ std::set<std::string> added;
+ std::set<std::string> removed;
+
+ // Get new add-ons
+ std::vector<AddonInfoPtr> newAddons;
+ CServiceBroker::GetAddonMgr().GetAddonInfos(newAddons, true, AddonType::PERIPHERALDLL);
+ std::transform(newAddons.begin(), newAddons.end(), std::inserter(newIds, newIds.end()),
+ GetAddonID);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // Get current add-ons
+ std::transform(m_addons.begin(), m_addons.end(), std::inserter(currentIds, currentIds.end()),
+ GetPeripheralAddonID);
+ std::transform(m_failedAddons.begin(), m_failedAddons.end(),
+ std::inserter(currentIds, currentIds.end()), GetPeripheralAddonID);
+
+ // Differences
+ std::set_difference(newIds.begin(), newIds.end(), currentIds.begin(), currentIds.end(),
+ std::inserter(added, added.end()));
+ std::set_difference(currentIds.begin(), currentIds.end(), newIds.begin(), newIds.end(),
+ std::inserter(removed, removed.end()));
+
+ // Register new add-ons
+ for (const std::string& addonId : added)
+ {
+ CLog::Log(LOGDEBUG, "Add-on bus: Registering add-on {}", addonId);
+
+ auto GetAddon = [&addonId](const AddonInfoPtr& addon) { return addon->ID() == addonId; };
+
+ auto it = std::find_if(newAddons.begin(), newAddons.end(), GetAddon);
+ if (it != newAddons.end())
+ {
+ PeripheralAddonPtr newAddon = std::make_shared<CPeripheralAddon>(*it, m_manager);
+ if (newAddon)
+ {
+ bool bCreated;
+
+ {
+ CSingleExit exit(m_critSection);
+ bCreated = newAddon->CreateAddon();
+ }
+
+ if (bCreated)
+ m_addons.emplace_back(std::move(newAddon));
+ else
+ m_failedAddons.emplace_back(std::move(newAddon));
+ }
+ }
+ }
+
+ // Destroy removed add-ons
+ for (const std::string& addonId : removed)
+ {
+ UnRegisterAddon(addonId);
+ }
+}
+
+void CPeripheralBusAddon::UnRegisterAddon(const std::string& addonId)
+{
+ PeripheralAddonPtr erased;
+ auto ErasePeripheralAddon = [&addonId, &erased](const PeripheralAddonPtr& addon) {
+ if (addon->ID() == addonId)
+ {
+ erased = addon;
+ return true;
+ }
+ return false;
+ };
+
+ m_addons.erase(std::remove_if(m_addons.begin(), m_addons.end(), ErasePeripheralAddon),
+ m_addons.end());
+ if (!erased)
+ m_failedAddons.erase(
+ std::remove_if(m_failedAddons.begin(), m_failedAddons.end(), ErasePeripheralAddon),
+ m_failedAddons.end());
+
+ if (erased)
+ {
+ CLog::Log(LOGDEBUG, "Add-on bus: Unregistered add-on {}", addonId);
+ CSingleExit exit(m_critSection);
+ erased->DestroyAddon();
+ }
+}
+
+void CPeripheralBusAddon::PromptEnableAddons(
+ const std::vector<std::shared_ptr<ADDON::CAddonInfo>>& disabledAddons)
+{
+ using namespace ADDON;
+ using namespace MESSAGING::HELPERS;
+
+ // True if the user confirms enabling the disabled peripheral add-on
+ bool bAccepted = false;
+
+ auto itAddon =
+ std::find_if(disabledAddons.begin(), disabledAddons.end(), [](const AddonInfoPtr& addonInfo) {
+ return CPeripheralAddon::ProvidesJoysticks(addonInfo);
+ });
+
+ if (itAddon != disabledAddons.end())
+ {
+ // "Unable to configure controllers"
+ // "Controller configuration depends on a disabled add-on. Would you like to enable it?"
+ bAccepted =
+ (ShowYesNoDialogLines(CVariant{35017}, CVariant{35018}) == DialogResponse::CHOICE_YES);
+ }
+
+ if (bAccepted)
+ {
+ for (const auto& addonInfo : disabledAddons)
+ {
+ if (CPeripheralAddon::ProvidesJoysticks(addonInfo))
+ CServiceBroker::GetAddonMgr().EnableAddon(addonInfo->ID());
+ }
+ }
+}
diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusAddon.h b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.h
new file mode 100644
index 0000000..696b189
--- /dev/null
+++ b/xbmc/peripherals/bus/virtual/PeripheralBusAddon.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "peripherals/PeripheralTypes.h"
+#include "peripherals/bus/PeripheralBus.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace ADDON
+{
+struct AddonEvent;
+class CAddonInfo;
+} // namespace ADDON
+
+namespace PERIPHERALS
+{
+class CPeripheralBusAddon : public CPeripheralBus
+{
+public:
+ explicit CPeripheralBusAddon(CPeripherals& manager);
+ ~CPeripheralBusAddon(void) override;
+
+ void UpdateAddons(void);
+
+ /*!
+ * \brief Get peripheral add-on that can provide button maps
+ */
+ bool GetAddonWithButtonMap(PeripheralAddonPtr& addon) const;
+
+ /*!
+ * \brief Get peripheral add-on that can provide button maps for the given device
+ */
+ bool GetAddonWithButtonMap(const CPeripheral* device, PeripheralAddonPtr& addon) const;
+
+ /*!
+ * \brief Set the rumble state of a rumble motor
+ *
+ * \param strLocation The location of the peripheral with the motor
+ * \param motorIndex The index of the motor being rumbled
+ * \param magnitude The amount of vibration in the closed interval [0.0, 1.0]
+ *
+ * \return true if the rumble motor's state is set, false otherwise
+ *
+ * TODO: Move declaration to parent class
+ */
+ bool SendRumbleEvent(const std::string& strLocation, unsigned int motorIndex, float magnitude);
+
+ // Inherited from CPeripheralBus
+ bool InitializeProperties(CPeripheral& peripheral) override;
+ void Register(const PeripheralPtr& peripheral) override;
+ void GetFeatures(std::vector<PeripheralFeature>& features) const override;
+ bool HasFeature(const PeripheralFeature feature) const override;
+ PeripheralPtr GetPeripheral(const std::string& strLocation) const override;
+ PeripheralPtr GetByPath(const std::string& strPath) const override;
+ bool SupportsFeature(PeripheralFeature feature) const override;
+ unsigned int GetPeripheralsWithFeature(PeripheralVector& results,
+ const PeripheralFeature feature) const override;
+ unsigned int GetNumberOfPeripherals(void) const override;
+ unsigned int GetNumberOfPeripheralsWithId(const int iVendorId,
+ const int iProductId) const override;
+ void GetDirectory(const std::string& strPath, CFileItemList& items) const override;
+ void ProcessEvents(void) override;
+ void EnableButtonMapping() override;
+ void PowerOff(const std::string& strLocation) override;
+
+ bool SplitLocation(const std::string& strLocation,
+ PeripheralAddonPtr& addon,
+ unsigned int& peripheralIndex) const;
+
+protected:
+ // Inherited from CPeripheralBus
+ bool PerformDeviceScan(PeripheralScanResults& results) override;
+ void UnregisterRemovedDevices(const PeripheralScanResults& results) override;
+
+private:
+ void OnEvent(const ADDON::AddonEvent& event);
+ void UnRegisterAddon(const std::string& addonId);
+
+ void PromptEnableAddons(const std::vector<std::shared_ptr<ADDON::CAddonInfo>>& disabledAddons);
+
+ PeripheralAddonVector m_addons;
+ PeripheralAddonVector m_failedAddons;
+};
+using PeripheralBusAddonPtr = std::shared_ptr<CPeripheralBusAddon>;
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp
new file mode 100644
index 0000000..4dad03c
--- /dev/null
+++ b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralBusApplication.h"
+
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralBusApplication::CPeripheralBusApplication(CPeripherals& manager)
+ : CPeripheralBus("PeripBusApplication", manager, PERIPHERAL_BUS_APPLICATION)
+{
+ // Initialize CPeripheralBus
+ m_bNeedsPolling = false;
+}
+
+void CPeripheralBusApplication::Initialise(void)
+{
+ CPeripheralBus::Initialise();
+ TriggerDeviceScan();
+}
+
+bool CPeripheralBusApplication::PerformDeviceScan(PeripheralScanResults& results)
+{
+ {
+ PeripheralScanResult result(Type());
+ result.m_type = PERIPHERAL_KEYBOARD;
+ result.m_strDeviceName = g_localizeStrings.Get(35150); // "Keyboard"
+ result.m_strLocation = PeripheralTypeTranslator::TypeToString(PERIPHERAL_KEYBOARD);
+ result.m_iVendorId = 0;
+ result.m_iProductId = 0;
+ result.m_mappedType = PERIPHERAL_KEYBOARD;
+ result.m_mappedBusType = Type();
+ result.m_iSequence = 0;
+
+ if (!results.ContainsResult(result))
+ results.m_results.push_back(result);
+ }
+
+ bool bHasMouse = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_INPUT_ENABLEMOUSE);
+
+ //! @todo Fix game clients to handle mouse disconnecting
+ //! For now mouse is always connected
+ bHasMouse = true;
+
+ if (bHasMouse)
+ {
+ PeripheralScanResult result(Type());
+ result.m_type = PERIPHERAL_MOUSE;
+ result.m_strDeviceName = g_localizeStrings.Get(35171); // "Mouse"
+ result.m_strLocation = PeripheralTypeTranslator::TypeToString(PERIPHERAL_MOUSE);
+ result.m_iVendorId = 0;
+ result.m_iProductId = 0;
+ result.m_mappedType = PERIPHERAL_MOUSE;
+ result.m_mappedBusType = Type();
+ result.m_iSequence = 0;
+
+ if (!results.ContainsResult(result))
+ results.m_results.push_back(result);
+ }
+
+ return true;
+}
+
+void CPeripheralBusApplication::GetDirectory(const std::string& strPath, CFileItemList& items) const
+{
+ // Don't list virtual devices in the GUI
+}
+
+std::string CPeripheralBusApplication::MakeLocation(unsigned int controllerIndex) const
+{
+ return std::to_string(controllerIndex);
+}
diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusApplication.h b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.h
new file mode 100644
index 0000000..4fcc40d
--- /dev/null
+++ b/xbmc/peripherals/bus/virtual/PeripheralBusApplication.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "peripherals/bus/PeripheralBus.h"
+
+namespace PERIPHERALS
+{
+/*!
+ * @class CPeripheralBusApplication
+ *
+ * This exposes peripherals that exist logically at the application level,
+ * such as emulated joysticks.
+ */
+class CPeripheralBusApplication : public CPeripheralBus
+{
+public:
+ explicit CPeripheralBusApplication(CPeripherals& manager);
+ ~CPeripheralBusApplication(void) override = default;
+
+ // implementation of CPeripheralBus
+ void Initialise(void) override;
+ void GetDirectory(const std::string& strPath, CFileItemList& items) const override;
+
+ /*!
+ * \brief Get the location for the specified controller index
+ */
+ std::string MakeLocation(unsigned int controllerIndex) const;
+
+protected:
+ // implementation of CPeripheralBus
+ bool PerformDeviceScan(PeripheralScanResults& results) override;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp
new file mode 100644
index 0000000..e119638
--- /dev/null
+++ b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralBusCEC.h"
+
+#include <libcec/cec.h>
+
+using namespace PERIPHERALS;
+using namespace CEC;
+
+CPeripheralBusCEC::CPeripheralBusCEC(CPeripherals& manager)
+ : CPeripheralBus("PeripBusCEC", manager, PERIPHERAL_BUS_CEC)
+{
+ m_cecAdapter = CECInitialise(&m_configuration);
+}
+
+CPeripheralBusCEC::~CPeripheralBusCEC(void)
+{
+ if (m_cecAdapter)
+ CECDestroy(m_cecAdapter);
+}
+
+bool CPeripheralBusCEC::PerformDeviceScan(PeripheralScanResults& results)
+{
+ cec_adapter_descriptor deviceList[10];
+ int8_t iFound = m_cecAdapter->DetectAdapters(deviceList, 10, NULL, true);
+
+ for (uint8_t iDevicePtr = 0; iDevicePtr < iFound; iDevicePtr++)
+ {
+ PeripheralScanResult result(m_type);
+ result.m_iVendorId = deviceList[iDevicePtr].iVendorId;
+ result.m_iProductId = deviceList[iDevicePtr].iProductId;
+ result.m_strLocation = deviceList[iDevicePtr].strComName;
+ result.m_type = PERIPHERAL_CEC;
+
+ // override the bus type, so users don't have to reconfigure their adapters
+ switch (deviceList[iDevicePtr].adapterType)
+ {
+ case ADAPTERTYPE_P8_EXTERNAL:
+ case ADAPTERTYPE_P8_DAUGHTERBOARD:
+ result.m_mappedBusType = PERIPHERAL_BUS_USB;
+ break;
+ default:
+ break;
+ }
+
+ result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId);
+ if (!results.ContainsResult(result))
+ results.m_results.push_back(result);
+ }
+ return true;
+}
diff --git a/xbmc/peripherals/bus/virtual/PeripheralBusCEC.h b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.h
new file mode 100644
index 0000000..9aa2643
--- /dev/null
+++ b/xbmc/peripherals/bus/virtual/PeripheralBusCEC.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "peripherals/bus/PeripheralBus.h"
+
+// undefine macro isset, it collides with function in cectypes.h
+#ifdef isset
+#undef isset
+#endif
+#include <libcec/cectypes.h>
+
+namespace CEC
+{
+class ICECAdapter;
+}
+
+namespace PERIPHERALS
+{
+class CPeripherals;
+
+class CPeripheralBusCEC : public CPeripheralBus
+{
+public:
+ explicit CPeripheralBusCEC(CPeripherals& manager);
+ ~CPeripheralBusCEC(void) override;
+
+ /*!
+ * @see PeripheralBus::PerformDeviceScan()
+ */
+ bool PerformDeviceScan(PeripheralScanResults& results) override;
+
+private:
+ CEC::ICECAdapter* m_cecAdapter;
+ CEC::libcec_configuration m_configuration;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/CMakeLists.txt b/xbmc/peripherals/devices/CMakeLists.txt
new file mode 100644
index 0000000..57795f5
--- /dev/null
+++ b/xbmc/peripherals/devices/CMakeLists.txt
@@ -0,0 +1,30 @@
+set(SOURCES Peripheral.cpp
+ PeripheralBluetooth.cpp
+ PeripheralDisk.cpp
+ PeripheralHID.cpp
+ PeripheralImon.cpp
+ PeripheralJoystick.cpp
+ PeripheralKeyboard.cpp
+ PeripheralMouse.cpp
+ PeripheralNIC.cpp
+ PeripheralNyxboard.cpp
+ PeripheralTuner.cpp)
+
+set(HEADERS Peripheral.h
+ PeripheralBluetooth.h
+ PeripheralDisk.h
+ PeripheralHID.h
+ PeripheralImon.h
+ PeripheralJoystick.h
+ PeripheralKeyboard.h
+ PeripheralMouse.h
+ PeripheralNIC.h
+ PeripheralNyxboard.h
+ PeripheralTuner.h)
+
+if(CEC_FOUND)
+ list(APPEND SOURCES PeripheralCecAdapter.cpp)
+ list(APPEND HEADERS PeripheralCecAdapter.h)
+endif()
+
+core_add_library(peripherals_devices)
diff --git a/xbmc/peripherals/devices/Peripheral.cpp b/xbmc/peripherals/devices/Peripheral.cpp
new file mode 100644
index 0000000..6c3f779
--- /dev/null
+++ b/xbmc/peripherals/devices/Peripheral.cpp
@@ -0,0 +1,771 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Peripheral.h"
+
+#include "Util.h"
+#include "XBDateTime.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerLayout.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/addons/AddonButtonMapping.h"
+#include "peripherals/addons/AddonInputHandling.h"
+#include "peripherals/addons/PeripheralAddon.h"
+#include "peripherals/bus/PeripheralBus.h"
+#include "peripherals/bus/virtual/PeripheralBusAddon.h"
+#include "settings/SettingAddon.h"
+#include "settings/lib/Setting.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <utility>
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+struct SortBySettingsOrder
+{
+ bool operator()(const PeripheralDeviceSetting& left, const PeripheralDeviceSetting& right)
+ {
+ return left.m_order < right.m_order;
+ }
+};
+
+CPeripheral::CPeripheral(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : m_manager(manager),
+ m_type(scanResult.m_mappedType),
+ m_busType(scanResult.m_busType),
+ m_mappedBusType(scanResult.m_mappedBusType),
+ m_strLocation(scanResult.m_strLocation),
+ m_strDeviceName(scanResult.m_strDeviceName),
+ m_iVendorId(scanResult.m_iVendorId),
+ m_iProductId(scanResult.m_iProductId),
+ m_strVersionInfo(g_localizeStrings.Get(13205)), // "unknown"
+ m_bInitialised(false),
+ m_bHidden(false),
+ m_bError(false),
+ m_bus(bus)
+{
+ PeripheralTypeTranslator::FormatHexString(scanResult.m_iVendorId, m_strVendorId);
+ PeripheralTypeTranslator::FormatHexString(scanResult.m_iProductId, m_strProductId);
+ if (scanResult.m_iSequence > 0)
+ {
+ m_strFileLocation =
+ StringUtils::Format("peripherals://{}/{}_{}.dev",
+ PeripheralTypeTranslator::BusTypeToString(scanResult.m_busType),
+ scanResult.m_strLocation, scanResult.m_iSequence);
+ }
+ else
+ {
+ m_strFileLocation = StringUtils::Format(
+ "peripherals://{}/{}.dev", PeripheralTypeTranslator::BusTypeToString(scanResult.m_busType),
+ scanResult.m_strLocation);
+ }
+}
+
+CPeripheral::~CPeripheral(void)
+{
+ PersistSettings(true);
+
+ m_subDevices.clear();
+
+ ClearSettings();
+}
+
+bool CPeripheral::operator==(const CPeripheral& right) const
+{
+ return m_type == right.m_type && m_strLocation == right.m_strLocation &&
+ m_iVendorId == right.m_iVendorId && m_iProductId == right.m_iProductId;
+}
+
+bool CPeripheral::operator!=(const CPeripheral& right) const
+{
+ return !(*this == right);
+}
+
+bool CPeripheral::HasFeature(const PeripheralFeature feature) const
+{
+ bool bReturn(false);
+
+ for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++)
+ {
+ if (m_features.at(iFeaturePtr) == feature)
+ {
+ bReturn = true;
+ break;
+ }
+ }
+
+ if (!bReturn)
+ {
+ for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++)
+ {
+ if (m_subDevices.at(iSubdevicePtr)->HasFeature(feature))
+ {
+ bReturn = true;
+ break;
+ }
+ }
+ }
+
+ return bReturn;
+}
+
+void CPeripheral::GetFeatures(std::vector<PeripheralFeature>& features) const
+{
+ for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++)
+ features.push_back(m_features.at(iFeaturePtr));
+
+ for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++)
+ m_subDevices.at(iSubdevicePtr)->GetFeatures(features);
+}
+
+bool CPeripheral::Initialise(void)
+{
+ bool bReturn(false);
+
+ if (m_bError)
+ return bReturn;
+
+ bReturn = true;
+ if (m_bInitialised)
+ return bReturn;
+
+ m_manager.GetSettingsFromMapping(*this);
+
+ std::string safeDeviceName = m_strDeviceName;
+ StringUtils::Replace(safeDeviceName, ' ', '_');
+
+ if (m_iVendorId == 0x0000 && m_iProductId == 0x0000)
+ {
+ m_strSettingsFile =
+ StringUtils::Format("special://profile/peripheral_data/{}_{}.xml",
+ PeripheralTypeTranslator::BusTypeToString(m_mappedBusType),
+ CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT));
+ }
+ else
+ {
+ // Backwards compatibility - old settings files didn't include the device name
+ m_strSettingsFile = StringUtils::Format(
+ "special://profile/peripheral_data/{}_{}_{}.xml",
+ PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), m_strVendorId, m_strProductId);
+
+ if (!CFileUtils::Exists(m_strSettingsFile))
+ m_strSettingsFile = StringUtils::Format(
+ "special://profile/peripheral_data/{}_{}_{}_{}.xml",
+ PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), m_strVendorId, m_strProductId,
+ CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT));
+ }
+
+ LoadPersistedSettings();
+
+ for (unsigned int iFeaturePtr = 0; iFeaturePtr < m_features.size(); iFeaturePtr++)
+ {
+ PeripheralFeature feature = m_features.at(iFeaturePtr);
+ bReturn &= InitialiseFeature(feature);
+ }
+
+ for (unsigned int iSubdevicePtr = 0; iSubdevicePtr < m_subDevices.size(); iSubdevicePtr++)
+ bReturn &= m_subDevices.at(iSubdevicePtr)->Initialise();
+
+ if (bReturn)
+ {
+ CLog::Log(LOGDEBUG, "{} - initialised peripheral on '{}' with {} features and {} sub devices",
+ __FUNCTION__, m_strLocation, (int)m_features.size(), (int)m_subDevices.size());
+ m_bInitialised = true;
+ }
+
+ return bReturn;
+}
+
+void CPeripheral::GetSubdevices(PeripheralVector& subDevices) const
+{
+ subDevices = m_subDevices;
+}
+
+bool CPeripheral::IsMultiFunctional(void) const
+{
+ return m_subDevices.size() > 0;
+}
+
+std::vector<std::shared_ptr<CSetting>> CPeripheral::GetSettings(void) const
+{
+ std::vector<PeripheralDeviceSetting> tmpSettings;
+ tmpSettings.reserve(m_settings.size());
+ for (const auto& it : m_settings)
+ tmpSettings.push_back(it.second);
+ sort(tmpSettings.begin(), tmpSettings.end(), SortBySettingsOrder());
+
+ std::vector<std::shared_ptr<CSetting>> settings;
+ settings.reserve(tmpSettings.size());
+ for (const auto& it : tmpSettings)
+ settings.push_back(it.m_setting);
+ return settings;
+}
+
+void CPeripheral::AddSetting(const std::string& strKey, const SettingConstPtr& setting, int order)
+{
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "{} - invalid setting", __FUNCTION__);
+ return;
+ }
+
+ if (!HasSetting(strKey))
+ {
+ PeripheralDeviceSetting deviceSetting = {NULL, order};
+ switch (setting->GetType())
+ {
+ case SettingType::Boolean:
+ {
+ std::shared_ptr<const CSettingBool> mappedSetting =
+ std::static_pointer_cast<const CSettingBool>(setting);
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::make_shared<CSettingBool>(strKey, *mappedSetting);
+ if (boolSetting)
+ {
+ boolSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = boolSetting;
+ }
+ }
+ break;
+ case SettingType::Integer:
+ {
+ std::shared_ptr<const CSettingInt> mappedSetting =
+ std::static_pointer_cast<const CSettingInt>(setting);
+ std::shared_ptr<CSettingInt> intSetting =
+ std::make_shared<CSettingInt>(strKey, *mappedSetting);
+ if (intSetting)
+ {
+ intSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = intSetting;
+ }
+ }
+ break;
+ case SettingType::Number:
+ {
+ std::shared_ptr<const CSettingNumber> mappedSetting =
+ std::static_pointer_cast<const CSettingNumber>(setting);
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::make_shared<CSettingNumber>(strKey, *mappedSetting);
+ if (floatSetting)
+ {
+ floatSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = floatSetting;
+ }
+ }
+ break;
+ case SettingType::String:
+ {
+ if (std::dynamic_pointer_cast<const CSettingAddon>(setting))
+ {
+ std::shared_ptr<const CSettingAddon> mappedSetting =
+ std::static_pointer_cast<const CSettingAddon>(setting);
+ std::shared_ptr<CSettingAddon> addonSetting =
+ std::make_shared<CSettingAddon>(strKey, *mappedSetting);
+ addonSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = addonSetting;
+ }
+ else
+ {
+ std::shared_ptr<const CSettingString> mappedSetting =
+ std::static_pointer_cast<const CSettingString>(setting);
+ std::shared_ptr<CSettingString> stringSetting =
+ std::make_shared<CSettingString>(strKey, *mappedSetting);
+ stringSetting->SetVisible(mappedSetting->IsVisible());
+ deviceSetting.m_setting = stringSetting;
+ }
+ }
+ break;
+ default:
+ //! @todo add more types if needed
+ break;
+ }
+
+ if (deviceSetting.m_setting != NULL)
+ m_settings.insert(make_pair(strKey, deviceSetting));
+ }
+}
+
+bool CPeripheral::HasSetting(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ return it != m_settings.end();
+}
+
+bool CPeripheral::HasSettings(void) const
+{
+ return !m_settings.empty();
+}
+
+bool CPeripheral::HasConfigurableSettings(void) const
+{
+ bool bReturn(false);
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.begin();
+ while (it != m_settings.end() && !bReturn)
+ {
+ if ((*it).second.m_setting->IsVisible())
+ {
+ bReturn = true;
+ break;
+ }
+
+ ++it;
+ }
+
+ return bReturn;
+}
+
+bool CPeripheral::GetSettingBool(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Boolean)
+ {
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::static_pointer_cast<CSettingBool>((*it).second.m_setting);
+ if (boolSetting)
+ return boolSetting->GetValue();
+ }
+
+ return false;
+}
+
+int CPeripheral::GetSettingInt(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Integer)
+ {
+ std::shared_ptr<CSettingInt> intSetting =
+ std::static_pointer_cast<CSettingInt>((*it).second.m_setting);
+ if (intSetting)
+ return intSetting->GetValue();
+ }
+
+ return 0;
+}
+
+float CPeripheral::GetSettingFloat(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Number)
+ {
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::static_pointer_cast<CSettingNumber>((*it).second.m_setting);
+ if (floatSetting)
+ return (float)floatSetting->GetValue();
+ }
+
+ return 0;
+}
+
+const std::string CPeripheral::GetSettingString(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::String)
+ {
+ std::shared_ptr<CSettingString> stringSetting =
+ std::static_pointer_cast<CSettingString>((*it).second.m_setting);
+ if (stringSetting)
+ return stringSetting->GetValue();
+ }
+
+ return "";
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, bool bValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Boolean)
+ {
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::static_pointer_cast<CSettingBool>((*it).second.m_setting);
+ if (boolSetting)
+ {
+ bChanged = boolSetting->GetValue() != bValue;
+ boolSetting->SetValue(bValue);
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ return bChanged;
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, int iValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Integer)
+ {
+ std::shared_ptr<CSettingInt> intSetting =
+ std::static_pointer_cast<CSettingInt>((*it).second.m_setting);
+ if (intSetting)
+ {
+ bChanged = intSetting->GetValue() != iValue;
+ intSetting->SetValue(iValue);
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ return bChanged;
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, float fValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end() && (*it).second.m_setting->GetType() == SettingType::Number)
+ {
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::static_pointer_cast<CSettingNumber>((*it).second.m_setting);
+ if (floatSetting)
+ {
+ bChanged = floatSetting->GetValue() != static_cast<double>(fValue);
+ floatSetting->SetValue(static_cast<double>(fValue));
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ return bChanged;
+}
+
+void CPeripheral::SetSettingVisible(const std::string& strKey, bool bSetTo)
+{
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end())
+ (*it).second.m_setting->SetVisible(bSetTo);
+}
+
+bool CPeripheral::IsSettingVisible(const std::string& strKey) const
+{
+ std::map<std::string, PeripheralDeviceSetting>::const_iterator it = m_settings.find(strKey);
+ if (it != m_settings.end())
+ return (*it).second.m_setting->IsVisible();
+ return false;
+}
+
+bool CPeripheral::SetSetting(const std::string& strKey, const std::string& strValue)
+{
+ bool bChanged(false);
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.find(strKey);
+ if (it != m_settings.end())
+ {
+ if ((*it).second.m_setting->GetType() == SettingType::String)
+ {
+ std::shared_ptr<CSettingString> stringSetting =
+ std::static_pointer_cast<CSettingString>((*it).second.m_setting);
+ if (stringSetting)
+ {
+ bChanged = !StringUtils::EqualsNoCase(stringSetting->GetValue(), strValue);
+ stringSetting->SetValue(strValue);
+ if (bChanged && m_bInitialised)
+ m_changedSettings.insert(strKey);
+ }
+ }
+ else if ((*it).second.m_setting->GetType() == SettingType::Integer)
+ bChanged = SetSetting(strKey, strValue.empty() ? 0 : atoi(strValue.c_str()));
+ else if ((*it).second.m_setting->GetType() == SettingType::Number)
+ bChanged = SetSetting(strKey, (float)(strValue.empty() ? 0 : atof(strValue.c_str())));
+ else if ((*it).second.m_setting->GetType() == SettingType::Boolean)
+ bChanged = SetSetting(strKey, strValue == "1");
+ }
+ return bChanged;
+}
+
+void CPeripheral::PersistSettings(bool bExiting /* = false */)
+{
+ CXBMCTinyXML doc;
+ TiXmlElement node("settings");
+ doc.InsertEndChild(node);
+ for (const auto& itr : m_settings)
+ {
+ TiXmlElement nodeSetting("setting");
+ nodeSetting.SetAttribute("id", itr.first.c_str());
+ std::string strValue;
+ switch (itr.second.m_setting->GetType())
+ {
+ case SettingType::String:
+ {
+ std::shared_ptr<CSettingString> stringSetting =
+ std::static_pointer_cast<CSettingString>(itr.second.m_setting);
+ if (stringSetting)
+ strValue = stringSetting->GetValue();
+ }
+ break;
+ case SettingType::Integer:
+ {
+ std::shared_ptr<CSettingInt> intSetting =
+ std::static_pointer_cast<CSettingInt>(itr.second.m_setting);
+ if (intSetting)
+ strValue = std::to_string(intSetting->GetValue());
+ }
+ break;
+ case SettingType::Number:
+ {
+ std::shared_ptr<CSettingNumber> floatSetting =
+ std::static_pointer_cast<CSettingNumber>(itr.second.m_setting);
+ if (floatSetting)
+ strValue = StringUtils::Format("{:.2f}", floatSetting->GetValue());
+ }
+ break;
+ case SettingType::Boolean:
+ {
+ std::shared_ptr<CSettingBool> boolSetting =
+ std::static_pointer_cast<CSettingBool>(itr.second.m_setting);
+ if (boolSetting)
+ strValue = std::to_string(boolSetting->GetValue() ? 1 : 0);
+ }
+ break;
+ default:
+ break;
+ }
+ nodeSetting.SetAttribute("value", strValue.c_str());
+ doc.RootElement()->InsertEndChild(nodeSetting);
+ }
+
+ doc.SaveFile(m_strSettingsFile);
+
+ if (!bExiting)
+ {
+ for (const auto& it : m_changedSettings)
+ OnSettingChanged(it);
+ }
+ m_changedSettings.clear();
+}
+
+void CPeripheral::LoadPersistedSettings(void)
+{
+ CXBMCTinyXML doc;
+ if (doc.LoadFile(m_strSettingsFile))
+ {
+ const TiXmlElement* setting = doc.RootElement()->FirstChildElement("setting");
+ while (setting)
+ {
+ std::string strId = XMLUtils::GetAttribute(setting, "id");
+ std::string strValue = XMLUtils::GetAttribute(setting, "value");
+ SetSetting(strId, strValue);
+
+ setting = setting->NextSiblingElement("setting");
+ }
+ }
+}
+
+void CPeripheral::ResetDefaultSettings(void)
+{
+ ClearSettings();
+ m_manager.GetSettingsFromMapping(*this);
+
+ std::map<std::string, PeripheralDeviceSetting>::iterator it = m_settings.begin();
+ while (it != m_settings.end())
+ {
+ m_changedSettings.insert((*it).first);
+ ++it;
+ }
+
+ PersistSettings();
+}
+
+void CPeripheral::ClearSettings(void)
+{
+ m_settings.clear();
+}
+
+void CPeripheral::RegisterInputHandler(IInputHandler* handler, bool bPromiscuous)
+{
+ auto it = m_inputHandlers.find(handler);
+ if (it == m_inputHandlers.end())
+ {
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (addon)
+ {
+ std::unique_ptr<CAddonInputHandling> addonInput = std::make_unique<CAddonInputHandling>(
+ m_manager, this, std::move(addon), handler, GetDriverReceiver());
+ if (addonInput->Load())
+ {
+ RegisterJoystickDriverHandler(addonInput.get(), bPromiscuous);
+ m_inputHandlers[handler] = std::move(addonInput);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation);
+ }
+ }
+}
+
+void CPeripheral::UnregisterInputHandler(IInputHandler* handler)
+{
+ handler->ResetInputReceiver();
+
+ auto it = m_inputHandlers.find(handler);
+ if (it != m_inputHandlers.end())
+ {
+ UnregisterJoystickDriverHandler(it->second.get());
+ m_inputHandlers.erase(it);
+ }
+}
+
+void CPeripheral::RegisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handler,
+ bool bPromiscuous)
+{
+ auto it = m_keyboardHandlers.find(handler);
+ if (it == m_keyboardHandlers.end())
+ {
+ std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler> keyboardDriverHandler;
+
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (addon)
+ {
+ std::unique_ptr<CAddonInputHandling> addonInput =
+ std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler);
+ if (addonInput->Load())
+ keyboardDriverHandler = std::move(addonInput);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation);
+ }
+
+ if (keyboardDriverHandler)
+ {
+ RegisterKeyboardDriverHandler(keyboardDriverHandler.get(), bPromiscuous);
+ m_keyboardHandlers[handler] = std::move(keyboardDriverHandler);
+ }
+ }
+}
+
+void CPeripheral::UnregisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handler)
+{
+ auto it = m_keyboardHandlers.find(handler);
+ if (it != m_keyboardHandlers.end())
+ {
+ UnregisterKeyboardDriverHandler(it->second.get());
+ m_keyboardHandlers.erase(it);
+ }
+}
+
+void CPeripheral::RegisterMouseHandler(MOUSE::IMouseInputHandler* handler, bool bPromiscuous)
+{
+ auto it = m_mouseHandlers.find(handler);
+ if (it == m_mouseHandlers.end())
+ {
+ std::unique_ptr<KODI::MOUSE::IMouseDriverHandler> mouseDriverHandler;
+
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (addon)
+ {
+ std::unique_ptr<CAddonInputHandling> addonInput =
+ std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler);
+ if (addonInput->Load())
+ mouseDriverHandler = std::move(addonInput);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Failed to locate add-on for \"{}\"", m_strLocation);
+ }
+
+ if (mouseDriverHandler)
+ {
+ RegisterMouseDriverHandler(mouseDriverHandler.get(), bPromiscuous);
+ m_mouseHandlers[handler] = std::move(mouseDriverHandler);
+ }
+ }
+}
+
+void CPeripheral::UnregisterMouseHandler(MOUSE::IMouseInputHandler* handler)
+{
+ auto it = m_mouseHandlers.find(handler);
+ if (it != m_mouseHandlers.end())
+ {
+ UnregisterMouseDriverHandler(it->second.get());
+ m_mouseHandlers.erase(it);
+ }
+}
+
+void CPeripheral::RegisterJoystickButtonMapper(IButtonMapper* mapper)
+{
+ auto it = m_buttonMappers.find(mapper);
+ if (it == m_buttonMappers.end())
+ {
+ std::unique_ptr<CAddonButtonMapping> addonMapping(
+ new CAddonButtonMapping(m_manager, this, mapper));
+
+ RegisterJoystickDriverHandler(addonMapping.get(), false);
+ RegisterKeyboardDriverHandler(addonMapping.get(), false);
+ RegisterMouseDriverHandler(addonMapping.get(), false);
+
+ m_buttonMappers[mapper] = std::move(addonMapping);
+ }
+}
+
+void CPeripheral::UnregisterJoystickButtonMapper(IButtonMapper* mapper)
+{
+ auto it = m_buttonMappers.find(mapper);
+ if (it != m_buttonMappers.end())
+ {
+ UnregisterMouseDriverHandler(it->second.get());
+ UnregisterKeyboardDriverHandler(it->second.get());
+ UnregisterJoystickDriverHandler(it->second.get());
+
+ m_buttonMappers.erase(it);
+ }
+}
+
+std::string CPeripheral::GetIcon() const
+{
+ std::string icon;
+
+ // Try controller profile
+ const GAME::ControllerPtr controller = ControllerProfile();
+ if (controller)
+ icon = controller->Layout().ImagePath();
+
+ // Try add-on
+ if (icon.empty() && m_busType == PERIPHERAL_BUS_ADDON)
+ {
+ CPeripheralBusAddon* bus = static_cast<CPeripheralBusAddon*>(m_bus);
+
+ PeripheralAddonPtr addon;
+ unsigned int index;
+ if (bus->SplitLocation(m_strLocation, addon, index))
+ {
+ std::string addonIcon = addon->Icon();
+ if (!addonIcon.empty())
+ icon = std::move(addonIcon);
+ }
+ }
+
+ // Fallback
+ if (icon.empty())
+ icon = "DefaultAddon.png";
+
+ return icon;
+}
+
+bool CPeripheral::operator==(const PeripheralScanResult& right) const
+{
+ return StringUtils::EqualsNoCase(m_strLocation, right.m_strLocation);
+}
+
+bool CPeripheral::operator!=(const PeripheralScanResult& right) const
+{
+ return !(*this == right);
+}
+
+CDateTime CPeripheral::LastActive()
+{
+ return CDateTime();
+}
diff --git a/xbmc/peripherals/devices/Peripheral.h b/xbmc/peripherals/devices/Peripheral.h
new file mode 100644
index 0000000..8c4049b
--- /dev/null
+++ b/xbmc/peripherals/devices/Peripheral.h
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "games/controllers/ControllerTypes.h"
+#include "input/joysticks/interfaces/IInputProvider.h"
+#include "input/keyboard/interfaces/IKeyboardInputProvider.h"
+#include "input/mouse/interfaces/IMouseInputProvider.h"
+#include "peripherals/PeripheralTypes.h"
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+class TiXmlDocument;
+class CDateTime;
+class CSetting;
+class IKeymap;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IButtonMapper;
+class IDriverHandler;
+class IDriverReceiver;
+class IInputHandler;
+} // namespace JOYSTICK
+
+namespace KEYBOARD
+{
+class IKeyboardDriverHandler;
+}
+
+namespace MOUSE
+{
+class IMouseDriverHandler;
+}
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CAddonButtonMapping;
+class CGUIDialogPeripheralSettings;
+class CPeripheralBus;
+class CPeripherals;
+
+typedef enum
+{
+ STATE_SWITCH_TOGGLE,
+ STATE_ACTIVATE_SOURCE,
+ STATE_STANDBY
+} CecStateChange;
+
+class CPeripheral : public KODI::JOYSTICK::IInputProvider,
+ public KODI::KEYBOARD::IKeyboardInputProvider,
+ public KODI::MOUSE::IMouseInputProvider
+{
+ friend class CGUIDialogPeripheralSettings;
+
+public:
+ CPeripheral(CPeripherals& manager, const PeripheralScanResult& scanResult, CPeripheralBus* bus);
+ ~CPeripheral(void) override;
+
+ bool operator==(const CPeripheral& right) const;
+ bool operator!=(const CPeripheral& right) const;
+ bool operator==(const PeripheralScanResult& right) const;
+ bool operator!=(const PeripheralScanResult& right) const;
+
+ const std::string& FileLocation(void) const { return m_strFileLocation; }
+ const std::string& Location(void) const { return m_strLocation; }
+ int VendorId(void) const { return m_iVendorId; }
+ const char* VendorIdAsString(void) const { return m_strVendorId.c_str(); }
+ int ProductId(void) const { return m_iProductId; }
+ const char* ProductIdAsString(void) const { return m_strProductId.c_str(); }
+ PeripheralType Type(void) const { return m_type; }
+ PeripheralBusType GetBusType(void) const { return m_busType; }
+ const std::string& DeviceName(void) const { return m_strDeviceName; }
+ bool IsHidden(void) const { return m_bHidden; }
+ void SetHidden(bool bSetTo = true) { m_bHidden = bSetTo; }
+ const std::string& GetVersionInfo(void) const { return m_strVersionInfo; }
+
+ /*!
+ * @brief Get an icon for this peripheral
+ * @return Path to an icon, or skin icon file name
+ */
+ virtual std::string GetIcon() const;
+
+ /*!
+ * @brief Check whether this device has the given feature.
+ * @param feature The feature to check for.
+ * @return True when the device has the feature, false otherwise.
+ */
+ bool HasFeature(const PeripheralFeature feature) const;
+
+ /*!
+ * @brief Get all features that are supported by this device.
+ * @param features The features.
+ */
+ void GetFeatures(std::vector<PeripheralFeature>& features) const;
+
+ /*!
+ * @brief Initialises the peripheral.
+ * @return True when the peripheral has been initialised successfully, false otherwise.
+ */
+ bool Initialise(void);
+
+ /*!
+ * @brief Initialise one of the features of this peripheral.
+ * @param feature The feature to initialise.
+ * @return True when the feature has been initialised successfully, false otherwise.
+ */
+ virtual bool InitialiseFeature(const PeripheralFeature feature) { return true; }
+
+ /*!
+ * @brief Briefly activate a feature to notify the user
+ */
+ virtual void OnUserNotification() {}
+
+ /*!
+ * @brief Briefly test one of the features of this peripheral.
+ * @param feature The feature to test.
+ * @return True if the test succeeded, false otherwise.
+ */
+ virtual bool TestFeature(PeripheralFeature feature) { return false; }
+
+ /*!
+ * @brief Called when a setting changed.
+ * @param strChangedSetting The changed setting.
+ */
+ virtual void OnSettingChanged(const std::string& strChangedSetting) {}
+
+ /*!
+ * @brief Called when this device is removed, before calling the destructor.
+ */
+ virtual void OnDeviceRemoved(void) {}
+
+ /*!
+ * @brief Get all subdevices if this device is multifunctional.
+ * @param subDevices The subdevices.
+ */
+ virtual void GetSubdevices(PeripheralVector& subDevices) const;
+
+ /*!
+ * @return True when this device is multifunctional, false otherwise.
+ */
+ virtual bool IsMultiFunctional(void) const;
+
+ /*!
+ * @brief Add a setting to this peripheral. This will overwrite a previous setting with the same
+ * key.
+ * @param strKey The key of the setting.
+ * @param setting The setting.
+ */
+ virtual void AddSetting(const std::string& strKey,
+ const std::shared_ptr<const CSetting>& setting,
+ int order);
+
+ /*!
+ * @brief Check whether a setting is known with the given key.
+ * @param strKey The key to search.
+ * @return True when found, false otherwise.
+ */
+ virtual bool HasSetting(const std::string& strKey) const;
+
+ /*!
+ * @return True when this device has any settings, false otherwise.
+ */
+ virtual bool HasSettings(void) const;
+
+ /*!
+ * @return True when this device has any configurable settings, false otherwise.
+ */
+ virtual bool HasConfigurableSettings(void) const;
+
+ /*!
+ * @brief Get the value of a setting.
+ * @param strKey The key to search.
+ * @return The value or an empty string if it wasn't found.
+ */
+ virtual const std::string GetSettingString(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, const std::string& strValue);
+ virtual void SetSettingVisible(const std::string& strKey, bool bSetTo);
+ virtual bool IsSettingVisible(const std::string& strKey) const;
+
+ virtual int GetSettingInt(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, int iValue);
+
+ virtual bool GetSettingBool(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, bool bValue);
+
+ virtual float GetSettingFloat(const std::string& strKey) const;
+ virtual bool SetSetting(const std::string& strKey, float fValue);
+
+ virtual void PersistSettings(bool bExiting = false);
+ virtual void LoadPersistedSettings(void);
+ virtual void ResetDefaultSettings(void);
+
+ virtual std::vector<std::shared_ptr<CSetting>> GetSettings(void) const;
+
+ virtual bool ErrorOccured(void) const { return m_bError; }
+
+ virtual void RegisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler,
+ bool bPromiscuous)
+ {
+ }
+ virtual void UnregisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler) {}
+
+ virtual void RegisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler,
+ bool bPromiscuous)
+ {
+ }
+ virtual void UnregisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler) {}
+
+ virtual void RegisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler,
+ bool bPromiscuous)
+ {
+ }
+ virtual void UnregisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler) {}
+
+ // implementation of IInputProvider
+ void RegisterInputHandler(KODI::JOYSTICK::IInputHandler* handler, bool bPromiscuous) override;
+ void UnregisterInputHandler(KODI::JOYSTICK::IInputHandler* handler) override;
+
+ // implementation of IKeyboardInputProvider
+ void RegisterKeyboardHandler(KODI::KEYBOARD::IKeyboardInputHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterKeyboardHandler(KODI::KEYBOARD::IKeyboardInputHandler* handler) override;
+
+ // implementation of IMouseInputProvider
+ void RegisterMouseHandler(KODI::MOUSE::IMouseInputHandler* handler, bool bPromiscuous) override;
+ void UnregisterMouseHandler(KODI::MOUSE::IMouseInputHandler* handler) override;
+
+ virtual void RegisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper);
+ virtual void UnregisterJoystickButtonMapper(KODI::JOYSTICK::IButtonMapper* mapper);
+
+ virtual KODI::JOYSTICK::IDriverReceiver* GetDriverReceiver() { return nullptr; }
+
+ virtual IKeymap* GetKeymap(const std::string& controllerId) { return nullptr; }
+
+ /*!
+ * \brief Return the last time this peripheral was active
+ *
+ * \return The time of last activation, or invalid if unknown/never active
+ */
+ virtual CDateTime LastActive();
+
+ /*!
+ * \brief Get the controller profile that best represents this peripheral
+ *
+ * \return The controller profile, or empty if unknown
+ */
+ virtual KODI::GAME::ControllerPtr ControllerProfile() const { return m_controllerProfile; }
+
+ /*!
+ * \brief Set the controller profile for this peripheral
+ *
+ * \param controller The new controller profile
+ */
+ virtual void SetControllerProfile(const KODI::GAME::ControllerPtr& controller)
+ {
+ m_controllerProfile = controller;
+ }
+
+protected:
+ virtual void ClearSettings(void);
+
+ CPeripherals& m_manager;
+ PeripheralType m_type;
+ PeripheralBusType m_busType;
+ PeripheralBusType m_mappedBusType;
+ std::string m_strLocation;
+ std::string m_strDeviceName;
+ std::string m_strSettingsFile;
+ std::string m_strFileLocation;
+ int m_iVendorId;
+ std::string m_strVendorId;
+ int m_iProductId;
+ std::string m_strProductId;
+ std::string m_strVersionInfo;
+ bool m_bInitialised;
+ bool m_bHidden;
+ bool m_bError;
+ std::vector<PeripheralFeature> m_features;
+ PeripheralVector m_subDevices;
+ std::map<std::string, PeripheralDeviceSetting> m_settings;
+ std::set<std::string> m_changedSettings;
+ CPeripheralBus* m_bus;
+ std::map<KODI::JOYSTICK::IInputHandler*, std::unique_ptr<KODI::JOYSTICK::IDriverHandler>>
+ m_inputHandlers;
+ std::map<KODI::KEYBOARD::IKeyboardInputHandler*,
+ std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler>>
+ m_keyboardHandlers;
+ std::map<KODI::MOUSE::IMouseInputHandler*, std::unique_ptr<KODI::MOUSE::IMouseDriverHandler>>
+ m_mouseHandlers;
+ std::map<KODI::JOYSTICK::IButtonMapper*, std::unique_ptr<CAddonButtonMapping>> m_buttonMappers;
+ KODI::GAME::ControllerPtr m_controllerProfile;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralBluetooth.cpp b/xbmc/peripherals/devices/PeripheralBluetooth.cpp
new file mode 100644
index 0000000..6f3802d
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralBluetooth.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralBluetooth.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralBluetooth::CPeripheralBluetooth(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_BLUETOOTH);
+}
diff --git a/xbmc/peripherals/devices/PeripheralBluetooth.h b/xbmc/peripherals/devices/PeripheralBluetooth.h
new file mode 100644
index 0000000..11fa7ca
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralBluetooth.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralBluetooth : public CPeripheral
+{
+public:
+ CPeripheralBluetooth(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralBluetooth(void) override = default;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.cpp b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp
new file mode 100644
index 0000000..bc62bea
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp
@@ -0,0 +1,1861 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralCecAdapter.h"
+
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationEnums.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationPowerHandling.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "input/remote/IRRemote.h"
+#include "messaging/ApplicationMessenger.h"
+#include "pictures/GUIWindowSlideShow.h"
+#include "utils/JobManager.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "xbmc/interfaces/AnnouncementManager.h"
+
+#include <mutex>
+
+#include <libcec/cec.h>
+
+using namespace PERIPHERALS;
+using namespace CEC;
+using namespace ANNOUNCEMENT;
+using namespace std::chrono_literals;
+
+#define CEC_LIB_SUPPORTED_VERSION LIBCEC_VERSION_TO_UINT(4, 0, 0)
+
+/* time in seconds to ignore standby commands from devices after the screensaver has been activated
+ */
+#define SCREENSAVER_TIMEOUT 20
+#define VOLUME_CHANGE_TIMEOUT 250
+#define VOLUME_REFRESH_TIMEOUT 100
+
+#define LOCALISED_ID_TV 36037
+#define LOCALISED_ID_AVR 36038
+#define LOCALISED_ID_TV_AVR 36039
+#define LOCALISED_ID_STOP 36044
+#define LOCALISED_ID_PAUSE 36045
+#define LOCALISED_ID_POWEROFF 13005
+#define LOCALISED_ID_SUSPEND 13011
+#define LOCALISED_ID_HIBERNATE 13010
+#define LOCALISED_ID_QUIT 13009
+#define LOCALISED_ID_IGNORE 36028
+#define LOCALISED_ID_RECORDING_DEVICE 36051
+#define LOCALISED_ID_PLAYBACK_DEVICE 36052
+#define LOCALISED_ID_TUNER_DEVICE 36053
+
+#define LOCALISED_ID_NONE 231
+
+/* time in seconds to suppress source activation after receiving OnStop */
+#define CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP 2
+
+CPeripheralCecAdapter::CPeripheralCecAdapter(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheralHID(manager, scanResult, bus), CThread("CECAdapter"), m_cecAdapter(NULL)
+{
+ ResetMembers();
+ m_features.push_back(FEATURE_CEC);
+ m_strComPort = scanResult.m_strLocation;
+}
+
+CPeripheralCecAdapter::~CPeripheralCecAdapter(void)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+ m_bStop = true;
+ }
+
+ StopThread(true);
+ delete m_queryThread;
+
+ if (m_cecAdapter)
+ {
+ CECDestroy(m_cecAdapter);
+ m_cecAdapter = NULL;
+ }
+}
+
+void CPeripheralCecAdapter::ResetMembers(void)
+{
+ if (m_cecAdapter)
+ CECDestroy(m_cecAdapter);
+ m_cecAdapter = NULL;
+ m_bStarted = false;
+ m_bHasButton = false;
+ m_bIsReady = false;
+ m_bHasConnectedAudioSystem = false;
+ m_strMenuLanguage = "???";
+ m_lastKeypress = {};
+ m_lastChange = VOLUME_CHANGE_NONE;
+ m_iExitCode = EXITCODE_QUIT;
+
+ //! @todo fetch the correct initial value when system audiostatus is
+ //! implemented in libCEC
+ m_bIsMuted = false;
+
+ m_bGoingToStandby = false;
+ m_bIsRunning = false;
+ m_bDeviceRemoved = false;
+ m_bActiveSourcePending = false;
+ m_bStandbyPending = false;
+ m_bActiveSourceBeforeStandby = false;
+ m_bOnPlayReceived = false;
+ m_bPlaybackPaused = false;
+ m_queryThread = NULL;
+ m_bPowerOnScreensaver = false;
+ m_bUseTVMenuLanguage = false;
+ m_bSendInactiveSource = false;
+ m_bPowerOffScreensaver = false;
+ m_bShutdownOnStandby = false;
+
+ m_currentButton.iButton = 0;
+ m_currentButton.iDuration = 0;
+ m_standbySent.SetValid(false);
+ m_configuration.Clear();
+}
+
+void CPeripheralCecAdapter::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnQuit" && m_bIsReady)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iExitCode = static_cast<int>(data["exitcode"].asInteger(EXITCODE_QUIT));
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+ StopThread(false);
+ }
+ else if (flag == ANNOUNCEMENT::GUI && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnScreensaverDeactivated" && m_bIsReady)
+ {
+ bool bIgnoreDeactivate(false);
+ if (data["shuttingdown"].isBoolean())
+ {
+ // don't respond to the deactivation if we are just going to suspend/shutdown anyway
+ // the tv will not have time to switch on before being told to standby and
+ // may not action the standby command.
+ bIgnoreDeactivate = data["shuttingdown"].asBoolean();
+ if (bIgnoreDeactivate)
+ CLog::Log(LOGDEBUG, "{} - ignoring OnScreensaverDeactivated for power action",
+ __FUNCTION__);
+ }
+ if (m_bPowerOnScreensaver && !bIgnoreDeactivate && m_configuration.bActivateSource)
+ {
+ ActivateSource();
+ }
+ }
+ else if (flag == ANNOUNCEMENT::GUI && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnScreensaverActivated" && m_bIsReady)
+ {
+ // Don't put devices to standby if application is currently playing
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer->IsPlaying() && m_bPowerOffScreensaver)
+ {
+ // only power off when we're the active source
+ if (m_cecAdapter->IsLibCECActiveSource())
+ StandbyDevices();
+ }
+ }
+ else if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnSleep")
+ {
+ // this will also power off devices when we're the active source
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bGoingToStandby = true;
+ }
+ StopThread();
+ }
+ else if (flag == ANNOUNCEMENT::System && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnWake")
+ {
+ CLog::Log(LOGDEBUG, "{} - reconnecting to the CEC adapter after standby mode", __FUNCTION__);
+ if (ReopenConnection())
+ {
+ bool bActivate(false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bActivate = m_bActiveSourceBeforeStandby;
+ m_bActiveSourceBeforeStandby = false;
+ }
+ if (bActivate)
+ ActivateSource();
+ }
+ }
+ else if (flag == ANNOUNCEMENT::Player && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ message == "OnStop")
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_preventActivateSourceOnPlay = CDateTime::GetCurrentDateTime();
+ m_bOnPlayReceived = false;
+ }
+ else if (flag == ANNOUNCEMENT::Player && sender == CAnnouncementManager::ANNOUNCEMENT_SENDER &&
+ (message == "OnPlay" || message == "OnResume"))
+ {
+ // activate the source when playback started, and the option is enabled
+ bool bActivateSource(false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bActivateSource = (m_configuration.bActivateSource && !m_bOnPlayReceived &&
+ !m_cecAdapter->IsLibCECActiveSource() &&
+ (!m_preventActivateSourceOnPlay.IsValid() ||
+ CDateTime::GetCurrentDateTime() - m_preventActivateSourceOnPlay >
+ CDateTimeSpan(0, 0, 0, CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP)));
+ m_bOnPlayReceived = true;
+ }
+ if (bActivateSource)
+ ActivateSource();
+ }
+}
+
+bool CPeripheralCecAdapter::InitialiseFeature(const PeripheralFeature feature)
+{
+ if (feature == FEATURE_CEC && !m_bStarted && GetSettingBool("enabled"))
+ {
+ // hide settings that have an override set
+ if (!GetSettingString("wake_devices_advanced").empty())
+ SetSettingVisible("wake_devices", false);
+ if (!GetSettingString("standby_devices_advanced").empty())
+ SetSettingVisible("standby_devices", false);
+
+ SetConfigurationFromSettings();
+ m_callbacks.Clear();
+ m_callbacks.logMessage = &CecLogMessage;
+ m_callbacks.keyPress = &CecKeyPress;
+ m_callbacks.commandReceived = &CecCommand;
+ m_callbacks.configurationChanged = &CecConfiguration;
+ m_callbacks.alert = &CecAlert;
+ m_callbacks.sourceActivated = &CecSourceActivated;
+ m_configuration.callbackParam = this;
+ m_configuration.callbacks = &m_callbacks;
+
+ m_cecAdapter = CECInitialise(&m_configuration);
+
+ if (m_configuration.serverVersion < CEC_LIB_SUPPORTED_VERSION)
+ {
+ /* unsupported libcec version */
+ CLog::Log(
+ LOGERROR,
+ "Detected version of libCEC interface ({0:x}) is lower than the supported version {1:x}",
+ m_cecAdapter ? m_configuration.serverVersion : -1, CEC_LIB_SUPPORTED_VERSION);
+
+ // display warning: incompatible libCEC
+ std::string strMessage = StringUtils::Format(
+ g_localizeStrings.Get(36040), m_cecAdapter ? m_configuration.serverVersion : -1,
+ CEC_LIB_SUPPORTED_VERSION);
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000),
+ strMessage);
+ m_bError = true;
+ if (m_cecAdapter)
+ CECDestroy(m_cecAdapter);
+ m_cecAdapter = NULL;
+
+ m_features.clear();
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - using libCEC v{}", __FUNCTION__,
+ m_cecAdapter->VersionToString(m_configuration.serverVersion));
+ SetVersionInfo(m_configuration);
+ }
+
+ m_bStarted = true;
+ Create();
+ }
+
+ return CPeripheral::InitialiseFeature(feature);
+}
+
+void CPeripheralCecAdapter::SetVersionInfo(const libcec_configuration& configuration)
+{
+ m_strVersionInfo = StringUtils::Format("libCEC {} - firmware v{}",
+ m_cecAdapter->VersionToString(configuration.serverVersion),
+ configuration.iFirmwareVersion);
+
+ // append firmware build date
+ if (configuration.iFirmwareBuildDate != CEC_FW_BUILD_UNKNOWN)
+ {
+ CDateTime dt((time_t)configuration.iFirmwareBuildDate);
+ m_strVersionInfo += StringUtils::Format(" ({})", dt.GetAsDBDate());
+ }
+}
+
+bool CPeripheralCecAdapter::OpenConnection(void)
+{
+ bool bIsOpen(false);
+
+ if (!GetSettingBool("enabled"))
+ {
+ CLog::Log(LOGDEBUG, "{} - CEC adapter is disabled in peripheral settings", __FUNCTION__);
+ m_bStarted = false;
+ return bIsOpen;
+ }
+
+ // open the CEC adapter
+ CLog::Log(LOGDEBUG, "{} - opening a connection to the CEC adapter: {}", __FUNCTION__,
+ m_strComPort);
+
+ // scanning the CEC bus takes about 5 seconds, so display a notification to inform users that
+ // we're busy
+ std::string strMessage =
+ StringUtils::Format(g_localizeStrings.Get(21336), g_localizeStrings.Get(36000));
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ strMessage);
+
+ bool bConnectionFailedDisplayed(false);
+
+ while (!m_bStop && !bIsOpen)
+ {
+ if ((bIsOpen = m_cecAdapter->Open(m_strComPort.c_str(), 10000)) == false)
+ {
+ // display warning: couldn't initialise libCEC
+ CLog::Log(LOGERROR, "{} - could not opening a connection to the CEC adapter", __FUNCTION__);
+ if (!bConnectionFailedDisplayed)
+ CGUIDialogKaiToast::QueueNotification(
+ CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36012));
+ bConnectionFailedDisplayed = true;
+
+ CThread::Sleep(10000ms);
+ }
+ }
+
+ if (bIsOpen)
+ {
+ CLog::Log(LOGDEBUG, "{} - connection to the CEC adapter opened", __FUNCTION__);
+
+ // read the configuration
+ libcec_configuration config;
+ if (m_cecAdapter->GetCurrentConfiguration(&config))
+ {
+ // update the local configuration
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ SetConfigurationFromLibCEC(config);
+ }
+ }
+
+ return bIsOpen;
+}
+
+void CPeripheralCecAdapter::Process(void)
+{
+ if (!OpenConnection())
+ return;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iExitCode = EXITCODE_QUIT;
+ m_bGoingToStandby = false;
+ m_bIsRunning = true;
+ m_bActiveSourceBeforeStandby = false;
+ }
+
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+
+ m_queryThread = new CPeripheralCecAdapterUpdateThread(this, &m_configuration);
+ m_queryThread->Create(false);
+
+ while (!m_bStop)
+ {
+ if (!m_bStop)
+ ProcessVolumeChange();
+
+ if (!m_bStop)
+ ProcessActivateSource();
+
+ if (!m_bStop)
+ ProcessStandbyDevices();
+
+ if (!m_bStop)
+ CThread::Sleep(5ms);
+ }
+
+ m_queryThread->StopThread(true);
+
+ bool bSendStandbyCommands(false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bSendStandbyCommands = m_iExitCode != EXITCODE_REBOOT && m_iExitCode != EXITCODE_RESTARTAPP &&
+ !m_bDeviceRemoved &&
+ (!m_bGoingToStandby || GetSettingBool("standby_tv_on_pc_standby")) &&
+ GetSettingBool("enabled");
+
+ if (m_bGoingToStandby)
+ m_bActiveSourceBeforeStandby = m_cecAdapter->IsLibCECActiveSource();
+ }
+
+ if (bSendStandbyCommands)
+ {
+ if (m_cecAdapter->IsLibCECActiveSource())
+ {
+ if (!m_configuration.powerOffDevices.IsEmpty())
+ {
+ CLog::Log(LOGDEBUG, "{} - sending standby commands", __FUNCTION__);
+ m_standbySent = CDateTime::GetCurrentDateTime();
+ m_cecAdapter->StandbyDevices();
+ }
+ else if (m_bSendInactiveSource)
+ {
+ CLog::Log(LOGDEBUG, "{} - sending inactive source commands", __FUNCTION__);
+ m_cecAdapter->SetInactiveView();
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - XBMC is not the active source, not sending any standby commands",
+ __FUNCTION__);
+ }
+ }
+
+ m_cecAdapter->Close();
+
+ CLog::Log(LOGDEBUG, "{} - CEC adapter processor thread ended", __FUNCTION__);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bStarted = false;
+ m_bIsRunning = false;
+ }
+}
+
+bool CPeripheralCecAdapter::HasAudioControl(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bHasConnectedAudioSystem;
+}
+
+void CPeripheralCecAdapter::SetAudioSystemConnected(bool bSetTo)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bHasConnectedAudioSystem = bSetTo;
+}
+
+void CPeripheralCecAdapter::ProcessVolumeChange(void)
+{
+ bool bSendRelease(false);
+ CecVolumeChange pendingVolumeChange = VOLUME_CHANGE_NONE;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastKeypress);
+ if (!m_volumeChangeQueue.empty())
+ {
+ /* get the first change from the queue */
+ pendingVolumeChange = m_volumeChangeQueue.front();
+ m_volumeChangeQueue.pop();
+
+ /* remove all dupe entries */
+ while (!m_volumeChangeQueue.empty() && m_volumeChangeQueue.front() == pendingVolumeChange)
+ m_volumeChangeQueue.pop();
+
+ /* send another keypress after VOLUME_REFRESH_TIMEOUT ms */
+
+ bool bRefresh(duration.count() > VOLUME_REFRESH_TIMEOUT);
+
+ /* only send the keypress when it hasn't been sent yet */
+ if (pendingVolumeChange != m_lastChange)
+ {
+ m_lastKeypress = std::chrono::steady_clock::now();
+ m_lastChange = pendingVolumeChange;
+ }
+ else if (bRefresh)
+ {
+ m_lastKeypress = std::chrono::steady_clock::now();
+ pendingVolumeChange = m_lastChange;
+ }
+ else
+ pendingVolumeChange = VOLUME_CHANGE_NONE;
+ }
+ else if (m_lastKeypress.time_since_epoch().count() > 0 &&
+ duration.count() > VOLUME_CHANGE_TIMEOUT)
+ {
+ /* send a key release */
+ m_lastKeypress = {};
+ bSendRelease = true;
+ m_lastChange = VOLUME_CHANGE_NONE;
+ }
+ }
+
+ switch (pendingVolumeChange)
+ {
+ case VOLUME_CHANGE_UP:
+ m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_UP, false);
+ break;
+ case VOLUME_CHANGE_DOWN:
+ m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_DOWN, false);
+ break;
+ case VOLUME_CHANGE_MUTE:
+ m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_MUTE, false);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bIsMuted = !m_bIsMuted;
+ }
+ break;
+ case VOLUME_CHANGE_NONE:
+ if (bSendRelease)
+ m_cecAdapter->SendKeyRelease(CECDEVICE_AUDIOSYSTEM, false);
+ break;
+ }
+}
+
+void CPeripheralCecAdapter::VolumeUp(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_volumeChangeQueue.push(VOLUME_CHANGE_UP);
+ }
+}
+
+void CPeripheralCecAdapter::VolumeDown(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_volumeChangeQueue.push(VOLUME_CHANGE_DOWN);
+ }
+}
+
+void CPeripheralCecAdapter::ToggleMute(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_volumeChangeQueue.push(VOLUME_CHANGE_MUTE);
+ }
+}
+
+bool CPeripheralCecAdapter::IsMuted(void)
+{
+ if (HasAudioControl())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bIsMuted;
+ }
+ return false;
+}
+
+void CPeripheralCecAdapter::SetMenuLanguage(const char* strLanguage)
+{
+ if (StringUtils::EqualsNoCase(m_strMenuLanguage, strLanguage))
+ return;
+
+ std::string strGuiLanguage;
+
+ if (!strcmp(strLanguage, "bul"))
+ strGuiLanguage = "bg_bg";
+ else if (!strcmp(strLanguage, "hrv"))
+ strGuiLanguage = "hr_hr";
+ else if (!strcmp(strLanguage, "cze"))
+ strGuiLanguage = "cs_cz";
+ else if (!strcmp(strLanguage, "dan"))
+ strGuiLanguage = "da_dk";
+ else if (!strcmp(strLanguage, "deu"))
+ strGuiLanguage = "de_de";
+ else if (!strcmp(strLanguage, "dut"))
+ strGuiLanguage = "nl_nl";
+ else if (!strcmp(strLanguage, "eng"))
+ strGuiLanguage = "en_gb";
+ else if (!strcmp(strLanguage, "fin"))
+ strGuiLanguage = "fi_fi";
+ else if (!strcmp(strLanguage, "fre"))
+ strGuiLanguage = "fr_fr";
+ else if (!strcmp(strLanguage, "ger"))
+ strGuiLanguage = "de_de";
+ else if (!strcmp(strLanguage, "gre"))
+ strGuiLanguage = "el_gr";
+ else if (!strcmp(strLanguage, "hun"))
+ strGuiLanguage = "hu_hu";
+ else if (!strcmp(strLanguage, "ita"))
+ strGuiLanguage = "it_it";
+ else if (!strcmp(strLanguage, "nor"))
+ strGuiLanguage = "nb_no";
+ else if (!strcmp(strLanguage, "pol"))
+ strGuiLanguage = "pl_pl";
+ else if (!strcmp(strLanguage, "por"))
+ strGuiLanguage = "pt_pt";
+ else if (!strcmp(strLanguage, "rum"))
+ strGuiLanguage = "ro_ro";
+ else if (!strcmp(strLanguage, "rus"))
+ strGuiLanguage = "ru_ru";
+ else if (!strcmp(strLanguage, "srp"))
+ strGuiLanguage = "sr_rs@latin";
+ else if (!strcmp(strLanguage, "slo"))
+ strGuiLanguage = "sk_sk";
+ else if (!strcmp(strLanguage, "slv"))
+ strGuiLanguage = "sl_si";
+ else if (!strcmp(strLanguage, "spa"))
+ strGuiLanguage = "es_es";
+ else if (!strcmp(strLanguage, "swe"))
+ strGuiLanguage = "sv_se";
+ else if (!strcmp(strLanguage, "tur"))
+ strGuiLanguage = "tr_tr";
+
+ if (!strGuiLanguage.empty())
+ {
+ strGuiLanguage = "resource.language." + strGuiLanguage;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SETLANGUAGE, -1, -1, nullptr, strGuiLanguage);
+ CLog::Log(LOGDEBUG, "{} - language set to '{}'", __FUNCTION__, strGuiLanguage);
+ }
+ else
+ CLog::Log(LOGWARNING, "{} - TV menu language set to unknown value '{}'", __FUNCTION__,
+ strLanguage);
+}
+
+void CPeripheralCecAdapter::OnTvStandby(void)
+{
+ int iActionOnTvStandby = GetSettingInt("standby_pc_on_tv_standby");
+ switch (iActionOnTvStandby)
+ {
+ case LOCALISED_ID_POWEROFF:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_SHUTDOWN);
+ break;
+ case LOCALISED_ID_SUSPEND:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_SUSPEND);
+ break;
+ case LOCALISED_ID_HIBERNATE:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SYSTEM_POWERDOWN, TMSG_HIBERNATE);
+ break;
+ case LOCALISED_ID_QUIT:
+ m_bStarted = false;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ break;
+ case LOCALISED_ID_PAUSE:
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PAUSE_IF_PLAYING);
+ break;
+ case LOCALISED_ID_STOP:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlaying())
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_STOP);
+ break;
+ }
+ case LOCALISED_ID_IGNORE:
+ break;
+ default:
+ CLog::Log(LOGERROR, "{} - Unexpected [standby_pc_on_tv_standby] setting value", __FUNCTION__);
+ break;
+ }
+}
+
+void CPeripheralCecAdapter::CecCommand(void* cbParam, const cec_command* command)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ if (adapter->m_bIsReady)
+ {
+ switch (command->opcode)
+ {
+ case CEC_OPCODE_STANDBY:
+ if (command->initiator == CECDEVICE_TV &&
+ (!adapter->m_standbySent.IsValid() ||
+ CDateTime::GetCurrentDateTime() - adapter->m_standbySent >
+ CDateTimeSpan(0, 0, 0, SCREENSAVER_TIMEOUT)))
+ {
+ adapter->OnTvStandby();
+ }
+ break;
+ case CEC_OPCODE_SET_MENU_LANGUAGE:
+ if (adapter->m_bUseTVMenuLanguage == 1 && command->initiator == CECDEVICE_TV &&
+ command->parameters.size == 3)
+ {
+ char strNewLanguage[4];
+ for (int iPtr = 0; iPtr < 3; iPtr++)
+ strNewLanguage[iPtr] = command->parameters[iPtr];
+ strNewLanguage[3] = 0;
+ adapter->SetMenuLanguage(strNewLanguage);
+ }
+ break;
+ case CEC_OPCODE_DECK_CONTROL:
+ if (command->initiator == CECDEVICE_TV && command->parameters.size == 1 &&
+ command->parameters[0] == CEC_DECK_CONTROL_MODE_STOP)
+ {
+ cec_keypress key;
+ key.duration = 500;
+ key.keycode = CEC_USER_CONTROL_CODE_STOP;
+ adapter->PushCecKeypress(key);
+ }
+ break;
+ case CEC_OPCODE_PLAY:
+ if (command->initiator == CECDEVICE_TV && command->parameters.size == 1)
+ {
+ if (command->parameters[0] == CEC_PLAY_MODE_PLAY_FORWARD)
+ {
+ cec_keypress key;
+ key.duration = 500;
+ key.keycode = CEC_USER_CONTROL_CODE_PLAY;
+ adapter->PushCecKeypress(key);
+ }
+ else if (command->parameters[0] == CEC_PLAY_MODE_PLAY_STILL)
+ {
+ cec_keypress key;
+ key.duration = 500;
+ key.keycode = CEC_USER_CONTROL_CODE_PAUSE;
+ adapter->PushCecKeypress(key);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void CPeripheralCecAdapter::CecConfiguration(void* cbParam, const libcec_configuration* config)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(adapter->m_critSection);
+ adapter->SetConfigurationFromLibCEC(*config);
+}
+
+void CPeripheralCecAdapter::CecAlert(void* cbParam,
+ const libcec_alert alert,
+ const libcec_parameter data)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ bool bReopenConnection(false);
+ int iAlertString(0);
+ switch (alert)
+ {
+ case CEC_ALERT_SERVICE_DEVICE:
+ iAlertString = 36027;
+ break;
+ case CEC_ALERT_CONNECTION_LOST:
+ bReopenConnection = true;
+ iAlertString = 36030;
+ break;
+#if defined(CEC_ALERT_PERMISSION_ERROR)
+ case CEC_ALERT_PERMISSION_ERROR:
+ bReopenConnection = true;
+ iAlertString = 36031;
+ break;
+ case CEC_ALERT_PORT_BUSY:
+ bReopenConnection = true;
+ iAlertString = 36032;
+ break;
+#endif
+ default:
+ break;
+ }
+
+ // display the alert
+ if (iAlertString)
+ {
+ std::string strLog(g_localizeStrings.Get(iAlertString));
+ if (data.paramType == CEC_PARAMETER_TYPE_STRING && data.paramData)
+ strLog += StringUtils::Format(" - {}", (const char*)data.paramData);
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ strLog);
+ }
+
+ if (bReopenConnection)
+ {
+ // Reopen the connection asynchronously. Otherwise a deadlock may occur.
+ // Reconnect means destruction and recreation of our libcec instance, but libcec
+ // calls this callback function synchronously and must not be destroyed meanwhile.
+ adapter->ReopenConnection(true);
+ }
+}
+
+void CPeripheralCecAdapter::CecKeyPress(void* cbParam, const cec_keypress* key)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!!adapter)
+ adapter->PushCecKeypress(*key);
+}
+
+void CPeripheralCecAdapter::GetNextKey(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bHasButton = false;
+ if (m_bIsReady)
+ {
+ std::vector<CecButtonPress>::iterator it = m_buttonQueue.begin();
+ if (it != m_buttonQueue.end())
+ {
+ m_currentButton = (*it);
+ m_buttonQueue.erase(it);
+ m_bHasButton = true;
+ }
+ }
+}
+
+void CPeripheralCecAdapter::PushCecKeypress(const CecButtonPress& key)
+{
+ CLog::Log(LOGDEBUG, "{} - received key {:2x} duration {}", __FUNCTION__, key.iButton,
+ key.iDuration);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // avoid the queue getting too long
+ if (m_configuration.iButtonRepeatRateMs && m_buttonQueue.size() > 5)
+ return;
+ if (m_configuration.iButtonRepeatRateMs == 0 && key.iDuration > 0)
+ {
+ if (m_currentButton.iButton == key.iButton && m_currentButton.iDuration == 0)
+ {
+ // update the duration
+ if (m_bHasButton)
+ m_currentButton.iDuration = key.iDuration;
+ // ignore this one, since it's already been handled by xbmc
+ return;
+ }
+ // if we received a keypress with a duration set, try to find the same one without a duration
+ // set, and replace it
+ for (std::vector<CecButtonPress>::reverse_iterator it = m_buttonQueue.rbegin();
+ it != m_buttonQueue.rend(); ++it)
+ {
+ if ((*it).iButton == key.iButton)
+ {
+ if ((*it).iDuration == 0)
+ {
+ // replace this entry
+ (*it).iDuration = key.iDuration;
+ return;
+ }
+ // add a new entry
+ break;
+ }
+ }
+ }
+
+ m_buttonQueue.push_back(key);
+}
+
+void CPeripheralCecAdapter::PushCecKeypress(const cec_keypress& key)
+{
+ CecButtonPress xbmcKey;
+ xbmcKey.iDuration = key.duration;
+
+ switch (key.keycode)
+ {
+ case CEC_USER_CONTROL_CODE_SELECT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SELECT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_LEFT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_LEFT_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_LEFT_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RIGHT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RIGHT_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RIGHT_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
+ PushCecKeypress(xbmcKey);
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_SETUP_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CONTENTS_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CONTENTS_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_ROOT_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_ROOT_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_TOP_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TOP_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DVD_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_DVD_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_FAVORITE_MENU:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_MENU;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_EXIT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_BACK;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_ENTER:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_ENTER;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CHANNEL_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CHANNEL_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PREVIOUS_CHANNEL:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_SOUND_SELECT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LANGUAGE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_POWER:
+ case CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION:
+ case CEC_USER_CONTROL_CODE_POWER_OFF_FUNCTION:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_POWER;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_VOLUME_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_VOLUME_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_MUTE:
+ case CEC_USER_CONTROL_CODE_MUTE_FUNCTION:
+ case CEC_USER_CONTROL_CODE_RESTORE_VOLUME_FUNCTION:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_MUTE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PLAY:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_PLAY;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_STOP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_STOP;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PAUSE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_PAUSE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_REWIND:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_REVERSE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_FAST_FORWARD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_FORWARD;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER0:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_0;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER1:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_1;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER2:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_2;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER3:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_3;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER4:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_4;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER5:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_5;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER6:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_6;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER7:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_7;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER8:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_8;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NUMBER9:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_9;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_RECORD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RECORD;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_CLEAR:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CLEAR;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_INFO;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PAGE_UP:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_PAGE_DOWN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_FORWARD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_PLUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_BACKWARD:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_MINUS;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F1_BLUE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_BLUE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F2_RED:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_RED;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F3_GREEN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_GREEN;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_F4_YELLOW:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_YELLOW;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_ELECTRONIC_PROGRAM_GUIDE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_GUIDE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_LIVE_TV;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_NEXT_FAVORITE:
+ case CEC_USER_CONTROL_CODE_DOT:
+ case CEC_USER_CONTROL_CODE_AN_RETURN:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE; // context menu
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_DATA:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_SUB_PICTURE:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_SUBTITLE;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_EJECT:
+ xbmcKey.iButton = XINPUT_IR_REMOTE_EJECT;
+ PushCecKeypress(xbmcKey);
+ break;
+ case CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION:
+ case CEC_USER_CONTROL_CODE_INPUT_SELECT:
+ case CEC_USER_CONTROL_CODE_INITIAL_CONFIGURATION:
+ case CEC_USER_CONTROL_CODE_HELP:
+ case CEC_USER_CONTROL_CODE_STOP_RECORD:
+ case CEC_USER_CONTROL_CODE_PAUSE_RECORD:
+ case CEC_USER_CONTROL_CODE_ANGLE:
+ case CEC_USER_CONTROL_CODE_VIDEO_ON_DEMAND:
+ case CEC_USER_CONTROL_CODE_TIMER_PROGRAMMING:
+ case CEC_USER_CONTROL_CODE_PLAY_FUNCTION:
+ case CEC_USER_CONTROL_CODE_PAUSE_PLAY_FUNCTION:
+ case CEC_USER_CONTROL_CODE_RECORD_FUNCTION:
+ case CEC_USER_CONTROL_CODE_PAUSE_RECORD_FUNCTION:
+ case CEC_USER_CONTROL_CODE_STOP_FUNCTION:
+ case CEC_USER_CONTROL_CODE_TUNE_FUNCTION:
+ case CEC_USER_CONTROL_CODE_SELECT_MEDIA_FUNCTION:
+ case CEC_USER_CONTROL_CODE_SELECT_AV_INPUT_FUNCTION:
+ case CEC_USER_CONTROL_CODE_SELECT_AUDIO_INPUT_FUNCTION:
+ case CEC_USER_CONTROL_CODE_F5:
+ case CEC_USER_CONTROL_CODE_NUMBER_ENTRY_MODE:
+ case CEC_USER_CONTROL_CODE_NUMBER11:
+ case CEC_USER_CONTROL_CODE_NUMBER12:
+ case CEC_USER_CONTROL_CODE_SELECT_BROADCAST_TYPE:
+ case CEC_USER_CONTROL_CODE_SELECT_SOUND_PRESENTATION:
+ case CEC_USER_CONTROL_CODE_UNKNOWN:
+ default:
+ break;
+ }
+}
+
+int CPeripheralCecAdapter::GetButton(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_bHasButton)
+ GetNextKey();
+
+ return m_bHasButton ? m_currentButton.iButton : 0;
+}
+
+unsigned int CPeripheralCecAdapter::GetHoldTime(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_bHasButton)
+ GetNextKey();
+
+ return m_bHasButton ? m_currentButton.iDuration : 0;
+}
+
+void CPeripheralCecAdapter::ResetButton(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bHasButton = false;
+
+ // wait for the key release if the duration isn't 0
+ if (m_currentButton.iDuration > 0)
+ {
+ m_currentButton.iButton = 0;
+ m_currentButton.iDuration = 0;
+ }
+}
+
+void CPeripheralCecAdapter::OnSettingChanged(const std::string& strChangedSetting)
+{
+ if (StringUtils::EqualsNoCase(strChangedSetting, "enabled"))
+ {
+ bool bEnabled(GetSettingBool("enabled"));
+ if (!bEnabled && IsRunning())
+ {
+ CLog::Log(LOGDEBUG, "{} - closing the CEC connection", __FUNCTION__);
+ StopThread(true);
+ }
+ else if (bEnabled && !IsRunning())
+ {
+ CLog::Log(LOGDEBUG, "{} - starting the CEC connection", __FUNCTION__);
+ SetConfigurationFromSettings();
+ InitialiseFeature(FEATURE_CEC);
+ }
+ }
+ else if (IsRunning())
+ {
+ if (m_queryThread->IsRunning())
+ {
+ CLog::Log(LOGDEBUG, "{} - sending the updated configuration to libCEC", __FUNCTION__);
+ SetConfigurationFromSettings();
+ m_queryThread->UpdateConfiguration(&m_configuration);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - restarting the CEC connection", __FUNCTION__);
+ SetConfigurationFromSettings();
+ InitialiseFeature(FEATURE_CEC);
+ }
+}
+
+void CPeripheralCecAdapter::CecSourceActivated(void* cbParam,
+ const CEC::cec_logical_address address,
+ const uint8_t activated)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ // wake up the screensaver, so the user doesn't switch to a black screen
+ if (activated == 1)
+ {
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ appPower->WakeUpScreenSaverAndDPMS();
+ }
+
+ if (adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") != LOCALISED_ID_NONE)
+ {
+ bool bShowingSlideshow =
+ (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW);
+ CGUIWindowSlideShow* pSlideShow =
+ bShowingSlideshow
+ ? CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(
+ WINDOW_SLIDESHOW)
+ : NULL;
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+
+ bool bPlayingAndDeactivated = activated == 0 && ((pSlideShow && pSlideShow->IsPlaying()) ||
+ !appPlayer->IsPausedPlayback());
+ bool bPausedAndActivated =
+ activated == 1 && adapter->m_bPlaybackPaused &&
+ ((pSlideShow && pSlideShow->IsPaused()) || (appPlayer && appPlayer->IsPausedPlayback()));
+ if (bPlayingAndDeactivated)
+ adapter->m_bPlaybackPaused = true;
+ else if (bPausedAndActivated)
+ adapter->m_bPlaybackPaused = false;
+
+ if ((bPlayingAndDeactivated || bPausedAndActivated) &&
+ adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") == LOCALISED_ID_PAUSE)
+ {
+ if (pSlideShow)
+ // pause/resume slideshow
+ pSlideShow->OnAction(CAction(ACTION_PAUSE));
+ else
+ // pause/resume player
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE);
+ }
+ else if (bPlayingAndDeactivated &&
+ adapter->GetSettingInt("pause_or_stop_playback_on_deactivate") == LOCALISED_ID_STOP)
+ {
+ if (pSlideShow)
+ pSlideShow->OnAction(CAction(ACTION_STOP));
+ else
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_STOP);
+ }
+ }
+}
+
+void CPeripheralCecAdapter::CecLogMessage(void* cbParam, const cec_log_message* message)
+{
+ CPeripheralCecAdapter* adapter = static_cast<CPeripheralCecAdapter*>(cbParam);
+ if (!adapter)
+ return;
+
+ int iLevel = -1;
+ switch (message->level)
+ {
+ case CEC_LOG_ERROR:
+ iLevel = LOGERROR;
+ break;
+ case CEC_LOG_WARNING:
+ iLevel = LOGWARNING;
+ break;
+ case CEC_LOG_NOTICE:
+ iLevel = LOGDEBUG;
+ break;
+ case CEC_LOG_TRAFFIC:
+ case CEC_LOG_DEBUG:
+ iLevel = LOGDEBUG;
+ break;
+ default:
+ break;
+ }
+
+ if (iLevel >= CEC_LOG_NOTICE ||
+ (iLevel >= 0 && CServiceBroker::GetLogging().IsLogLevelLogged(LOGDEBUG)))
+ CLog::Log(iLevel, LOGCEC, "{} - {}", __FUNCTION__, message->message);
+}
+
+void CPeripheralCecAdapter::SetConfigurationFromLibCEC(const CEC::libcec_configuration& config)
+{
+ bool bChanged(false);
+
+ // set the primary device type
+ m_configuration.deviceTypes.Clear();
+ m_configuration.deviceTypes.Add(config.deviceTypes[0]);
+
+ // hide the "connected device" and "hdmi port number" settings when the PA was autodetected
+ bool bPAAutoDetected(config.bAutodetectAddress == 1);
+
+ SetSettingVisible("connected_device", !bPAAutoDetected);
+ SetSettingVisible("cec_hdmi_port", !bPAAutoDetected);
+
+ // set the connected device
+ m_configuration.baseDevice = config.baseDevice;
+ bChanged |=
+ SetSetting("connected_device",
+ config.baseDevice == CECDEVICE_AUDIOSYSTEM ? LOCALISED_ID_AVR : LOCALISED_ID_TV);
+
+ // set the HDMI port number
+ m_configuration.iHDMIPort = config.iHDMIPort;
+ bChanged |= SetSetting("cec_hdmi_port", config.iHDMIPort);
+
+ // set the physical address, when baseDevice or iHDMIPort are not set
+ std::string strPhysicalAddress("0");
+ if (!bPAAutoDetected && (m_configuration.baseDevice == CECDEVICE_UNKNOWN ||
+ m_configuration.iHDMIPort < CEC_MIN_HDMI_PORTNUMBER ||
+ m_configuration.iHDMIPort > CEC_MAX_HDMI_PORTNUMBER))
+ {
+ m_configuration.iPhysicalAddress = config.iPhysicalAddress;
+ strPhysicalAddress = StringUtils::Format("{:x}", config.iPhysicalAddress);
+ }
+ bChanged |= SetSetting("physical_address", strPhysicalAddress);
+
+ // set the devices to wake when starting
+ m_configuration.wakeDevices = config.wakeDevices;
+ bChanged |= WriteLogicalAddresses(config.wakeDevices, "wake_devices", "wake_devices_advanced");
+
+ // set the devices to power off when stopping
+ m_configuration.powerOffDevices = config.powerOffDevices;
+ bChanged |=
+ WriteLogicalAddresses(config.powerOffDevices, "standby_devices", "standby_devices_advanced");
+
+ // set the boolean settings
+ m_configuration.bActivateSource = config.bActivateSource;
+ bChanged |= SetSetting("activate_source", m_configuration.bActivateSource == 1);
+
+ m_configuration.iDoubleTapTimeoutMs = config.iDoubleTapTimeoutMs;
+ bChanged |= SetSetting("double_tap_timeout_ms", (int)m_configuration.iDoubleTapTimeoutMs);
+
+ m_configuration.iButtonRepeatRateMs = config.iButtonRepeatRateMs;
+ bChanged |= SetSetting("button_repeat_rate_ms", (int)m_configuration.iButtonRepeatRateMs);
+
+ m_configuration.iButtonReleaseDelayMs = config.iButtonReleaseDelayMs;
+ bChanged |= SetSetting("button_release_delay_ms", (int)m_configuration.iButtonReleaseDelayMs);
+
+ m_configuration.bPowerOffOnStandby = config.bPowerOffOnStandby;
+
+ m_configuration.iFirmwareVersion = config.iFirmwareVersion;
+
+ memcpy(m_configuration.strDeviceLanguage, config.strDeviceLanguage, 3);
+ m_configuration.iFirmwareBuildDate = config.iFirmwareBuildDate;
+
+ SetVersionInfo(m_configuration);
+
+ if (bChanged)
+ CLog::Log(LOGDEBUG, "SetConfigurationFromLibCEC - settings updated by libCEC");
+}
+
+void CPeripheralCecAdapter::SetConfigurationFromSettings(void)
+{
+ // client version matches the version of libCEC that we originally used the API from
+ m_configuration.clientVersion = LIBCEC_VERSION_TO_UINT(4, 0, 0);
+
+ // device name 'XBMC'
+ snprintf(m_configuration.strDeviceName, 13, "%s", GetSettingString("device_name").c_str());
+
+ // set the primary device type
+ m_configuration.deviceTypes.Clear();
+ switch (GetSettingInt("device_type"))
+ {
+ case LOCALISED_ID_PLAYBACK_DEVICE:
+ m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+ break;
+ case LOCALISED_ID_TUNER_DEVICE:
+ m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_TUNER);
+ break;
+ case LOCALISED_ID_RECORDING_DEVICE:
+ default:
+ m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE);
+ break;
+ }
+
+ // always try to autodetect the address.
+ // when the firmware supports this, it will override the physical address, connected device and
+ // hdmi port settings
+ m_configuration.bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS;
+
+ // set the physical address
+ // when set, it will override the connected device and hdmi port settings
+ std::string strPhysicalAddress = GetSettingString("physical_address");
+ int iPhysicalAddress;
+ if (sscanf(strPhysicalAddress.c_str(), "%x", &iPhysicalAddress) &&
+ iPhysicalAddress >= CEC_PHYSICAL_ADDRESS_TV && iPhysicalAddress <= CEC_MAX_PHYSICAL_ADDRESS)
+ m_configuration.iPhysicalAddress = iPhysicalAddress;
+ else
+ m_configuration.iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV;
+
+ // set the connected device
+ int iConnectedDevice = GetSettingInt("connected_device");
+ if (iConnectedDevice == LOCALISED_ID_AVR)
+ m_configuration.baseDevice = CECDEVICE_AUDIOSYSTEM;
+ else if (iConnectedDevice == LOCALISED_ID_TV)
+ m_configuration.baseDevice = CECDEVICE_TV;
+
+ // set the HDMI port number
+ int iHDMIPort = GetSettingInt("cec_hdmi_port");
+ if (iHDMIPort >= CEC_MIN_HDMI_PORTNUMBER && iHDMIPort <= CEC_MAX_HDMI_PORTNUMBER)
+ m_configuration.iHDMIPort = iHDMIPort;
+
+ // set the tv vendor override
+ int iVendor = GetSettingInt("tv_vendor");
+ if (iVendor >= CEC_MIN_VENDORID && iVendor <= CEC_MAX_VENDORID)
+ m_configuration.tvVendor = iVendor;
+
+ // read the devices to wake when starting
+ std::string strWakeDevices = GetSettingString("wake_devices_advanced");
+ StringUtils::Trim(strWakeDevices);
+ m_configuration.wakeDevices.Clear();
+ if (!strWakeDevices.empty())
+ ReadLogicalAddresses(strWakeDevices, m_configuration.wakeDevices);
+ else
+ ReadLogicalAddresses(GetSettingInt("wake_devices"), m_configuration.wakeDevices);
+
+ // read the devices to power off when stopping
+ std::string strStandbyDevices = GetSettingString("standby_devices_advanced");
+ StringUtils::Trim(strStandbyDevices);
+ m_configuration.powerOffDevices.Clear();
+ if (!strStandbyDevices.empty())
+ ReadLogicalAddresses(strStandbyDevices, m_configuration.powerOffDevices);
+ else
+ ReadLogicalAddresses(GetSettingInt("standby_devices"), m_configuration.powerOffDevices);
+
+ // read the boolean settings
+ m_bUseTVMenuLanguage = GetSettingBool("use_tv_menu_language");
+ m_configuration.bActivateSource = GetSettingBool("activate_source") ? 1 : 0;
+ m_bPowerOffScreensaver = GetSettingBool("cec_standby_screensaver");
+ m_bPowerOnScreensaver = GetSettingBool("cec_wake_screensaver");
+ m_bSendInactiveSource = GetSettingBool("send_inactive_source");
+ m_configuration.bAutoWakeAVR = GetSettingBool("power_avr_on_as") ? 1 : 0;
+
+ // read the mutually exclusive boolean settings
+ int iStandbyAction(GetSettingInt("standby_pc_on_tv_standby"));
+ m_configuration.bPowerOffOnStandby =
+ (iStandbyAction == LOCALISED_ID_SUSPEND || iStandbyAction == LOCALISED_ID_HIBERNATE) ? 1 : 0;
+ m_bShutdownOnStandby = iStandbyAction == LOCALISED_ID_POWEROFF;
+
+ // double tap prevention timeout in ms
+ m_configuration.iDoubleTapTimeoutMs = GetSettingInt("double_tap_timeout_ms");
+ m_configuration.iButtonRepeatRateMs = GetSettingInt("button_repeat_rate_ms");
+ m_configuration.iButtonReleaseDelayMs = GetSettingInt("button_release_delay_ms");
+
+ if (GetSettingBool("pause_playback_on_deactivate"))
+ {
+ SetSetting("pause_or_stop_playback_on_deactivate", LOCALISED_ID_PAUSE);
+ SetSetting("pause_playback_on_deactivate", false);
+ }
+}
+
+void CPeripheralCecAdapter::ReadLogicalAddresses(const std::string& strString,
+ cec_logical_addresses& addresses)
+{
+ for (size_t iPtr = 0; iPtr < strString.size(); iPtr++)
+ {
+ std::string strDevice = strString.substr(iPtr, 1);
+ StringUtils::Trim(strDevice);
+ if (!strDevice.empty())
+ {
+ int iDevice(0);
+ if (sscanf(strDevice.c_str(), "%x", &iDevice) == 1 && iDevice >= 0 && iDevice <= 0xF)
+ addresses.Set((cec_logical_address)iDevice);
+ }
+ }
+}
+
+void CPeripheralCecAdapter::ReadLogicalAddresses(int iLocalisedId, cec_logical_addresses& addresses)
+{
+ addresses.Clear();
+ switch (iLocalisedId)
+ {
+ case LOCALISED_ID_TV:
+ addresses.Set(CECDEVICE_TV);
+ break;
+ case LOCALISED_ID_AVR:
+ addresses.Set(CECDEVICE_AUDIOSYSTEM);
+ break;
+ case LOCALISED_ID_TV_AVR:
+ addresses.Set(CECDEVICE_TV);
+ addresses.Set(CECDEVICE_AUDIOSYSTEM);
+ break;
+ case LOCALISED_ID_NONE:
+ default:
+ break;
+ }
+}
+
+bool CPeripheralCecAdapter::WriteLogicalAddresses(const cec_logical_addresses& addresses,
+ const std::string& strSettingName,
+ const std::string& strAdvancedSettingName)
+{
+ bool bChanged(false);
+
+ // only update the advanced setting if it was set by the user
+ if (!GetSettingString(strAdvancedSettingName).empty())
+ {
+ std::string strPowerOffDevices;
+ for (unsigned int iPtr = CECDEVICE_TV; iPtr <= CECDEVICE_BROADCAST; iPtr++)
+ if (addresses[iPtr])
+ strPowerOffDevices += StringUtils::Format(" {:X}", iPtr);
+ StringUtils::Trim(strPowerOffDevices);
+ bChanged = SetSetting(strAdvancedSettingName, strPowerOffDevices);
+ }
+
+ int iSettingPowerOffDevices = LOCALISED_ID_NONE;
+ if (addresses[CECDEVICE_TV] && addresses[CECDEVICE_AUDIOSYSTEM])
+ iSettingPowerOffDevices = LOCALISED_ID_TV_AVR;
+ else if (addresses[CECDEVICE_TV])
+ iSettingPowerOffDevices = LOCALISED_ID_TV;
+ else if (addresses[CECDEVICE_AUDIOSYSTEM])
+ iSettingPowerOffDevices = LOCALISED_ID_AVR;
+ return SetSetting(strSettingName, iSettingPowerOffDevices) || bChanged;
+}
+
+CPeripheralCecAdapterUpdateThread::CPeripheralCecAdapterUpdateThread(
+ CPeripheralCecAdapter* adapter, libcec_configuration* configuration)
+ : CThread("CECAdapterUpdate"),
+ m_adapter(adapter),
+ m_configuration(*configuration),
+ m_bNextConfigurationScheduled(false),
+ m_bIsUpdating(true)
+{
+ m_nextConfiguration.Clear();
+ m_event.Reset();
+}
+
+CPeripheralCecAdapterUpdateThread::~CPeripheralCecAdapterUpdateThread(void)
+{
+ StopThread(false);
+ m_event.Set();
+ StopThread(true);
+}
+
+void CPeripheralCecAdapterUpdateThread::Signal(void)
+{
+ m_event.Set();
+}
+
+bool CPeripheralCecAdapterUpdateThread::UpdateConfiguration(libcec_configuration* configuration)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!configuration)
+ return false;
+
+ if (m_bIsUpdating)
+ {
+ m_bNextConfigurationScheduled = true;
+ m_nextConfiguration = *configuration;
+ }
+ else
+ {
+ m_configuration = *configuration;
+ m_event.Set();
+ }
+ return true;
+}
+
+bool CPeripheralCecAdapterUpdateThread::WaitReady(void)
+{
+ // don't wait if we're not powering up anything
+ if (m_configuration.wakeDevices.IsEmpty() && m_configuration.bActivateSource == 0)
+ return true;
+
+ // wait for the TV if we're configured to become the active source.
+ // wait for the first device in the wake list otherwise.
+ cec_logical_address waitFor =
+ (m_configuration.bActivateSource == 1) ? CECDEVICE_TV : m_configuration.wakeDevices.primary;
+
+ cec_power_status powerStatus(CEC_POWER_STATUS_UNKNOWN);
+ bool bContinue(true);
+ while (bContinue && !m_adapter->m_bStop && !m_bStop && powerStatus != CEC_POWER_STATUS_ON)
+ {
+ powerStatus = m_adapter->m_cecAdapter->GetDevicePowerStatus(waitFor);
+ if (powerStatus != CEC_POWER_STATUS_ON)
+ bContinue = !m_event.Wait(1000ms);
+ }
+
+ return powerStatus == CEC_POWER_STATUS_ON;
+}
+
+void CPeripheralCecAdapterUpdateThread::UpdateMenuLanguage(void)
+{
+ // request the menu language of the TV
+ if (m_adapter->m_bUseTVMenuLanguage == 1)
+ {
+ CLog::Log(LOGDEBUG, "{} - requesting the menu language of the TV", __FUNCTION__);
+ std::string language(m_adapter->m_cecAdapter->GetDeviceMenuLanguage(CECDEVICE_TV));
+ m_adapter->SetMenuLanguage(language.c_str());
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - using TV menu language is disabled", __FUNCTION__);
+ }
+}
+
+std::string CPeripheralCecAdapterUpdateThread::UpdateAudioSystemStatus(void)
+{
+ std::string strAmpName;
+
+ /* disable the mute setting when an amp is found, because the amp handles the mute setting and
+ set PCM output to 100% */
+ if (m_adapter->m_cecAdapter->IsActiveDeviceType(CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+ {
+ // request the OSD name of the amp
+ std::string ampName(m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_AUDIOSYSTEM));
+ CLog::Log(LOGDEBUG,
+ "{} - CEC capable amplifier found ({}). volume will be controlled on the amp",
+ __FUNCTION__, ampName);
+ strAmpName += ampName;
+
+ // set amp present
+ m_adapter->SetAudioSystemConnected(true);
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ appVolume->SetMute(false);
+ appVolume->SetVolume(CApplicationVolumeHandling::VOLUME_MAXIMUM, false);
+ }
+ else
+ {
+ // set amp present
+ CLog::Log(LOGDEBUG, "{} - no CEC capable amplifier found", __FUNCTION__);
+ m_adapter->SetAudioSystemConnected(false);
+ }
+
+ return strAmpName;
+}
+
+bool CPeripheralCecAdapterUpdateThread::SetInitialConfiguration(void)
+{
+ // the option to make XBMC the active source is set
+ if (m_configuration.bActivateSource == 1)
+ m_adapter->m_cecAdapter->SetActiveSource();
+
+ // devices to wake are set
+ cec_logical_addresses tvOnly;
+ tvOnly.Clear();
+ tvOnly.Set(CECDEVICE_TV);
+ if (!m_configuration.wakeDevices.IsEmpty() &&
+ (m_configuration.wakeDevices != tvOnly || m_configuration.bActivateSource == 0))
+ m_adapter->m_cecAdapter->PowerOnDevices(CECDEVICE_BROADCAST);
+
+ // wait until devices are powered up
+ if (!WaitReady())
+ return false;
+
+ UpdateMenuLanguage();
+
+ // request the OSD name of the TV
+ std::string strNotification;
+ std::string tvName(m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_TV));
+ strNotification = StringUtils::Format("{}: {}", g_localizeStrings.Get(36016), tvName);
+
+ std::string strAmpName = UpdateAudioSystemStatus();
+ if (!strAmpName.empty())
+ strNotification += StringUtils::Format("- {}", strAmpName);
+
+ m_adapter->m_bIsReady = true;
+
+ // and let the gui know that we're done
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ strNotification);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bIsUpdating = false;
+ return true;
+}
+
+bool CPeripheralCecAdapter::IsRunning(void) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_bIsRunning;
+}
+
+void CPeripheralCecAdapterUpdateThread::Process(void)
+{
+ // set the initial configuration
+ if (!SetInitialConfiguration())
+ return;
+
+ // and wait for updates
+ bool bUpdate(false);
+ while (!m_bStop)
+ {
+ // update received
+ if (bUpdate || m_event.Wait(500ms))
+ {
+ if (m_bStop)
+ return;
+ // set the new configuration
+ libcec_configuration configuration;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ configuration = m_configuration;
+ m_bIsUpdating = false;
+ }
+
+ CLog::Log(LOGDEBUG, "{} - updating the configuration", __FUNCTION__);
+ bool bConfigSet(m_adapter->m_cecAdapter->SetConfiguration(&configuration));
+ // display message: config updated / failed to update
+ if (!bConfigSet)
+ CLog::Log(LOGERROR, "{} - libCEC couldn't set the new configuration", __FUNCTION__);
+ else
+ {
+ UpdateMenuLanguage();
+ UpdateAudioSystemStatus();
+ }
+
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000),
+ g_localizeStrings.Get(bConfigSet ? 36023 : 36024));
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if ((bUpdate = m_bNextConfigurationScheduled) == true)
+ {
+ // another update is scheduled
+ m_bNextConfigurationScheduled = false;
+ m_configuration = m_nextConfiguration;
+ }
+ else
+ {
+ // nothing left to do, wait for updates
+ m_bIsUpdating = false;
+ m_event.Reset();
+ }
+ }
+ }
+ }
+}
+
+void CPeripheralCecAdapter::OnDeviceRemoved(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bDeviceRemoved = true;
+}
+
+namespace PERIPHERALS
+{
+
+class CPeripheralCecAdapterReopenJob : public CJob
+{
+public:
+ CPeripheralCecAdapterReopenJob(CPeripheralCecAdapter* adapter) : m_adapter(adapter) {}
+ ~CPeripheralCecAdapterReopenJob() override = default;
+
+ bool DoWork(void) override { return m_adapter->ReopenConnection(false); }
+
+private:
+ CPeripheralCecAdapter* m_adapter;
+};
+
+}; // namespace PERIPHERALS
+
+bool CPeripheralCecAdapter::ReopenConnection(bool bAsync /* = false */)
+{
+ if (bAsync)
+ {
+ CServiceBroker::GetJobManager()->AddJob(new CPeripheralCecAdapterReopenJob(this), nullptr,
+ CJob::PRIORITY_NORMAL);
+ return true;
+ }
+
+ // stop running thread
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iExitCode = EXITCODE_RESTARTAPP;
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+ StopThread(false);
+ }
+ StopThread();
+
+ // reset all members to their defaults
+ ResetMembers();
+
+ // reopen the connection
+ return InitialiseFeature(FEATURE_CEC);
+}
+
+void CPeripheralCecAdapter::ActivateSource(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bActiveSourcePending = true;
+}
+
+void CPeripheralCecAdapter::ProcessActivateSource(void)
+{
+ bool bActivate(false);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bActivate = m_bActiveSourcePending;
+ m_bActiveSourcePending = false;
+ }
+
+ if (bActivate)
+ m_cecAdapter->SetActiveSource();
+}
+
+void CPeripheralCecAdapter::StandbyDevices(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bStandbyPending = true;
+}
+
+void CPeripheralCecAdapter::ProcessStandbyDevices(void)
+{
+ bool bStandby(false);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ bStandby = m_bStandbyPending;
+ m_bStandbyPending = false;
+ if (bStandby)
+ m_bGoingToStandby = true;
+ }
+
+ if (bStandby)
+ {
+ if (!m_configuration.powerOffDevices.IsEmpty())
+ {
+ m_standbySent = CDateTime::GetCurrentDateTime();
+ m_cecAdapter->StandbyDevices(CECDEVICE_BROADCAST);
+ }
+ else if (m_bSendInactiveSource == 1)
+ {
+ CLog::Log(LOGDEBUG, "{} - sending inactive source commands", __FUNCTION__);
+ m_cecAdapter->SetInactiveView();
+ }
+ }
+}
+
+bool CPeripheralCecAdapter::ToggleDeviceState(CecStateChange mode /*= STATE_SWITCH_TOGGLE */,
+ bool forceType /*= false */)
+{
+ if (!IsRunning())
+ return false;
+ if (m_cecAdapter->IsLibCECActiveSource() &&
+ (mode == STATE_SWITCH_TOGGLE || mode == STATE_STANDBY))
+ {
+ CLog::Log(LOGDEBUG, "{} - putting CEC device on standby...", __FUNCTION__);
+ StandbyDevices();
+ return false;
+ }
+ else if (mode == STATE_SWITCH_TOGGLE || mode == STATE_ACTIVATE_SOURCE)
+ {
+ CLog::Log(LOGDEBUG, "{} - waking up CEC device...", __FUNCTION__);
+ ActivateSource();
+ return true;
+ }
+
+ return false;
+}
diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.h b/xbmc/peripherals/devices/PeripheralCecAdapter.h
new file mode 100644
index 0000000..b3755b3
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralCecAdapter.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#if !defined(HAVE_LIBCEC)
+#include "Peripheral.h"
+
+// an empty implementation, so CPeripherals can be compiled without a bunch of #ifdef's when libCEC
+// is not available
+namespace PERIPHERALS
+{
+class CPeripheralCecAdapter : public CPeripheral
+{
+public:
+ bool HasAudioControl(void) { return false; }
+ void VolumeUp(void) {}
+ void VolumeDown(void) {}
+ bool IsMuted(void) { return false; }
+ void ToggleMute(void) {}
+ bool ToggleDeviceState(CecStateChange mode = STATE_SWITCH_TOGGLE, bool forceType = false)
+ {
+ return false;
+ }
+
+ int GetButton(void) { return 0; }
+ unsigned int GetHoldTime(void) { return 0; }
+ void ResetButton(void) {}
+};
+} // namespace PERIPHERALS
+
+#else
+
+#include "PeripheralHID.h"
+#include "XBDateTime.h"
+#include "interfaces/AnnouncementManager.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <chrono>
+#include <queue>
+#include <vector>
+
+// undefine macro isset, it collides with function in cectypes.h
+#ifdef isset
+#undef isset
+#endif
+#include <libcec/cectypes.h>
+
+class CVariant;
+
+namespace CEC
+{
+class ICECAdapter;
+};
+
+namespace PERIPHERALS
+{
+class CPeripheralCecAdapterUpdateThread;
+class CPeripheralCecAdapterReopenJob;
+
+typedef struct
+{
+ int iButton;
+ unsigned int iDuration;
+} CecButtonPress;
+
+typedef enum
+{
+ VOLUME_CHANGE_NONE,
+ VOLUME_CHANGE_UP,
+ VOLUME_CHANGE_DOWN,
+ VOLUME_CHANGE_MUTE
+} CecVolumeChange;
+
+class CPeripheralCecAdapter : public CPeripheralHID,
+ public ANNOUNCEMENT::IAnnouncer,
+ private CThread
+{
+ friend class CPeripheralCecAdapterUpdateThread;
+ friend class CPeripheralCecAdapterReopenJob;
+
+public:
+ CPeripheralCecAdapter(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralCecAdapter(void) override;
+
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
+ // audio control
+ bool HasAudioControl(void);
+ void VolumeUp(void);
+ void VolumeDown(void);
+ void ToggleMute(void);
+ bool IsMuted(void);
+
+ // CPeripheral callbacks
+ void OnSettingChanged(const std::string& strChangedSetting) override;
+ void OnDeviceRemoved(void) override;
+
+ // input
+ int GetButton(void);
+ unsigned int GetHoldTime(void);
+ void ResetButton(void);
+
+ // public CEC methods
+ void ActivateSource(void);
+ void StandbyDevices(void);
+ bool ToggleDeviceState(CecStateChange mode = STATE_SWITCH_TOGGLE, bool forceType = false);
+
+private:
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void ResetMembers(void);
+ void Process(void) override;
+ bool IsRunning(void) const;
+
+ bool OpenConnection(void);
+ bool ReopenConnection(bool bAsync = false);
+
+ void SetConfigurationFromSettings(void);
+ void SetConfigurationFromLibCEC(const CEC::libcec_configuration& config);
+ void SetVersionInfo(const CEC::libcec_configuration& configuration);
+
+ static void ReadLogicalAddresses(const std::string& strString,
+ CEC::cec_logical_addresses& addresses);
+ static void ReadLogicalAddresses(int iLocalisedId, CEC::cec_logical_addresses& addresses);
+ bool WriteLogicalAddresses(const CEC::cec_logical_addresses& addresses,
+ const std::string& strSettingName,
+ const std::string& strAdvancedSettingName);
+
+ void ProcessActivateSource(void);
+ void ProcessStandbyDevices(void);
+ void ProcessVolumeChange(void);
+
+ void PushCecKeypress(const CEC::cec_keypress& key);
+ void PushCecKeypress(const CecButtonPress& key);
+ void GetNextKey(void);
+
+ void SetAudioSystemConnected(bool bSetTo);
+ void SetMenuLanguage(const char* strLanguage);
+ void OnTvStandby(void);
+
+ // callbacks from libCEC
+ static void CecLogMessage(void* cbParam, const CEC::cec_log_message* message);
+ static void CecCommand(void* cbParam, const CEC::cec_command* command);
+ static void CecConfiguration(void* cbParam, const CEC::libcec_configuration* config);
+ static void CecAlert(void* cbParam,
+ const CEC::libcec_alert alert,
+ const CEC::libcec_parameter data);
+ static void CecSourceActivated(void* param,
+ const CEC::cec_logical_address address,
+ const uint8_t activated);
+ static void CecKeyPress(void* cbParam, const CEC::cec_keypress* key);
+
+ CEC::ICECAdapter* m_cecAdapter;
+ bool m_bStarted;
+ bool m_bHasButton;
+ bool m_bIsReady;
+ bool m_bHasConnectedAudioSystem;
+ std::string m_strMenuLanguage;
+ CDateTime m_standbySent;
+ std::vector<CecButtonPress> m_buttonQueue;
+ CecButtonPress m_currentButton;
+ std::queue<CecVolumeChange> m_volumeChangeQueue;
+ std::chrono::time_point<std::chrono::steady_clock> m_lastKeypress;
+ CecVolumeChange m_lastChange;
+ int m_iExitCode;
+ bool m_bIsMuted;
+ bool m_bGoingToStandby;
+ bool m_bIsRunning;
+ bool m_bDeviceRemoved;
+ CPeripheralCecAdapterUpdateThread* m_queryThread;
+ CEC::ICECCallbacks m_callbacks;
+ mutable CCriticalSection m_critSection;
+ CEC::libcec_configuration m_configuration;
+ bool m_bActiveSourcePending;
+ bool m_bStandbyPending;
+ CDateTime m_preventActivateSourceOnPlay;
+ bool m_bActiveSourceBeforeStandby;
+ bool m_bOnPlayReceived;
+ bool m_bPlaybackPaused;
+ std::string m_strComPort;
+ bool m_bPowerOnScreensaver;
+ bool m_bUseTVMenuLanguage;
+ bool m_bSendInactiveSource;
+ bool m_bPowerOffScreensaver;
+ bool m_bShutdownOnStandby;
+};
+
+class CPeripheralCecAdapterUpdateThread : public CThread
+{
+public:
+ CPeripheralCecAdapterUpdateThread(CPeripheralCecAdapter* adapter,
+ CEC::libcec_configuration* configuration);
+ ~CPeripheralCecAdapterUpdateThread(void) override;
+
+ void Signal(void);
+ bool UpdateConfiguration(CEC::libcec_configuration* configuration);
+
+protected:
+ void UpdateMenuLanguage(void);
+ std::string UpdateAudioSystemStatus(void);
+ bool WaitReady(void);
+ bool SetInitialConfiguration(void);
+ void Process(void) override;
+
+ CPeripheralCecAdapter* m_adapter;
+ CEvent m_event;
+ CCriticalSection m_critSection;
+ CEC::libcec_configuration m_configuration;
+ CEC::libcec_configuration m_nextConfiguration;
+ bool m_bNextConfigurationScheduled;
+ bool m_bIsUpdating;
+};
+} // namespace PERIPHERALS
+
+#endif
diff --git a/xbmc/peripherals/devices/PeripheralDisk.cpp b/xbmc/peripherals/devices/PeripheralDisk.cpp
new file mode 100644
index 0000000..9b0682c
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralDisk.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralDisk.h"
+
+#include "guilib/LocalizeStrings.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralDisk::CPeripheralDisk(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35003)
+ : scanResult.m_strDeviceName;
+ m_features.push_back(FEATURE_DISK);
+}
diff --git a/xbmc/peripherals/devices/PeripheralDisk.h b/xbmc/peripherals/devices/PeripheralDisk.h
new file mode 100644
index 0000000..f81b31e
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralDisk.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralDisk : public CPeripheral
+{
+public:
+ CPeripheralDisk(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralDisk(void) override = default;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralHID.cpp b/xbmc/peripherals/devices/PeripheralHID.cpp
new file mode 100644
index 0000000..e3d061b
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralHID.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralHID.h"
+
+#include "guilib/LocalizeStrings.h"
+#include "input/InputManager.h"
+#include "peripherals/Peripherals.h"
+#include "utils/log.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralHID::CPeripheralHID(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35001)
+ : scanResult.m_strDeviceName;
+ m_features.push_back(FEATURE_HID);
+}
+
+CPeripheralHID::~CPeripheralHID(void)
+{
+ if (!m_strKeymap.empty() && !GetSettingBool("do_not_use_custom_keymap"))
+ {
+ CLog::Log(LOGDEBUG, "{} - switching active keymapping to: default", __FUNCTION__);
+ m_manager.GetInputManager().RemoveKeymap(m_strKeymap);
+ }
+}
+
+bool CPeripheralHID::InitialiseFeature(const PeripheralFeature feature)
+{
+ if (feature == FEATURE_HID && !m_bInitialised)
+ {
+ m_bInitialised = true;
+
+ if (HasSetting("keymap"))
+ m_strKeymap = GetSettingString("keymap");
+
+ if (m_strKeymap.empty())
+ {
+ m_strKeymap = StringUtils::Format("v{}p{}", VendorIdAsString(), ProductIdAsString());
+ SetSetting("keymap", m_strKeymap);
+ }
+
+ if (!IsSettingVisible("keymap"))
+ SetSettingVisible("do_not_use_custom_keymap", false);
+
+ if (!m_strKeymap.empty())
+ {
+ bool bKeymapEnabled(!GetSettingBool("do_not_use_custom_keymap"));
+ if (bKeymapEnabled)
+ {
+ CLog::Log(LOGDEBUG, "{} - adding keymapping for: {}", __FUNCTION__, m_strKeymap);
+ m_manager.GetInputManager().AddKeymap(m_strKeymap);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - removing keymapping for: {}", __FUNCTION__, m_strKeymap);
+ m_manager.GetInputManager().RemoveKeymap(m_strKeymap);
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "{} - initialised HID device ({}:{})", __FUNCTION__, m_strVendorId,
+ m_strProductId);
+ }
+
+ return CPeripheral::InitialiseFeature(feature);
+}
+
+void CPeripheralHID::OnSettingChanged(const std::string& strChangedSetting)
+{
+ if (m_bInitialised && ((StringUtils::EqualsNoCase(strChangedSetting, "keymap") &&
+ !GetSettingBool("do_not_use_custom_keymap")) ||
+ StringUtils::EqualsNoCase(strChangedSetting, "keymap_enabled")))
+ {
+ m_bInitialised = false;
+ InitialiseFeature(FEATURE_HID);
+ }
+}
diff --git a/xbmc/peripherals/devices/PeripheralHID.h b/xbmc/peripherals/devices/PeripheralHID.h
new file mode 100644
index 0000000..3b35dfd
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralHID.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "input/XBMC_keyboard.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralHID : public CPeripheral
+{
+public:
+ CPeripheralHID(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralHID(void) override;
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ virtual bool LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode)
+ {
+ return false;
+ }
+ void OnSettingChanged(const std::string& strChangedSetting) override;
+
+protected:
+ std::string m_strKeymap;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralImon.cpp b/xbmc/peripherals/devices/PeripheralImon.cpp
new file mode 100644
index 0000000..6683f8b
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralImon.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralImon.h"
+
+#include "input/InputManager.h"
+#include "settings/Settings.h"
+#include "utils/log.h"
+
+using namespace PERIPHERALS;
+
+std::atomic<long> CPeripheralImon::m_lCountOfImonsConflictWithDInput(0L);
+
+CPeripheralImon::CPeripheralImon(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheralHID(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_IMON);
+ m_bImonConflictsWithDInput = false;
+}
+
+void CPeripheralImon::OnDeviceRemoved()
+{
+ if (m_bImonConflictsWithDInput)
+ {
+ if (--m_lCountOfImonsConflictWithDInput == 0)
+ ActionOnImonConflict(false);
+ }
+}
+
+bool CPeripheralImon::InitialiseFeature(const PeripheralFeature feature)
+{
+ if (feature == FEATURE_IMON)
+ {
+#if defined(TARGET_WINDOWS)
+ if (HasSetting("disable_winjoystick") && GetSettingBool("disable_winjoystick"))
+ m_bImonConflictsWithDInput = true;
+ else
+#endif // TARGET_WINDOWS
+ m_bImonConflictsWithDInput = false;
+
+ if (m_bImonConflictsWithDInput)
+ {
+ ++m_lCountOfImonsConflictWithDInput;
+ ActionOnImonConflict(true);
+ }
+ return CPeripheral::InitialiseFeature(feature);
+ }
+
+ return CPeripheralHID::InitialiseFeature(feature);
+}
+
+void CPeripheralImon::AddSetting(const std::string& strKey,
+ const std::shared_ptr<const CSetting>& setting,
+ int order)
+{
+#if !defined(TARGET_WINDOWS)
+ if (strKey.compare("disable_winjoystick") != 0)
+#endif // !TARGET_WINDOWS
+ CPeripheralHID::AddSetting(strKey, setting, order);
+}
+
+void CPeripheralImon::OnSettingChanged(const std::string& strChangedSetting)
+{
+ if (strChangedSetting.compare("disable_winjoystick") == 0)
+ {
+ if (m_bImonConflictsWithDInput && !GetSettingBool("disable_winjoystick"))
+ {
+ m_bImonConflictsWithDInput = false;
+ if (--m_lCountOfImonsConflictWithDInput == 0)
+ ActionOnImonConflict(false);
+ }
+ else if (!m_bImonConflictsWithDInput && GetSettingBool("disable_winjoystick"))
+ {
+ m_bImonConflictsWithDInput = true;
+ ++m_lCountOfImonsConflictWithDInput;
+ ActionOnImonConflict(true);
+ }
+ }
+}
+
+void CPeripheralImon::ActionOnImonConflict(bool deviceInserted /*= true*/)
+{
+}
diff --git a/xbmc/peripherals/devices/PeripheralImon.h b/xbmc/peripherals/devices/PeripheralImon.h
new file mode 100644
index 0000000..612d059
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralImon.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PeripheralHID.h"
+
+#include <atomic>
+
+class CSetting;
+
+namespace PERIPHERALS
+{
+class CPeripheralImon : public CPeripheralHID
+{
+public:
+ CPeripheralImon(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralImon(void) override = default;
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void OnSettingChanged(const std::string& strChangedSetting) override;
+ void OnDeviceRemoved() override;
+ void AddSetting(const std::string& strKey,
+ const std::shared_ptr<const CSetting>& setting,
+ int order) override;
+ inline bool IsImonConflictsWithDInput() { return m_bImonConflictsWithDInput; }
+ static inline long GetCountOfImonsConflictWithDInput()
+ {
+ return m_lCountOfImonsConflictWithDInput;
+ }
+ static void ActionOnImonConflict(bool deviceInserted = true);
+
+private:
+ bool m_bImonConflictsWithDInput;
+ static std::atomic<long> m_lCountOfImonsConflictWithDInput;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralJoystick.cpp b/xbmc/peripherals/devices/PeripheralJoystick.cpp
new file mode 100644
index 0000000..bccad91
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralJoystick.cpp
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralJoystick.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonInstaller.h"
+#include "addons/AddonManager.h"
+#include "application/Application.h"
+#include "games/GameServices.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerIDs.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/InputManager.h"
+#include "input/joysticks/DeadzoneFilter.h"
+#include "input/joysticks/JoystickMonitor.h"
+#include "input/joysticks/JoystickTranslator.h"
+#include "input/joysticks/RumbleGenerator.h"
+#include "input/joysticks/interfaces/IDriverHandler.h"
+#include "input/joysticks/keymaps/KeymapHandling.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/addons/AddonButtonMap.h"
+#include "peripherals/bus/virtual/PeripheralBusAddon.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace KODI;
+using namespace JOYSTICK;
+using namespace PERIPHERALS;
+
+CPeripheralJoystick::CPeripheralJoystick(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus),
+ m_requestedPort(JOYSTICK_PORT_UNKNOWN),
+ m_buttonCount(0),
+ m_hatCount(0),
+ m_axisCount(0),
+ m_motorCount(0),
+ m_supportsPowerOff(false),
+ m_rumbleGenerator(new CRumbleGenerator)
+{
+ m_features.push_back(FEATURE_JOYSTICK);
+ // FEATURE_RUMBLE conditionally added via SetMotorCount()
+}
+
+CPeripheralJoystick::~CPeripheralJoystick(void)
+{
+ if (m_rumbleGenerator)
+ {
+ m_rumbleGenerator->AbortRumble();
+ m_rumbleGenerator.reset();
+ }
+
+ if (m_joystickMonitor)
+ {
+ UnregisterInputHandler(m_joystickMonitor.get());
+ m_joystickMonitor.reset();
+ }
+
+ m_appInput.reset();
+ m_deadzoneFilter.reset();
+ m_buttonMap.reset();
+
+ // Wait for remaining install tasks
+ for (std::future<void>& task : m_installTasks)
+ task.wait();
+}
+
+bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ if (CPeripheral::InitialiseFeature(feature))
+ {
+ if (feature == FEATURE_JOYSTICK)
+ {
+ // Ensure an add-on is present to translate input
+ PeripheralAddonPtr addon = m_manager.GetAddonWithButtonMap(this);
+ if (!addon)
+ {
+ CLog::Log(LOGERROR, "CPeripheralJoystick: No button mapping add-on for {}", m_strLocation);
+ }
+ else
+ {
+ if (m_bus->InitializeProperties(*this))
+ bSuccess = true;
+ else
+ CLog::Log(LOGERROR, "CPeripheralJoystick: Invalid location ({})", m_strLocation);
+ }
+
+ if (bSuccess)
+ {
+ m_buttonMap =
+ std::make_unique<CAddonButtonMap>(this, addon, DEFAULT_CONTROLLER_ID, m_manager);
+ if (m_buttonMap->Load())
+ {
+ InitializeDeadzoneFiltering(*m_buttonMap);
+ InitializeControllerProfile(*m_buttonMap);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "CPeripheralJoystick: Failed to load button map for {}",
+ m_strLocation);
+ m_buttonMap.reset();
+ }
+
+ // Give joystick monitor priority over default controller
+ m_appInput.reset(
+ new CKeymapHandling(this, false, m_manager.GetInputManager().KeymapEnvironment()));
+ m_joystickMonitor.reset(new CJoystickMonitor);
+ RegisterInputHandler(m_joystickMonitor.get(), false);
+ }
+ }
+ else if (feature == FEATURE_RUMBLE)
+ {
+ bSuccess = true; // Nothing to do
+ }
+ else if (feature == FEATURE_POWER_OFF)
+ {
+ bSuccess = true; // Nothing to do
+ }
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralJoystick::InitializeDeadzoneFiltering(IButtonMap& buttonMap)
+{
+ m_deadzoneFilter.reset(new CDeadzoneFilter(&buttonMap, this));
+}
+
+void CPeripheralJoystick::InitializeControllerProfile(IButtonMap& buttonMap)
+{
+ const std::string controllerId = buttonMap.GetAppearance();
+ if (controllerId.empty())
+ return;
+
+ auto controller = m_manager.GetControllerProfiles().GetController(controllerId);
+ if (controller)
+ CPeripheral::SetControllerProfile(controller);
+ else
+ {
+ std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex);
+
+ // Deposit controller into queue
+ m_controllersToInstall.emplace(controllerId);
+
+ // Clean up finished install tasks
+ m_installTasks.erase(std::remove_if(m_installTasks.begin(), m_installTasks.end(),
+ [](std::future<void>& task) {
+ return task.wait_for(std::chrono::seconds(0)) ==
+ std::future_status::ready;
+ }),
+ m_installTasks.end());
+
+ // Install controller off-thread
+ std::future<void> installTask = std::async(std::launch::async, [this]() {
+ // Withdraw controller from queue
+ std::string controllerToInstall;
+ {
+ std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex);
+ if (!m_controllersToInstall.empty())
+ {
+ controllerToInstall = m_controllersToInstall.front();
+ m_controllersToInstall.pop();
+ }
+ }
+
+ // Do the install
+ GAME::ControllerPtr controller = InstallAsync(controllerToInstall);
+ if (controller)
+ CPeripheral::SetControllerProfile(controller);
+ });
+
+ // Hold the task to prevent the destructor from completing during an install
+ m_installTasks.emplace_back(std::move(installTask));
+ }
+}
+
+void CPeripheralJoystick::OnUserNotification()
+{
+ IInputReceiver* inputReceiver = m_appInput->GetInputReceiver(m_rumbleGenerator->ControllerID());
+ m_rumbleGenerator->NotifyUser(inputReceiver);
+}
+
+bool CPeripheralJoystick::TestFeature(PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ switch (feature)
+ {
+ case FEATURE_RUMBLE:
+ {
+ IInputReceiver* inputReceiver =
+ m_appInput->GetInputReceiver(m_rumbleGenerator->ControllerID());
+ bSuccess = m_rumbleGenerator->DoTest(inputReceiver);
+ break;
+ }
+ case FEATURE_POWER_OFF:
+ if (m_supportsPowerOff)
+ {
+ PowerOff();
+ bSuccess = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralJoystick::PowerOff()
+{
+ m_bus->PowerOff(m_strLocation);
+}
+
+void CPeripheralJoystick::RegisterJoystickDriverHandler(IDriverHandler* handler, bool bPromiscuous)
+{
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ DriverHandler driverHandler = {handler, bPromiscuous};
+ m_driverHandlers.insert(m_driverHandlers.begin(), driverHandler);
+}
+
+void CPeripheralJoystick::UnregisterJoystickDriverHandler(IDriverHandler* handler)
+{
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ m_driverHandlers.erase(std::remove_if(m_driverHandlers.begin(), m_driverHandlers.end(),
+ [handler](const DriverHandler& driverHandler) {
+ return driverHandler.handler == handler;
+ }),
+ m_driverHandlers.end());
+}
+
+IKeymap* CPeripheralJoystick::GetKeymap(const std::string& controllerId)
+{
+ return m_appInput->GetKeymap(controllerId);
+}
+
+GAME::ControllerPtr CPeripheralJoystick::ControllerProfile() const
+{
+ // Button map has the freshest state
+ if (m_buttonMap)
+ {
+ const std::string controllerId = m_buttonMap->GetAppearance();
+ if (!controllerId.empty())
+ {
+ auto controller = m_manager.GetControllerProfiles().GetController(controllerId);
+ if (controller)
+ return controller;
+ }
+ }
+
+ // Fall back to last set controller profile
+ if (m_controllerProfile)
+ return m_controllerProfile;
+
+ // Fall back to default controller
+ return m_manager.GetControllerProfiles().GetDefaultController();
+}
+
+void CPeripheralJoystick::SetControllerProfile(const KODI::GAME::ControllerPtr& controller)
+{
+ CPeripheral::SetControllerProfile(controller);
+
+ // Save preference to buttonmap
+ if (m_buttonMap)
+ {
+ if (m_buttonMap->SetAppearance(controller->ID()))
+ m_buttonMap->SaveButtonMap();
+ }
+}
+
+bool CPeripheralJoystick::OnButtonMotion(unsigned int buttonIndex, bool bPressed)
+{
+ // Silence debug log if controllers are not enabled
+ if (m_manager.GetInputManager().IsControllerEnabled())
+ {
+ CLog::Log(LOGDEBUG, "BUTTON [ {} ] on \"{}\" {}", buttonIndex, DeviceName(),
+ bPressed ? "pressed" : "released");
+ }
+
+ // Avoid sending activated input if the app is in the background
+ if (bPressed && !g_application.IsAppFocused())
+ return false;
+
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ // Check GUI setting and send button release if controllers are disabled
+ if (!m_manager.GetInputManager().IsControllerEnabled())
+ {
+ for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin();
+ it != m_driverHandlers.end(); ++it)
+ it->handler->OnButtonMotion(buttonIndex, false);
+ return true;
+ }
+
+ // Process promiscuous handlers
+ for (auto& it : m_driverHandlers)
+ {
+ if (it.bPromiscuous)
+ it.handler->OnButtonMotion(buttonIndex, bPressed);
+ }
+
+ bool bHandled = false;
+
+ // Process regular handlers until one is handled
+ for (auto& it : m_driverHandlers)
+ {
+ if (!it.bPromiscuous)
+ {
+ bHandled |= it.handler->OnButtonMotion(buttonIndex, bPressed);
+
+ // If button is released, force bHandled to false to notify all handlers.
+ // This avoids "sticking".
+ if (!bPressed)
+ bHandled = false;
+
+ // Once a button is handled, we're done
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+bool CPeripheralJoystick::OnHatMotion(unsigned int hatIndex, HAT_STATE state)
+{
+ // Silence debug log if controllers are not enabled
+ if (m_manager.GetInputManager().IsControllerEnabled())
+ {
+ CLog::Log(LOGDEBUG, "HAT [ {} ] on \"{}\" {}", hatIndex, DeviceName(),
+ CJoystickTranslator::HatStateToString(state));
+ }
+
+ // Avoid sending activated input if the app is in the background
+ if (state != HAT_STATE::NONE && !g_application.IsAppFocused())
+ return false;
+
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ // Check GUI setting and send hat unpressed if controllers are disabled
+ if (!m_manager.GetInputManager().IsControllerEnabled())
+ {
+ for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin();
+ it != m_driverHandlers.end(); ++it)
+ it->handler->OnHatMotion(hatIndex, HAT_STATE::NONE);
+ return true;
+ }
+
+ // Process promiscuous handlers
+ for (auto& it : m_driverHandlers)
+ {
+ if (it.bPromiscuous)
+ it.handler->OnHatMotion(hatIndex, state);
+ }
+
+ bool bHandled = false;
+
+ // Process regular handlers until one is handled
+ for (auto& it : m_driverHandlers)
+ {
+ if (!it.bPromiscuous)
+ {
+ bHandled |= it.handler->OnHatMotion(hatIndex, state);
+
+ // If hat is centered, force bHandled to false to notify all handlers.
+ // This avoids "sticking".
+ if (state == HAT_STATE::NONE)
+ bHandled = false;
+
+ // Once a hat is handled, we're done
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+bool CPeripheralJoystick::OnAxisMotion(unsigned int axisIndex, float position)
+{
+ // Get axis properties
+ int center = 0;
+ unsigned int range = 1;
+ if (m_buttonMap)
+ m_buttonMap->GetAxisProperties(axisIndex, center, range);
+
+ // Apply deadzone filtering
+ if (center == 0 && m_deadzoneFilter)
+ position = m_deadzoneFilter->FilterAxis(axisIndex, position);
+
+ // Avoid sending activated input if the app is in the background
+ if (position != static_cast<float>(center) && !g_application.IsAppFocused())
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ // Check GUI setting and send analog axis centered if controllers are disabled
+ if (!m_manager.GetInputManager().IsControllerEnabled())
+ {
+ for (std::vector<DriverHandler>::iterator it = m_driverHandlers.begin();
+ it != m_driverHandlers.end(); ++it)
+ it->handler->OnAxisMotion(axisIndex, static_cast<float>(center), center, range);
+ return true;
+ }
+
+ // Process promiscuous handlers
+ for (auto& it : m_driverHandlers)
+ {
+ if (it.bPromiscuous)
+ it.handler->OnAxisMotion(axisIndex, position, center, range);
+ }
+
+ bool bHandled = false;
+
+ // Process regular handlers until one is handled
+ for (auto& it : m_driverHandlers)
+ {
+ if (!it.bPromiscuous)
+ {
+ bHandled |= it.handler->OnAxisMotion(axisIndex, position, center, range);
+
+ // If axis is centered, force bHandled to false to notify all handlers.
+ // This avoids "sticking".
+ if (position == static_cast<float>(center))
+ bHandled = false;
+
+ // Once an axis is handled, we're done
+ if (bHandled)
+ break;
+ }
+ }
+
+ if (bHandled)
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ return bHandled;
+}
+
+void CPeripheralJoystick::OnInputFrame(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_handlerMutex);
+
+ for (auto& it : m_driverHandlers)
+ it.handler->OnInputFrame();
+}
+
+bool CPeripheralJoystick::SetMotorState(unsigned int motorIndex, float magnitude)
+{
+ bool bHandled = false;
+
+ if (m_mappedBusType == PERIPHERAL_BUS_ADDON)
+ {
+ CPeripheralBusAddon* addonBus = static_cast<CPeripheralBusAddon*>(m_bus);
+ if (addonBus)
+ {
+ bHandled = addonBus->SendRumbleEvent(m_strLocation, motorIndex, magnitude);
+ }
+ }
+ return bHandled;
+}
+
+void CPeripheralJoystick::SetMotorCount(unsigned int motorCount)
+{
+ m_motorCount = motorCount;
+
+ if (m_motorCount == 0)
+ m_features.erase(std::remove(m_features.begin(), m_features.end(), FEATURE_RUMBLE),
+ m_features.end());
+ else if (std::find(m_features.begin(), m_features.end(), FEATURE_RUMBLE) == m_features.end())
+ m_features.push_back(FEATURE_RUMBLE);
+}
+
+void CPeripheralJoystick::SetSupportsPowerOff(bool bSupportsPowerOff)
+{
+ m_supportsPowerOff = bSupportsPowerOff;
+
+ if (!m_supportsPowerOff)
+ m_features.erase(std::remove(m_features.begin(), m_features.end(), FEATURE_POWER_OFF),
+ m_features.end());
+ else if (std::find(m_features.begin(), m_features.end(), FEATURE_POWER_OFF) == m_features.end())
+ m_features.push_back(FEATURE_POWER_OFF);
+}
+
+GAME::ControllerPtr CPeripheralJoystick::InstallAsync(const std::string& controllerId)
+{
+ GAME::ControllerPtr controller;
+
+ // Only 1 install at a time. Remaining installs will wake when this one
+ // is done.
+ std::unique_lock<CCriticalSection> lockInstall(m_manager.GetAddonInstallMutex());
+
+ CLog::LogF(LOGDEBUG, "Installing {}", controllerId);
+
+ if (InstallSync(controllerId))
+ controller = m_manager.GetControllerProfiles().GetController(controllerId);
+ else
+ CLog::LogF(LOGERROR, "Failed to install {}", controllerId);
+
+ return controller;
+}
+
+bool CPeripheralJoystick::InstallSync(const std::string& controllerId)
+{
+ // If the addon isn't installed we need to install it
+ bool installed = CServiceBroker::GetAddonMgr().IsAddonInstalled(controllerId);
+ if (!installed)
+ {
+ ADDON::AddonPtr installedAddon;
+ installed = ADDON::CAddonInstaller::GetInstance().InstallModal(
+ controllerId, installedAddon, ADDON::InstallModalPrompt::CHOICE_NO);
+ }
+
+ if (installed)
+ {
+ // Make sure add-on is enabled
+ if (CServiceBroker::GetAddonMgr().IsAddonDisabled(controllerId))
+ CServiceBroker::GetAddonMgr().EnableAddon(controllerId);
+ }
+
+ return installed;
+}
diff --git a/xbmc/peripherals/devices/PeripheralJoystick.h b/xbmc/peripherals/devices/PeripheralJoystick.h
new file mode 100644
index 0000000..aa55ad7
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralJoystick.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "XBDateTime.h"
+#include "games/controllers/ControllerTypes.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "input/joysticks/interfaces/IDriverReceiver.h"
+#include "threads/CriticalSection.h"
+
+#include <future>
+#include <memory>
+#include <queue>
+#include <string>
+#include <vector>
+
+#define JOYSTICK_PORT_UNKNOWN (-1)
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CDeadzoneFilter;
+class CKeymapHandling;
+class CRumbleGenerator;
+class IButtonMap;
+class IDriverHandler;
+class IInputHandler;
+} // namespace JOYSTICK
+} // namespace KODI
+
+namespace PERIPHERALS
+{
+class CPeripherals;
+
+class CPeripheralJoystick : public CPeripheral, //! @todo extend CPeripheralHID
+ public KODI::JOYSTICK::IDriverReceiver
+{
+public:
+ CPeripheralJoystick(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+
+ ~CPeripheralJoystick(void) override;
+
+ // implementation of CPeripheral
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void OnUserNotification() override;
+ bool TestFeature(PeripheralFeature feature) override;
+ void RegisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterJoystickDriverHandler(KODI::JOYSTICK::IDriverHandler* handler) override;
+ KODI::JOYSTICK::IDriverReceiver* GetDriverReceiver() override { return this; }
+ IKeymap* GetKeymap(const std::string& controllerId) override;
+ CDateTime LastActive() override { return m_lastActive; }
+ KODI::GAME::ControllerPtr ControllerProfile() const override;
+ void SetControllerProfile(const KODI::GAME::ControllerPtr& controller) override;
+
+ bool OnButtonMotion(unsigned int buttonIndex, bool bPressed);
+ bool OnHatMotion(unsigned int hatIndex, KODI::JOYSTICK::HAT_STATE state);
+ bool OnAxisMotion(unsigned int axisIndex, float position);
+ void OnInputFrame(void);
+
+ // implementation of IDriverReceiver
+ bool SetMotorState(unsigned int motorIndex, float magnitude) override;
+
+ /*!
+ * \brief Get the name of the driver or API providing this joystick
+ */
+ const std::string& Provider(void) const { return m_strProvider; }
+
+ /*!
+ * \brief Get the specific port number requested by this joystick
+ *
+ * This could indicate that the joystick is connected to a hardware port
+ * with a number label; some controllers, such as the Xbox 360 controller,
+ * also have LEDs that indicate the controller is on a specific port.
+ *
+ * \return The 0-indexed port number, or JOYSTICK_PORT_UNKNOWN if no port is requested
+ */
+ int RequestedPort(void) const { return m_requestedPort; }
+
+ /*!
+ * \brief Get the number of elements reported by the driver
+ */
+ unsigned int ButtonCount(void) const { return m_buttonCount; }
+ unsigned int HatCount(void) const { return m_hatCount; }
+ unsigned int AxisCount(void) const { return m_axisCount; }
+ unsigned int MotorCount(void) const { return m_motorCount; }
+ bool SupportsPowerOff(void) const { return m_supportsPowerOff; }
+
+ /*!
+ * \brief Set joystick properties
+ */
+ void SetProvider(const std::string& provider) { m_strProvider = provider; }
+ void SetRequestedPort(int port) { m_requestedPort = port; }
+ void SetButtonCount(unsigned int buttonCount) { m_buttonCount = buttonCount; }
+ void SetHatCount(unsigned int hatCount) { m_hatCount = hatCount; }
+ void SetAxisCount(unsigned int axisCount) { m_axisCount = axisCount; }
+ void SetMotorCount(unsigned int motorCount); // specialized to update m_features
+ void SetSupportsPowerOff(bool bSupportsPowerOff); // specialized to update m_features
+
+protected:
+ void InitializeDeadzoneFiltering(KODI::JOYSTICK::IButtonMap& buttonMap);
+ void InitializeControllerProfile(KODI::JOYSTICK::IButtonMap& buttonMap);
+
+ void PowerOff();
+
+ // Helper functions
+ KODI::GAME::ControllerPtr InstallAsync(const std::string& controllerId);
+ static bool InstallSync(const std::string& controllerId);
+
+ struct DriverHandler
+ {
+ KODI::JOYSTICK::IDriverHandler* handler;
+ bool bPromiscuous;
+ };
+
+ // State parameters
+ std::string m_strProvider;
+ int m_requestedPort;
+ unsigned int m_buttonCount;
+ unsigned int m_hatCount;
+ unsigned int m_axisCount;
+ unsigned int m_motorCount;
+ bool m_supportsPowerOff;
+ CDateTime m_lastActive;
+ std::queue<std::string> m_controllersToInstall;
+ std::vector<std::future<void>> m_installTasks;
+
+ // Input clients
+ std::unique_ptr<KODI::JOYSTICK::CKeymapHandling> m_appInput;
+ std::unique_ptr<KODI::JOYSTICK::CRumbleGenerator> m_rumbleGenerator;
+ std::unique_ptr<KODI::JOYSTICK::IInputHandler> m_joystickMonitor;
+ std::unique_ptr<KODI::JOYSTICK::IButtonMap> m_buttonMap;
+ std::unique_ptr<KODI::JOYSTICK::CDeadzoneFilter> m_deadzoneFilter;
+ std::vector<DriverHandler> m_driverHandlers;
+
+ // Synchronization parameters
+ CCriticalSection m_handlerMutex;
+ CCriticalSection m_controllerInstallMutex;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralKeyboard.cpp b/xbmc/peripherals/devices/PeripheralKeyboard.cpp
new file mode 100644
index 0000000..271d8b1
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralKeyboard.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralKeyboard.h"
+
+#include "games/GameServices.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/InputManager.h"
+#include "peripherals/Peripherals.h"
+
+#include <mutex>
+#include <sstream>
+
+using namespace KODI;
+using namespace PERIPHERALS;
+
+CPeripheralKeyboard::CPeripheralKeyboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ // Initialize CPeripheral
+ m_features.push_back(FEATURE_KEYBOARD);
+}
+
+CPeripheralKeyboard::~CPeripheralKeyboard(void)
+{
+ m_manager.GetInputManager().UnregisterKeyboardDriverHandler(this);
+}
+
+bool CPeripheralKeyboard::InitialiseFeature(const PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ if (CPeripheral::InitialiseFeature(feature))
+ {
+ switch (feature)
+ {
+ case FEATURE_KEYBOARD:
+ {
+ m_manager.GetInputManager().RegisterKeyboardDriverHandler(this);
+ break;
+ }
+ default:
+ break;
+ }
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralKeyboard::RegisterKeyboardDriverHandler(
+ KODI::KEYBOARD::IKeyboardDriverHandler* handler, bool bPromiscuous)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ KeyboardHandle handle{handler, bPromiscuous};
+ m_keyboardHandlers.insert(m_keyboardHandlers.begin(), handle);
+}
+
+void CPeripheralKeyboard::UnregisterKeyboardDriverHandler(
+ KODI::KEYBOARD::IKeyboardDriverHandler* handler)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ auto it =
+ std::find_if(m_keyboardHandlers.begin(), m_keyboardHandlers.end(),
+ [handler](const KeyboardHandle& handle) { return handle.handler == handler; });
+
+ if (it != m_keyboardHandlers.end())
+ m_keyboardHandlers.erase(it);
+}
+
+GAME::ControllerPtr CPeripheralKeyboard::ControllerProfile() const
+{
+ if (m_controllerProfile)
+ return m_controllerProfile;
+
+ return m_manager.GetControllerProfiles().GetDefaultKeyboard();
+}
+
+bool CPeripheralKeyboard::OnKeyPress(const CKey& key)
+{
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ bool bHandled = false;
+
+ // Process promiscuous handlers
+ for (const KeyboardHandle& handle : m_keyboardHandlers)
+ {
+ if (handle.bPromiscuous)
+ handle.handler->OnKeyPress(key);
+ }
+
+ // Process handlers until one is handled
+ for (const KeyboardHandle& handle : m_keyboardHandlers)
+ {
+ if (!handle.bPromiscuous)
+ {
+ bHandled = handle.handler->OnKeyPress(key);
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+void CPeripheralKeyboard::OnKeyRelease(const CKey& key)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ for (const KeyboardHandle& handle : m_keyboardHandlers)
+ handle.handler->OnKeyRelease(key);
+}
diff --git a/xbmc/peripherals/devices/PeripheralKeyboard.h b/xbmc/peripherals/devices/PeripheralKeyboard.h
new file mode 100644
index 0000000..2cf2934
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralKeyboard.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "XBDateTime.h"
+#include "input/keyboard/interfaces/IKeyboardDriverHandler.h"
+#include "threads/CriticalSection.h"
+
+#include <vector>
+
+namespace PERIPHERALS
+{
+class CPeripheralKeyboard : public CPeripheral, public KODI::KEYBOARD::IKeyboardDriverHandler
+{
+public:
+ CPeripheralKeyboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+
+ ~CPeripheralKeyboard(void) override;
+
+ // implementation of CPeripheral
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void RegisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler) override;
+ CDateTime LastActive() override { return m_lastActive; }
+ KODI::GAME::ControllerPtr ControllerProfile() const override;
+
+ // implementation of IKeyboardDriverHandler
+ bool OnKeyPress(const CKey& key) override;
+ void OnKeyRelease(const CKey& key) override;
+
+private:
+ struct KeyboardHandle
+ {
+ KODI::KEYBOARD::IKeyboardDriverHandler* handler;
+ bool bPromiscuous;
+ };
+
+ std::vector<KeyboardHandle> m_keyboardHandlers;
+ CCriticalSection m_mutex;
+ CDateTime m_lastActive;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralMouse.cpp b/xbmc/peripherals/devices/PeripheralMouse.cpp
new file mode 100644
index 0000000..4f26236
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralMouse.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralMouse.h"
+
+#include "games/GameServices.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/InputManager.h"
+#include "peripherals/Peripherals.h"
+
+#include <mutex>
+#include <sstream>
+
+using namespace KODI;
+using namespace PERIPHERALS;
+
+CPeripheralMouse::CPeripheralMouse(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ // Initialize CPeripheral
+ m_features.push_back(FEATURE_MOUSE);
+}
+
+CPeripheralMouse::~CPeripheralMouse(void)
+{
+ m_manager.GetInputManager().UnregisterMouseDriverHandler(this);
+}
+
+bool CPeripheralMouse::InitialiseFeature(const PeripheralFeature feature)
+{
+ bool bSuccess = false;
+
+ if (CPeripheral::InitialiseFeature(feature))
+ {
+ if (feature == FEATURE_MOUSE)
+ {
+ m_manager.GetInputManager().RegisterMouseDriverHandler(this);
+ }
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+void CPeripheralMouse::RegisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler,
+ bool bPromiscuous)
+{
+ using namespace KEYBOARD;
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ MouseHandle handle{handler, bPromiscuous};
+ m_mouseHandlers.insert(m_mouseHandlers.begin(), handle);
+}
+
+void CPeripheralMouse::UnregisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ auto it =
+ std::find_if(m_mouseHandlers.begin(), m_mouseHandlers.end(),
+ [handler](const MouseHandle& handle) { return handle.handler == handler; });
+
+ if (it != m_mouseHandlers.end())
+ m_mouseHandlers.erase(it);
+}
+
+GAME::ControllerPtr CPeripheralMouse::ControllerProfile() const
+{
+ if (m_controllerProfile)
+ return m_controllerProfile;
+
+ return m_manager.GetControllerProfiles().GetDefaultMouse();
+}
+
+bool CPeripheralMouse::OnPosition(int x, int y)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ bool bHandled = false;
+
+ // Process promiscuous handlers
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (handle.bPromiscuous)
+ handle.handler->OnPosition(x, y);
+ }
+
+ // Process handlers until one is handled
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (!handle.bPromiscuous)
+ {
+ bHandled = handle.handler->OnPosition(x, y);
+ if (bHandled)
+ break;
+ }
+ }
+
+ if (bHandled)
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ return bHandled;
+}
+
+bool CPeripheralMouse::OnButtonPress(MOUSE::BUTTON_ID button)
+{
+ m_lastActive = CDateTime::GetCurrentDateTime();
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ bool bHandled = false;
+
+ // Process promiscuous handlers
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (handle.bPromiscuous)
+ handle.handler->OnButtonPress(button);
+ }
+
+ // Process handlers until one is handled
+ for (const MouseHandle& handle : m_mouseHandlers)
+ {
+ if (!handle.bPromiscuous)
+ {
+ bHandled = handle.handler->OnButtonPress(button);
+ if (bHandled)
+ break;
+ }
+ }
+
+ return bHandled;
+}
+
+void CPeripheralMouse::OnButtonRelease(MOUSE::BUTTON_ID button)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ for (const MouseHandle& handle : m_mouseHandlers)
+ handle.handler->OnButtonRelease(button);
+}
diff --git a/xbmc/peripherals/devices/PeripheralMouse.h b/xbmc/peripherals/devices/PeripheralMouse.h
new file mode 100644
index 0000000..b46776d
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralMouse.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+#include "XBDateTime.h"
+#include "input/mouse/interfaces/IMouseDriverHandler.h"
+#include "threads/CriticalSection.h"
+
+#include <vector>
+
+namespace PERIPHERALS
+{
+class CPeripheralMouse : public CPeripheral, public KODI::MOUSE::IMouseDriverHandler
+{
+public:
+ CPeripheralMouse(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+
+ ~CPeripheralMouse(void) override;
+
+ // implementation of CPeripheral
+ bool InitialiseFeature(const PeripheralFeature feature) override;
+ void RegisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler,
+ bool bPromiscuous) override;
+ void UnregisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler) override;
+ CDateTime LastActive() override { return m_lastActive; }
+ KODI::GAME::ControllerPtr ControllerProfile() const override;
+
+ // implementation of IMouseDriverHandler
+ bool OnPosition(int x, int y) override;
+ bool OnButtonPress(KODI::MOUSE::BUTTON_ID button) override;
+ void OnButtonRelease(KODI::MOUSE::BUTTON_ID button) override;
+
+private:
+ struct MouseHandle
+ {
+ KODI::MOUSE::IMouseDriverHandler* handler;
+ bool bPromiscuous;
+ };
+
+ std::vector<MouseHandle> m_mouseHandlers;
+ CCriticalSection m_mutex;
+ CDateTime m_lastActive;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralNIC.cpp b/xbmc/peripherals/devices/PeripheralNIC.cpp
new file mode 100644
index 0000000..a5b5c32
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNIC.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralNIC.h"
+
+#include "guilib/LocalizeStrings.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralNIC::CPeripheralNIC(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_strDeviceName = scanResult.m_strDeviceName.empty() ? g_localizeStrings.Get(35002)
+ : scanResult.m_strDeviceName;
+ m_features.push_back(FEATURE_NIC);
+}
diff --git a/xbmc/peripherals/devices/PeripheralNIC.h b/xbmc/peripherals/devices/PeripheralNIC.h
new file mode 100644
index 0000000..01fe5f3
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNIC.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralNIC : public CPeripheral
+{
+public:
+ CPeripheralNIC(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralNIC(void) override = default;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralNyxboard.cpp b/xbmc/peripherals/devices/PeripheralNyxboard.cpp
new file mode 100644
index 0000000..548d8cf
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNyxboard.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralNyxboard.h"
+
+#include "PeripheralHID.h"
+#include "application/Application.h"
+#include "utils/log.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralNyxboard::CPeripheralNyxboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheralHID(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_NYXBOARD);
+}
+
+bool CPeripheralNyxboard::LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode)
+{
+ std::string strCommand;
+ if (keysym.sym == XBMCK_F7 && keysym.mod == XBMCKMOD_NONE &&
+ GetSettingBool("enable_flip_commands"))
+ {
+ /* switched to keyboard side */
+ CLog::Log(LOGDEBUG, "{} - switched to keyboard side", __FUNCTION__);
+ strCommand = GetSettingString("flip_keyboard");
+ }
+ else if (keysym.sym == XBMCK_F7 && keysym.mod == XBMCKMOD_LCTRL &&
+ GetSettingBool("enable_flip_commands"))
+ {
+ /* switched to remote side */
+ CLog::Log(LOGDEBUG, "{} - switched to remote side", __FUNCTION__);
+ strCommand = GetSettingString("flip_remote");
+ }
+
+ if (!strCommand.empty())
+ {
+ CLog::Log(LOGDEBUG, "{} - executing command '{}'", __FUNCTION__, strCommand);
+ if (g_application.ExecuteXBMCAction(strCommand))
+ {
+ *key = 0;
+ *unicode = (char)0;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/peripherals/devices/PeripheralNyxboard.h b/xbmc/peripherals/devices/PeripheralNyxboard.h
new file mode 100644
index 0000000..f81c3ef
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralNyxboard.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PeripheralHID.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralNyxboard : public CPeripheralHID
+{
+public:
+ CPeripheralNyxboard(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralNyxboard(void) override = default;
+ bool LookupSymAndUnicode(XBMC_keysym& keysym, uint8_t* key, char* unicode) override;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/devices/PeripheralTuner.cpp b/xbmc/peripherals/devices/PeripheralTuner.cpp
new file mode 100644
index 0000000..770656d
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralTuner.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PeripheralTuner.h"
+
+using namespace PERIPHERALS;
+
+CPeripheralTuner::CPeripheralTuner(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus)
+ : CPeripheral(manager, scanResult, bus)
+{
+ m_features.push_back(FEATURE_TUNER);
+}
diff --git a/xbmc/peripherals/devices/PeripheralTuner.h b/xbmc/peripherals/devices/PeripheralTuner.h
new file mode 100644
index 0000000..aedfc12
--- /dev/null
+++ b/xbmc/peripherals/devices/PeripheralTuner.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Peripheral.h"
+
+namespace PERIPHERALS
+{
+class CPeripheralTuner : public CPeripheral
+{
+public:
+ CPeripheralTuner(CPeripherals& manager,
+ const PeripheralScanResult& scanResult,
+ CPeripheralBus* bus);
+ ~CPeripheralTuner(void) override = default;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/dialogs/CMakeLists.txt b/xbmc/peripherals/dialogs/CMakeLists.txt
new file mode 100644
index 0000000..28aca66
--- /dev/null
+++ b/xbmc/peripherals/dialogs/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES GUIDialogPeripherals.cpp
+ GUIDialogPeripheralSettings.cpp)
+
+set(HEADERS GUIDialogPeripherals.h
+ GUIDialogPeripheralSettings.h)
+
+core_add_library(peripherals_dialogs)
diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp
new file mode 100644
index 0000000..73816df
--- /dev/null
+++ b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIDialogPeripheralSettings.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "addons/Skin.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "guilib/GUIMessage.h"
+#include "peripherals/Peripherals.h"
+#include "settings/SettingAddon.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingSection.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <string_view>
+#include <utility>
+
+using namespace KODI;
+using namespace PERIPHERALS;
+
+// Settings for peripherals
+constexpr std::string_view SETTING_APPEARANCE = "appearance";
+
+CGUIDialogPeripheralSettings::CGUIDialogPeripheralSettings()
+ : CGUIDialogSettingsManualBase(WINDOW_DIALOG_PERIPHERAL_SETTINGS, "DialogSettings.xml"),
+ m_item(NULL)
+{
+}
+
+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_SETTINGS_CUSTOM_BUTTON)
+ {
+ OnResetSettings();
+ return true;
+ }
+
+ return CGUIDialogSettingsManualBase::OnMessage(message);
+}
+
+void CGUIDialogPeripheralSettings::RegisterPeripheralManager(CPeripherals& manager)
+{
+ m_manager = &manager;
+}
+
+void CGUIDialogPeripheralSettings::UnregisterPeripheralManager()
+{
+ m_manager = nullptr;
+}
+
+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 std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == NULL)
+ return;
+
+ CGUIDialogSettingsManualBase::OnSettingChanged(setting);
+
+ const std::string& settingId = setting->GetId();
+
+ // we need to copy the new value of the setting from the copy to the
+ // original setting
+ std::map<std::string, std::shared_ptr<CSetting>>::iterator itSetting =
+ m_settingsMap.find(settingId);
+ if (itSetting == m_settingsMap.end())
+ return;
+
+ itSetting->second->FromString(setting->ToString());
+
+ // Get peripheral associated with this setting
+ PeripheralPtr peripheral;
+ if (m_item != nullptr)
+ peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath());
+
+ if (!peripheral)
+ return;
+
+ if (settingId == SETTING_APPEARANCE)
+ {
+ // Get the controller profile of the new appearance
+ GAME::ControllerPtr controller;
+
+ if (setting->GetType() == SettingType::String)
+ {
+ std::shared_ptr<const CSettingString> settingString =
+ std::static_pointer_cast<const CSettingString>(setting);
+ const std::string& addonId = settingString->GetValue();
+
+ if (m_manager != nullptr)
+ controller = m_manager->GetControllerProfiles().GetController(addonId);
+ }
+
+ if (controller)
+ peripheral->SetControllerProfile(controller);
+ }
+}
+
+bool CGUIDialogPeripheralSettings::Save()
+{
+ if (m_item == NULL || m_initialising)
+ return true;
+
+ PeripheralPtr peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath());
+ if (!peripheral)
+ return true;
+
+ peripheral->PersistSettings();
+
+ return true;
+}
+
+void CGUIDialogPeripheralSettings::OnResetSettings()
+{
+ if (m_item == NULL)
+ return;
+
+ PeripheralPtr peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath());
+ if (!peripheral)
+ return;
+
+ if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{10041}, CVariant{10042}))
+ return;
+
+ // reset the settings in the peripheral
+ peripheral->ResetDefaultSettings();
+
+ // re-create all settings and their controls
+ SetupView();
+}
+
+void CGUIDialogPeripheralSettings::SetupView()
+{
+ CGUIDialogSettingsManualBase::SetupView();
+
+ SetHeading(m_item->GetLabel());
+ SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 186);
+ SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222);
+ SET_CONTROL_LABEL(CONTROL_SETTINGS_CUSTOM_BUTTON, 409);
+}
+
+void CGUIDialogPeripheralSettings::InitializeSettings()
+{
+ if (m_item == NULL)
+ {
+ m_initialising = false;
+ return;
+ }
+
+ m_initialising = true;
+ bool usePopup = g_SkinInfo->HasSkinFile("DialogSlider.xml");
+
+ PeripheralPtr peripheral = CServiceBroker::GetPeripherals().GetByPath(m_item->GetPath());
+ if (!peripheral)
+ {
+ CLog::Log(LOGDEBUG, "{} - no peripheral", __FUNCTION__);
+ m_initialising = false;
+ return;
+ }
+
+ m_settingsMap.clear();
+ CGUIDialogSettingsManualBase::InitializeSettings();
+
+ const std::shared_ptr<CSettingCategory> category = AddCategory("peripheralsettings", -1);
+ if (category == NULL)
+ {
+ CLog::Log(LOGERROR, "CGUIDialogPeripheralSettings: unable to setup settings");
+ return;
+ }
+
+ const std::shared_ptr<CSettingGroup> group = AddGroup(category);
+ if (group == NULL)
+ {
+ CLog::Log(LOGERROR, "CGUIDialogPeripheralSettings: unable to setup settings");
+ return;
+ }
+
+ std::vector<SettingPtr> settings = peripheral->GetSettings();
+ for (auto& setting : settings)
+ {
+ if (setting == NULL)
+ continue;
+
+ if (!setting->IsVisible())
+ {
+ CLog::Log(LOGDEBUG, "{} - invisible", __FUNCTION__);
+ continue;
+ }
+
+ // we need to create a copy of the setting because the CSetting instances
+ // are destroyed when leaving the dialog
+ SettingPtr settingCopy;
+ switch (setting->GetType())
+ {
+ case SettingType::Boolean:
+ {
+ std::shared_ptr<CSettingBool> settingBool = std::make_shared<CSettingBool>(
+ setting->GetId(), *std::static_pointer_cast<CSettingBool>(setting));
+ settingBool->SetControl(GetCheckmarkControl());
+
+ settingCopy = std::static_pointer_cast<CSetting>(settingBool);
+ break;
+ }
+
+ case SettingType::Integer:
+ {
+ std::shared_ptr<CSettingInt> settingInt = std::make_shared<CSettingInt>(
+ setting->GetId(), *std::static_pointer_cast<CSettingInt>(setting));
+ if (settingInt->GetTranslatableOptions().empty())
+ settingInt->SetControl(GetSliderControl("integer", false, -1, usePopup, -1, "{:d}"));
+ else
+ settingInt->SetControl(GetSpinnerControl("string"));
+
+ settingCopy = std::static_pointer_cast<CSetting>(settingInt);
+ break;
+ }
+
+ case SettingType::Number:
+ {
+ std::shared_ptr<CSettingNumber> settingNumber = std::make_shared<CSettingNumber>(
+ setting->GetId(), *std::static_pointer_cast<CSettingNumber>(setting));
+ settingNumber->SetControl(GetSliderControl("number", false, -1, usePopup, -1, "{:2.2f}"));
+
+ settingCopy = std::static_pointer_cast<CSetting>(settingNumber);
+ break;
+ }
+
+ case SettingType::String:
+ {
+ if (auto settingAsAddon = std::dynamic_pointer_cast<const CSettingAddon>(setting))
+ {
+ std::shared_ptr<CSettingAddon> settingAddon =
+ std::make_shared<CSettingAddon>(setting->GetId(), *settingAsAddon);
+
+ // Control properties
+ const std::string format = "addon";
+ const bool delayed = false;
+ const int heading = -1;
+ const bool hideValue = false;
+ const bool showInstalledAddons = true;
+ const bool showInstallableAddons = true;
+ const bool showMoreAddons = false;
+
+ settingAddon->SetControl(GetButtonControl(format, delayed, heading, hideValue,
+ showInstalledAddons, showInstallableAddons,
+ showMoreAddons));
+
+ GAME::ControllerPtr controller = peripheral->ControllerProfile();
+ if (controller)
+ settingAddon->SetValue(controller->ID());
+
+ settingCopy = std::static_pointer_cast<CSetting>(settingAddon);
+ }
+ else
+ {
+ std::shared_ptr<CSettingString> settingString = std::make_shared<CSettingString>(
+ setting->GetId(), *std::static_pointer_cast<CSettingString>(setting));
+ settingString->SetControl(GetEditControl("string"));
+
+ settingCopy = std::static_pointer_cast<CSetting>(settingString);
+ }
+ break;
+ }
+
+ default:
+ //! @todo add more types if needed
+ CLog::Log(LOGDEBUG, "{} - unknown type", __FUNCTION__);
+ break;
+ }
+
+ if (settingCopy != NULL && settingCopy->GetControl() != NULL)
+ {
+ settingCopy->SetLevel(SettingLevel::Basic);
+ group->AddSetting(settingCopy);
+ m_settingsMap.insert(std::make_pair(setting->GetId(), setting));
+ }
+ }
+
+ m_initialising = false;
+}
diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.h b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.h
new file mode 100644
index 0000000..33ad2ee
--- /dev/null
+++ b/xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "settings/dialogs/GUIDialogSettingsManualBase.h"
+
+class CFileItem;
+
+namespace PERIPHERALS
+{
+class CPeripherals;
+
+class CGUIDialogPeripheralSettings : public CGUIDialogSettingsManualBase
+{
+public:
+ CGUIDialogPeripheralSettings();
+ ~CGUIDialogPeripheralSettings() override;
+
+ // specializations of CGUIControl
+ bool OnMessage(CGUIMessage& message) override;
+
+ void RegisterPeripheralManager(CPeripherals& manager);
+ void UnregisterPeripheralManager();
+
+ virtual void SetFileItem(const CFileItem* item);
+
+protected:
+ // implementations of ISettingCallback
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+
+ // specialization of CGUIDialogSettingsBase
+ bool AllowResettingSettings() const override { return false; }
+ bool Save() override;
+ void OnResetSettings() override;
+ void SetupView() override;
+
+ // specialization of CGUIDialogSettingsManualBase
+ void InitializeSettings() override;
+
+ // Dialog state
+ CPeripherals* m_manager{nullptr};
+ CFileItem* m_item;
+ bool m_initialising = false;
+ std::map<std::string, std::shared_ptr<CSetting>> m_settingsMap;
+};
+} // namespace PERIPHERALS
diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripherals.cpp b/xbmc/peripherals/dialogs/GUIDialogPeripherals.cpp
new file mode 100644
index 0000000..7594cb2
--- /dev/null
+++ b/xbmc/peripherals/dialogs/GUIDialogPeripherals.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIDialogPeripherals.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "peripherals/Peripherals.h"
+#include "peripherals/dialogs/GUIDialogPeripheralSettings.h"
+#include "utils/Variant.h"
+
+#include <mutex>
+
+using namespace KODI;
+using namespace PERIPHERALS;
+
+CGUIDialogPeripherals::CGUIDialogPeripherals()
+{
+ // Initialize CGUIControl via CGUIDialogSelect
+ SetID(WINDOW_DIALOG_PERIPHERALS);
+}
+
+CGUIDialogPeripherals::~CGUIDialogPeripherals() = default;
+
+void CGUIDialogPeripherals::OnInitWindow()
+{
+ UpdatePeripheralsSync();
+ CGUIDialogSelect::OnInitWindow();
+}
+
+void CGUIDialogPeripherals::RegisterPeripheralManager(CPeripherals& manager)
+{
+ m_manager = &manager;
+ m_manager->RegisterObserver(this);
+}
+
+void CGUIDialogPeripherals::UnregisterPeripheralManager()
+{
+ if (m_manager != nullptr)
+ {
+ m_manager->UnregisterObserver(this);
+ m_manager = nullptr;
+ }
+}
+
+CFileItemPtr CGUIDialogPeripherals::GetItem(unsigned int pos) const
+{
+ CFileItemPtr item;
+
+ std::unique_lock<CCriticalSection> lock(m_peripheralsMutex);
+
+ if (static_cast<int>(pos) < m_peripherals.Size())
+ item = m_peripherals[pos];
+
+ return item;
+}
+
+void CGUIDialogPeripherals::Show(CPeripherals& manager)
+{
+ CGUIDialogPeripherals* pDialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPeripherals>(
+ WINDOW_DIALOG_PERIPHERALS);
+ if (pDialog == nullptr)
+ return;
+
+ pDialog->Reset();
+
+ int iPos = -1;
+ do
+ {
+ pDialog->SetHeading(CVariant{35000});
+ pDialog->SetUseDetails(true);
+
+ pDialog->RegisterPeripheralManager(manager);
+
+ pDialog->Open();
+
+ pDialog->UnregisterPeripheralManager();
+
+ iPos = pDialog->IsConfirmed() ? pDialog->GetSelectedItem() : -1;
+
+ if (iPos >= 0)
+ {
+ CFileItemPtr pItem = pDialog->GetItem(iPos);
+
+ // Show an error if the peripheral doesn't have any settings
+ PeripheralPtr peripheral = manager.GetByPath(pItem->GetPath());
+ if (!peripheral || peripheral->GetSettings().empty())
+ {
+ MESSAGING::HELPERS::ShowOKDialogText(CVariant{35000}, CVariant{35004});
+ continue;
+ }
+
+ CGUIDialogPeripheralSettings* pSettingsDialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPeripheralSettings>(
+ WINDOW_DIALOG_PERIPHERAL_SETTINGS);
+ if (pItem && pSettingsDialog)
+ {
+ // Pass peripheral item properties to settings dialog so skin authors
+ // Can use it to show more detailed information about the device
+ pSettingsDialog->SetProperty("vendor", pItem->GetProperty("vendor"));
+ pSettingsDialog->SetProperty("product", pItem->GetProperty("product"));
+ pSettingsDialog->SetProperty("bus", pItem->GetProperty("bus"));
+ pSettingsDialog->SetProperty("location", pItem->GetProperty("location"));
+ pSettingsDialog->SetProperty("class", pItem->GetProperty("class"));
+ pSettingsDialog->SetProperty("version", pItem->GetProperty("version"));
+
+ // Open settings dialog
+ pSettingsDialog->SetFileItem(pItem.get());
+ pSettingsDialog->RegisterPeripheralManager(manager);
+ pSettingsDialog->Open();
+ pSettingsDialog->UnregisterPeripheralManager();
+ }
+ }
+ } while (pDialog->IsConfirmed());
+}
+
+bool CGUIDialogPeripherals::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_REFRESH_LIST:
+ {
+ if (m_manager && message.GetControlId() == -1)
+ UpdatePeripheralsSync();
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return CGUIDialogSelect::OnMessage(message);
+}
+
+void CGUIDialogPeripherals::Notify(const Observable& obs, const ObservableMessage msg)
+{
+ switch (msg)
+ {
+ case ObservableMessagePeripheralsChanged:
+ UpdatePeripheralsAsync();
+ break;
+ default:
+ break;
+ }
+}
+
+void CGUIDialogPeripherals::UpdatePeripheralsAsync()
+{
+ CGUIMessage msg(GUI_MSG_REFRESH_LIST, GetID(), -1);
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(msg);
+}
+
+void CGUIDialogPeripherals::UpdatePeripheralsSync()
+{
+ int iPos = GetSelectedItem();
+
+ std::unique_lock<CCriticalSection> lock(m_peripheralsMutex);
+
+ CFileItemPtr selectedItem;
+ if (iPos > 0)
+ selectedItem = GetItem(iPos);
+
+ m_peripherals.Clear();
+ m_manager->GetDirectory("peripherals://all/", m_peripherals);
+ SetItems(m_peripherals);
+
+ if (selectedItem)
+ {
+ for (int i = 0; i < m_peripherals.Size(); i++)
+ {
+ if (m_peripherals[i]->GetPath() == selectedItem->GetPath())
+ SetSelected(i);
+ }
+ }
+}
diff --git a/xbmc/peripherals/dialogs/GUIDialogPeripherals.h b/xbmc/peripherals/dialogs/GUIDialogPeripherals.h
new file mode 100644
index 0000000..d4a2f5d
--- /dev/null
+++ b/xbmc/peripherals/dialogs/GUIDialogPeripherals.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "FileItem.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "threads/CriticalSection.h"
+#include "utils/Observer.h"
+
+namespace PERIPHERALS
+{
+class CPeripherals;
+
+class CGUIDialogPeripherals : public CGUIDialogSelect, protected Observer
+{
+public:
+ CGUIDialogPeripherals();
+ ~CGUIDialogPeripherals() override;
+
+ void RegisterPeripheralManager(CPeripherals& manager);
+ void UnregisterPeripheralManager();
+
+ CFileItemPtr GetItem(unsigned int pos) const;
+
+ static void Show(CPeripherals& manager);
+
+ // implementation of CGUIControl via CGUIDialogSelect
+ bool OnMessage(CGUIMessage& message) override;
+
+ // implementation of Observer
+ void Notify(const Observable& obs, const ObservableMessage msg) override;
+
+private:
+ // implementation of CGUIWindow via CGUIDialogSelect
+ void OnInitWindow() override;
+
+ void ShowInternal();
+ void UpdatePeripheralsAsync();
+ void UpdatePeripheralsSync();
+
+ CPeripherals* m_manager = nullptr;
+ CFileItemList m_peripherals;
+ mutable CCriticalSection m_peripheralsMutex;
+};
+} // namespace PERIPHERALS