summaryrefslogtreecommitdiffstats
path: root/xbmc/platform/linux
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/platform/linux
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 '')
-rw-r--r--xbmc/platform/linux/AppParamParserLinux.cpp93
-rw-r--r--xbmc/platform/linux/AppParamParserLinux.h22
-rw-r--r--xbmc/platform/linux/CMakeLists.txt32
-rw-r--r--xbmc/platform/linux/CPUInfoLinux.cpp379
-rw-r--r--xbmc/platform/linux/CPUInfoLinux.h30
-rw-r--r--xbmc/platform/linux/DBusMessage.cpp198
-rw-r--r--xbmc/platform/linux/DBusMessage.h149
-rw-r--r--xbmc/platform/linux/DBusUtil.cpp296
-rw-r--r--xbmc/platform/linux/DBusUtil.h88
-rw-r--r--xbmc/platform/linux/FDEventMonitor.cpp243
-rw-r--r--xbmc/platform/linux/FDEventMonitor.h74
-rw-r--r--xbmc/platform/linux/MemUtils.cpp79
-rw-r--r--xbmc/platform/linux/OptionalsReg.cpp114
-rw-r--r--xbmc/platform/linux/OptionalsReg.h59
-rw-r--r--xbmc/platform/linux/PlatformLinux.cpp163
-rw-r--r--xbmc/platform/linux/PlatformLinux.h30
-rw-r--r--xbmc/platform/linux/SysfsPath.cpp47
-rw-r--r--xbmc/platform/linux/SysfsPath.h58
-rw-r--r--xbmc/platform/linux/TimeUtils.cpp14
-rw-r--r--xbmc/platform/linux/TimeUtils.h28
-rw-r--r--xbmc/platform/linux/input/CMakeLists.txt27
-rw-r--r--xbmc/platform/linux/input/LIRC.cpp209
-rw-r--r--xbmc/platform/linux/input/LIRC.h35
-rw-r--r--xbmc/platform/linux/input/LibInputHandler.cpp301
-rw-r--r--xbmc/platform/linux/input/LibInputHandler.h56
-rw-r--r--xbmc/platform/linux/input/LibInputKeyboard.cpp387
-rw-r--r--xbmc/platform/linux/input/LibInputKeyboard.h48
-rw-r--r--xbmc/platform/linux/input/LibInputPointer.cpp153
-rw-r--r--xbmc/platform/linux/input/LibInputPointer.h32
-rw-r--r--xbmc/platform/linux/input/LibInputSettings.cpp169
-rw-r--r--xbmc/platform/linux/input/LibInputSettings.h35
-rw-r--r--xbmc/platform/linux/input/LibInputTouch.cpp109
-rw-r--r--xbmc/platform/linux/input/LibInputTouch.h40
-rw-r--r--xbmc/platform/linux/network/CMakeLists.txt4
-rw-r--r--xbmc/platform/linux/network/NetworkLinux.cpp223
-rw-r--r--xbmc/platform/linux/network/NetworkLinux.h40
-rw-r--r--xbmc/platform/linux/network/zeroconf/CMakeLists.txt8
-rw-r--r--xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.cpp434
-rw-r--r--xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.h79
-rw-r--r--xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.cpp383
-rw-r--r--xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.h94
-rw-r--r--xbmc/platform/linux/peripherals/CMakeLists.txt11
-rw-r--r--xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.cpp78
-rw-r--r--xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.h34
-rw-r--r--xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp254
-rw-r--r--xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.h43
-rw-r--r--xbmc/platform/linux/powermanagement/CMakeLists.txt17
-rw-r--r--xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.cpp50
-rw-r--r--xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.h23
-rw-r--r--xbmc/platform/linux/powermanagement/FallbackPowerSyscall.h26
-rw-r--r--xbmc/platform/linux/powermanagement/LinuxPowerSyscall.cpp65
-rw-r--r--xbmc/platform/linux/powermanagement/LinuxPowerSyscall.h18
-rw-r--r--xbmc/platform/linux/powermanagement/LogindUPowerSyscall.cpp342
-rw-r--r--xbmc/platform/linux/powermanagement/LogindUPowerSyscall.h52
-rw-r--r--xbmc/platform/linux/powermanagement/UPowerSyscall.cpp207
-rw-r--r--xbmc/platform/linux/powermanagement/UPowerSyscall.h61
-rw-r--r--xbmc/platform/linux/sse4/CMakeLists.txt8
-rw-r--r--xbmc/platform/linux/sse4/CopyFrame.cpp102
-rw-r--r--xbmc/platform/linux/sse4/DllLibSSE4.h32
-rw-r--r--xbmc/platform/linux/storage/CMakeLists.txt19
-rw-r--r--xbmc/platform/linux/storage/LinuxStorageProvider.cpp91
-rw-r--r--xbmc/platform/linux/storage/LinuxStorageProvider.h31
-rw-r--r--xbmc/platform/linux/storage/UDevProvider.cpp298
-rw-r--r--xbmc/platform/linux/storage/UDevProvider.h42
-rw-r--r--xbmc/platform/linux/storage/UDisks2Provider.cpp861
-rw-r--r--xbmc/platform/linux/storage/UDisks2Provider.h210
-rw-r--r--xbmc/platform/linux/storage/UDisksProvider.cpp412
-rw-r--r--xbmc/platform/linux/storage/UDisksProvider.h131
-rw-r--r--xbmc/platform/linux/test/CMakeLists.txt3
-rw-r--r--xbmc/platform/linux/test/TestSysfsPath.cpp91
-rw-r--r--xbmc/platform/linux/threads/CMakeLists.txt4
-rw-r--r--xbmc/platform/linux/threads/ThreadImplLinux.cpp171
-rw-r--r--xbmc/platform/linux/threads/ThreadImplLinux.h26
73 files changed, 8875 insertions, 0 deletions
diff --git a/xbmc/platform/linux/AppParamParserLinux.cpp b/xbmc/platform/linux/AppParamParserLinux.cpp
new file mode 100644
index 0000000..3a1adf9
--- /dev/null
+++ b/xbmc/platform/linux/AppParamParserLinux.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2005-2021 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 "AppParamParserLinux.h"
+
+#include "CompileInfo.h"
+#include "application/AppParams.h"
+#include "utils/StringUtils.h"
+
+#include <algorithm>
+#include <array>
+#include <iostream>
+#include <vector>
+
+namespace
+{
+std::vector<std::string> availableWindowSystems = CCompileInfo::GetAvailableWindowSystems();
+std::array<std::string, 1> availableLogTargets = {"console"};
+
+constexpr const char* windowingText =
+ R"""(
+Selected window system not available: {}
+ Available window systems: {}
+)""";
+
+constexpr const char* loggingText =
+ R"""(
+Selected logging target not available: {}
+ Available log targest: {}
+)""";
+
+constexpr const char* helpText =
+ R"""(
+Linux Specific Arguments:
+ --windowing=<system> Select which windowing method to use.
+ Available window systems are: {}
+ --logging=<target> Select which log target to use (log file will always be used in conjunction).
+ Available log targets are: {}
+)""";
+
+
+} // namespace
+
+CAppParamParserLinux::CAppParamParserLinux() : CAppParamParser()
+{
+}
+
+CAppParamParserLinux::~CAppParamParserLinux() = default;
+
+void CAppParamParserLinux::ParseArg(const std::string& arg)
+{
+ CAppParamParser::ParseArg(arg);
+
+ if (arg.substr(0, 12) == "--windowing=")
+ {
+ if (std::find(availableWindowSystems.begin(), availableWindowSystems.end(), arg.substr(12)) !=
+ availableWindowSystems.end())
+ GetAppParams()->SetWindowing(arg.substr(12));
+ else
+ {
+ std::cout << StringUtils::Format(windowingText, arg.substr(12),
+ StringUtils::Join(availableWindowSystems, ", "));
+ exit(0);
+ }
+ }
+ else if (arg.substr(0, 10) == "--logging=")
+ {
+ if (std::find(availableLogTargets.begin(), availableLogTargets.end(), arg.substr(10)) !=
+ availableLogTargets.end())
+ {
+ GetAppParams()->SetLogTarget(arg.substr(10));
+ }
+ else
+ {
+ std::cout << StringUtils::Format(loggingText, arg.substr(10),
+ StringUtils::Join(availableLogTargets, ", "));
+ exit(0);
+ }
+ }
+}
+
+void CAppParamParserLinux::DisplayHelp()
+{
+ CAppParamParser::DisplayHelp();
+
+ std::cout << StringUtils::Format(helpText, StringUtils::Join(availableWindowSystems, ", "),
+ StringUtils::Join(availableLogTargets, ", "));
+}
diff --git a/xbmc/platform/linux/AppParamParserLinux.h b/xbmc/platform/linux/AppParamParserLinux.h
new file mode 100644
index 0000000..ea866ca
--- /dev/null
+++ b/xbmc/platform/linux/AppParamParserLinux.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2005-2021 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 "application/AppParamParser.h"
+
+class CAppParamParserLinux : public CAppParamParser
+{
+public:
+ CAppParamParserLinux();
+ ~CAppParamParserLinux();
+
+protected:
+ void ParseArg(const std::string& arg) override;
+ void DisplayHelp() override;
+};
diff --git a/xbmc/platform/linux/CMakeLists.txt b/xbmc/platform/linux/CMakeLists.txt
new file mode 100644
index 0000000..31f4cef
--- /dev/null
+++ b/xbmc/platform/linux/CMakeLists.txt
@@ -0,0 +1,32 @@
+set(SOURCES AppParamParserLinux.cpp
+ CPUInfoLinux.cpp
+ MemUtils.cpp
+ OptionalsReg.cpp
+ PlatformLinux.cpp
+ SysfsPath.cpp
+ TimeUtils.cpp)
+
+set(HEADERS AppParamParserLinux.h
+ CPUInfoLinux.h
+ OptionalsReg.h
+ PlatformLinux.h
+ SysfsPath.h
+ TimeUtils.h)
+
+if(ALSA_FOUND)
+ list(APPEND SOURCES FDEventMonitor.cpp)
+ list(APPEND HEADERS FDEventMonitor.h)
+endif()
+
+if(DBUS_FOUND)
+ list(APPEND SOURCES DBusMessage.cpp
+ DBusUtil.cpp)
+ list(APPEND HEADERS DBusMessage.h
+ DBusUtil.h)
+endif()
+
+if(ADDONS_CONFIGURE_AT_STARTUP)
+ add_compile_definitions(ADDONS_CONFIGURE_AT_STARTUP)
+endif()
+
+core_add_library(linuxsupport)
diff --git a/xbmc/platform/linux/CPUInfoLinux.cpp b/xbmc/platform/linux/CPUInfoLinux.cpp
new file mode 100644
index 0000000..ef34bbd
--- /dev/null
+++ b/xbmc/platform/linux/CPUInfoLinux.cpp
@@ -0,0 +1,379 @@
+/*
+ * 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 "CPUInfoLinux.h"
+
+#include "utils/StringUtils.h"
+#include "utils/Temperature.h"
+
+#include "platform/linux/SysfsPath.h"
+
+#include <exception>
+#include <fstream>
+#include <regex>
+#include <sstream>
+#include <vector>
+
+#if (defined(__arm__) && defined(HAS_NEON)) || defined(__aarch64__)
+#include <asm/hwcap.h>
+#include <sys/auxv.h>
+#elif defined(__i386__) || defined(__x86_64__)
+#include <cpuid.h>
+#endif
+
+#include <unistd.h>
+
+namespace
+{
+enum CpuStates
+{
+ STATE_USER,
+ STATE_NICE,
+ STATE_SYSTEM,
+ STATE_IDLE,
+ STATE_IOWAIT,
+ STATE_IRQ,
+ STATE_SOFTIRQ,
+ STATE_STEAL,
+ STATE_GUEST,
+ STATE_GUEST_NICE,
+ STATE_MAX
+};
+
+struct CpuData
+{
+public:
+ std::size_t GetActiveTime() const
+ {
+ return state[STATE_USER] + state[STATE_NICE] + state[STATE_SYSTEM] + state[STATE_IRQ] +
+ state[STATE_SOFTIRQ] + state[STATE_STEAL] + state[STATE_GUEST] + state[STATE_GUEST_NICE];
+ }
+
+ std::size_t GetIdleTime() const { return state[STATE_IDLE] + state[STATE_IOWAIT]; }
+
+ std::size_t GetTotalTime() const { return GetActiveTime() + GetIdleTime(); }
+
+ std::string cpu;
+ std::size_t state[STATE_MAX];
+};
+} // namespace
+
+std::shared_ptr<CCPUInfo> CCPUInfo::GetCPUInfo()
+{
+ return std::make_shared<CCPUInfoLinux>();
+}
+
+CCPUInfoLinux::CCPUInfoLinux()
+{
+ CSysfsPath machinePath{"/sys/bus/soc/devices/soc0/machine"};
+ if (machinePath.Exists())
+ m_cpuHardware = machinePath.Get<std::string>().value_or("");
+
+ CSysfsPath familyPath{"/sys/bus/soc/devices/soc0/family"};
+ if (familyPath.Exists())
+ m_cpuSoC = familyPath.Get<std::string>().value_or("");
+
+ CSysfsPath socPath{"/sys/bus/soc/devices/soc0/soc_id"};
+ if (socPath.Exists())
+ m_cpuSoC += " " + socPath.Get<std::string>().value_or("");
+
+ CSysfsPath revisionPath{"/sys/bus/soc/devices/soc0/revision"};
+ if (revisionPath.Exists())
+ m_cpuRevision = revisionPath.Get<std::string>().value_or("");
+
+ CSysfsPath serialPath{"/sys/bus/soc/devices/soc0/serial_number"};
+ if (serialPath.Exists())
+ m_cpuSerial = serialPath.Get<std::string>().value_or("");
+
+ const std::string freqStr{"/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"};
+ CSysfsPath freqPath{freqStr};
+ if (freqPath.Exists())
+ m_freqPath = freqStr;
+
+ const std::array<std::string, 4> modules = {
+ "coretemp",
+ "k10temp",
+ "scpi_sensors",
+ "imx_thermal_zone",
+ };
+
+ for (int i = 0; i < 20; i++)
+ {
+ CSysfsPath path{"/sys/class/hwmon/hwmon" + std::to_string(i) + "/name"};
+ if (!path.Exists())
+ continue;
+
+ auto name = path.Get<std::string>();
+
+ if (!name.has_value())
+ continue;
+
+ for (const auto& module : modules)
+ {
+ if (module == name)
+ {
+ std::string tempStr{"/sys/class/hwmon/hwmon" + std::to_string(i) + "/temp1_input"};
+ CSysfsPath tempPath{tempStr};
+ if (!tempPath.Exists())
+ continue;
+
+ m_tempPath = tempStr;
+ break;
+ }
+ }
+
+ if (!m_tempPath.empty())
+ break;
+ }
+
+ m_cpuCount = sysconf(_SC_NPROCESSORS_ONLN);
+
+ for (int core = 0; core < m_cpuCount; core++)
+ {
+ CoreInfo coreInfo;
+ coreInfo.m_id = core;
+ m_cores.emplace_back(coreInfo);
+ }
+
+#if defined(__i386__) || defined(__x86_64__)
+ unsigned int eax;
+ unsigned int ebx;
+ unsigned int ecx;
+ unsigned int edx;
+
+ m_cpuVendor.clear();
+
+ if (__get_cpuid(CPUID_INFOTYPE_MANUFACTURER, &eax, &ebx, &ecx, &edx))
+ {
+ m_cpuVendor.append(reinterpret_cast<const char*>(&ebx), 4);
+ m_cpuVendor.append(reinterpret_cast<const char*>(&edx), 4);
+ m_cpuVendor.append(reinterpret_cast<const char*>(&ecx), 4);
+ }
+
+ if (__get_cpuid(CPUID_INFOTYPE_EXTENDED_IMPLEMENTED, &eax, &ebx, &ecx, &edx))
+ {
+ if (eax >= CPUID_INFOTYPE_PROCESSOR_3)
+ {
+ m_cpuModel.clear();
+
+ if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_1, &eax, &ebx, &ecx, &edx))
+ {
+ m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4);
+ }
+
+ if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_2, &eax, &ebx, &ecx, &edx))
+ {
+ m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4);
+ }
+
+ if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_3, &eax, &ebx, &ecx, &edx))
+ {
+ m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4);
+ m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4);
+ }
+ }
+ }
+
+ if (__get_cpuid(CPUID_INFOTYPE_STANDARD, &eax, &eax, &ecx, &edx))
+ {
+ if (edx & CPUID_00000001_EDX_MMX)
+ m_cpuFeatures |= CPU_FEATURE_MMX;
+
+ if (edx & CPUID_00000001_EDX_SSE)
+ m_cpuFeatures |= CPU_FEATURE_SSE;
+
+ if (edx & CPUID_00000001_EDX_SSE2)
+ m_cpuFeatures |= CPU_FEATURE_SSE2;
+
+ if (ecx & CPUID_00000001_ECX_SSE3)
+ m_cpuFeatures |= CPU_FEATURE_SSE3;
+
+ if (ecx & CPUID_00000001_ECX_SSSE3)
+ m_cpuFeatures |= CPU_FEATURE_SSSE3;
+
+ if (ecx & CPUID_00000001_ECX_SSE4)
+ m_cpuFeatures |= CPU_FEATURE_SSE4;
+
+ if (ecx & CPUID_00000001_ECX_SSE42)
+ m_cpuFeatures |= CPU_FEATURE_SSE42;
+ }
+
+ if (__get_cpuid(CPUID_INFOTYPE_EXTENDED_IMPLEMENTED, &eax, &eax, &ecx, &edx))
+ {
+ if (eax >= CPUID_INFOTYPE_EXTENDED)
+ {
+ if (edx & CPUID_80000001_EDX_MMX)
+ m_cpuFeatures |= CPU_FEATURE_MMX;
+
+ if (edx & CPUID_80000001_EDX_MMX2)
+ m_cpuFeatures |= CPU_FEATURE_MMX2;
+
+ if (edx & CPUID_80000001_EDX_3DNOW)
+ m_cpuFeatures |= CPU_FEATURE_3DNOW;
+
+ if (edx & CPUID_80000001_EDX_3DNOWEXT)
+ m_cpuFeatures |= CPU_FEATURE_3DNOWEXT;
+ }
+ }
+#else
+ std::ifstream cpuinfo("/proc/cpuinfo");
+ std::regex re(".*: (.*)$");
+
+ for (std::string line; std::getline(cpuinfo, line);)
+ {
+ std::smatch match;
+
+ if (std::regex_match(line, match, re))
+ {
+ if (match.size() == 2)
+ {
+ std::ssub_match value = match[1];
+
+ if (line.find("model name") != std::string::npos)
+ {
+ if (m_cpuModel.empty())
+ m_cpuModel = value.str();
+ }
+
+ if (line.find("BogoMIPS") != std::string::npos)
+ {
+ if (m_cpuBogoMips.empty())
+ m_cpuBogoMips = value.str();
+ }
+
+ if (line.find("Hardware") != std::string::npos)
+ {
+ if (m_cpuHardware.empty())
+ m_cpuHardware = value.str();
+ }
+
+ if (line.find("Serial") != std::string::npos)
+ {
+ if (m_cpuSerial.empty())
+ m_cpuSerial = value.str();
+ }
+
+ if (line.find("Revision") != std::string::npos)
+ {
+ if (m_cpuRevision.empty())
+ m_cpuRevision = value.str();
+ }
+ }
+ }
+ }
+#endif
+
+ m_cpuModel = m_cpuModel.substr(0, m_cpuModel.find(char(0))); // remove extra null terminations
+
+#if defined(HAS_NEON) && defined(__arm__)
+ if (getauxval(AT_HWCAP) & HWCAP_NEON)
+ m_cpuFeatures |= CPU_FEATURE_NEON;
+#endif
+
+#if defined(HAS_NEON) && defined(__aarch64__)
+ if (getauxval(AT_HWCAP) & HWCAP_ASIMD)
+ m_cpuFeatures |= CPU_FEATURE_NEON;
+#endif
+
+ // Set MMX2 when SSE is present as SSE is a superset of MMX2 and Intel doesn't set the MMX2 cap
+ if (m_cpuFeatures & CPU_FEATURE_SSE)
+ m_cpuFeatures |= CPU_FEATURE_MMX2;
+}
+
+int CCPUInfoLinux::GetUsedPercentage()
+{
+ if (!m_nextUsedReadTime.IsTimePast())
+ return m_lastUsedPercentage;
+
+ std::vector<CpuData> cpuData;
+
+ std::ifstream infile("/proc/stat");
+
+ for (std::string line; std::getline(infile, line);)
+ {
+ if (line.find("cpu") != std::string::npos)
+ {
+ std::istringstream ss(line);
+ CpuData info;
+
+ ss >> info.cpu;
+
+ for (int i = 0; i < STATE_MAX; i++)
+ {
+ ss >> info.state[i];
+ }
+
+ cpuData.emplace_back(info);
+ }
+ }
+
+ auto activeTime = cpuData.front().GetActiveTime() - m_activeTime;
+ auto idleTime = cpuData.front().GetIdleTime() - m_idleTime;
+ auto totalTime = cpuData.front().GetTotalTime() - m_totalTime;
+
+ m_activeTime += activeTime;
+ m_idleTime += idleTime;
+ m_totalTime += totalTime;
+
+ m_lastUsedPercentage = activeTime * 100.0f / totalTime;
+ m_nextUsedReadTime.Set(MINIMUM_TIME_BETWEEN_READS);
+
+ cpuData.erase(cpuData.begin());
+
+ for (std::size_t core = 0; core < cpuData.size(); core++)
+ {
+ auto activeTime = cpuData[core].GetActiveTime() - m_cores[core].m_activeTime;
+ auto idleTime = cpuData[core].GetIdleTime() - m_cores[core].m_idleTime;
+ auto totalTime = cpuData[core].GetTotalTime() - m_cores[core].m_totalTime;
+
+ m_cores[core].m_usagePercent = activeTime * 100.0 / totalTime;
+
+ m_cores[core].m_activeTime += activeTime;
+ m_cores[core].m_idleTime += idleTime;
+ m_cores[core].m_totalTime += totalTime;
+ }
+
+ return static_cast<int>(m_lastUsedPercentage);
+}
+
+float CCPUInfoLinux::GetCPUFrequency()
+{
+ if (m_freqPath.empty())
+ return 0;
+
+ auto freq = CSysfsPath(m_freqPath).Get<float>();
+ return freq.has_value() ? *freq / 1000.0f : 0.0f;
+}
+
+bool CCPUInfoLinux::GetTemperature(CTemperature& temperature)
+{
+ if (CheckUserTemperatureCommand(temperature))
+ return true;
+
+ if (m_tempPath.empty())
+ return false;
+
+ auto temp = CSysfsPath(m_tempPath).Get<double>();
+ if (!temp.has_value())
+ return false;
+
+ double value = *temp / 1000.0;
+
+ temperature = CTemperature::CreateFromCelsius(value);
+ temperature.SetValid(true);
+
+ return true;
+}
diff --git a/xbmc/platform/linux/CPUInfoLinux.h b/xbmc/platform/linux/CPUInfoLinux.h
new file mode 100644
index 0000000..ed79afc
--- /dev/null
+++ b/xbmc/platform/linux/CPUInfoLinux.h
@@ -0,0 +1,30 @@
+/*
+ * 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 "utils/Temperature.h"
+
+#include "platform/posix/CPUInfoPosix.h"
+
+#include <string>
+
+class CCPUInfoLinux : public CCPUInfoPosix
+{
+public:
+ CCPUInfoLinux();
+ ~CCPUInfoLinux() = default;
+
+ int GetUsedPercentage() override;
+ float GetCPUFrequency() override;
+ bool GetTemperature(CTemperature& temperature) override;
+
+private:
+ std::string m_freqPath;
+ std::string m_tempPath;
+};
diff --git a/xbmc/platform/linux/DBusMessage.cpp b/xbmc/platform/linux/DBusMessage.cpp
new file mode 100644
index 0000000..ab09c3e
--- /dev/null
+++ b/xbmc/platform/linux/DBusMessage.cpp
@@ -0,0 +1,198 @@
+/*
+ * 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 "DBusMessage.h"
+
+#include "DBusUtil.h"
+#include "settings/AdvancedSettings.h"
+#include "utils/log.h"
+
+CDBusMessage::CDBusMessage(const char *destination, const char *object, const char *interface, const char *method)
+{
+ m_message.reset(dbus_message_new_method_call(destination, object, interface, method));
+ if (!m_message)
+ {
+ // Fails only due to memory allocation failure
+ throw std::runtime_error("dbus_message_new_method_call");
+ }
+ m_haveArgs = false;
+
+ CLog::Log(LOGDEBUG, LOGDBUS, "DBus: Creating message to {} on {} with interface {} and method {}",
+ destination, object, interface, method);
+}
+
+CDBusMessage::CDBusMessage(const std::string& destination, const std::string& object, const std::string& interface, const std::string& method)
+: CDBusMessage(destination.c_str(), object.c_str(), interface.c_str(), method.c_str())
+{
+}
+
+void DBusMessageDeleter::operator()(DBusMessage* message) const
+{
+ dbus_message_unref(message);
+}
+
+void CDBusMessage::AppendObjectPath(const char *object)
+{
+ AppendWithType(DBUS_TYPE_OBJECT_PATH, &object);
+}
+
+template<>
+void CDBusMessage::AppendArgument<bool>(const bool arg)
+{
+ // dbus_bool_t width might not match C++ bool width
+ dbus_bool_t convArg = (arg == true);
+ AppendWithType(DBUS_TYPE_BOOLEAN, &convArg);
+}
+
+template<>
+void CDBusMessage::AppendArgument<std::string>(const std::string arg)
+{
+ AppendArgument(arg.c_str());
+}
+
+void CDBusMessage::AppendArgument(const char **arrayString, unsigned int length)
+{
+ PrepareArgument();
+ DBusMessageIter sub;
+ if (!dbus_message_iter_open_container(&m_args, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &sub))
+ {
+ throw std::runtime_error("dbus_message_iter_open_container");
+ }
+
+ for (unsigned int i = 0; i < length; i++)
+ {
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &arrayString[i]))
+ {
+ throw std::runtime_error("dbus_message_iter_append_basic");
+ }
+ }
+
+ if (!dbus_message_iter_close_container(&m_args, &sub))
+ {
+ throw std::runtime_error("dbus_message_iter_close_container");
+ }
+}
+
+void CDBusMessage::AppendWithType(int type, const void* value)
+{
+ PrepareArgument();
+ if (!dbus_message_iter_append_basic(&m_args, type, value))
+ {
+ throw std::runtime_error("dbus_message_iter_append_basic");
+ }
+}
+
+DBusMessageIter * CDBusMessage::GetArgumentIter() {
+ PrepareArgument();
+ return &m_args;
+}
+
+DBusMessage *CDBusMessage::SendSystem()
+{
+ return Send(DBUS_BUS_SYSTEM);
+}
+
+DBusMessage *CDBusMessage::SendSession()
+{
+ return Send(DBUS_BUS_SESSION);
+}
+
+DBusMessage *CDBusMessage::SendSystem(CDBusError& error)
+{
+ return Send(DBUS_BUS_SYSTEM, error);
+}
+
+DBusMessage *CDBusMessage::SendSession(CDBusError& error)
+{
+ return Send(DBUS_BUS_SESSION, error);
+}
+
+bool CDBusMessage::SendAsyncSystem()
+{
+ return SendAsync(DBUS_BUS_SYSTEM);
+}
+
+bool CDBusMessage::SendAsyncSession()
+{
+ return SendAsync(DBUS_BUS_SESSION);
+}
+
+DBusMessage *CDBusMessage::Send(DBusBusType type)
+{
+ CDBusError error;
+ CDBusConnection con;
+ if (!con.Connect(type))
+ return nullptr;
+
+ DBusMessage *returnMessage = Send(con, error);
+
+ if (error)
+ error.Log();
+
+ return returnMessage;
+}
+
+DBusMessage *CDBusMessage::Send(DBusBusType type, CDBusError& error)
+{
+ CDBusConnection con;
+ if (!con.Connect(type, error))
+ return nullptr;
+
+ DBusMessage *returnMessage = Send(con, error);
+
+ return returnMessage;
+}
+
+bool CDBusMessage::SendAsync(DBusBusType type)
+{
+ CDBusConnection con;
+ if (!con.Connect(type))
+ return false;
+
+ return dbus_connection_send(con, m_message.get(), nullptr);
+}
+
+DBusMessage *CDBusMessage::Send(DBusConnection *con, CDBusError& error)
+{
+ m_reply.reset(dbus_connection_send_with_reply_and_block(con, m_message.get(), -1, error));
+ return m_reply.get();
+}
+
+void CDBusMessage::PrepareArgument()
+{
+ if (!m_haveArgs)
+ dbus_message_iter_init_append(m_message.get(), &m_args);
+
+ m_haveArgs = true;
+}
+
+bool CDBusMessage::InitializeReplyIter(DBusMessageIter* iter)
+{
+ if (!m_reply)
+ {
+ throw std::logic_error("Cannot get reply arguments of message that does not have reply");
+ }
+ if (!dbus_message_iter_init(m_reply.get(), iter))
+ {
+ CLog::Log(LOGWARNING, "Tried to obtain reply arguments from message that has zero arguments");
+ return false;
+ }
+ return true;
+}
+
+bool CDBusMessage::CheckTypeAndGetValue(DBusMessageIter* iter, int expectType, void* dest)
+{
+ const int haveType = dbus_message_iter_get_arg_type(iter);
+ if (haveType != expectType)
+ {
+ CLog::Log(LOGDEBUG, "DBus argument type mismatch: expected {}, got {}", expectType, haveType);
+ return false;
+ }
+
+ dbus_message_iter_get_basic(iter, dest);
+ return true;
+}
diff --git a/xbmc/platform/linux/DBusMessage.h b/xbmc/platform/linux/DBusMessage.h
new file mode 100644
index 0000000..80c7cd6
--- /dev/null
+++ b/xbmc/platform/linux/DBusMessage.h
@@ -0,0 +1,149 @@
+/*
+ * 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 <cstdint>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+#include <dbus/dbus.h>
+
+class CDBusError;
+
+template<typename T>
+struct ToDBusType;
+template<> struct ToDBusType<bool> { static constexpr int TYPE = DBUS_TYPE_BOOLEAN; };
+template<> struct ToDBusType<char*> { static constexpr int TYPE = DBUS_TYPE_STRING; };
+template<> struct ToDBusType<const char*> { static constexpr int TYPE = DBUS_TYPE_STRING; };
+template<> struct ToDBusType<std::uint8_t> { static constexpr int TYPE = DBUS_TYPE_BYTE; };
+template<> struct ToDBusType<std::int16_t> { static constexpr int TYPE = DBUS_TYPE_INT16; };
+template<> struct ToDBusType<std::uint16_t> { static constexpr int TYPE = DBUS_TYPE_UINT16; };
+template<> struct ToDBusType<std::int32_t> { static constexpr int TYPE = DBUS_TYPE_INT32; };
+template<> struct ToDBusType<std::uint32_t> { static constexpr int TYPE = DBUS_TYPE_UINT32; };
+template<> struct ToDBusType<std::int64_t> { static constexpr int TYPE = DBUS_TYPE_INT64; };
+template<> struct ToDBusType<std::uint64_t> { static constexpr int TYPE = DBUS_TYPE_UINT64; };
+template<> struct ToDBusType<double> { static constexpr int TYPE = DBUS_TYPE_DOUBLE; };
+
+template<typename T>
+using ToDBusTypeFromPointer = ToDBusType<typename std::remove_pointer<T>::type>;
+
+struct DBusMessageDeleter
+{
+ void operator()(DBusMessage* message) const;
+};
+using DBusMessagePtr = std::unique_ptr<DBusMessage, DBusMessageDeleter>;
+
+class CDBusMessage
+{
+public:
+ CDBusMessage(const char *destination, const char *object, const char *interface, const char *method);
+ CDBusMessage(std::string const& destination, std::string const& object, std::string const& interface, std::string const& method);
+
+ void AppendObjectPath(const char *object);
+
+ template<typename T>
+ void AppendArgument(const T arg)
+ {
+ AppendWithType(ToDBusType<T>::TYPE, &arg);
+ }
+ void AppendArgument(const char **arrayString, unsigned int length);
+
+ template<typename TFirst>
+ void AppendArguments(const TFirst first)
+ {
+ AppendArgument(first);
+ // Recursion end
+ }
+ template<typename TFirst, typename... TArgs>
+ void AppendArguments(const TFirst first, const TArgs... args)
+ {
+ AppendArgument(first);
+ AppendArguments(args...);
+ }
+
+ DBusMessageIter * GetArgumentIter();
+
+ /**
+ * Retrieve simple arguments from DBus reply message
+ *
+ * You MUST use the correct fixed-width integer typedefs (e.g. std::uint16_t)
+ * corresponding to the DBus types for the variables or you will get potentially
+ * differing behavior between architectures since the DBus argument type detection
+ * is based on the width of the type.
+ *
+ * Complex arguments (arrays, structs) are not supported.
+ *
+ * Returned pointers for strings are only valid until the instance of this class
+ * is deleted.
+ *
+ * \throw std::logic_error if the message has no reply
+ * \return whether all arguments could be retrieved (false on argument type
+ * mismatch or when more arguments were to be retrieved than there are
+ * in the message)
+ */
+ template<typename... TArgs>
+ bool GetReplyArguments(TArgs*... args)
+ {
+ DBusMessageIter iter;
+ if (!InitializeReplyIter(&iter))
+ {
+ return false;
+ }
+ return GetReplyArgumentsWithIter(&iter, args...);
+ }
+
+ DBusMessage *SendSystem();
+ DBusMessage *SendSession();
+ DBusMessage *SendSystem(CDBusError& error);
+ DBusMessage *SendSession(CDBusError& error);
+
+ bool SendAsyncSystem();
+ bool SendAsyncSession();
+
+ DBusMessage *Send(DBusBusType type);
+ DBusMessage *Send(DBusBusType type, CDBusError& error);
+ DBusMessage *Send(DBusConnection *con, CDBusError& error);
+private:
+ void AppendWithType(int type, const void* value);
+ bool SendAsync(DBusBusType type);
+
+ void PrepareArgument();
+
+ bool InitializeReplyIter(DBusMessageIter* iter);
+ bool CheckTypeAndGetValue(DBusMessageIter* iter, int expectType, void* dest);
+ template<typename TFirst>
+ bool GetReplyArgumentsWithIter(DBusMessageIter* iter, TFirst* first)
+ {
+ // Recursion end
+ return CheckTypeAndGetValue(iter, ToDBusTypeFromPointer<TFirst>::TYPE, first);
+ }
+ template<typename TFirst, typename... TArgs>
+ bool GetReplyArgumentsWithIter(DBusMessageIter* iter, TFirst* first, TArgs*... args)
+ {
+ if (!CheckTypeAndGetValue(iter, ToDBusTypeFromPointer<TFirst>::TYPE, first))
+ {
+ return false;
+ }
+ // Ignore return value, if we try to read past the end of the message this
+ // will be catched by the type check (past-end type is DBUS_TYPE_INVALID)
+ dbus_message_iter_next(iter);
+ return GetReplyArgumentsWithIter(iter, args...);
+ }
+
+ DBusMessagePtr m_message;
+ DBusMessagePtr m_reply = nullptr;
+ DBusMessageIter m_args;
+ bool m_haveArgs;
+};
+
+template<>
+void CDBusMessage::AppendArgument<bool>(const bool arg);
+template<>
+void CDBusMessage::AppendArgument<std::string>(const std::string arg);
diff --git a/xbmc/platform/linux/DBusUtil.cpp b/xbmc/platform/linux/DBusUtil.cpp
new file mode 100644
index 0000000..2efb75c
--- /dev/null
+++ b/xbmc/platform/linux/DBusUtil.cpp
@@ -0,0 +1,296 @@
+/*
+ * 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 "DBusUtil.h"
+
+#include "utils/log.h"
+
+CVariant CDBusUtil::GetVariant(const char *destination, const char *object, const char *interface, const char *property)
+{
+//dbus-send --system --print-reply --dest=destination object org.freedesktop.DBus.Properties.Get string:interface string:property
+ CDBusMessage message(destination, object, "org.freedesktop.DBus.Properties", "Get");
+ CVariant result;
+
+ message.AppendArgument(interface);
+ message.AppendArgument(property);
+ DBusMessage *reply = message.SendSystem();
+
+ if (reply)
+ {
+ DBusMessageIter iter;
+
+ if (dbus_message_iter_init(reply, &iter))
+ {
+ if (!dbus_message_has_signature(reply, "v"))
+ CLog::Log(LOGERROR, "DBus: wrong signature on Get - should be \"v\" but was {}",
+ dbus_message_iter_get_signature(&iter));
+ else
+ result = ParseVariant(&iter);
+ }
+ }
+
+ return result;
+}
+
+CVariant CDBusUtil::GetAll(const char *destination, const char *object, const char *interface)
+{
+ CDBusMessage message(destination, object, "org.freedesktop.DBus.Properties", "GetAll");
+ CVariant properties;
+ message.AppendArgument(interface);
+ DBusMessage *reply = message.SendSystem();
+ if (reply)
+ {
+ DBusMessageIter iter;
+ if (dbus_message_iter_init(reply, &iter))
+ {
+ if (!dbus_message_has_signature(reply, "a{sv}"))
+ CLog::Log(LOGERROR, "DBus: wrong signature on GetAll - should be \"a{sv}\" but was {}",
+ dbus_message_iter_get_signature(&iter));
+ else
+ {
+ do
+ {
+ DBusMessageIter sub;
+ dbus_message_iter_recurse(&iter, &sub);
+ do
+ {
+ DBusMessageIter dict;
+ dbus_message_iter_recurse(&sub, &dict);
+ do
+ {
+ const char * key = NULL;
+
+ dbus_message_iter_get_basic(&dict, &key);
+ if (!dbus_message_iter_next(&dict))
+ break;
+
+ CVariant value = ParseVariant(&dict);
+
+ if (!value.isNull())
+ properties[key] = value;
+
+ } while (dbus_message_iter_next(&dict));
+
+ } while (dbus_message_iter_next(&sub));
+
+ } while (dbus_message_iter_next(&iter));
+ }
+ }
+ }
+
+ return properties;
+}
+
+CVariant CDBusUtil::ParseVariant(DBusMessageIter *itr)
+{
+ DBusMessageIter variant;
+ dbus_message_iter_recurse(itr, &variant);
+
+ return ParseType(&variant);
+}
+
+CVariant CDBusUtil::ParseType(DBusMessageIter *itr)
+{
+ CVariant value;
+ const char * string = NULL;
+ dbus_int32_t int32 = 0;
+ dbus_uint32_t uint32 = 0;
+ dbus_int64_t int64 = 0;
+ dbus_uint64_t uint64 = 0;
+ dbus_bool_t boolean = false;
+ double doublev = 0;
+
+ int type = dbus_message_iter_get_arg_type(itr);
+ switch (type)
+ {
+ case DBUS_TYPE_OBJECT_PATH:
+ case DBUS_TYPE_STRING:
+ dbus_message_iter_get_basic(itr, &string);
+ value = string;
+ break;
+ case DBUS_TYPE_UINT32:
+ dbus_message_iter_get_basic(itr, &uint32);
+ value = (uint64_t)uint32;
+ break;
+ case DBUS_TYPE_BYTE:
+ case DBUS_TYPE_INT32:
+ dbus_message_iter_get_basic(itr, &int32);
+ value = (int64_t)int32;
+ break;
+ case DBUS_TYPE_UINT64:
+ dbus_message_iter_get_basic(itr, &uint64);
+ value = (uint64_t)uint64;
+ break;
+ case DBUS_TYPE_INT64:
+ dbus_message_iter_get_basic(itr, &int64);
+ value = (int64_t)int64;
+ break;
+ case DBUS_TYPE_BOOLEAN:
+ dbus_message_iter_get_basic(itr, &boolean);
+ value = (bool)boolean;
+ break;
+ case DBUS_TYPE_DOUBLE:
+ dbus_message_iter_get_basic(itr, &doublev);
+ value = doublev;
+ break;
+ case DBUS_TYPE_ARRAY:
+ DBusMessageIter array;
+ dbus_message_iter_recurse(itr, &array);
+
+ value = CVariant::VariantTypeArray;
+
+ do
+ {
+ CVariant item = ParseType(&array);
+ if (!item.isNull())
+ value.push_back(item);
+ } while (dbus_message_iter_next(&array));
+ break;
+ }
+
+ return value;
+}
+
+bool CDBusUtil::TryMethodCall(DBusBusType bus, const char* destination, const char* object, const char* interface, const char* method)
+{
+ CDBusMessage message(destination, object, interface, method);
+ CDBusError error;
+ message.Send(bus, error);
+ if (error)
+ {
+ error.Log(LOGDEBUG, std::string("DBus method call to ") + interface + "." + method + " at " + object + " of " + destination + " failed");
+ }
+ return !error;
+}
+
+bool CDBusUtil::TryMethodCall(DBusBusType bus, std::string const& destination, std::string const& object, std::string const& interface, std::string const& method)
+{
+ return TryMethodCall(bus, destination.c_str(), object.c_str(), interface.c_str(), method.c_str());
+}
+
+CDBusConnection::CDBusConnection() = default;
+
+bool CDBusConnection::Connect(DBusBusType bus, bool openPrivate)
+{
+ CDBusError error;
+ Connect(bus, error, openPrivate);
+ if (error)
+ {
+ error.Log(LOGWARNING, "DBus connection failed");
+ return false;
+ }
+
+ return true;
+}
+
+bool CDBusConnection::Connect(DBusBusType bus, CDBusError& error, bool openPrivate)
+{
+ if (m_connection)
+ {
+ throw std::logic_error("Cannot reopen connected DBus connection");
+ }
+
+ m_connection.get_deleter().closeBeforeUnref = openPrivate;
+
+ if (openPrivate)
+ {
+ m_connection.reset(dbus_bus_get_private(bus, error));
+ }
+ else
+ {
+ m_connection.reset(dbus_bus_get(bus, error));
+ }
+
+ return !!m_connection;
+}
+
+CDBusConnection::operator DBusConnection*()
+{
+ return m_connection.get();
+}
+
+void CDBusConnection::DBusConnectionDeleter::operator()(DBusConnection* connection) const
+{
+ if (closeBeforeUnref)
+ {
+ dbus_connection_close(connection);
+ }
+ dbus_connection_unref(connection);
+}
+
+void CDBusConnection::Destroy()
+{
+ m_connection.reset();
+}
+
+
+CDBusError::CDBusError()
+{
+ dbus_error_init(&m_error);
+}
+
+CDBusError::~CDBusError()
+{
+ Reset();
+}
+
+void CDBusError::Reset()
+{
+ dbus_error_free(&m_error);
+}
+
+CDBusError::operator DBusError*()
+{
+ return &m_error;
+}
+
+bool CDBusError::IsSet() const
+{
+ return dbus_error_is_set(&m_error);
+}
+
+CDBusError::operator bool()
+{
+ return IsSet();
+}
+
+CDBusError::operator bool() const
+{
+ return IsSet();
+}
+
+std::string CDBusError::Name() const
+{
+ if (!IsSet())
+ {
+ throw std::logic_error("Cannot retrieve name of unset DBus error");
+ }
+ return m_error.name;
+}
+
+std::string CDBusError::Message() const
+{
+ if (!IsSet())
+ {
+ throw std::logic_error("Cannot retrieve message of unset DBus error");
+ }
+ return m_error.message;
+}
+
+void CDBusError::Log(std::string const& message) const
+{
+ Log(LOGERROR, message);
+}
+
+void CDBusError::Log(int level, const std::string& message) const
+{
+ if (!IsSet())
+ {
+ throw std::logic_error("Cannot log unset DBus error");
+ }
+ CLog::Log(level, "{}: {} - {}", message, m_error.name, m_error.message);
+}
diff --git a/xbmc/platform/linux/DBusUtil.h b/xbmc/platform/linux/DBusUtil.h
new file mode 100644
index 0000000..57349ac
--- /dev/null
+++ b/xbmc/platform/linux/DBusUtil.h
@@ -0,0 +1,88 @@
+/*
+ * 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 "DBusMessage.h"
+#include "utils/Variant.h"
+
+#include <memory>
+#include <string>
+
+#include <dbus/dbus.h>
+
+class CDBusUtil
+{
+public:
+ static CVariant GetAll(const char *destination, const char *object, const char *interface);
+ static CVariant GetVariant(const char *destination, const char *object, const char *interface, const char *property);
+ /**
+ * Try to call a DBus method and return whether the call succeeded
+ */
+ static bool TryMethodCall(DBusBusType bus, const char* destination, const char* object, const char* interface, const char* method);
+ /**
+ * Try to call a DBus method and return whether the call succeeded
+ */
+ static bool TryMethodCall(DBusBusType bus, std::string const& destination, std::string const& object, std::string const& interface, std::string const& method);
+
+private:
+ static CVariant ParseType(DBusMessageIter *itr);
+ static CVariant ParseVariant(DBusMessageIter *itr);
+};
+
+class CDBusConnection
+{
+public:
+ CDBusConnection();
+ bool Connect(DBusBusType bus, bool openPrivate = false);
+ bool Connect(DBusBusType bus, CDBusError& error, bool openPrivate = false);
+ void Destroy();
+ operator DBusConnection*();
+
+private:
+ CDBusConnection(CDBusConnection const& other) = delete;
+ CDBusConnection& operator=(CDBusConnection const& other) = delete;
+
+ struct DBusConnectionDeleter
+ {
+ DBusConnectionDeleter() {}
+ bool closeBeforeUnref = false;
+ void operator()(DBusConnection* connection) const;
+ };
+ std::unique_ptr<DBusConnection, DBusConnectionDeleter> m_connection;
+};
+
+class CDBusError
+{
+public:
+ CDBusError();
+ ~CDBusError();
+ operator DBusError*();
+ bool IsSet() const;
+ /**
+ * Reset this error wrapper
+ *
+ * If there was an error, it was handled and this error wrapper should be used
+ * again in a new call, it must be reset before that call.
+ */
+ void Reset();
+ // Keep because operator DBusError* would be used for if-statements on
+ // non-const CDBusError instead
+ operator bool();
+ operator bool() const;
+ std::string Name() const;
+ std::string Message() const;
+ void Log(std::string const& message = "DBus error") const;
+ void Log(int level, std::string const& message = "DBus error") const;
+
+private:
+ CDBusError(CDBusError const& other) = delete;
+ CDBusError& operator=(CDBusError const& other) = delete;
+
+ DBusError m_error;
+};
diff --git a/xbmc/platform/linux/FDEventMonitor.cpp b/xbmc/platform/linux/FDEventMonitor.cpp
new file mode 100644
index 0000000..9cd27c1
--- /dev/null
+++ b/xbmc/platform/linux/FDEventMonitor.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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 "FDEventMonitor.h"
+
+#include "threads/SingleLock.h"
+#include "utils/log.h"
+
+#include <errno.h>
+#include <mutex>
+
+#include <poll.h>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+#include "PlatformDefs.h"
+
+CFDEventMonitor::CFDEventMonitor() :
+ CThread("FDEventMonitor")
+{
+}
+
+CFDEventMonitor::~CFDEventMonitor()
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ InterruptPoll();
+
+ if (m_wakeupfd >= 0)
+ {
+ /* sets m_bStop */
+ StopThread(false);
+
+ /* wake up the poll() call */
+ eventfd_write(m_wakeupfd, 1);
+
+ /* Wait for the thread to stop */
+ {
+ CSingleExit exit(m_mutex);
+ StopThread(true);
+ }
+
+ close(m_wakeupfd);
+ }
+}
+
+void CFDEventMonitor::AddFD(const MonitoredFD& monitoredFD, int& id)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ InterruptPoll();
+
+ AddFDLocked(monitoredFD, id);
+
+ StartMonitoring();
+}
+
+void CFDEventMonitor::AddFDs(const std::vector<MonitoredFD>& monitoredFDs,
+ std::vector<int>& ids)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ InterruptPoll();
+
+ for (unsigned int i = 0; i < monitoredFDs.size(); ++i)
+ {
+ int id;
+ AddFDLocked(monitoredFDs[i], id);
+ ids.push_back(id);
+ }
+
+ StartMonitoring();
+}
+
+void CFDEventMonitor::RemoveFD(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ InterruptPoll();
+
+ if (m_monitoredFDs.erase(id) != 1)
+ {
+ CLog::Log(LOGERROR, "CFDEventMonitor::RemoveFD - Tried to remove non-existing monitoredFD {}",
+ id);
+ }
+
+ UpdatePollDescs();
+}
+
+void CFDEventMonitor::RemoveFDs(const std::vector<int>& ids)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ InterruptPoll();
+
+ for (unsigned int i = 0; i < ids.size(); ++i)
+ {
+ if (m_monitoredFDs.erase(ids[i]) != 1)
+ {
+ CLog::Log(LOGERROR,
+ "CFDEventMonitor::RemoveFDs - Tried to remove non-existing monitoredFD {} while "
+ "removing {} FDs",
+ ids[i], (unsigned)ids.size());
+ }
+ }
+
+ UpdatePollDescs();
+}
+
+void CFDEventMonitor::Process()
+{
+ eventfd_t dummy;
+
+ while (!m_bStop)
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ std::unique_lock<CCriticalSection> pollLock(m_pollMutex);
+
+ /*
+ * Leave the main mutex here to allow another thread to
+ * lock it while we are in poll().
+ * By then calling InterruptPoll() the other thread can
+ * wake up poll and wait for the processing to pause at
+ * the above lock(m_mutex).
+ */
+ lock.unlock();
+
+ int err = poll(&m_pollDescs[0], m_pollDescs.size(), -1);
+
+ if (err < 0 && errno != EINTR)
+ {
+ CLog::Log(LOGERROR, "CFDEventMonitor::Process - poll() failed, error {}, stopping monitoring",
+ errno);
+ StopThread(false);
+ }
+
+ // Something woke us up - either there is data available or we are being
+ // paused/stopped via m_wakeupfd.
+
+ for (unsigned int i = 0; i < m_pollDescs.size(); ++i)
+ {
+ struct pollfd& pollDesc = m_pollDescs[i];
+ int id = m_monitoredFDbyPollDescs[i];
+ const MonitoredFD& monitoredFD = m_monitoredFDs[id];
+
+ if (pollDesc.revents)
+ {
+ if (monitoredFD.callback)
+ {
+ monitoredFD.callback(id, pollDesc.fd, pollDesc.revents,
+ monitoredFD.callbackData);
+ }
+
+ if (pollDesc.revents & (POLLERR | POLLHUP | POLLNVAL))
+ {
+ CLog::Log(LOGERROR,
+ "CFDEventMonitor::Process - polled fd {} got revents 0x{:x}, removing it",
+ pollDesc.fd, pollDesc.revents);
+
+ /* Probably would be nice to inform our caller that their FD was
+ * dropped, but oh well... */
+ m_monitoredFDs.erase(id);
+ UpdatePollDescs();
+ }
+
+ pollDesc.revents = 0;
+ }
+ }
+
+ /* flush wakeup fd */
+ eventfd_read(m_wakeupfd, &dummy);
+
+ }
+}
+
+void CFDEventMonitor::AddFDLocked(const MonitoredFD& monitoredFD, int& id)
+{
+ id = m_nextID;
+
+ while (m_monitoredFDs.count(id))
+ {
+ ++id;
+ }
+ m_nextID = id + 1;
+
+ m_monitoredFDs[id] = monitoredFD;
+
+ AddPollDesc(id, monitoredFD.fd, monitoredFD.events);
+}
+
+void CFDEventMonitor::AddPollDesc(int id, int fd, short events)
+{
+ struct pollfd newPollFD;
+ newPollFD.fd = fd;
+ newPollFD.events = events;
+ newPollFD.revents = 0;
+
+ m_pollDescs.push_back(newPollFD);
+ m_monitoredFDbyPollDescs.push_back(id);
+}
+
+void CFDEventMonitor::UpdatePollDescs()
+{
+ m_monitoredFDbyPollDescs.clear();
+ m_pollDescs.clear();
+
+ for (const auto& it : m_monitoredFDs)
+ {
+ AddPollDesc(it.first, it.second.fd, it.second.events);
+ }
+}
+
+void CFDEventMonitor::StartMonitoring()
+{
+ if (!IsRunning())
+ {
+ /* Start the monitoring thread */
+
+ m_wakeupfd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+ if (m_wakeupfd < 0)
+ {
+ CLog::Log(LOGERROR, "CFDEventMonitor::StartMonitoring - Failed to create eventfd, error {}",
+ errno);
+ return;
+ }
+
+ /* Add wakeup fd to the fd list */
+ int id;
+ AddFDLocked(MonitoredFD(m_wakeupfd, POLLIN, NULL, NULL), id);
+
+ Create(false);
+ }
+}
+
+void CFDEventMonitor::InterruptPoll()
+{
+ if (m_wakeupfd >= 0)
+ {
+ eventfd_write(m_wakeupfd, 1);
+ /* wait for the poll() result handling (if any) to end */
+ std::unique_lock<CCriticalSection> pollLock(m_pollMutex);
+ }
+}
diff --git a/xbmc/platform/linux/FDEventMonitor.h b/xbmc/platform/linux/FDEventMonitor.h
new file mode 100644
index 0000000..c0b8395
--- /dev/null
+++ b/xbmc/platform/linux/FDEventMonitor.h
@@ -0,0 +1,74 @@
+/*
+ * 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 "platform/Platform.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <map>
+#include <vector>
+
+#include <sys/epoll.h>
+
+/**
+ * Monitor a file descriptor with callback on poll() events.
+ */
+class CFDEventMonitor : public IPlatformService, private CThread
+{
+public:
+
+ typedef void (*EventCallback)(int id, int fd, short revents, void *data);
+
+ struct MonitoredFD
+ {
+ int fd = -1; /**< File descriptor to be monitored */
+ short events = 0; /**< Events to be monitored (see poll(2)) */
+
+ EventCallback callback = nullptr; /** Callback to be called on events */
+ void *callbackData = nullptr; /** data parameter for EventCallback */
+
+ MonitoredFD(int fd_, short events_, EventCallback callback_, void *callbackData_) :
+ fd(fd_), events(events_), callback(callback_), callbackData(callbackData_) {}
+ MonitoredFD() = default;
+ };
+
+ CFDEventMonitor();
+ ~CFDEventMonitor() override;
+
+ void AddFD(const MonitoredFD& monitoredFD, int& id);
+ void AddFDs(const std::vector<MonitoredFD>& monitoredFDs, std::vector<int>& ids);
+
+ void RemoveFD(int id);
+ void RemoveFDs(const std::vector<int>& ids);
+
+protected:
+ void Process() override;
+
+private:
+ void AddFDLocked(const MonitoredFD& monitoredFD, int& id);
+
+ void AddPollDesc(int id, int fd, short events);
+ void UpdatePollDescs();
+
+ void StartMonitoring();
+ void InterruptPoll();
+
+ std::map<int, MonitoredFD> m_monitoredFDs;
+
+ /* these are kept synchronized */
+ std::vector<int> m_monitoredFDbyPollDescs;
+ std::vector<struct pollfd> m_pollDescs;
+
+ int m_nextID = 0;
+ int m_wakeupfd = -1;
+
+ CCriticalSection m_mutex;
+ CCriticalSection m_pollMutex;
+};
diff --git a/xbmc/platform/linux/MemUtils.cpp b/xbmc/platform/linux/MemUtils.cpp
new file mode 100644
index 0000000..09dd22f
--- /dev/null
+++ b/xbmc/platform/linux/MemUtils.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 "utils/MemUtils.h"
+
+#include <cstdlib>
+#include <fstream>
+
+namespace KODI
+{
+namespace MEMORY
+{
+
+void* AlignedMalloc(size_t s, size_t alignTo)
+{
+ void* p = nullptr;
+ int res = posix_memalign(&p, alignTo, s);
+ if (res == EINVAL)
+ {
+ throw std::runtime_error("Failed to align memory, alignment is not a multiple of 2");
+ }
+ else if (res == ENOMEM)
+ {
+ throw std::runtime_error("Failed to align memory, insufficient memory available");
+ }
+ return p;
+}
+
+void AlignedFree(void* p)
+{
+ if (!p)
+ return;
+
+ free(p);
+}
+
+void GetMemoryStatus(MemoryStatus* buffer)
+{
+ if (!buffer)
+ return;
+
+ std::ifstream file("/proc/meminfo");
+
+ if (!file.is_open())
+ return;
+
+ uint64_t buffers;
+ uint64_t cached;
+ uint64_t free;
+ uint64_t total;
+ uint64_t reclaimable;
+
+ std::string token;
+
+ while (file >> token)
+ {
+ if (token == "Buffers:")
+ file >> buffers;
+ if (token == "Cached:")
+ file >> cached;
+ if (token == "MemFree:")
+ file >> free;
+ if (token == "MemTotal:")
+ file >> total;
+ if (token == "SReclaimable:")
+ file >> reclaimable;
+ }
+
+ buffer->totalPhys = total * 1024;
+ buffer->availPhys = (free + cached + reclaimable + buffers) * 1024;
+}
+
+}
+}
diff --git a/xbmc/platform/linux/OptionalsReg.cpp b/xbmc/platform/linux/OptionalsReg.cpp
new file mode 100644
index 0000000..82fb176
--- /dev/null
+++ b/xbmc/platform/linux/OptionalsReg.cpp
@@ -0,0 +1,114 @@
+/*
+ * 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 "OptionalsReg.h"
+
+
+//-----------------------------------------------------------------------------
+// ALSA
+//-----------------------------------------------------------------------------
+
+#ifdef HAS_ALSA
+#include "cores/AudioEngine/Sinks/AESinkALSA.h"
+bool OPTIONALS::ALSARegister()
+{
+ CAESinkALSA::Register();
+ return true;
+}
+#else
+bool OPTIONALS::ALSARegister()
+{
+ return false;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// PulseAudio
+//-----------------------------------------------------------------------------
+
+#ifdef HAS_PULSEAUDIO
+#include "cores/AudioEngine/Sinks/AESinkPULSE.h"
+bool OPTIONALS::PulseAudioRegister()
+{
+ bool ret = CAESinkPULSE::Register();
+ return ret;
+}
+#else
+bool OPTIONALS::PulseAudioRegister()
+{
+ return false;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Pipewire
+//-----------------------------------------------------------------------------
+
+#ifdef HAS_PIPEWIRE
+#include "cores/AudioEngine/Sinks/pipewire/AESinkPipewire.h"
+bool OPTIONALS::PipewireRegister()
+{
+ bool ret = AE::SINK::CAESinkPipewire::Register();
+ return ret;
+}
+#else
+bool OPTIONALS::PipewireRegister()
+{
+ return false;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// sndio
+//-----------------------------------------------------------------------------
+
+#ifdef HAS_SNDIO
+#include "cores/AudioEngine/Sinks/AESinkSNDIO.h"
+bool OPTIONALS::SndioRegister()
+{
+ CAESinkSNDIO::Register();
+ return true;
+}
+#else
+bool OPTIONALS::SndioRegister()
+{
+ return false;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Lirc
+//-----------------------------------------------------------------------------
+
+#ifdef HAS_LIRC
+#include "platform/linux/input/LIRC.h"
+#include "ServiceBroker.h"
+class OPTIONALS::CLircContainer
+{
+public:
+ CLircContainer()
+ {
+ m_lirc.Start();
+ }
+protected:
+ CLirc m_lirc;
+};
+#else
+class OPTIONALS::CLircContainer
+{
+};
+#endif
+
+OPTIONALS::CLircContainer* OPTIONALS::LircRegister()
+{
+ return new CLircContainer();
+}
+void OPTIONALS::delete_CLircContainer::operator()(CLircContainer *p) const
+{
+ delete p;
+}
diff --git a/xbmc/platform/linux/OptionalsReg.h b/xbmc/platform/linux/OptionalsReg.h
new file mode 100644
index 0000000..75fdb6a
--- /dev/null
+++ b/xbmc/platform/linux/OptionalsReg.h
@@ -0,0 +1,59 @@
+/*
+ * 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
+
+//-----------------------------------------------------------------------------
+// ALSA
+//-----------------------------------------------------------------------------
+
+namespace OPTIONALS
+{
+bool ALSARegister();
+}
+
+//-----------------------------------------------------------------------------
+// PulseAudio
+//-----------------------------------------------------------------------------
+
+namespace OPTIONALS
+{
+bool PulseAudioRegister();
+}
+
+//-----------------------------------------------------------------------------
+// Pipewire
+//-----------------------------------------------------------------------------
+
+namespace OPTIONALS
+{
+bool PipewireRegister();
+}
+
+//-----------------------------------------------------------------------------
+// sndio
+//-----------------------------------------------------------------------------
+
+namespace OPTIONALS
+{
+bool SndioRegister();
+}
+
+//-----------------------------------------------------------------------------
+// Lirc
+//-----------------------------------------------------------------------------
+
+namespace OPTIONALS
+{
+class CLircContainer;
+CLircContainer* LircRegister();
+struct delete_CLircContainer
+{
+ void operator()(CLircContainer *p) const;
+};
+}
diff --git a/xbmc/platform/linux/PlatformLinux.cpp b/xbmc/platform/linux/PlatformLinux.cpp
new file mode 100644
index 0000000..a7af098
--- /dev/null
+++ b/xbmc/platform/linux/PlatformLinux.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2005-2020 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 "PlatformLinux.h"
+
+#if defined(HAS_ALSA)
+#include "cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.h"
+#include "cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.h"
+#endif
+
+#include "utils/StringUtils.h"
+
+#if defined(HAS_ALSA)
+#include "platform/linux/FDEventMonitor.h"
+#endif
+
+#include "platform/linux/powermanagement/LinuxPowerSyscall.h"
+
+// clang-format off
+#if defined(HAS_GLES)
+#if defined(HAVE_WAYLAND)
+#include "windowing/wayland/WinSystemWaylandEGLContextGLES.h"
+#endif
+#if defined(HAVE_X11)
+#include "windowing/X11/WinSystemX11GLESContext.h"
+#endif
+#if defined(HAVE_GBM)
+#include "windowing/gbm/WinSystemGbmGLESContext.h"
+#endif
+#endif
+
+#if defined(HAS_GL)
+#if defined(HAVE_WAYLAND)
+#include "windowing/wayland/WinSystemWaylandEGLContextGL.h"
+#endif
+#if defined(HAVE_X11)
+#include "windowing/X11/WinSystemX11GLContext.h"
+#endif
+#if defined(HAVE_GBM)
+#include "windowing/gbm/WinSystemGbmGLContext.h"
+#endif
+#endif
+// clang-format on
+
+#include <cstdlib>
+
+CPlatform* CPlatform::CreateInstance()
+{
+ return new CPlatformLinux();
+}
+
+bool CPlatformLinux::InitStageOne()
+{
+ if (!CPlatformPosix::InitStageOne())
+ return false;
+
+ setenv("OS", "Linux", true); // for python scripts that check the OS
+
+#if defined(HAS_GLES)
+#if defined(HAVE_WAYLAND)
+ KODI::WINDOWING::WAYLAND::CWinSystemWaylandEGLContextGLES::Register();
+#endif
+#if defined(HAVE_X11)
+ KODI::WINDOWING::X11::CWinSystemX11GLESContext::Register();
+#endif
+#if defined(HAVE_GBM)
+ KODI::WINDOWING::GBM::CWinSystemGbmGLESContext::Register();
+#endif
+#endif
+
+#if defined(HAS_GL)
+#if defined(HAVE_WAYLAND)
+ KODI::WINDOWING::WAYLAND::CWinSystemWaylandEGLContextGL::Register();
+#endif
+#if defined(HAVE_X11)
+ KODI::WINDOWING::X11::CWinSystemX11GLContext::Register();
+#endif
+#if defined(HAVE_GBM)
+ KODI::WINDOWING::GBM::CWinSystemGbmGLContext::Register();
+#endif
+#endif
+
+ CLinuxPowerSyscall::Register();
+
+ std::string envSink;
+ if (getenv("KODI_AE_SINK"))
+ envSink = getenv("KODI_AE_SINK");
+
+ if (StringUtils::EqualsNoCase(envSink, "ALSA"))
+ {
+ OPTIONALS::ALSARegister();
+ }
+ else if (StringUtils::EqualsNoCase(envSink, "PULSE"))
+ {
+ OPTIONALS::PulseAudioRegister();
+ }
+ else if (StringUtils::EqualsNoCase(envSink, "PIPEWIRE"))
+ {
+ OPTIONALS::PipewireRegister();
+ }
+ else if (StringUtils::EqualsNoCase(envSink, "SNDIO"))
+ {
+ OPTIONALS::SndioRegister();
+ }
+ else if (StringUtils::EqualsNoCase(envSink, "ALSA+PULSE"))
+ {
+ OPTIONALS::ALSARegister();
+ OPTIONALS::PulseAudioRegister();
+ }
+ else
+ {
+ if (!OPTIONALS::PulseAudioRegister())
+ {
+ if (!OPTIONALS::PipewireRegister())
+ {
+ if (!OPTIONALS::ALSARegister())
+ {
+ OPTIONALS::SndioRegister();
+ }
+ }
+ }
+ }
+
+ m_lirc.reset(OPTIONALS::LircRegister());
+
+#if defined(HAS_ALSA)
+ RegisterComponent(std::make_shared<CFDEventMonitor>());
+#if defined(HAVE_LIBUDEV)
+ RegisterComponent(std::make_shared<CALSADeviceMonitor>());
+#endif
+#if !defined(HAVE_X11)
+ RegisterComponent(std::make_shared<CALSAHControlMonitor>());
+#endif
+#endif // HAS_ALSA
+ return true;
+}
+
+void CPlatformLinux::DeinitStageOne()
+{
+#if defined(HAS_ALSA)
+#if !defined(HAVE_X11)
+ DeregisterComponent(typeid(CALSAHControlMonitor));
+#endif
+#if defined(HAVE_LIBUDEV)
+ DeregisterComponent(typeid(CALSADeviceMonitor));
+#endif
+ DeregisterComponent(typeid(CFDEventMonitor));
+#endif // HAS_ALSA
+}
+
+bool CPlatformLinux::IsConfigureAddonsAtStartupEnabled()
+{
+#if defined(ADDONS_CONFIGURE_AT_STARTUP)
+ return true;
+#else
+ return false;
+#endif
+}
diff --git a/xbmc/platform/linux/PlatformLinux.h b/xbmc/platform/linux/PlatformLinux.h
new file mode 100644
index 0000000..b74deb6
--- /dev/null
+++ b/xbmc/platform/linux/PlatformLinux.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005-2020 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 "platform/linux/OptionalsReg.h"
+#include "platform/posix/PlatformPosix.h"
+
+#include <memory>
+
+class CPlatformLinux : public CPlatformPosix
+{
+public:
+ CPlatformLinux() = default;
+
+ ~CPlatformLinux() override = default;
+
+ bool InitStageOne() override;
+ void DeinitStageOne() override;
+
+ bool IsConfigureAddonsAtStartupEnabled() override;
+
+private:
+ std::unique_ptr<OPTIONALS::CLircContainer, OPTIONALS::delete_CLircContainer> m_lirc;
+};
diff --git a/xbmc/platform/linux/SysfsPath.cpp b/xbmc/platform/linux/SysfsPath.cpp
new file mode 100644
index 0000000..af4f9cb
--- /dev/null
+++ b/xbmc/platform/linux/SysfsPath.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011-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 "SysfsPath.h"
+
+#include <exception>
+
+bool CSysfsPath::Exists()
+{
+ std::ifstream file(m_path);
+
+ if (!file.is_open())
+ return false;
+
+ return true;
+}
+
+template<>
+std::optional<std::string> CSysfsPath::Get()
+{
+ try
+ {
+ std::ifstream file(m_path);
+
+ std::string value;
+
+ std::getline(file, value);
+
+ if (file.bad())
+ {
+ CLog::LogF(LOGERROR, "error reading from '{}'", m_path);
+ return std::nullopt;
+ }
+
+ return value;
+ }
+ catch (const std::exception& e)
+ {
+ CLog::LogF(LOGERROR, "exception reading from '{}': {}", m_path, e.what());
+ return std::nullopt;
+ }
+}
diff --git a/xbmc/platform/linux/SysfsPath.h b/xbmc/platform/linux/SysfsPath.h
new file mode 100644
index 0000000..3c19703
--- /dev/null
+++ b/xbmc/platform/linux/SysfsPath.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011-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 "utils/log.h"
+
+#include <exception>
+#include <fstream>
+#include <optional>
+#include <string>
+
+class CSysfsPath
+{
+public:
+ CSysfsPath() = default;
+ CSysfsPath(const std::string& path) : m_path(path) {}
+ ~CSysfsPath() = default;
+
+ bool Exists();
+
+ template<typename T>
+ std::optional<T> Get()
+ {
+ try
+ {
+ std::ifstream file(m_path);
+
+ T value;
+
+ file >> value;
+
+ if (file.bad())
+ {
+ CLog::LogF(LOGERROR, "error reading from '{}'", m_path);
+ return std::nullopt;
+ }
+
+ return value;
+ }
+ catch (const std::exception& e)
+ {
+ CLog::LogF(LOGERROR, "exception reading from '{}': {}", m_path, e.what());
+ return std::nullopt;
+ }
+ }
+
+private:
+ std::string m_path;
+};
+
+template<>
+std::optional<std::string> CSysfsPath::Get<std::string>();
diff --git a/xbmc/platform/linux/TimeUtils.cpp b/xbmc/platform/linux/TimeUtils.cpp
new file mode 100644
index 0000000..55cce92
--- /dev/null
+++ b/xbmc/platform/linux/TimeUtils.cpp
@@ -0,0 +1,14 @@
+/*
+ * 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 "TimeUtils.h"
+
+std::int64_t KODI::LINUX::TimespecDifference(timespec const& start, timespec const& end)
+{
+ return (end.tv_sec - start.tv_sec) * UINT64_C(1000000000) + (end.tv_nsec - start.tv_nsec);
+} \ No newline at end of file
diff --git a/xbmc/platform/linux/TimeUtils.h b/xbmc/platform/linux/TimeUtils.h
new file mode 100644
index 0000000..abbeb13
--- /dev/null
+++ b/xbmc/platform/linux/TimeUtils.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <cstdint>
+#include <time.h>
+
+namespace KODI
+{
+namespace LINUX
+{
+
+/**
+ * Calculate difference between two timespecs in nanoseconds
+ * \param start earlier time
+ * \param end later time
+ * \return (end - start) in nanoseconds
+ */
+std::int64_t TimespecDifference(timespec const& start, timespec const& end);
+
+}
+}
diff --git a/xbmc/platform/linux/input/CMakeLists.txt b/xbmc/platform/linux/input/CMakeLists.txt
new file mode 100644
index 0000000..ebb2ccd
--- /dev/null
+++ b/xbmc/platform/linux/input/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(SOURCES "")
+set(HEADERS "")
+
+if(LIRCCLIENT_FOUND)
+ list(APPEND SOURCES LIRC.cpp)
+ list(APPEND HEADERS LIRC.h)
+endif()
+
+if("gbm" IN_LIST CORE_PLATFORM_NAME_LC)
+ if(LIBINPUT_FOUND)
+ list(APPEND SOURCES LibInputHandler.cpp
+ LibInputKeyboard.cpp
+ LibInputPointer.cpp
+ LibInputSettings.cpp
+ LibInputTouch.cpp)
+
+ list(APPEND HEADERS LibInputHandler.h
+ LibInputKeyboard.h
+ LibInputPointer.h
+ LibInputSettings.h
+ LibInputTouch.h)
+ endif()
+endif()
+
+if(SOURCES)
+ core_add_library(input_linux)
+endif()
diff --git a/xbmc/platform/linux/input/LIRC.cpp b/xbmc/platform/linux/input/LIRC.cpp
new file mode 100644
index 0000000..6900f5c
--- /dev/null
+++ b/xbmc/platform/linux/input/LIRC.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2007-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 "LIRC.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "profiles/ProfileManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SingleLock.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+#include <fcntl.h>
+#include <lirc/lirc_client.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "PlatformDefs.h"
+
+using namespace std::chrono_literals;
+
+CLirc::CLirc() : CThread("Lirc")
+{
+}
+
+CLirc::~CLirc()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_fd > 0)
+ shutdown(m_fd, SHUT_RDWR);
+ }
+ StopThread();
+}
+
+void CLirc::Start()
+{
+ Create();
+ SetPriority(ThreadPriority::LOWEST);
+}
+
+void CLirc::Process()
+{
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ throw std::runtime_error("CSettingsComponent needs to exist before starting CLirc");
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ throw std::runtime_error("CSettings needs to exist before starting CLirc");
+
+ while (!settings->IsLoaded())
+ CThread::Sleep(1000ms);
+
+ m_profileId = settingsComponent->GetProfileManager()->GetCurrentProfileId();
+ m_irTranslator.Load("Lircmap.xml");
+
+ // make sure work-around (CheckDaemon) uses the same socket path as lirc_init
+ const char* socket_path = getenv("LIRC_SOCKET_PATH");
+ socket_path = socket_path ? socket_path : "/var/run/lirc/lircd";
+ setenv("LIRC_SOCKET_PATH", socket_path, 0);
+
+ while (!m_bStop)
+ {
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // lirc_client is buggy because it does not close socket, if connect fails
+ // work around by checking if daemon is running before calling lirc_init
+ if (!CheckDaemon())
+ {
+ CSingleExit lock(m_critSection);
+ CThread::Sleep(1000ms);
+ continue;
+ }
+
+ m_fd = lirc_init(const_cast<char*>("kodi"), 0);
+ if (m_fd <= 0)
+ {
+ CSingleExit lock(m_critSection);
+ CThread::Sleep(1000ms);
+ continue;
+ }
+ }
+
+ char *code;
+ while (!m_bStop)
+ {
+ int ret = lirc_nextcode(&code);
+ if (ret < 0)
+ {
+ lirc_deinit();
+ CThread::Sleep(1000ms);
+ break;
+ }
+ if (code != nullptr)
+ {
+ int profileId = settingsComponent->GetProfileManager()->GetCurrentProfileId();
+ if (m_profileId != profileId)
+ {
+ m_profileId = profileId;
+ m_irTranslator.Load("Lircmap.xml");
+ }
+ ProcessCode(code);
+ free(code);
+ }
+ }
+ }
+
+ lirc_deinit();
+}
+
+void CLirc::ProcessCode(char *buf)
+{
+ // Parse the result. Sample line:
+ // 000000037ff07bdd 00 OK mceusb
+ char scanCode[128];
+ char buttonName[128];
+ char repeatStr[4];
+ char deviceName[128];
+ sscanf(buf, "%s %s %s %s", &scanCode[0], &repeatStr[0], &buttonName[0], &deviceName[0]);
+
+ // Some template LIRC configuration have button names in apostrophes or quotes.
+ // If we got a quoted button name, strip 'em
+ unsigned int buttonNameLen = strlen(buttonName);
+ if (buttonNameLen > 2 &&
+ ((buttonName[0] == '\'' && buttonName[buttonNameLen-1] == '\'') ||
+ ((buttonName[0] == '"' && buttonName[buttonNameLen-1] == '"'))))
+ {
+ memmove(buttonName, buttonName + 1, buttonNameLen - 2);
+ buttonName[buttonNameLen - 2] = '\0';
+ }
+
+ int button = m_irTranslator.TranslateButton(deviceName, buttonName);
+
+ char *end = nullptr;
+ long repeat = strtol(repeatStr, &end, 16);
+ if (!end || *end != 0)
+ CLog::Log(LOGERROR, "LIRC: invalid non-numeric character in expression {}", repeatStr);
+
+ if (repeat == 0)
+ {
+ CLog::Log(LOGDEBUG, "LIRC: - NEW {} {} {} {} ({})", &scanCode[0], &repeatStr[0], &buttonName[0],
+ &deviceName[0], buttonName);
+ m_firstClickTime = std::chrono::steady_clock::now();
+
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_BUTTON;
+ newEvent.keybutton.button = button;
+ newEvent.keybutton.holdtime = 0;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ return;
+ }
+ else if (repeat > CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_remoteDelay)
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_BUTTON;
+ newEvent.keybutton.button = button;
+
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_firstClickTime);
+
+ newEvent.keybutton.holdtime = duration.count();
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+}
+
+bool CLirc::CheckDaemon()
+{
+ const char* socket_path = getenv("LIRC_SOCKET_PATH");
+
+ struct sockaddr_un addr_un;
+ if (strlen(socket_path) + 1 > sizeof(addr_un.sun_path))
+ {
+ return false;
+ }
+
+ addr_un.sun_family = AF_UNIX;
+ strcpy(addr_un.sun_path, socket_path);
+
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ return false;
+ }
+
+ if (connect(fd, reinterpret_cast<struct sockaddr*>(&addr_un), sizeof(addr_un)) == -1)
+ {
+ close(fd);
+ return false;
+ }
+
+ close(fd);
+ return true;
+}
diff --git a/xbmc/platform/linux/input/LIRC.h b/xbmc/platform/linux/input/LIRC.h
new file mode 100644
index 0000000..f08638d
--- /dev/null
+++ b/xbmc/platform/linux/input/LIRC.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007-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/IRTranslator.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <chrono>
+#include <string>
+
+class CLirc : CThread
+{
+public:
+ CLirc();
+ ~CLirc() override;
+ void Start();
+
+protected:
+ void Process() override;
+ void ProcessCode(char *buf);
+ bool CheckDaemon();
+
+ int m_fd = -1;
+ std::chrono::time_point<std::chrono::steady_clock> m_firstClickTime;
+ CCriticalSection m_critSection;
+ CIRTranslator m_irTranslator;
+ int m_profileId;
+};
diff --git a/xbmc/platform/linux/input/LibInputHandler.cpp b/xbmc/platform/linux/input/LibInputHandler.cpp
new file mode 100644
index 0000000..a8c39f5
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputHandler.cpp
@@ -0,0 +1,301 @@
+/*
+ * 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 "LibInputHandler.h"
+
+#include "LibInputKeyboard.h"
+#include "LibInputPointer.h"
+#include "LibInputSettings.h"
+#include "LibInputTouch.h"
+#include "ServiceBroker.h"
+#include "interfaces/AnnouncementManager.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <string.h>
+
+#include <fcntl.h>
+#include <linux/input.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+static int open_restricted(const char *path, int flags, void __attribute__((unused)) *user_data)
+{
+ int fd = open(path, flags);
+
+ if (fd < 0)
+ {
+ CLog::Log(LOGERROR, "{} - failed to open {} ({})", __FUNCTION__, path, strerror(errno));
+ return -errno;
+ }
+
+ auto ret = ioctl(fd, EVIOCGRAB, (void*)1);
+ if (ret < 0)
+ {
+ CLog::Log(LOGDEBUG, "{} - grab requested, but failed for {} ({})", __FUNCTION__, path,
+ strerror(errno));
+ }
+
+ return fd;
+}
+
+static void close_restricted(int fd, void __attribute__((unused)) *user_data)
+{
+ close(fd);
+}
+
+static const struct libinput_interface m_interface =
+{
+ open_restricted,
+ close_restricted,
+};
+
+static void LogHandler(libinput __attribute__((unused)) *libinput, libinput_log_priority priority, const char *format, va_list args)
+{
+ if (priority == LIBINPUT_LOG_PRIORITY_DEBUG)
+ {
+ char buf[512];
+ int n = vsnprintf(buf, sizeof(buf), format, args);
+ if (n > 0)
+ CLog::Log(LOGDEBUG, "libinput: {}", buf);
+ }
+}
+
+CLibInputHandler::CLibInputHandler() : CThread("libinput")
+{
+ m_udev = udev_new();
+ if (!m_udev)
+ {
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to get udev context for libinput",
+ __FUNCTION__);
+ return;
+ }
+
+ m_li = libinput_udev_create_context(&m_interface, nullptr, m_udev);
+ if (!m_li)
+ {
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to get libinput context", __FUNCTION__);
+ return;
+ }
+
+ libinput_log_set_handler(m_li, LogHandler);
+ libinput_log_set_priority(m_li, LIBINPUT_LOG_PRIORITY_DEBUG);
+
+ auto ret = libinput_udev_assign_seat(m_li, "seat0");
+ if (ret < 0)
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to assign seat", __FUNCTION__);
+
+ m_liFd = libinput_get_fd(m_li);
+
+ m_keyboard.reset(new CLibInputKeyboard());
+ m_pointer.reset(new CLibInputPointer());
+ m_touch.reset(new CLibInputTouch());
+ m_settings.reset(new CLibInputSettings(this));
+
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+}
+
+CLibInputHandler::~CLibInputHandler()
+{
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+ StopThread();
+
+ libinput_unref(m_li);
+ udev_unref(m_udev);
+}
+
+void CLibInputHandler::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag & (ANNOUNCEMENT::System))
+ {
+ if (message == "OnSleep")
+ libinput_suspend(m_li);
+ else if (message == "OnWake")
+ {
+ auto ret = libinput_resume(m_li);
+ if (ret < 0)
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to resume monitoring", __FUNCTION__);
+ }
+ }
+}
+
+bool CLibInputHandler::SetKeymap(const std::string& layout)
+{
+ return m_keyboard->SetKeymap(layout);
+}
+
+void CLibInputHandler::Start()
+{
+ Create();
+ SetPriority(ThreadPriority::LOWEST);
+}
+
+void CLibInputHandler::Process()
+{
+ int epollFd = epoll_create1(EPOLL_CLOEXEC);
+ if (epollFd < 0)
+ {
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to create epoll file descriptor: {}",
+ __FUNCTION__, strerror(-errno));
+ return;
+ }
+
+ epoll_event event;
+ event.events = EPOLLIN;
+ event.data.fd = m_liFd;
+
+ auto ret = epoll_ctl(epollFd, EPOLL_CTL_ADD, m_liFd, &event);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to add file descriptor to epoll: {}",
+ __FUNCTION__, strerror(-errno));
+ close(epollFd);
+ return;
+ }
+
+ while (!m_bStop)
+ {
+ epoll_wait(epollFd, &event, 1, 200);
+
+ ret = libinput_dispatch(m_li);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - libinput_dispatch failed: {}", __FUNCTION__,
+ strerror(-errno));
+ close(epollFd);
+ return;
+ }
+
+ libinput_event *ev;
+ while ((ev = libinput_get_event(m_li)) != nullptr)
+ {
+ ProcessEvent(ev);
+ libinput_event_destroy(ev);
+ }
+ }
+
+ ret = close(epollFd);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to close epoll file descriptor: {}",
+ __FUNCTION__, strerror(-errno));
+ return;
+ }
+}
+
+void CLibInputHandler::ProcessEvent(libinput_event *ev)
+{
+ libinput_event_type type = libinput_event_get_type(ev);
+ libinput_device *dev = libinput_event_get_device(ev);
+
+ switch (type)
+ {
+ case LIBINPUT_EVENT_DEVICE_ADDED:
+ DeviceAdded(dev);
+ break;
+ case LIBINPUT_EVENT_DEVICE_REMOVED:
+ DeviceRemoved(dev);
+ break;
+ case LIBINPUT_EVENT_POINTER_BUTTON:
+ m_pointer->ProcessButton(libinput_event_get_pointer_event(ev));
+ break;
+ case LIBINPUT_EVENT_POINTER_MOTION:
+ m_pointer->ProcessMotion(libinput_event_get_pointer_event(ev));
+ break;
+ case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
+ m_pointer->ProcessMotionAbsolute(libinput_event_get_pointer_event(ev));
+ break;
+ case LIBINPUT_EVENT_POINTER_AXIS:
+ m_pointer->ProcessAxis(libinput_event_get_pointer_event(ev));
+ break;
+ case LIBINPUT_EVENT_KEYBOARD_KEY:
+ m_keyboard->ProcessKey(libinput_event_get_keyboard_event(ev));
+ m_keyboard->UpdateLeds(dev);
+ break;
+ case LIBINPUT_EVENT_TOUCH_DOWN:
+ m_touch->ProcessTouchDown(libinput_event_get_touch_event(ev));
+ break;
+ case LIBINPUT_EVENT_TOUCH_MOTION:
+ m_touch->ProcessTouchMotion(libinput_event_get_touch_event(ev));
+ break;
+ case LIBINPUT_EVENT_TOUCH_UP:
+ m_touch->ProcessTouchUp(libinput_event_get_touch_event(ev));
+ break;
+ case LIBINPUT_EVENT_TOUCH_CANCEL:
+ m_touch->ProcessTouchCancel(libinput_event_get_touch_event(ev));
+ break;
+ case LIBINPUT_EVENT_TOUCH_FRAME:
+ m_touch->ProcessTouchFrame(libinput_event_get_touch_event(ev));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void CLibInputHandler::DeviceAdded(libinput_device *dev)
+{
+ const char *sysname = libinput_device_get_sysname(dev);
+ const char *name = libinput_device_get_name(dev);
+
+ if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_TOUCH))
+ {
+ CLog::Log(LOGDEBUG, "CLibInputHandler::{} - touch type device added: {} ({})", __FUNCTION__,
+ name, sysname);
+ m_devices.push_back(libinput_device_ref(dev));
+ }
+
+ if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_POINTER))
+ {
+ CLog::Log(LOGDEBUG, "CLibInputHandler::{} - pointer type device added: {} ({})", __FUNCTION__,
+ name, sysname);
+ m_devices.push_back(libinput_device_ref(dev));
+ }
+
+ if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_KEYBOARD))
+ {
+ CLog::Log(LOGDEBUG, "CLibInputHandler::{} - keyboard type device added: {} ({})", __FUNCTION__,
+ name, sysname);
+ m_devices.push_back(libinput_device_ref(dev));
+ m_keyboard->GetRepeat(dev);
+ }
+}
+
+void CLibInputHandler::DeviceRemoved(libinput_device *dev)
+{
+ const char *sysname = libinput_device_get_sysname(dev);
+ const char *name = libinput_device_get_name(dev);
+
+ if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_TOUCH))
+ {
+ CLog::Log(LOGDEBUG, "CLibInputHandler::{} - touch type device removed: {} ({})", __FUNCTION__,
+ name, sysname);
+ auto device = std::find(m_devices.begin(), m_devices.end(), libinput_device_unref(dev));
+ m_devices.erase(device);
+ }
+
+ if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_POINTER))
+ {
+ CLog::Log(LOGDEBUG, "CLibInputHandler::{} - pointer type device removed: {} ({})", __FUNCTION__,
+ name, sysname);
+ auto device = std::find(m_devices.begin(), m_devices.end(), libinput_device_unref(dev));
+ m_devices.erase(device);
+ }
+
+ if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_KEYBOARD))
+ {
+ CLog::Log(LOGDEBUG, "CLibInputHandler::{} - keyboard type device removed: {} ({})",
+ __FUNCTION__, name, sysname);
+ auto device = std::find(m_devices.begin(), m_devices.end(), libinput_device_unref(dev));
+ m_devices.erase(device);
+ }
+}
diff --git a/xbmc/platform/linux/input/LibInputHandler.h b/xbmc/platform/linux/input/LibInputHandler.h
new file mode 100644
index 0000000..659d086
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputHandler.h
@@ -0,0 +1,56 @@
+/*
+ * 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 "interfaces/IAnnouncer.h"
+#include "threads/Thread.h"
+
+#include <memory>
+#include <vector>
+
+#include <libinput.h>
+#include <libudev.h>
+
+class CLibInputKeyboard;
+class CLibInputPointer;
+class CLibInputSettings;
+class CLibInputTouch;
+
+class CLibInputHandler : CThread, public ANNOUNCEMENT::IAnnouncer
+{
+public:
+ CLibInputHandler();
+ ~CLibInputHandler() override;
+
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
+ void Start();
+
+ bool SetKeymap(const std::string& layout);
+
+private:
+ void Process() override;
+ void ProcessEvent(libinput_event *ev);
+ void DeviceAdded(libinput_device *dev);
+ void DeviceRemoved(libinput_device *dev);
+
+ udev *m_udev;
+ libinput *m_li;
+ int m_liFd;
+
+ std::unique_ptr<CLibInputKeyboard> m_keyboard;
+ std::unique_ptr<CLibInputPointer> m_pointer;
+ std::unique_ptr<CLibInputSettings> m_settings;
+ std::unique_ptr<CLibInputTouch> m_touch;
+ std::vector<libinput_device*> m_devices;
+};
+
diff --git a/xbmc/platform/linux/input/LibInputKeyboard.cpp b/xbmc/platform/linux/input/LibInputKeyboard.cpp
new file mode 100644
index 0000000..657588a
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputKeyboard.cpp
@@ -0,0 +1,387 @@
+/*
+ * 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 "LibInputKeyboard.h"
+
+#include "LibInputSettings.h"
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <map>
+#include <string.h>
+
+#include <fcntl.h>
+#include <linux/input.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <xkbcommon/xkbcommon-keysyms.h>
+#include <xkbcommon/xkbcommon-names.h>
+
+namespace
+{
+constexpr int REPEAT_DELAY = 400;
+constexpr int REPEAT_RATE = 80;
+
+static const std::map<xkb_keysym_t, XBMCKey> xkbMap =
+{
+ // Function keys before start of ASCII printable character range
+ { XKB_KEY_BackSpace, XBMCK_BACKSPACE },
+ { XKB_KEY_Tab, XBMCK_TAB },
+ { XKB_KEY_Clear, XBMCK_CLEAR },
+ { XKB_KEY_Return, XBMCK_RETURN },
+ { XKB_KEY_Pause, XBMCK_PAUSE },
+ { XKB_KEY_Escape, XBMCK_ESCAPE },
+
+ // ASCII printable range - not included here
+
+ // Function keys after end of ASCII printable character range
+ { XKB_KEY_Delete, XBMCK_DELETE },
+
+ // Multimedia keys
+ { XKB_KEY_XF86Back, XBMCK_BROWSER_BACK },
+ { XKB_KEY_XF86Forward, XBMCK_BROWSER_FORWARD },
+ { XKB_KEY_XF86Refresh, XBMCK_BROWSER_REFRESH },
+ { XKB_KEY_XF86Stop, XBMCK_BROWSER_STOP },
+ { XKB_KEY_XF86Search, XBMCK_BROWSER_SEARCH },
+ // XKB_KEY_XF86Favorites could be XBMCK_BROWSER_FAVORITES or XBMCK_FAVORITES,
+ // XBMCK_FAVORITES was chosen here because it is more general
+ { XKB_KEY_XF86HomePage, XBMCK_BROWSER_HOME },
+ { XKB_KEY_XF86AudioMute, XBMCK_VOLUME_MUTE },
+ { XKB_KEY_XF86AudioLowerVolume, XBMCK_VOLUME_DOWN },
+ { XKB_KEY_XF86AudioRaiseVolume, XBMCK_VOLUME_UP },
+ { XKB_KEY_XF86AudioNext, XBMCK_MEDIA_NEXT_TRACK },
+ { XKB_KEY_XF86AudioPrev, XBMCK_MEDIA_PREV_TRACK },
+ { XKB_KEY_XF86AudioStop, XBMCK_MEDIA_STOP },
+ { XKB_KEY_XF86AudioPause, XBMCK_MEDIA_PLAY_PAUSE },
+ { XKB_KEY_XF86Mail, XBMCK_LAUNCH_MAIL },
+ { XKB_KEY_XF86Select, XBMCK_LAUNCH_MEDIA_SELECT },
+ { XKB_KEY_XF86Launch0, XBMCK_LAUNCH_APP1 },
+ { XKB_KEY_XF86Launch1, XBMCK_LAUNCH_APP2 },
+ { XKB_KEY_XF86WWW, XBMCK_LAUNCH_FILE_BROWSER },
+ { XKB_KEY_XF86AudioMedia, XBMCK_LAUNCH_MEDIA_CENTER },
+ { XKB_KEY_XF86AudioRewind, XBMCK_MEDIA_REWIND },
+ { XKB_KEY_XF86AudioForward, XBMCK_MEDIA_FASTFORWARD },
+
+ // Numeric keypad
+ { XKB_KEY_KP_0, XBMCK_KP0 },
+ { XKB_KEY_KP_1, XBMCK_KP1 },
+ { XKB_KEY_KP_2, XBMCK_KP2 },
+ { XKB_KEY_KP_3, XBMCK_KP3 },
+ { XKB_KEY_KP_4, XBMCK_KP4 },
+ { XKB_KEY_KP_5, XBMCK_KP5 },
+ { XKB_KEY_KP_6, XBMCK_KP6 },
+ { XKB_KEY_KP_7, XBMCK_KP7 },
+ { XKB_KEY_KP_8, XBMCK_KP8 },
+ { XKB_KEY_KP_9, XBMCK_KP9 },
+ { XKB_KEY_KP_Decimal, XBMCK_KP_PERIOD },
+ { XKB_KEY_KP_Divide, XBMCK_KP_DIVIDE },
+ { XKB_KEY_KP_Multiply, XBMCK_KP_MULTIPLY },
+ { XKB_KEY_KP_Subtract, XBMCK_KP_MINUS },
+ { XKB_KEY_KP_Add, XBMCK_KP_PLUS },
+ { XKB_KEY_KP_Enter, XBMCK_KP_ENTER },
+ { XKB_KEY_KP_Equal, XBMCK_KP_EQUALS },
+
+ // Arrows + Home/End pad
+ { XKB_KEY_Up, XBMCK_UP },
+ { XKB_KEY_Down, XBMCK_DOWN },
+ { XKB_KEY_Right, XBMCK_RIGHT },
+ { XKB_KEY_Left, XBMCK_LEFT },
+ { XKB_KEY_Insert, XBMCK_INSERT },
+ { XKB_KEY_Home, XBMCK_HOME },
+ { XKB_KEY_End, XBMCK_END },
+ { XKB_KEY_Page_Up, XBMCK_PAGEUP },
+ { XKB_KEY_Page_Down, XBMCK_PAGEDOWN },
+
+ // Key state modifier keys
+ { XKB_KEY_Num_Lock, XBMCK_NUMLOCK },
+ { XKB_KEY_Caps_Lock, XBMCK_CAPSLOCK },
+ { XKB_KEY_Scroll_Lock, XBMCK_SCROLLOCK },
+ { XKB_KEY_Shift_R, XBMCK_RSHIFT },
+ { XKB_KEY_Shift_L, XBMCK_LSHIFT },
+ { XKB_KEY_Control_R, XBMCK_RCTRL },
+ { XKB_KEY_Control_L, XBMCK_LCTRL },
+ { XKB_KEY_Alt_R, XBMCK_RALT },
+ { XKB_KEY_Alt_L, XBMCK_LALT },
+ { XKB_KEY_Meta_R, XBMCK_RMETA },
+ { XKB_KEY_Meta_L, XBMCK_LMETA },
+ { XKB_KEY_Super_R, XBMCK_RSUPER },
+ { XKB_KEY_Super_L, XBMCK_LSUPER },
+ // XKB does not have XBMCK_MODE/"Alt Gr" - probably equal to XKB_KEY_Alt_R
+ { XKB_KEY_Multi_key, XBMCK_COMPOSE },
+
+ // Miscellaneous function keys
+ { XKB_KEY_Help, XBMCK_HELP },
+ { XKB_KEY_Print, XBMCK_PRINT },
+ { XKB_KEY_Sys_Req, XBMCK_SYSREQ},
+ { XKB_KEY_Break, XBMCK_BREAK },
+ { XKB_KEY_Menu, XBMCK_MENU },
+ { XKB_KEY_XF86PowerOff, XBMCK_POWER },
+ { XKB_KEY_EcuSign, XBMCK_EURO },
+ { XKB_KEY_Undo, XBMCK_UNDO },
+ { XKB_KEY_XF86Sleep, XBMCK_SLEEP },
+ // Unmapped: XBMCK_GUIDE, XBMCK_SETTINGS, XBMCK_INFO
+ { XKB_KEY_XF86Red, XBMCK_RED },
+ { XKB_KEY_XF86Green, XBMCK_GREEN },
+ { XKB_KEY_XF86Yellow, XBMCK_YELLOW },
+ { XKB_KEY_XF86Blue, XBMCK_BLUE },
+ // Unmapped: XBMCK_ZOOM, XBMCK_TEXT
+ { XKB_KEY_XF86Favorites, XBMCK_FAVORITES },
+ { XKB_KEY_XF86HomePage, XBMCK_HOMEPAGE },
+ // Unmapped: XBMCK_CONFIG, XBMCK_EPG
+
+ // Media keys
+ { XKB_KEY_XF86Eject, XBMCK_EJECT },
+ { XKB_KEY_Cancel, XBMCK_STOP },
+ { XKB_KEY_XF86AudioRecord, XBMCK_RECORD },
+ // XBMCK_REWIND clashes with XBMCK_MEDIA_REWIND
+ { XKB_KEY_XF86Phone, XBMCK_PHONE },
+ { XKB_KEY_XF86AudioPlay, XBMCK_PLAY },
+ { XKB_KEY_XF86AudioRandomPlay, XBMCK_SHUFFLE }
+ // XBMCK_FASTFORWARD clashes with XBMCK_MEDIA_FASTFORWARD
+};
+} // namespace
+
+CLibInputKeyboard::CLibInputKeyboard()
+ : m_repeatTimer(std::bind(&CLibInputKeyboard::KeyRepeatTimeout, this))
+{
+ m_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (!m_ctx)
+ {
+ CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to create xkb context", __FUNCTION__);
+ return;
+ }
+
+ std::string layout = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CLibInputSettings::SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT);
+
+ if (!SetKeymap(layout))
+ {
+ CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed set default keymap", __FUNCTION__);
+ return;
+ }
+}
+
+CLibInputKeyboard::~CLibInputKeyboard()
+{
+ xkb_state_unref(m_state);
+ xkb_keymap_unref(m_keymap);
+ xkb_context_unref(m_ctx);
+}
+
+bool CLibInputKeyboard::SetKeymap(const std::string& layout)
+{
+ xkb_state_unref(m_state);
+ xkb_keymap_unref(m_keymap);
+
+ xkb_rule_names names;
+
+ names.rules = nullptr;
+ names.model = nullptr;
+ names.layout = layout.c_str();
+ names.variant = nullptr;
+ names.options = nullptr;
+
+ m_keymap = xkb_keymap_new_from_names(m_ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!m_keymap)
+ {
+ CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to compile keymap", __FUNCTION__);
+ return false;
+ }
+
+ m_state = xkb_state_new(m_keymap);
+ if (!m_state)
+ {
+ CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to create xkb state", __FUNCTION__);
+ return false;
+ }
+
+ m_modindex[0] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL);
+ m_modindex[1] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT);
+ m_modindex[2] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT);
+ m_modindex[3] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO);
+
+ m_ledindex[0] = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_NUM);
+ m_ledindex[1] = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_CAPS);
+ m_ledindex[2] = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_SCROLL);
+
+ m_leds = 0;
+
+ return true;
+}
+
+void CLibInputKeyboard::ProcessKey(libinput_event_keyboard *e)
+{
+ if (!m_ctx || !m_keymap || !m_state)
+ return;
+
+ XBMC_Event event = {};
+
+ const uint32_t xkbkey = libinput_event_keyboard_get_key(e) + 8;
+ const bool pressed = libinput_event_keyboard_get_key_state(e) == LIBINPUT_KEY_STATE_PRESSED;
+
+ event.type = pressed ? XBMC_KEYDOWN : XBMC_KEYUP;
+ xkb_state_update_key(m_state, xkbkey, pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
+
+ const xkb_keysym_t keysym = xkb_state_key_get_one_sym(m_state, xkbkey);
+
+ int mod = XBMCKMOD_NONE;
+
+ xkb_state_component modtype = xkb_state_component(XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[0], modtype) && ((keysym != XBMCK_LCTRL) || !pressed))
+ mod |= XBMCKMOD_CTRL;
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[0], modtype) && ((keysym != XBMCK_RCTRL) || !pressed))
+ mod |= XBMCKMOD_CTRL;
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[1], modtype) && ((keysym != XBMCK_LALT) || !pressed))
+ mod |= XBMCKMOD_ALT;
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[1], modtype) && ((keysym != XBMCK_RALT) || !pressed))
+ mod |= XBMCKMOD_ALT;
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[2], modtype) && ((keysym != XBMCK_LSHIFT) || !pressed))
+ mod |= XBMCKMOD_SHIFT;
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[2], modtype) && ((keysym != XBMCK_RSHIFT) || !pressed))
+ mod |= XBMCKMOD_SHIFT;
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[3], modtype) && ((keysym != XBMCK_LMETA) || !pressed))
+ mod |= XBMCKMOD_META;
+ if (xkb_state_mod_index_is_active(m_state, m_modindex[3], modtype) && ((keysym != XBMCK_RMETA) || !pressed))
+ mod |= XBMCKMOD_META;
+
+ m_leds = 0;
+
+ if (xkb_state_led_index_is_active(m_state, m_ledindex[0]) && ((keysym != XBMCK_NUMLOCK) || !pressed))
+ {
+ m_leds |= LIBINPUT_LED_NUM_LOCK;
+ mod |= XBMCKMOD_NUM;
+ }
+ if (xkb_state_led_index_is_active(m_state, m_ledindex[1]) && ((keysym != XBMCK_CAPSLOCK) || !pressed))
+ {
+ m_leds |= LIBINPUT_LED_CAPS_LOCK;
+ mod |= XBMCKMOD_CAPS;
+ }
+ if (xkb_state_led_index_is_active(m_state, m_ledindex[2]) && ((keysym != XBMCK_SCROLLOCK) || !pressed))
+ {
+ m_leds |= LIBINPUT_LED_SCROLL_LOCK;
+ //mod |= XBMCK_SCROLLOCK;
+ }
+
+ uint32_t unicode = xkb_state_key_get_utf32(m_state, xkbkey);
+ if (unicode > std::numeric_limits<std::uint16_t>::max())
+ {
+ // Kodi event system only supports UTF16, so ignore the codepoint if it does not fit
+ unicode = 0;
+ }
+
+ uint32_t scancode = libinput_event_keyboard_get_key(e);
+ if (scancode > std::numeric_limits<unsigned char>::max())
+ {
+ // Kodi scancodes are limited to unsigned char, pretend the scancode is unknown on overflow
+ scancode = 0;
+ }
+
+ event.key.keysym.mod = XBMCMod(mod);
+ event.key.keysym.sym = XBMCKeyForKeysym(keysym, scancode);
+ event.key.keysym.scancode = scancode;
+ event.key.keysym.unicode = unicode;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(event);
+
+ if (pressed && xkb_keymap_key_repeats(m_keymap, xkbkey))
+ {
+ libinput_event *ev = libinput_event_keyboard_get_base_event(e);
+ libinput_device *dev = libinput_event_get_device(ev);
+ auto data = m_repeatData.find(dev);
+ if (data != m_repeatData.end())
+ {
+ CLog::Log(LOGDEBUG, "CLibInputKeyboard::{} - using delay: {}ms repeat: {}ms", __FUNCTION__,
+ data->second.at(0), data->second.at(1));
+
+ m_repeatRate = data->second.at(1);
+ m_repeatTimer.Stop(true);
+ m_repeatEvent = event;
+ m_repeatTimer.Start(std::chrono::milliseconds(data->second.at(0)), false);
+ }
+ }
+ else
+ {
+ m_repeatTimer.Stop();
+ }
+}
+
+XBMCKey CLibInputKeyboard::XBMCKeyForKeysym(xkb_keysym_t sym, uint32_t scancode)
+{
+ if (sym >= 'A' && sym <= 'Z')
+ {
+ return static_cast<XBMCKey> (sym + 'a' - 'A');
+ }
+ else if (sym >= XKB_KEY_space && sym <= XKB_KEY_asciitilde)
+ {
+ return static_cast<XBMCKey> (sym);
+ }
+ else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F15)
+ {
+ return static_cast<XBMCKey> (XBMCK_F1 + ((int)sym - XKB_KEY_F1));
+ }
+
+ auto xkbmapping = xkbMap.find(sym);
+ if (xkbmapping != xkbMap.end())
+ return xkbmapping->second;
+
+ return XBMCK_UNKNOWN;
+}
+
+void CLibInputKeyboard::KeyRepeatTimeout()
+{
+ m_repeatTimer.RestartAsync(std::chrono::milliseconds(m_repeatRate));
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(m_repeatEvent);
+}
+
+void CLibInputKeyboard::UpdateLeds(libinput_device *dev)
+{
+ libinput_device_led_update(dev, static_cast<libinput_led>(m_leds));
+}
+
+void CLibInputKeyboard::GetRepeat(libinput_device *dev)
+{
+ int kbdrep[2] = {REPEAT_DELAY, REPEAT_RATE};
+ const char *name = libinput_device_get_name(dev);
+ const char *sysname = libinput_device_get_sysname(dev);
+ std::string path("/dev/input/");
+ path.append(sysname);
+ auto fd = open(path.c_str(), O_RDONLY);
+
+ if (fd < 0)
+ {
+ CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to open {} ({})", __FUNCTION__, sysname,
+ strerror(errno));
+ }
+ else
+ {
+ auto ret = ioctl(fd, EVIOCGREP, &kbdrep);
+ if (ret < 0)
+ CLog::Log(LOGDEBUG, "CLibInputKeyboard::{} - could not get key repeat for {} ({})",
+ __FUNCTION__, sysname, strerror(errno));
+
+ CLog::Log(LOGDEBUG, "CLibInputKeyboard::{} - delay: {}ms repeat: {}ms for {} ({})",
+ __FUNCTION__, kbdrep[0], kbdrep[1], name, sysname);
+ close(fd);
+ }
+
+ std::vector<int> kbdrepvec(std::begin(kbdrep), std::end(kbdrep));
+
+ auto data = m_repeatData.find(dev);
+ if (data == m_repeatData.end())
+ {
+ m_repeatData.insert(std::make_pair(dev, kbdrepvec));
+ }
+}
diff --git a/xbmc/platform/linux/input/LibInputKeyboard.h b/xbmc/platform/linux/input/LibInputKeyboard.h
new file mode 100644
index 0000000..76aaeff
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputKeyboard.h
@@ -0,0 +1,48 @@
+/*
+ * 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 "threads/Timer.h"
+#include "windowing/XBMC_events.h"
+
+#include <map>
+#include <vector>
+
+#include <libinput.h>
+#include <xkbcommon/xkbcommon.h>
+
+class CLibInputKeyboard
+{
+public:
+ CLibInputKeyboard();
+ ~CLibInputKeyboard();
+
+ void ProcessKey(libinput_event_keyboard *e);
+ void UpdateLeds(libinput_device *dev);
+ void GetRepeat(libinput_device *dev);
+
+ bool SetKeymap(const std::string& layout);
+
+private:
+ XBMCKey XBMCKeyForKeysym(xkb_keysym_t sym, uint32_t scancode);
+ void KeyRepeatTimeout();
+
+ xkb_context *m_ctx = nullptr;
+ xkb_keymap *m_keymap = nullptr;
+ xkb_state *m_state = nullptr;
+ xkb_mod_index_t m_modindex[4];
+ xkb_led_index_t m_ledindex[3];
+
+ int m_leds;
+
+ XBMC_Event m_repeatEvent;
+ std::map<libinput_device*, std::vector<int>> m_repeatData;
+ CTimer m_repeatTimer;
+ int m_repeatRate;
+};
diff --git a/xbmc/platform/linux/input/LibInputPointer.cpp b/xbmc/platform/linux/input/LibInputPointer.cpp
new file mode 100644
index 0000000..42d2bbe
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputPointer.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 "LibInputPointer.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "input/mouse/MouseStat.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <algorithm>
+#include <string.h>
+
+#include <linux/input.h>
+
+void CLibInputPointer::ProcessButton(libinput_event_pointer *e)
+{
+ const uint32_t b = libinput_event_pointer_get_button(e);
+ const bool pressed = libinput_event_pointer_get_button_state(e) == LIBINPUT_BUTTON_STATE_PRESSED;
+ unsigned char xbmc_button = 0;
+
+ switch (b)
+ {
+ case BTN_LEFT:
+ xbmc_button = XBMC_BUTTON_LEFT;
+ break;
+ case BTN_RIGHT:
+ xbmc_button = XBMC_BUTTON_RIGHT;
+ break;
+ case BTN_MIDDLE:
+ xbmc_button = XBMC_BUTTON_MIDDLE;
+ break;
+ case BTN_SIDE:
+ xbmc_button = XBMC_BUTTON_X1;
+ break;
+ case BTN_EXTRA:
+ xbmc_button = XBMC_BUTTON_X2;
+ break;
+ case BTN_FORWARD:
+ xbmc_button = XBMC_BUTTON_X3;
+ break;
+ case BTN_BACK:
+ xbmc_button = XBMC_BUTTON_X4;
+ break;
+ default:
+ break;
+ }
+
+ XBMC_Event event = {};
+
+ event.type = pressed ? XBMC_MOUSEBUTTONDOWN : XBMC_MOUSEBUTTONUP;
+ event.button.button = xbmc_button;
+ event.button.x = static_cast<uint16_t>(m_pos.X);
+ event.button.y = static_cast<uint16_t>(m_pos.Y);
+
+ CLog::Log(LOGDEBUG,
+ "CLibInputPointer::{} - event.type: {}, event.button.button: {}, event.button.x: {}, "
+ "event.button.y: {}",
+ __FUNCTION__, event.type, event.button.button, event.button.x, event.button.y);
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(event);
+}
+
+void CLibInputPointer::ProcessMotion(libinput_event_pointer *e)
+{
+ const double dx = libinput_event_pointer_get_dx(e);
+ const double dy = libinput_event_pointer_get_dy(e);
+
+ m_pos.X += static_cast<int>(dx);
+ m_pos.Y += static_cast<int>(dy);
+
+ // limit the mouse to the screen width
+ m_pos.X = std::min(CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), m_pos.X);
+ m_pos.X = std::max(0, m_pos.X);
+
+ // limit the mouse to the screen height
+ m_pos.Y = std::min(CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), m_pos.Y);
+ m_pos.Y = std::max(0, m_pos.Y);
+
+ XBMC_Event event = {};
+
+ event.type = XBMC_MOUSEMOTION;
+ event.motion.x = static_cast<uint16_t>(m_pos.X);
+ event.motion.y = static_cast<uint16_t>(m_pos.Y);
+
+ CLog::Log(LOGDEBUG,
+ "CLibInputPointer::{} - event.type: {}, event.motion.x: {}, event.motion.y: {}",
+ __FUNCTION__, event.type, event.motion.x, event.motion.y);
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(event);
+}
+
+void CLibInputPointer::ProcessMotionAbsolute(libinput_event_pointer *e)
+{
+ m_pos.X = static_cast<int>(libinput_event_pointer_get_absolute_x_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth()));
+ m_pos.Y = static_cast<int>(libinput_event_pointer_get_absolute_y_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()));
+
+ XBMC_Event event = {};
+ event.type = XBMC_MOUSEMOTION;
+ event.motion.x = static_cast<uint16_t>(m_pos.X);
+ event.motion.y = static_cast<uint16_t>(m_pos.Y);
+
+ CLog::Log(LOGDEBUG,
+ "CLibInputPointer::{} - event.type: {}, event.motion.x: {}, event.motion.y: {}",
+ __FUNCTION__, event.type, event.motion.x, event.motion.y);
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(event);
+}
+
+void CLibInputPointer::ProcessAxis(libinput_event_pointer *e)
+{
+ unsigned char scroll = 0;
+ if (!libinput_event_pointer_has_axis(e, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL))
+ return;
+
+ const double v = libinput_event_pointer_get_axis_value(e, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+ if (v < 0)
+ scroll = XBMC_BUTTON_WHEELUP;
+ else
+ scroll = XBMC_BUTTON_WHEELDOWN;
+
+ XBMC_Event event = {};
+
+ event.type = XBMC_MOUSEBUTTONDOWN;
+ event.button.button = scroll;
+ event.button.x = static_cast<uint16_t>(m_pos.X);
+ event.button.y = static_cast<uint16_t>(m_pos.Y);
+
+ CLog::Log(LOGDEBUG, "CLibInputPointer::{} - scroll: {}, event.button.x: {}, event.button.y: {}",
+ __FUNCTION__, scroll == XBMC_BUTTON_WHEELUP ? "up" : "down", event.button.x,
+ event.button.y);
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(event);
+
+ event.type = XBMC_MOUSEBUTTONUP;
+
+ if (appPort)
+ appPort->OnEvent(event);
+}
diff --git a/xbmc/platform/linux/input/LibInputPointer.h b/xbmc/platform/linux/input/LibInputPointer.h
new file mode 100644
index 0000000..608b438
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputPointer.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <libinput.h>
+
+struct pos
+{
+ int X;
+ int Y;
+};
+
+class CLibInputPointer
+{
+public:
+ CLibInputPointer() = default;
+ ~CLibInputPointer() = default;
+
+ void ProcessButton(libinput_event_pointer *e);
+ void ProcessMotion(libinput_event_pointer *e);
+ void ProcessMotionAbsolute(libinput_event_pointer *e);
+ void ProcessAxis(libinput_event_pointer *e);
+
+private:
+ struct pos m_pos = { 0, 0 };
+};
diff --git a/xbmc/platform/linux/input/LibInputSettings.cpp b/xbmc/platform/linux/input/LibInputSettings.cpp
new file mode 100644
index 0000000..25a916e
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputSettings.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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 "LibInputSettings.h"
+
+#include "LibInputHandler.h"
+#include "ServiceBroker.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "settings/lib/SettingsManager.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+const std::string CLibInputSettings::SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT = "input.libinputkeyboardlayout";
+static std::vector<StringSettingOption> layouts;
+
+namespace
+{
+ inline bool LayoutSort(const StringSettingOption& i, const StringSettingOption& j)
+ {
+ return (i.value < j.value);
+ }
+} // unnamed namespace
+
+CLibInputSettings::CLibInputSettings(CLibInputHandler *handler) :
+ m_libInputHandler(handler)
+{
+ const auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return;
+
+ auto settingsManager = settings->GetSettingsManager();
+ if (!settingsManager)
+ return;
+
+ auto setting = settings->GetSetting(SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT);
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "Failed to load setting for: {}", SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT);
+ return;
+ }
+
+ setting->SetVisible(true);
+
+ std::set<std::string> settingSet;
+ settingSet.insert(SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT);
+ settingsManager->RegisterCallback(this, settingSet);
+ settingsManager->RegisterSettingOptionsFiller("libinputkeyboardlayout",
+ SettingOptionsKeyboardLayoutsFiller);
+
+ /* load the keyboard layouts from xkeyboard-config */
+ std::string xkbFile("/usr/share/X11/xkb/rules/base.xml");
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(xkbFile))
+ {
+ CLog::Log(LOGWARNING, "CLibInputSettings: unable to open: {}", xkbFile);
+ return;
+ }
+
+ const TiXmlElement* rootElement = xmlDoc.RootElement();
+ if (!rootElement)
+ {
+ CLog::Log(LOGWARNING, "CLibInputSettings: missing or invalid XML root element in: {}", xkbFile);
+ return;
+ }
+
+ if (rootElement->ValueStr() != "xkbConfigRegistry")
+ {
+ CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML root element {} in: {}",
+ rootElement->Value(), xkbFile);
+ return;
+ }
+
+ const TiXmlElement* layoutListElement = rootElement->FirstChildElement("layoutList");
+ if (!layoutListElement)
+ {
+ CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}",
+ layoutListElement->Value(), xkbFile);
+ return;
+ }
+
+ const TiXmlElement* layoutElement = layoutListElement->FirstChildElement("layout");
+ while (layoutElement)
+ {
+ const TiXmlElement* configElement = layoutElement->FirstChildElement("configItem");
+ if (!configElement)
+ {
+ CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}",
+ layoutListElement->Value(), xkbFile);
+ return;
+ }
+
+ const TiXmlElement* nameElement = configElement->FirstChildElement("name");
+ if (!nameElement)
+ {
+ CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}",
+ configElement->Value(), xkbFile);
+ return;
+ }
+
+ const TiXmlElement* descriptionElement = configElement->FirstChildElement("description");
+ if (!descriptionElement)
+ {
+ CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}",
+ configElement->Value(), xkbFile);
+ return;
+ }
+
+ std::string layout = nameElement->GetText();
+ std::string layoutDescription = descriptionElement->GetText();
+
+ if (!layout.empty() && !layoutDescription.empty())
+ layouts.emplace_back(StringSettingOption(layoutDescription, layout));
+
+ layoutElement = layoutElement->NextSiblingElement();
+ }
+
+ std::sort(layouts.begin(), layouts.end(), LayoutSort);
+}
+
+CLibInputSettings::~CLibInputSettings()
+{
+ const auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return;
+
+ const std::shared_ptr<CSettings> settings = settingsComponent->GetSettings();
+ if (!settings)
+ return;
+
+ settings->GetSettingsManager()->UnregisterSettingOptionsFiller("libinputkeyboardlayout");
+ settings->GetSettingsManager()->UnregisterCallback(this);
+}
+
+void CLibInputSettings::SettingOptionsKeyboardLayoutsFiller(
+ const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ list = layouts;
+}
+
+void CLibInputSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == nullptr)
+ return;
+
+ const std::string &settingId = setting->GetId();
+ if (settingId == SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT)
+ {
+ std::string layout = std::dynamic_pointer_cast<const CSettingString>(setting)->GetValue();
+ m_libInputHandler->SetKeymap(layout);
+ }
+}
diff --git a/xbmc/platform/linux/input/LibInputSettings.h b/xbmc/platform/linux/input/LibInputSettings.h
new file mode 100644
index 0000000..83d5edf
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputSettings.h
@@ -0,0 +1,35 @@
+/*
+ * 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 "settings/Settings.h"
+#include "settings/lib/ISettingCallback.h"
+#include "settings/lib/ISettingsHandler.h"
+
+#include <memory>
+#include <vector>
+
+class CLibInputHandler;
+struct StringSettingOption;
+
+class CLibInputSettings : public ISettingCallback, public ISettingsHandler
+{
+public:
+ static const std::string SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT;
+
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+ static void SettingOptionsKeyboardLayoutsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
+
+ CLibInputSettings(CLibInputHandler *handler);
+ ~CLibInputSettings() override;
+
+private:
+ CLibInputHandler *m_libInputHandler{nullptr};
+};
diff --git a/xbmc/platform/linux/input/LibInputTouch.cpp b/xbmc/platform/linux/input/LibInputTouch.cpp
new file mode 100644
index 0000000..545d99a
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputTouch.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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 "LibInputTouch.h"
+
+#include "ServiceBroker.h"
+#include "input/touch/generic/GenericTouchActionHandler.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+static inline CPoint GetPos(libinput_event_touch *e)
+{
+ const double x = libinput_event_touch_get_x_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth());
+ const double y = libinput_event_touch_get_y_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight());
+
+ CLog::Log(LOGDEBUG, "CLibInputTouch::{} - x: {:f} y: {:f}", __FUNCTION__, x, y);
+
+ return CPoint(x, y);
+}
+
+CLibInputTouch::CLibInputTouch()
+{
+ m_points.reserve(2);
+ CGenericTouchInputHandler::GetInstance().RegisterHandler(&CGenericTouchActionHandler::GetInstance());
+}
+
+void CLibInputTouch::CheckSlot(int slot)
+{
+ if (slot + 1 > static_cast<int>(m_points.size()))
+ m_points.resize(slot + 1, std::make_pair(TouchInputUnchanged, CPoint(0, 0)));
+}
+
+TouchInput CLibInputTouch::GetEvent(int slot)
+{
+ CheckSlot(slot);
+ return m_points.at(slot).first;
+}
+
+void CLibInputTouch::SetEvent(int slot, TouchInput event)
+{
+ CheckSlot(slot);
+ m_points.at(slot).first = event;
+}
+
+void CLibInputTouch::SetPosition(int slot, CPoint point)
+{
+ CheckSlot(slot);
+ m_points.at(slot).second = point;
+}
+
+void CLibInputTouch::ProcessTouchDown(libinput_event_touch *e)
+{
+ int slot = libinput_event_touch_get_seat_slot(e);
+
+ SetPosition(slot, GetPos(e));
+ SetEvent(slot, TouchInputDown);
+ CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input down", __FUNCTION__);
+}
+
+void CLibInputTouch::ProcessTouchMotion(libinput_event_touch *e)
+{
+ int slot = libinput_event_touch_get_seat_slot(e);
+ uint64_t nanotime = libinput_event_touch_get_time_usec(e) * 1000LL;
+
+ SetPosition(slot, GetPos(e));
+
+ if (GetEvent(slot) != TouchInputDown)
+ SetEvent(slot, TouchInputMove);
+ CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input move", __FUNCTION__);
+
+ CGenericTouchInputHandler::GetInstance().UpdateTouchPointer(slot, GetX(slot), GetY(slot), nanotime);
+}
+
+void CLibInputTouch::ProcessTouchUp(libinput_event_touch *e)
+{
+ int slot = libinput_event_touch_get_seat_slot(e);
+
+ SetEvent(slot, TouchInputUp);
+ CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input up", __FUNCTION__);
+}
+
+void CLibInputTouch::ProcessTouchCancel(libinput_event_touch *e)
+{
+ int slot = libinput_event_touch_get_seat_slot(e);
+ uint64_t nanotime = libinput_event_touch_get_time_usec(e) * 1000LL;
+
+ CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input cancel", __FUNCTION__);
+ CGenericTouchInputHandler::GetInstance().HandleTouchInput(TouchInputAbort, GetX(slot), GetY(slot), nanotime, slot);
+}
+
+void CLibInputTouch::ProcessTouchFrame(libinput_event_touch *e)
+{
+ uint64_t nanotime = libinput_event_touch_get_time_usec(e) * 1000LL;
+
+ for (size_t slot = 0; slot < m_points.size(); ++slot)
+ {
+ CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input frame: event {}", __FUNCTION__,
+ GetEvent(slot));
+ CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input frame: slot {}", __FUNCTION__, slot);
+ CGenericTouchInputHandler::GetInstance().HandleTouchInput(GetEvent(slot), GetX(slot), GetY(slot), nanotime, slot);
+ SetEvent(slot, TouchInputUnchanged);
+ }
+}
+
diff --git a/xbmc/platform/linux/input/LibInputTouch.h b/xbmc/platform/linux/input/LibInputTouch.h
new file mode 100644
index 0000000..9805599
--- /dev/null
+++ b/xbmc/platform/linux/input/LibInputTouch.h
@@ -0,0 +1,40 @@
+/*
+ * 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 "input/touch/generic/GenericTouchInputHandler.h"
+#include "utils/Geometry.h"
+
+#include <vector>
+
+#include <libinput.h>
+
+struct libinput_event_touch;
+struct libinput_device;
+
+class CLibInputTouch
+{
+public:
+ CLibInputTouch();
+ void ProcessTouchDown(libinput_event_touch *e);
+ void ProcessTouchMotion(libinput_event_touch *e);
+ void ProcessTouchUp(libinput_event_touch *e);
+ void ProcessTouchCancel(libinput_event_touch *e);
+ void ProcessTouchFrame(libinput_event_touch *e);
+
+private:
+ void CheckSlot(int slot);
+ TouchInput GetEvent(int slot);
+ void SetEvent(int slot, TouchInput event);
+ void SetPosition(int slot, CPoint point);
+ int GetX(int slot) { return m_points.at(slot).second.x; }
+ int GetY(int slot) { return m_points.at(slot).second.y; }
+
+ std::vector<std::pair<TouchInput, CPoint>> m_points{std::make_pair(TouchInputUnchanged, CPoint(0, 0))};
+};
diff --git a/xbmc/platform/linux/network/CMakeLists.txt b/xbmc/platform/linux/network/CMakeLists.txt
new file mode 100644
index 0000000..44aa2e7
--- /dev/null
+++ b/xbmc/platform/linux/network/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES NetworkLinux.cpp)
+set(HEADERS NetworkLinux.h)
+
+core_add_library(platform_linux_network)
diff --git a/xbmc/platform/linux/network/NetworkLinux.cpp b/xbmc/platform/linux/network/NetworkLinux.cpp
new file mode 100644
index 0000000..e9a936a
--- /dev/null
+++ b/xbmc/platform/linux/network/NetworkLinux.cpp
@@ -0,0 +1,223 @@
+/*
+ * 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 "NetworkLinux.h"
+
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <errno.h>
+#include <utility>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <resolv.h>
+#include <sys/ioctl.h>
+
+CNetworkInterfaceLinux::CNetworkInterfaceLinux(CNetworkPosix* network,
+ std::string interfaceName,
+ char interfaceMacAddrRaw[6])
+ : CNetworkInterfacePosix(network, std::move(interfaceName), interfaceMacAddrRaw)
+{
+}
+
+std::string CNetworkInterfaceLinux::GetCurrentDefaultGateway() const
+{
+ std::string result;
+
+ FILE* fp = fopen("/proc/net/route", "r");
+ if (!fp)
+ {
+ // TBD: Error
+ return result;
+ }
+
+ char* line = NULL;
+ char iface[16];
+ char dst[128];
+ char gateway[128];
+ size_t linel = 0;
+ int n;
+ int linenum = 0;
+ while (getdelim(&line, &linel, '\n', fp) > 0)
+ {
+ // skip first two lines
+ if (linenum++ < 1)
+ continue;
+
+ // search where the word begins
+ n = sscanf(line, "%15s %127s %127s", iface, dst, gateway);
+
+ if (n < 3)
+ continue;
+
+ if (strcmp(iface, m_interfaceName.c_str()) == 0 && strcmp(dst, "00000000") == 0 &&
+ strcmp(gateway, "00000000") != 0)
+ {
+ unsigned char gatewayAddr[4];
+ int len = CNetworkBase::ParseHex(gateway, gatewayAddr);
+ if (len == 4)
+ {
+ struct in_addr in;
+ in.s_addr = (gatewayAddr[0] << 24) | (gatewayAddr[1] << 16) | (gatewayAddr[2] << 8) |
+ (gatewayAddr[3]);
+ result = inet_ntoa(in);
+ break;
+ }
+ }
+ }
+ free(line);
+ fclose(fp);
+
+ return result;
+}
+
+bool CNetworkInterfaceLinux::GetHostMacAddress(unsigned long host_ip, std::string& mac) const
+{
+ struct arpreq areq;
+ struct sockaddr_in* sin;
+
+ memset(&areq, 0x0, sizeof(areq));
+
+ sin = (struct sockaddr_in*)&areq.arp_pa;
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = host_ip;
+
+ sin = (struct sockaddr_in*)&areq.arp_ha;
+ sin->sin_family = ARPHRD_ETHER;
+
+ strncpy(areq.arp_dev, m_interfaceName.c_str(), sizeof(areq.arp_dev));
+ areq.arp_dev[sizeof(areq.arp_dev) - 1] = '\0';
+
+ int result = ioctl(m_network->GetSocket(), SIOCGARP, (caddr_t)&areq);
+
+ if (result != 0)
+ {
+ // CLog::Log(LOGERROR, "{} - GetHostMacAddress/ioctl failed with errno ({})", __FUNCTION__, errno);
+ return false;
+ }
+
+ struct sockaddr* res = &areq.arp_ha;
+ mac = StringUtils::Format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", (uint8_t)res->sa_data[0],
+ (uint8_t)res->sa_data[1], (uint8_t)res->sa_data[2],
+ (uint8_t)res->sa_data[3], (uint8_t)res->sa_data[4],
+ (uint8_t)res->sa_data[5]);
+
+ for (int i = 0; i < 6; ++i)
+ if (res->sa_data[i])
+ return true;
+
+ return false;
+}
+
+std::unique_ptr<CNetworkBase> CNetworkBase::GetNetwork()
+{
+ return std::make_unique<CNetworkLinux>();
+}
+
+CNetworkLinux::CNetworkLinux() : CNetworkPosix()
+{
+ queryInterfaceList();
+}
+
+void CNetworkLinux::GetMacAddress(const std::string& interfaceName, char rawMac[6])
+{
+ memset(rawMac, 0, 6);
+
+ struct ifreq ifr;
+ strcpy(ifr.ifr_name, interfaceName.c_str());
+ if (ioctl(GetSocket(), SIOCGIFHWADDR, &ifr) >= 0)
+ {
+ memcpy(rawMac, ifr.ifr_hwaddr.sa_data, 6);
+ }
+}
+
+void CNetworkLinux::queryInterfaceList()
+{
+ char macAddrRaw[6];
+ m_interfaces.clear();
+
+ FILE* fp = fopen("/proc/net/dev", "r");
+ if (!fp)
+ {
+ // TBD: Error
+ return;
+ }
+
+ char* line = NULL;
+ size_t linel = 0;
+ int n;
+ char* p;
+ int linenum = 0;
+ while (getdelim(&line, &linel, '\n', fp) > 0)
+ {
+ // skip first two lines
+ if (linenum++ < 2)
+ continue;
+
+ // search where the word begins
+ p = line;
+ while (isspace(*p))
+ ++p;
+
+ // read word until :
+ n = strcspn(p, ": \t");
+ p[n] = 0;
+
+ // save the result
+ std::string interfaceName = p;
+ GetMacAddress(interfaceName, macAddrRaw);
+
+ // only add interfaces with non-zero mac addresses
+ if (macAddrRaw[0] || macAddrRaw[1] || macAddrRaw[2] || macAddrRaw[3] || macAddrRaw[4] ||
+ macAddrRaw[5])
+ m_interfaces.push_back(new CNetworkInterfaceLinux(this, interfaceName, macAddrRaw));
+ }
+ free(line);
+ fclose(fp);
+}
+
+std::vector<std::string> CNetworkLinux::GetNameServers()
+{
+ std::vector<std::string> result;
+
+ res_init();
+
+ for (int i = 0; i < _res.nscount; i++)
+ {
+ std::string ns = inet_ntoa(_res.nsaddr_list[i].sin_addr);
+ result.push_back(ns);
+ }
+ return result;
+}
+
+bool CNetworkLinux::PingHost(unsigned long remote_ip, unsigned int timeout_ms)
+{
+ char cmd_line[64];
+
+ struct in_addr host_ip;
+ host_ip.s_addr = remote_ip;
+
+ sprintf(cmd_line, "ping -c 1 -w %d %s", timeout_ms / 1000 + (timeout_ms % 1000) != 0,
+ inet_ntoa(host_ip));
+
+ int status = -1;
+ status = system(cmd_line);
+ int result = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+
+ // http://linux.about.com/od/commands/l/blcmdl8_ping.htm ;
+ // 0 reply
+ // 1 no reply
+ // else some error
+
+ if (result < 0 || result > 1)
+ CLog::Log(LOGERROR, "Ping fail : status = {}, errno = {} : '{}'", status, errno, cmd_line);
+
+ return result == 0;
+}
diff --git a/xbmc/platform/linux/network/NetworkLinux.h b/xbmc/platform/linux/network/NetworkLinux.h
new file mode 100644
index 0000000..dc7b4c6
--- /dev/null
+++ b/xbmc/platform/linux/network/NetworkLinux.h
@@ -0,0 +1,40 @@
+/*
+ * 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 "platform/posix/network/NetworkPosix.h"
+
+#include <string>
+#include <vector>
+
+class CNetworkInterfaceLinux : public CNetworkInterfacePosix
+{
+public:
+ CNetworkInterfaceLinux(CNetworkPosix* network,
+ std::string interfaceName,
+ char interfaceMacAddrRaw[6]);
+ ~CNetworkInterfaceLinux() override = default;
+
+ std::string GetCurrentDefaultGateway() const override;
+ bool GetHostMacAddress(unsigned long host, std::string& mac) const override;
+};
+
+class CNetworkLinux : public CNetworkPosix
+{
+public:
+ CNetworkLinux();
+ ~CNetworkLinux() override = default;
+
+ bool PingHost(unsigned long host, unsigned int timeout_ms = 2000) override;
+ std::vector<std::string> GetNameServers(void) override;
+
+private:
+ void GetMacAddress(const std::string& interfaceName, char macAddrRaw[6]) override;
+ void queryInterfaceList() override;
+};
diff --git a/xbmc/platform/linux/network/zeroconf/CMakeLists.txt b/xbmc/platform/linux/network/zeroconf/CMakeLists.txt
new file mode 100644
index 0000000..b31ab73
--- /dev/null
+++ b/xbmc/platform/linux/network/zeroconf/CMakeLists.txt
@@ -0,0 +1,8 @@
+if(AVAHI_FOUND)
+ set(SOURCES ZeroconfAvahi.cpp
+ ZeroconfBrowserAvahi.cpp)
+ set(HEADERS ZeroconfAvahi.h
+ ZeroconfBrowserAvahi.h)
+
+ core_add_library(platform_linux_network_zeroconf)
+endif()
diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.cpp b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.cpp
new file mode 100644
index 0000000..dea038a
--- /dev/null
+++ b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.cpp
@@ -0,0 +1,434 @@
+/*
+ * 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 "ZeroconfAvahi.h"
+
+#include "utils/log.h"
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <avahi-client/client.h>
+#include <avahi-common/alternative.h>
+#include <avahi-common/error.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/thread-watch.h>
+#include <unistd.h> //gethostname
+
+#include "PlatformDefs.h"
+
+///helper RAII-struct to block event loop for modifications
+struct ScopedEventLoopBlock
+{
+ explicit ScopedEventLoopBlock(AvahiThreadedPoll* fp_poll):mp_poll(fp_poll)
+ {
+ avahi_threaded_poll_lock(mp_poll);
+ }
+
+ ~ScopedEventLoopBlock()
+ {
+ avahi_threaded_poll_unlock(mp_poll);
+ }
+private:
+ AvahiThreadedPoll* mp_poll;
+};
+
+///helper to store information on howto create an avahi-group to publish
+struct CZeroconfAvahi::ServiceInfo
+{
+ ServiceInfo(const std::string& fcr_type, const std::string& fcr_name,
+ unsigned int f_port, AvahiStringList *txt, AvahiEntryGroup* fp_group = 0):
+ m_type(fcr_type), m_name(fcr_name), m_port(f_port), mp_txt(txt), mp_group(fp_group)
+ {
+ }
+
+ std::string m_type;
+ std::string m_name;
+ unsigned int m_port;
+ AvahiStringList* mp_txt;
+ AvahiEntryGroup* mp_group;
+};
+
+CZeroconfAvahi::CZeroconfAvahi()
+{
+ if (! (mp_poll = avahi_threaded_poll_new()))
+ {
+ CLog::Log(LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create threaded poll object");
+ //! @todo throw exception?
+ return;
+ }
+
+ if (!createClient())
+ {
+ CLog::Log(LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create client");
+ //yeah, what if not? but should always succeed
+ }
+
+ //start event loop thread
+ if (avahi_threaded_poll_start(mp_poll) < 0)
+ {
+ CLog::Log(LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Failed to start avahi client thread");
+ }
+}
+
+CZeroconfAvahi::~CZeroconfAvahi()
+{
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::~CZeroconfAvahi() Going down! cleaning up...");
+
+ if (mp_poll)
+ {
+ //normally we would stop the avahi thread here and do our work, but
+ //it looks like this does not work -> www.avahi.org/ticket/251
+ //so instead of calling
+ //avahi_threaded_poll_stop(mp_poll);
+ //we set m_shutdown=true, post an event and wait for it to stop itself
+ struct timeval tv = { 0, 0 }; //! @todo does tv survive the thread?
+ AvahiTimeout* lp_timeout;
+ {
+ ScopedEventLoopBlock l_block(mp_poll);
+ const AvahiPoll* cp_apoll = avahi_threaded_poll_get(mp_poll);
+ m_shutdown = true;
+ lp_timeout = cp_apoll->timeout_new(cp_apoll,
+ &tv,
+ shutdownCallback,
+ this);
+ }
+
+ //now wait for the thread to stop
+ assert(m_thread_id);
+ pthread_join(m_thread_id, NULL);
+ avahi_threaded_poll_get(mp_poll)->timeout_free(lp_timeout);
+ }
+
+ //free the client (frees all browsers, groups, ...)
+ if (mp_client)
+ avahi_client_free(mp_client);
+ if (mp_poll)
+ avahi_threaded_poll_free(mp_poll);
+}
+
+bool CZeroconfAvahi::doPublishService(const std::string& fcr_identifier,
+ const std::string& fcr_type,
+ const std::string& fcr_name,
+ unsigned int f_port,
+ const std::vector<std::pair<std::string, std::string> >& txt)
+{
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::doPublishService identifier: {} type: {} name:{} port:{}",
+ fcr_identifier, fcr_type, fcr_name, f_port);
+
+ ScopedEventLoopBlock l_block(mp_poll);
+ tServiceMap::iterator it = m_services.find(fcr_identifier);
+ if (it != m_services.end())
+ {
+ //fcr_identifier exists, no update functionality yet, so exit
+ return false;
+ }
+
+ //txt records to AvahiStringList
+ AvahiStringList *txtList = NULL;
+ for (const auto& it : txt)
+ {
+ txtList = avahi_string_list_add_pair(txtList, it.first.c_str(), it.second.c_str());
+ }
+
+ //create service info and add it to service map
+ tServiceMap::mapped_type p_service_info(new CZeroconfAvahi::ServiceInfo(fcr_type, fcr_name, f_port, txtList));
+ it = m_services.insert(it, std::make_pair(fcr_identifier, p_service_info));
+
+ //if client is already running, directly try to add the new service
+ if ( mp_client && avahi_client_get_state(mp_client) == AVAHI_CLIENT_S_RUNNING )
+ {
+ //client's already running, add this new service
+ addService(p_service_info, mp_client);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::doPublishService: client not running, queued for publishing");
+ }
+ return true;
+}
+
+bool CZeroconfAvahi::doForceReAnnounceService(const std::string& fcr_identifier)
+{
+ bool ret = false;
+ ScopedEventLoopBlock l_block(mp_poll);
+ tServiceMap::iterator it = m_services.find(fcr_identifier);
+ if (it != m_services.end() && it->second->mp_group)
+ {
+ // to force a reannounce on avahi its enough to reverse the txtrecord list
+ it->second->mp_txt = avahi_string_list_reverse(it->second->mp_txt);
+
+ // this will trigger the reannouncement
+ if ((avahi_entry_group_update_service_txt_strlst(it->second->mp_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0),
+ it->second->m_name.c_str(),
+ it->second->m_type.c_str(), NULL, it->second->mp_txt)) >= 0)
+ ret = true;
+ }
+
+ return ret;
+}
+
+bool CZeroconfAvahi::doRemoveService(const std::string& fcr_ident)
+{
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::doRemoveService named: {}", fcr_ident);
+ ScopedEventLoopBlock l_block(mp_poll);
+ tServiceMap::iterator it = m_services.find(fcr_ident);
+ if (it == m_services.end())
+ {
+ return false;
+ }
+
+ if (it->second->mp_group)
+ {
+ avahi_entry_group_free(it->second->mp_group);
+ it->second->mp_group = 0;
+ }
+
+ if(it->second->mp_txt)
+ {
+ avahi_string_list_free(it->second->mp_txt);
+ it->second->mp_txt = NULL;
+ }
+
+ m_services.erase(it);
+ return true;
+}
+
+void CZeroconfAvahi::doStop()
+{
+ ScopedEventLoopBlock l_block(mp_poll);
+ for (auto& it : m_services)
+ {
+ if (it.second->mp_group)
+ {
+ avahi_entry_group_free(it.second->mp_group);
+ it.second->mp_group = 0;
+ }
+
+ if (it.second->mp_txt)
+ {
+ avahi_string_list_free(it.second->mp_txt);
+ it.second->mp_txt = nullptr;
+ }
+ }
+ m_services.clear();
+}
+
+void CZeroconfAvahi::clientCallback(AvahiClient* fp_client, AvahiClientState f_state, void* fp_data)
+{
+ CZeroconfAvahi* p_instance = static_cast<CZeroconfAvahi*>(fp_data);
+
+ //store our thread ID and check for shutdown -> check details in destructor
+ p_instance->m_thread_id = pthread_self();
+
+ if (p_instance->m_shutdown)
+ {
+ avahi_threaded_poll_quit(p_instance->mp_poll);
+ return;
+ }
+ switch(f_state)
+ {
+ case AVAHI_CLIENT_S_RUNNING:
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::clientCallback: client is up and running");
+ p_instance->updateServices(fp_client);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ CLog::Log(LOGINFO, "CZeroconfAvahi::clientCallback: client failure. avahi-daemon stopped? Recreating client...");
+ //We were forced to disconnect from server. now free and recreate the client object
+ avahi_client_free(fp_client);
+ p_instance->mp_client = 0;
+ //freeing the client also frees all groups and browsers, pointers are undefined afterwards, so fix that now
+ for (auto& it : p_instance->m_services)
+ {
+ it.second->mp_group = 0;
+ }
+ p_instance->createClient();
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ case AVAHI_CLIENT_S_REGISTERING:
+ //HERE WE SHOULD REMOVE ALL OF OUR SERVICES AND "RESCHEDULE" them for later addition
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::clientCallback: uiuui; coll or reg, anyways, resetting groups");
+ for (const auto& it : p_instance->m_services)
+ {
+ if (it.second->mp_group)
+ avahi_entry_group_reset(it.second->mp_group);
+ }
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ CLog::Log(LOGINFO, "CZeroconfAvahi::clientCallback: avahi server not available. But may become later...");
+ break;
+ }
+}
+
+void CZeroconfAvahi::groupCallback(AvahiEntryGroup *fp_group, AvahiEntryGroupState f_state, void * fp_data)
+{
+ CZeroconfAvahi* p_instance = static_cast<CZeroconfAvahi*>(fp_data);
+ //store our thread ID and check for shutdown -> check details in destructor
+ p_instance->m_thread_id = pthread_self();
+ if (p_instance->m_shutdown)
+ {
+ avahi_threaded_poll_quit(p_instance->mp_poll);
+ return;
+ }
+
+ switch (f_state)
+ {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED :
+ // The entry group has been established successfully
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::groupCallback: Service successfully established");
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION :
+ {
+ //need to find the ServiceInfo struct for this group
+ tServiceMap::iterator it = p_instance->m_services.begin();
+ for(; it != p_instance->m_services.end(); ++it)
+ {
+ if (it->second->mp_group == fp_group)
+ break;
+ }
+ if( it != p_instance->m_services.end() ) {
+ char* alt_name = avahi_alternative_service_name( it->second->m_name.c_str() );
+ it->second->m_name = alt_name;
+ avahi_free(alt_name);
+ CLog::Log(LOGINFO, "CZeroconfAvahi::groupCallback: Service name collision. Renamed to: {}",
+ it->second->m_name);
+ p_instance->addService(it->second, p_instance->mp_client);
+ }
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ CLog::Log(LOGERROR, "CZeroconfAvahi::groupCallback: Entry group failure: {} ",
+ (fp_group)
+ ? avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(fp_group)))
+ : "Unknown");
+ //free the group and set to 0 so it may be added later
+ if (fp_group)
+ {
+ //need to find the ServiceInfo struct for this group
+ for (auto& it : p_instance->m_services)
+ {
+ if (it.second->mp_group == fp_group)
+ {
+ avahi_entry_group_free(it.second->mp_group);
+ it.second->mp_group = 0;
+ if (it.second->mp_txt)
+ {
+ avahi_string_list_free(it.second->mp_txt);
+ it.second->mp_txt = nullptr;
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ default:
+ break;
+ }
+}
+
+void CZeroconfAvahi::shutdownCallback(AvahiTimeout *fp_e, void *fp_data)
+{
+ CZeroconfAvahi* p_instance = static_cast<CZeroconfAvahi*>(fp_data);
+ //should only be called on shutdown
+ if (p_instance->m_shutdown)
+ {
+ avahi_threaded_poll_quit(p_instance->mp_poll);
+ }
+}
+
+bool CZeroconfAvahi::createClient()
+{
+ if (mp_client)
+ {
+ avahi_client_free(mp_client);
+ }
+
+ int error = 0;
+ mp_client = avahi_client_new(avahi_threaded_poll_get(mp_poll),
+ AVAHI_CLIENT_NO_FAIL, &clientCallback,this,&error);
+ if (!mp_client)
+ {
+ CLog::Log(LOGERROR, "CZeroconfAvahi::createClient() failed with {}", error);
+ return false;
+ }
+ return true;
+}
+
+void CZeroconfAvahi::updateServices(AvahiClient* fp_client)
+{
+ for (const auto& it : m_services)
+ {
+ if (!it.second->mp_group)
+ addService(it.second, fp_client);
+ }
+}
+
+void CZeroconfAvahi::addService(const tServiceMap::mapped_type& fp_service_info,
+ AvahiClient* fp_client)
+{
+ assert(fp_client);
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::addService() named: {} type: {} port:{}",
+ fp_service_info->m_name, fp_service_info->m_type, fp_service_info->m_port);
+ //create the group if it doesn't exist
+ if (!fp_service_info->mp_group)
+ {
+ if (!(fp_service_info->mp_group = avahi_entry_group_new(fp_client, &CZeroconfAvahi::groupCallback, this)))
+ {
+ CLog::Log(LOGDEBUG, "CZeroconfAvahi::addService() avahi_entry_group_new() failed: {}",
+ avahi_strerror(avahi_client_errno(fp_client)));
+ fp_service_info->mp_group = 0;
+ return;
+ }
+ }
+
+
+ // add entries to the group if it's empty
+ int ret;
+ if (avahi_entry_group_is_empty(fp_service_info->mp_group))
+ {
+ if ((ret = avahi_entry_group_add_service_strlst(fp_service_info->mp_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0),
+ fp_service_info->m_name.c_str(),
+ fp_service_info->m_type.c_str(), NULL, NULL, fp_service_info->m_port, fp_service_info->mp_txt)) < 0)
+ {
+ if (ret == AVAHI_ERR_COLLISION)
+ {
+ char* alt_name = avahi_alternative_service_name(fp_service_info->m_name.c_str());
+ fp_service_info->m_name = alt_name;
+ avahi_free(alt_name);
+ CLog::Log(LOGINFO, "CZeroconfAvahi::addService: Service name collision. Renamed to: {}",
+ fp_service_info->m_name);
+ addService(fp_service_info, fp_client);
+ return;
+ }
+ CLog::Log(LOGERROR,
+ "CZeroconfAvahi::addService(): failed to add service named:{}@$(HOSTNAME) type:{} "
+ "port:{}. Error:{} :/ FIXME!",
+ fp_service_info->m_name, fp_service_info->m_type, fp_service_info->m_port,
+ avahi_strerror(ret));
+ return;
+ }
+ }
+
+ // Tell the server to register the service
+ if ((ret = avahi_entry_group_commit(fp_service_info->mp_group)) < 0)
+ {
+ CLog::Log(LOGERROR, "CZeroconfAvahi::addService(): Failed to commit entry group! Error:{}",
+ avahi_strerror(ret));
+ //! @todo what now? reset the group? free it?
+ }
+}
diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.h b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.h
new file mode 100644
index 0000000..102df9b
--- /dev/null
+++ b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.h
@@ -0,0 +1,79 @@
+/*
+ * 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 "network/Zeroconf.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/defs.h>
+
+struct AvahiThreadedPoll;
+
+class CZeroconfAvahi : public CZeroconf
+{
+public:
+ CZeroconfAvahi();
+ ~CZeroconfAvahi() override;
+
+protected:
+ //implement base CZeroConf interface
+ bool doPublishService(const std::string& fcr_identifier,
+ const std::string& fcr_type,
+ const std::string& fcr_name,
+ unsigned int f_port,
+ const std::vector<std::pair<std::string, std::string> >& txt) override;
+
+ bool doForceReAnnounceService(const std::string& fcr_identifier) override;
+ bool doRemoveService(const std::string& fcr_ident) override;
+
+ void doStop() override;
+
+private:
+ ///this is where the client calls us if state changes
+ static void clientCallback(AvahiClient* fp_client, AvahiClientState f_state, void*);
+ ///here we get notified of group changes
+ static void groupCallback(AvahiEntryGroup *fp_group, AvahiEntryGroupState f_state, void *);
+ //shutdown callback; works around a problem in avahi < 0.6.24 see destructor for details
+ static void shutdownCallback(AvahiTimeout *fp_e, void *);
+
+ ///creates the avahi client;
+ ///@return true on success
+ bool createClient();
+
+ //don't access stuff below without stopping the client thread
+ //see http://avahi.org/wiki/RunningAvahiClientAsThread
+ //and use struct ScopedEventLoopBlock
+
+ //helper struct for holding information about creating a service / AvahiEntryGroup
+ //we have to hold that as it's needed to recreate the service
+ struct ServiceInfo;
+ typedef std::map<std::string, std::shared_ptr<ServiceInfo> > tServiceMap;
+
+ //goes through a list of todos and publishs them (takes the client a param, as it might be called from
+ // from the callbacks)
+ void updateServices(AvahiClient* fp_client);
+ //helper that actually does the work of publishing
+ void addService(const tServiceMap::mapped_type& fp_service_info, AvahiClient* fp_client);
+
+ AvahiClient* mp_client = 0;
+ AvahiThreadedPoll* mp_poll = 0;
+
+ //this holds all published and unpublished services including info on howto create them
+ tServiceMap m_services;
+
+ //2 variables below are needed for workaround of avahi bug (see destructor for details)
+ bool m_shutdown = false;
+ pthread_t m_thread_id = 0;
+};
diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.cpp b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.cpp
new file mode 100644
index 0000000..491b98b
--- /dev/null
+++ b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.cpp
@@ -0,0 +1,383 @@
+/*
+ * 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 "ZeroconfBrowserAvahi.h"
+
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "utils/log.h"
+
+#include <avahi-common/error.h>
+#include <avahi-common/malloc.h>
+namespace
+{
+///helper RAII-struct to block event loop for modifications
+struct ScopedEventLoopBlock
+{
+ explicit ScopedEventLoopBlock ( AvahiThreadedPoll* fp_poll ) : mp_poll ( fp_poll )
+ {
+ avahi_threaded_poll_lock ( mp_poll );
+ }
+
+ ~ScopedEventLoopBlock()
+ {
+ avahi_threaded_poll_unlock ( mp_poll );
+ }
+private:
+ AvahiThreadedPoll* mp_poll;
+};
+}
+
+CZeroconfBrowserAvahi::CZeroconfBrowserAvahi()
+{
+ if ( ! ( mp_poll = avahi_threaded_poll_new() ) )
+ {
+ CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create threaded poll object" );
+ //! @todo throw exception? can this even happen?
+ return;
+ }
+
+ if ( !createClient() )
+ {
+ CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create client" );
+ //yeah, what if not? but should always succeed (as client_no_fail or something is passed)
+ }
+
+ //start event loop thread
+ if ( avahi_threaded_poll_start ( mp_poll ) < 0 )
+ {
+ CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Failed to start avahi client thread" );
+ }
+}
+
+CZeroconfBrowserAvahi::~CZeroconfBrowserAvahi()
+{
+ CLog::Log ( LOGDEBUG, "CZeroconfAvahi::~CZeroconfAvahi() Going down! cleaning up..." );
+ if ( mp_poll )
+ {
+ //stop avahi polling thread
+ avahi_threaded_poll_stop(mp_poll);
+ }
+ //free the client (frees all browsers, groups, ...)
+ if ( mp_client )
+ avahi_client_free ( mp_client );
+ if ( mp_poll )
+ avahi_threaded_poll_free ( mp_poll );
+}
+
+bool CZeroconfBrowserAvahi::doAddServiceType ( const std::string& fcr_service_type )
+{
+ ScopedEventLoopBlock lock ( mp_poll );
+ tBrowserMap::iterator it = m_browsers.find ( fcr_service_type );
+ if ( it != m_browsers.end() )
+ return false;
+ else
+ it = m_browsers.insert ( std::make_pair ( fcr_service_type, ( AvahiServiceBrowser* ) 0 ) ).first;
+
+ //if the client is running, we directly create a browser for the service here
+ if ( mp_client && avahi_client_get_state ( mp_client ) == AVAHI_CLIENT_S_RUNNING )
+ {
+ AvahiServiceBrowser* browser = createServiceBrowser ( fcr_service_type, mp_client, this);
+ if ( !browser )
+ {
+ m_browsers.erase ( it );
+ return false;
+ }
+ else
+ {
+ it->second = browser;
+ return true;
+ }
+ }
+ else
+ {
+ CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::doAddServiceType client not available. service browsing queued" );
+ return true;
+ }
+}
+
+bool CZeroconfBrowserAvahi::doRemoveServiceType ( const std::string& fcr_service_type )
+{
+ ScopedEventLoopBlock lock ( mp_poll );
+ tBrowserMap::iterator it = m_browsers.find ( fcr_service_type );
+ if ( it == m_browsers.end() )
+ return false;
+ else
+ {
+ if ( it->second )
+ {
+ avahi_service_browser_free ( it->second );
+ m_all_for_now_browsers.erase ( it->second );
+ }
+ m_browsers.erase ( it );
+ //remove this serviceType from the list of discovered services
+ for (auto itr = m_discovered_services.begin(); itr != m_discovered_services.end();)
+ {
+ if (itr->first.GetType() == fcr_service_type)
+ itr = m_discovered_services.erase(itr);
+ else
+ ++itr;
+ }
+ }
+ return true;
+}
+
+std::vector<CZeroconfBrowser::ZeroconfService> CZeroconfBrowserAvahi::doGetFoundServices()
+{
+ std::vector<CZeroconfBrowser::ZeroconfService> ret;
+ ScopedEventLoopBlock lock ( mp_poll );
+ ret.reserve ( m_discovered_services.size() );
+ for (const auto& it : m_discovered_services)
+ ret.push_back(it.first);
+ return ret;
+}
+
+bool CZeroconfBrowserAvahi::doResolveService ( CZeroconfBrowser::ZeroconfService& fr_service, double f_timeout )
+{
+ {
+ //wait for lock on event-loop to schedule resolving
+ ScopedEventLoopBlock lock ( mp_poll );
+ //avahi can only resolve already discovered services, as it needs info from there
+ tDiscoveredServices::const_iterator it = m_discovered_services.find( fr_service );
+ if ( it == m_discovered_services.end() )
+ {
+ CLog::Log ( LOGERROR, "CZeroconfBrowserAvahi::doResolveService called with undiscovered service, resolving is NOT possible" );
+ return false;
+ }
+ //start resolving
+ m_resolving_service = fr_service;
+ m_resolved_event.Reset();
+ if ( !avahi_service_resolver_new ( mp_client, it->second.interface, it->second.protocol,
+ it->first.GetName().c_str(), it->first.GetType().c_str(), it->first.GetDomain().c_str(),
+ AVAHI_PROTO_UNSPEC, AvahiLookupFlags ( 0 ), resolveCallback, this ) )
+ {
+ CLog::Log(LOGERROR,
+ "CZeroconfBrowserAvahi::doResolveService Failed to resolve service '{}': {}\n",
+ it->first.GetName(), avahi_strerror(avahi_client_errno(mp_client)));
+ return false;
+ }
+ } // end of this block releases lock of eventloop
+
+ //wait for resolve to return or timeout
+ m_resolved_event.Wait(std::chrono::duration<double, std::milli>(f_timeout * 1000));
+ {
+ ScopedEventLoopBlock lock ( mp_poll );
+ fr_service = m_resolving_service;
+ return (!fr_service.GetIP().empty());
+ }
+}
+
+void CZeroconfBrowserAvahi::clientCallback ( AvahiClient* fp_client, AvahiClientState f_state, void* fp_data )
+{
+ CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( fp_data );
+ switch ( f_state )
+ {
+ case AVAHI_CLIENT_S_RUNNING:
+ {
+ CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::clientCallback: client is up and running" );
+ for (auto& it : p_instance->m_browsers)
+ {
+ assert(!it.second);
+ it.second = createServiceBrowser(it.first, fp_client, fp_data);
+ }
+ break;
+ }
+ case AVAHI_CLIENT_FAILURE:
+ {
+ CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::clientCallback: client failure. avahi-daemon stopped? Recreating client..." );
+ //We were forced to disconnect from server. now free and recreate the client object
+ avahi_client_free ( fp_client );
+ p_instance->mp_client = 0;
+ //freeing the client also frees all groups and browsers, pointers are undefined afterwards, so fix that now
+ for (auto& it : p_instance->m_browsers)
+ it.second = (AvahiServiceBrowser*)0;
+ //clean the list of discovered services and update gui (if someone is interested)
+ p_instance->m_discovered_services.clear();
+ CGUIMessage message ( GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH );
+ message.SetStringParam ( "zeroconf://" );
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage ( message );
+ p_instance->createClient();
+ break;
+ }
+ case AVAHI_CLIENT_S_COLLISION:
+ case AVAHI_CLIENT_S_REGISTERING:
+ //HERE WE SHOULD REMOVE ALL OF OUR SERVICES AND "RESCHEDULE" them for later addition
+ CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::clientCallback: This should not happen" );
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::clientCallback: avahi server not available. But may become later..." );
+ break;
+ }
+}
+void CZeroconfBrowserAvahi::browseCallback (
+ AvahiServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event,
+ const char *name, const char *type, const char *domain,
+ AvahiLookupResultFlags flags, void* fp_data )
+{
+ CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( fp_data );
+ assert ( browser );
+ bool update_gui = false;
+ /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
+ switch ( event )
+ {
+ case AVAHI_BROWSER_FAILURE:
+ CLog::Log(LOGERROR, "CZeroconfBrowserAvahi::browseCallback error: {}\n",
+ avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(browser))));
+ //! @todo implement
+ return;
+ case AVAHI_BROWSER_NEW:
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "CZeroconfBrowserAvahi::browseCallback NEW: service '{}' of type '{}' in domain '{}'\n",
+ name, type, domain);
+ //store the service
+ ZeroconfService service(name, type, domain);
+ AvahiSpecificInfos info;
+ info.interface = interface;
+ info.protocol = protocol;
+ p_instance->m_discovered_services.insert ( std::make_pair ( service, info ) );
+ //if this browser already sent the all for now message, we need to update the gui now
+ if( p_instance->m_all_for_now_browsers.find(browser) != p_instance->m_all_for_now_browsers.end() )
+ update_gui = true;
+ break;
+ }
+ case AVAHI_BROWSER_REMOVE:
+ {
+ //remove the service
+ ZeroconfService service(name, type, domain);
+ p_instance->m_discovered_services.erase ( service );
+ CLog::Log(LOGDEBUG,
+ "CZeroconfBrowserAvahi::browseCallback REMOVE: service '{}' of type '{}' in "
+ "domain '{}'\n",
+ name, type, domain);
+ //if this browser already sent the all for now message, we need to update the gui now
+ if( p_instance->m_all_for_now_browsers.find(browser) != p_instance->m_all_for_now_browsers.end() )
+ update_gui = true;
+ break;
+ }
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ //do we need that?
+ break;
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ CLog::Log(LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback all for now (service = {})", type);
+ //if this browser already sent the all for now message, we need to update the gui now
+ bool success = p_instance->m_all_for_now_browsers.insert(browser).second;
+ if(!success)
+ CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback AVAHI_BROWSER_ALL_FOR_NOW sent twice?!");
+ update_gui = true;
+ break;
+ }
+ if ( update_gui )
+ {
+ CGUIMessage message ( GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH );
+ message.SetStringParam ( "zeroconf://" );
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage ( message );
+ CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback sent gui update for path zeroconf://" );
+ }
+}
+
+CZeroconfBrowser::ZeroconfService::tTxtRecordMap GetTxtRecords(AvahiStringList *txt)
+{
+ AvahiStringList *i = NULL;
+ CZeroconfBrowser::ZeroconfService::tTxtRecordMap recordMap;
+
+ for( i = txt; i; i = i->next )
+ {
+ char *key, *value;
+
+ if( avahi_string_list_get_pair( i, &key, &value, NULL ) < 0 )
+ continue;
+
+ recordMap.insert(
+ std::make_pair(
+ std::string(key),
+ std::string(value)
+ )
+ );
+
+ if( key )
+ avahi_free(key);
+ if( value )
+ avahi_free(value);
+ }
+ return recordMap;
+}
+
+void CZeroconfBrowserAvahi::resolveCallback(
+ AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event,
+ const char *name, const char *type, const char *domain, const char *host_name,
+ const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata )
+{
+ assert ( r );
+ assert ( userdata );
+ CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( userdata );
+ switch ( event )
+ {
+ case AVAHI_RESOLVER_FAILURE:
+ CLog::Log(LOGERROR,
+ "CZeroconfBrowserAvahi::resolveCallback Failed to resolve service '{}' of type "
+ "'{}' in domain '{}': {}\n",
+ name, type, domain,
+ avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+ break;
+ case AVAHI_RESOLVER_FOUND:
+ {
+ char a[AVAHI_ADDRESS_STR_MAX];
+ CLog::Log(LOGDEBUG,
+ "CZeroconfBrowserAvahi::resolveCallback resolved service '{}' of type '{}' in "
+ "domain '{}':\n",
+ name, type, domain);
+
+ avahi_address_snprint ( a, sizeof ( a ), address );
+ p_instance->m_resolving_service.SetIP(a);
+ p_instance->m_resolving_service.SetPort(port);
+ //get txt-record list
+ p_instance->m_resolving_service.SetTxtRecords(GetTxtRecords(txt));
+ break;
+ }
+ }
+ avahi_service_resolver_free ( r );
+ p_instance->m_resolved_event.Set();
+}
+
+bool CZeroconfBrowserAvahi::createClient()
+{
+ assert ( mp_poll );
+ if ( mp_client )
+ {
+ avahi_client_free ( mp_client );
+ }
+ mp_client = avahi_client_new ( avahi_threaded_poll_get ( mp_poll ),
+ AVAHI_CLIENT_NO_FAIL, &clientCallback, this, 0 );
+ if ( !mp_client )
+ {
+ mp_client = 0;
+ return false;
+ }
+ return true;
+}
+
+AvahiServiceBrowser* CZeroconfBrowserAvahi::createServiceBrowser ( const std::string& fcr_service_type, AvahiClient* fp_client, void* fp_userdata)
+{
+ assert(fp_client);
+ AvahiServiceBrowser* ret = avahi_service_browser_new ( fp_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, fcr_service_type.c_str(),
+ NULL, ( AvahiLookupFlags ) 0, browseCallback, fp_userdata );
+ if ( !ret )
+ {
+ CLog::Log(
+ LOGERROR,
+ "CZeroconfBrowserAvahi::createServiceBrowser Failed to create service ({}) browser: {}",
+ avahi_strerror(avahi_client_errno(fp_client)), fcr_service_type);
+ }
+ return ret;
+}
diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.h b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.h
new file mode 100644
index 0000000..cc8c6c4
--- /dev/null
+++ b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.h
@@ -0,0 +1,94 @@
+/*
+ * 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 "network/ZeroconfBrowser.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+#include <avahi-common/defs.h>
+#include <avahi-common/thread-watch.h>
+
+//platform specific implementation of zeroconfbrowser interface using avahi
+class CZeroconfBrowserAvahi : public CZeroconfBrowser
+{
+ public:
+ CZeroconfBrowserAvahi();
+ ~CZeroconfBrowserAvahi() override;
+
+ private:
+ ///implementation if CZeroconfBrowser interface
+ ///@{
+ bool doAddServiceType(const std::string& fcr_service_type) override;
+ bool doRemoveServiceType(const std::string& fcr_service_type) override;
+
+ std::vector<CZeroconfBrowser::ZeroconfService> doGetFoundServices() override;
+ bool doResolveService(CZeroconfBrowser::ZeroconfService& fr_service, double f_timeout) override;
+ ///@}
+
+ /// avahi callbacks
+ ///@{
+ static void clientCallback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
+ static void browseCallback(
+ AvahiServiceBrowser *b,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+ void* userdata);
+ static void resolveCallback(
+ AvahiServiceResolver *r,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ const char *host_name,
+ const AvahiAddress *address,
+ uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ AVAHI_GCC_UNUSED void* userdata);
+ //helpers
+ bool createClient();
+ static AvahiServiceBrowser* createServiceBrowser(const std::string& fcr_service_type, AvahiClient* fp_client, void* fp_userdata);
+
+ //shared variables between avahi thread and interface
+ AvahiClient* mp_client = 0 ;
+ AvahiThreadedPoll* mp_poll = 0 ;
+ // tBrowserMap maps service types the corresponding browser
+ typedef std::map<std::string, AvahiServiceBrowser*> tBrowserMap;
+ tBrowserMap m_browsers;
+
+ // if a browser is in this set, it already sent an ALL_FOR_NOW message
+ // (needed to bundle GUI_MSG_UPDATE_PATH messages
+ std::set<AvahiServiceBrowser*> m_all_for_now_browsers;
+
+ //this information is needed for avahi to resolve a service,
+ //so unfortunately we'll only be able to resolve services already discovered
+ struct AvahiSpecificInfos
+ {
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ };
+ typedef std::map<CZeroconfBrowser::ZeroconfService, AvahiSpecificInfos> tDiscoveredServices;
+ tDiscoveredServices m_discovered_services;
+ CZeroconfBrowser::ZeroconfService m_resolving_service;
+ CEvent m_resolved_event;
+};
diff --git a/xbmc/platform/linux/peripherals/CMakeLists.txt b/xbmc/platform/linux/peripherals/CMakeLists.txt
new file mode 100644
index 0000000..626aa40
--- /dev/null
+++ b/xbmc/platform/linux/peripherals/CMakeLists.txt
@@ -0,0 +1,11 @@
+if(UDEV_FOUND)
+ list(APPEND SOURCES PeripheralBusUSBLibUdev.cpp)
+ list(APPEND HEADERS PeripheralBusUSBLibUdev.h)
+elseif(LIBUSB_FOUND)
+ list(APPEND SOURCES PeripheralBusUSBLibUSB.cpp)
+ list(APPEND HEADERS PeripheralBusUSBLibUSB.h)
+endif()
+
+if(SOURCES)
+ core_add_library(platform_linux_peripherals)
+endif()
diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.cpp b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.cpp
new file mode 100644
index 0000000..5c788bb
--- /dev/null
+++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "PeripheralBusUSBLibUSB.h"
+
+#include "peripherals/Peripherals.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <usb.h>
+
+using namespace PERIPHERALS;
+
+CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals& manager) :
+ CPeripheralBus("PeripBusUSB", manager, PERIPHERAL_BUS_USB)
+{
+ usb_init();
+ usb_find_busses();
+ m_busses = usb_get_busses();
+ CLog::Log(LOGDEBUG, "{} - using libusb peripheral scanning", __FUNCTION__);
+}
+
+bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results)
+{
+ struct usb_bus *bus;
+ usb_find_devices();
+ for (bus = m_busses; bus; bus = bus->next)
+ {
+ struct usb_device *dev;
+ for (dev = bus->devices; dev; dev = dev->next)
+ {
+ PeripheralScanResult result(m_type);
+ result.m_iVendorId = dev->descriptor.idVendor;
+ result.m_iProductId = dev->descriptor.idProduct;
+ result.m_type = (dev->descriptor.bDeviceClass == USB_CLASS_PER_INTERFACE && dev->descriptor.bNumConfigurations > 0 &&
+ dev->config[0].bNumInterfaces > 0 && dev->config[0].interface[0].num_altsetting > 0) ?
+ GetType(dev->config[0].interface[0].altsetting[0].bInterfaceClass) :
+ GetType(dev->descriptor.bDeviceClass);
+#ifdef TARGET_FREEBSD
+ result.m_strLocation = std::to_string(dev->filename);
+#else
+ result.m_strLocation = StringUtils::Format("/bus{}/dev{}", bus->dirname, dev->filename);
+#endif
+ result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId);
+ if (!results.ContainsResult(result))
+ results.m_results.push_back(result);
+ }
+ }
+
+ return true;
+}
+
+const PeripheralType CPeripheralBusUSB::GetType(int iDeviceClass)
+{
+ switch (iDeviceClass)
+ {
+ case USB_CLASS_HID:
+ return PERIPHERAL_HID;
+ case USB_CLASS_COMM:
+ return PERIPHERAL_NIC;
+ case USB_CLASS_MASS_STORAGE:
+ return PERIPHERAL_DISK;
+ case USB_CLASS_PER_INTERFACE:
+ case USB_CLASS_AUDIO:
+ case USB_CLASS_PRINTER:
+ case USB_CLASS_PTP:
+ case USB_CLASS_HUB:
+ case USB_CLASS_DATA:
+ case USB_CLASS_VENDOR_SPEC:
+ default:
+ return PERIPHERAL_UNKNOWN;
+ }
+}
diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.h b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.h
new file mode 100644
index 0000000..d2e7e48
--- /dev/null
+++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.h
@@ -0,0 +1,34 @@
+/*
+ * 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"
+#include "peripherals/devices/Peripheral.h"
+
+struct usb_bus;
+
+namespace PERIPHERALS
+{
+ class CPeripherals;
+
+ class CPeripheralBusUSB : public CPeripheralBus
+ {
+ public:
+ explicit CPeripheralBusUSB(CPeripherals& manager);
+
+ /*!
+ * @see PeripheralBus::PerformDeviceScan()
+ */
+ bool PerformDeviceScan(PeripheralScanResults &results);
+
+ protected:
+ static const PeripheralType GetType(int iDeviceClass);
+ struct usb_bus *m_busses;
+ };
+}
diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp
new file mode 100644
index 0000000..aeb5e71
--- /dev/null
+++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp
@@ -0,0 +1,254 @@
+/*
+ * 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 "PeripheralBusUSBLibUdev.h"
+#include "peripherals/Peripherals.h"
+extern "C" {
+#include <libudev.h>
+}
+#include <cassert>
+#include <poll.h>
+#include "utils/log.h"
+
+#ifndef USB_CLASS_PER_INTERFACE
+#define USB_CLASS_PER_INTERFACE 0
+#endif
+
+#ifndef USB_CLASS_AUDIO
+#define USB_CLASS_AUDIO 1
+#endif
+
+#ifndef USB_CLASS_COMM
+#define USB_CLASS_COMM 2
+#endif
+
+#ifndef USB_CLASS_HID
+#define USB_CLASS_HID 3
+#endif
+
+#ifndef USB_CLASS_PHYSICAL
+#define USB_CLASS_PHYSICAL 5
+#endif
+
+#ifndef USB_CLASS_PTP
+#define USB_CLASS_PTP 6
+#endif
+
+#ifndef USB_CLASS_PRINTER
+#define USB_CLASS_PRINTER 7
+#endif
+
+#ifndef USB_CLASS_MASS_STORAGE
+#define USB_CLASS_MASS_STORAGE 8
+#endif
+
+#ifndef USB_CLASS_HUB
+#define USB_CLASS_HUB 9
+#endif
+
+#ifndef USB_CLASS_DATA
+#define USB_CLASS_DATA 10
+#endif
+
+#ifndef USB_CLASS_APP_SPEC
+#define USB_CLASS_APP_SPEC 0xfe
+#endif
+
+#ifndef USB_CLASS_VENDOR_SPEC
+#define USB_CLASS_VENDOR_SPEC 0xff
+#endif
+
+using namespace PERIPHERALS;
+
+CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals& manager) :
+ CPeripheralBus("PeripBusUSBUdev", manager, PERIPHERAL_BUS_USB)
+{
+ /* the Process() method in this class overrides the one in CPeripheralBus, so leave this set to true */
+ m_bNeedsPolling = true;
+
+ m_udev = NULL;
+ m_udevMon = NULL;
+}
+
+CPeripheralBusUSB::~CPeripheralBusUSB(void)
+{
+ StopThread(true);
+}
+
+bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results)
+{
+ // We don't want this one to be called from outside world
+ assert(IsCurrentThread());
+
+ struct udev_enumerate *enumerate;
+ struct udev_list_entry *devices, *dev_list_entry;
+ struct udev_device *dev(NULL), *parent(NULL);
+ enumerate = udev_enumerate_new(m_udev);
+ udev_enumerate_scan_devices(enumerate);
+ devices = udev_enumerate_get_list_entry(enumerate);
+
+ bool bContinue(true);
+ std::string strPath, strClass;
+ udev_list_entry_foreach(dev_list_entry, devices)
+ {
+ strPath = udev_list_entry_get_name(dev_list_entry);
+ if (strPath.empty())
+ bContinue = false;
+
+ if (bContinue)
+ {
+ if (!(parent = udev_device_new_from_syspath(m_udev, strPath.c_str())))
+ bContinue = false;
+ }
+
+ if (bContinue)
+ {
+ dev = udev_device_get_parent(udev_device_get_parent(parent));
+ if (!dev || !udev_device_get_sysattr_value(dev,"idVendor") || !udev_device_get_sysattr_value(dev, "idProduct"))
+ bContinue = false;
+ }
+
+ if (bContinue)
+ {
+ strClass = udev_device_get_sysattr_value(dev, "bDeviceClass");
+ if (strClass.empty())
+ bContinue = false;
+ }
+
+ if (bContinue)
+ {
+ int iClass = PeripheralTypeTranslator::HexStringToInt(strClass.c_str());
+ if (iClass == USB_CLASS_PER_INTERFACE)
+ {
+ //! @todo just assume this is a HID device for now, since the only devices that we're currently
+ //! interested in are HID devices
+ iClass = USB_CLASS_HID;
+ }
+
+ PeripheralScanResult result(m_type);
+ result.m_iVendorId = PeripheralTypeTranslator::HexStringToInt(udev_device_get_sysattr_value(dev, "idVendor"));
+ result.m_iProductId = PeripheralTypeTranslator::HexStringToInt(udev_device_get_sysattr_value(dev, "idProduct"));
+ result.m_type = GetType(iClass);
+ result.m_strLocation = udev_device_get_syspath(dev);
+ result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId);
+ if (!results.ContainsResult(result))
+ results.m_results.push_back(result);
+ }
+
+ bContinue = true;
+ if (parent)
+ {
+ /* unref the _parent_ device */
+ udev_device_unref(parent);
+ parent = NULL;
+ }
+ }
+ /* Free the enumerator object */
+ udev_enumerate_unref(enumerate);
+
+ return true;
+}
+
+PeripheralType CPeripheralBusUSB::GetType(int iDeviceClass)
+{
+ switch (iDeviceClass)
+ {
+ case USB_CLASS_HID:
+ return PERIPHERAL_HID;
+ case USB_CLASS_COMM:
+ return PERIPHERAL_NIC;
+ case USB_CLASS_MASS_STORAGE:
+ return PERIPHERAL_DISK;
+ case USB_CLASS_PER_INTERFACE:
+ case USB_CLASS_AUDIO:
+ case USB_CLASS_PRINTER:
+ case USB_CLASS_PTP:
+ case USB_CLASS_HUB:
+ case USB_CLASS_DATA:
+ case USB_CLASS_VENDOR_SPEC:
+ default:
+ return PERIPHERAL_UNKNOWN;
+ }
+}
+
+void CPeripheralBusUSB::Process(void)
+{
+ if (!(m_udev = udev_new()))
+ {
+ CLog::Log(LOGERROR, "{} - failed to allocate udev context", __FUNCTION__);
+ return;
+ }
+
+ /* set up a devices monitor that listen for any device change */
+ m_udevMon = udev_monitor_new_from_netlink(m_udev, "udev");
+
+ /* filter to only receive usb events */
+ if (udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "usb", nullptr) < 0)
+ {
+ CLog::Log(LOGERROR, "Could not limit filter on USB only");
+ }
+
+ CLog::Log(LOGDEBUG, "{} - initialised udev monitor", __FUNCTION__);
+
+ udev_monitor_enable_receiving(m_udevMon);
+ bool bUpdated(false);
+ ScanForDevices();
+ while (!m_bStop)
+ {
+ bUpdated = WaitForUpdate();
+ if (bUpdated && !m_bStop)
+ ScanForDevices();
+ }
+ udev_monitor_unref(m_udevMon);
+ udev_unref(m_udev);
+}
+
+void CPeripheralBusUSB::Clear(void)
+{
+ StopThread(false);
+
+ CPeripheralBus::Clear();
+}
+
+bool CPeripheralBusUSB::WaitForUpdate()
+{
+ int udevFd = udev_monitor_get_fd(m_udevMon);
+
+ if (udevFd < 0)
+ {
+ CLog::Log(LOGERROR, "{} - get udev monitor", __FUNCTION__);
+ return false;
+ }
+
+ /* poll for udev changes */
+ struct pollfd pollFd;
+ pollFd.fd = udevFd;
+ pollFd.events = POLLIN;
+ int iPollResult;
+ while (!m_bStop && ((iPollResult = poll(&pollFd, 1, 100)) <= 0))
+ if (errno != EINTR && iPollResult != 0)
+ break;
+
+ /* the thread is being stopped, so just return false */
+ if (m_bStop)
+ return false;
+
+ /* we have to read the message from the queue, even though we're not actually using it */
+ struct udev_device *dev = udev_monitor_receive_device(m_udevMon);
+ if (dev)
+ udev_device_unref(dev);
+ else
+ {
+ CLog::Log(LOGERROR, "{} - failed to get device from udev_monitor_receive_device()",
+ __FUNCTION__);
+ Clear();
+ return false;
+ }
+
+ return true;
+}
diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.h b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.h
new file mode 100644
index 0000000..72cb437
--- /dev/null
+++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.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"
+#include "peripherals/devices/Peripheral.h"
+
+struct udev;
+struct udev_monitor;
+
+namespace PERIPHERALS
+{
+ class CPeripherals;
+
+ class CPeripheralBusUSB : public CPeripheralBus
+ {
+ public:
+ explicit CPeripheralBusUSB(CPeripherals& manager);
+ ~CPeripheralBusUSB(void) override;
+
+ void Clear(void) override;
+
+ /*!
+ * @see PeripheralBus::PerformDeviceScan()
+ */
+ bool PerformDeviceScan(PeripheralScanResults &results) override;
+
+ protected:
+ static PeripheralType GetType(int iDeviceClass);
+
+ void Process(void) override;
+ bool WaitForUpdate(void);
+
+ struct udev * m_udev;
+ struct udev_monitor *m_udevMon;
+ };
+}
diff --git a/xbmc/platform/linux/powermanagement/CMakeLists.txt b/xbmc/platform/linux/powermanagement/CMakeLists.txt
new file mode 100644
index 0000000..3694a79
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(SOURCES LinuxPowerSyscall.cpp)
+
+set(HEADERS FallbackPowerSyscall.h
+ LinuxPowerSyscall.h)
+
+if(DBUS_FOUND)
+ list(APPEND SOURCES ConsoleUPowerSyscall.cpp
+ LogindUPowerSyscall.cpp
+ UPowerSyscall.cpp)
+ list(APPEND HEADERS ConsoleUPowerSyscall.h
+ LogindUPowerSyscall.h
+ UPowerSyscall.h)
+endif()
+
+if(SOURCES)
+ core_add_library(platform_linux_powermanagement)
+endif()
diff --git a/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.cpp
new file mode 100644
index 0000000..6f04dd7
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 "ConsoleUPowerSyscall.h"
+
+#include "utils/log.h"
+
+CConsoleUPowerSyscall::CConsoleUPowerSyscall()
+{
+ m_CanPowerdown = ConsoleKitMethodCall("CanStop");
+ m_CanReboot = ConsoleKitMethodCall("CanRestart");
+}
+
+bool CConsoleUPowerSyscall::Powerdown()
+{
+ CDBusMessage message("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Stop");
+ return message.SendSystem() != NULL;
+}
+
+bool CConsoleUPowerSyscall::Reboot()
+{
+ CDBusMessage message("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Restart");
+ return message.SendSystem() != NULL;
+}
+
+bool CConsoleUPowerSyscall::HasConsoleKitAndUPower()
+{
+ return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "CanStop")
+ && HasUPower();
+}
+
+bool CConsoleUPowerSyscall::ConsoleKitMethodCall(const char *method)
+{
+ CDBusMessage message("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", method);
+ DBusMessage *reply = message.SendSystem();
+ if (reply)
+ {
+ dbus_bool_t boolean = FALSE;
+
+ if (dbus_message_get_args (reply, NULL, DBUS_TYPE_BOOLEAN, &boolean, DBUS_TYPE_INVALID))
+ return boolean;
+ }
+
+ return false;
+}
diff --git a/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.h b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.h
new file mode 100644
index 0000000..9fdd157
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.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 "DBusUtil.h"
+#include "UPowerSyscall.h"
+
+class CConsoleUPowerSyscall : public CUPowerSyscall
+{
+public:
+ CConsoleUPowerSyscall();
+ bool Powerdown() override;
+ bool Reboot() override;
+ static bool HasConsoleKitAndUPower();
+private:
+ static bool ConsoleKitMethodCall(const char *method);
+};
diff --git a/xbmc/platform/linux/powermanagement/FallbackPowerSyscall.h b/xbmc/platform/linux/powermanagement/FallbackPowerSyscall.h
new file mode 100644
index 0000000..6988543
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/FallbackPowerSyscall.h
@@ -0,0 +1,26 @@
+/*
+ * 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 "powermanagement/IPowerSyscall.h"
+
+class CFallbackPowerSyscall : public CPowerSyscallWithoutEvents
+{
+public:
+ bool Powerdown() override {return true; }
+ bool Suspend() override {return false; }
+ bool Hibernate() override {return false; }
+ bool Reboot() override {return true; }
+
+ bool CanPowerdown() override {return true; }
+ bool CanSuspend() override {return false; }
+ bool CanHibernate() override {return false; }
+ bool CanReboot() override {return true; }
+ int BatteryLevel() override {return 0; }
+};
diff --git a/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.cpp
new file mode 100644
index 0000000..f3dbcdb
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 "LinuxPowerSyscall.h"
+#include "FallbackPowerSyscall.h"
+#if defined(HAS_DBUS)
+#include "ConsoleUPowerSyscall.h"
+#include "LogindUPowerSyscall.h"
+#include "UPowerSyscall.h"
+#endif // HAS_DBUS
+
+#include <functional>
+#include <list>
+#include <memory>
+#include <utility>
+
+IPowerSyscall* CLinuxPowerSyscall::CreateInstance()
+{
+#if defined(HAS_DBUS)
+ std::unique_ptr<IPowerSyscall> bestPowerManager;
+ std::unique_ptr<IPowerSyscall> currPowerManager;
+ int bestCount = -1;
+ int currCount = -1;
+
+ std::list< std::pair< std::function<bool()>,
+ std::function<IPowerSyscall*()> > > powerManagers =
+ {
+ std::make_pair(CConsoleUPowerSyscall::HasConsoleKitAndUPower,
+ [] { return new CConsoleUPowerSyscall(); }),
+ std::make_pair(CLogindUPowerSyscall::HasLogind,
+ [] { return new CLogindUPowerSyscall(); }),
+ std::make_pair(CUPowerSyscall::HasUPower,
+ [] { return new CUPowerSyscall(); })
+ };
+ for(const auto& powerManager : powerManagers)
+ {
+ if (powerManager.first())
+ {
+ currPowerManager.reset(powerManager.second());
+ currCount = currPowerManager->CountPowerFeatures();
+ if (currCount > bestCount)
+ {
+ bestCount = currCount;
+ bestPowerManager = std::move(currPowerManager);
+ }
+ if (bestCount == IPowerSyscall::MAX_COUNT_POWER_FEATURES)
+ break;
+ }
+ }
+ if (bestPowerManager)
+ return bestPowerManager.release();
+ else
+#endif // HAS_DBUS
+ return new CFallbackPowerSyscall();
+}
+
+void CLinuxPowerSyscall::Register()
+{
+ IPowerSyscall::RegisterPowerSyscall(CLinuxPowerSyscall::CreateInstance);
+}
diff --git a/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.h b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.h
new file mode 100644
index 0000000..b5cc218
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.h
@@ -0,0 +1,18 @@
+/*
+ * 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 "powermanagement/IPowerSyscall.h"
+
+class CLinuxPowerSyscall
+{
+public:
+ static IPowerSyscall* CreateInstance();
+ static void Register();
+};
diff --git a/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.cpp
new file mode 100644
index 0000000..bd04197
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.cpp
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2012 Denis Yantarev
+ * 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 "LogindUPowerSyscall.h"
+
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <string.h>
+
+#include <unistd.h>
+
+// logind DBus interface specification:
+// http://www.freedesktop.org/wiki/Software/Logind/logind
+//
+// Inhibitor Locks documentation:
+// http://www.freedesktop.org/wiki/Software/Logind/inhibit/
+
+#define LOGIND_DEST "org.freedesktop.login1"
+#define LOGIND_PATH "/org/freedesktop/login1"
+#define LOGIND_IFACE "org.freedesktop.login1.Manager"
+
+CLogindUPowerSyscall::CLogindUPowerSyscall()
+{
+ m_lowBattery = false;
+
+ CLog::Log(LOGINFO, "Selected Logind/UPower as PowerSyscall");
+
+ // Check if we have UPower. If not, we avoid any battery related operations.
+ CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices");
+ m_hasUPower = message.SendSystem() != NULL;
+
+ if (!m_hasUPower)
+ CLog::Log(LOGINFO, "LogindUPowerSyscall - UPower not found, battery information will not be available");
+
+ m_canPowerdown = LogindCheckCapability("CanPowerOff");
+ m_canReboot = LogindCheckCapability("CanReboot");
+ m_canHibernate = LogindCheckCapability("CanHibernate");
+ m_canSuspend = LogindCheckCapability("CanSuspend");
+
+ InhibitDelayLockSleep();
+
+ m_batteryLevel = 0;
+ if (m_hasUPower)
+ UpdateBatteryLevel();
+
+ if (!m_connection.Connect(DBUS_BUS_SYSTEM, true))
+ {
+ return;
+ }
+
+ CDBusError error;
+ dbus_connection_set_exit_on_disconnect(m_connection, false);
+ dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'", error);
+
+ if (!error && m_hasUPower)
+ dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UPower',member='DeviceChanged'", error);
+
+ dbus_connection_flush(m_connection);
+
+ if (error)
+ {
+ error.Log("UPowerSyscall: Failed to attach to signal");
+ m_connection.Destroy();
+ }
+}
+
+CLogindUPowerSyscall::~CLogindUPowerSyscall()
+{
+ ReleaseDelayLockSleep();
+ ReleaseDelayLockShutdown();
+}
+
+bool CLogindUPowerSyscall::Powerdown()
+{
+ // delay shutdown so that the app can close properly
+ InhibitDelayLockShutdown();
+ return LogindSetPowerState("PowerOff");
+}
+
+bool CLogindUPowerSyscall::Reboot()
+{
+ return LogindSetPowerState("Reboot");
+}
+
+bool CLogindUPowerSyscall::Suspend()
+{
+ return LogindSetPowerState("Suspend");
+}
+
+bool CLogindUPowerSyscall::Hibernate()
+{
+ return LogindSetPowerState("Hibernate");
+}
+
+bool CLogindUPowerSyscall::CanPowerdown()
+{
+ return m_canPowerdown;
+}
+
+bool CLogindUPowerSyscall::CanSuspend()
+{
+ return m_canSuspend;
+}
+
+bool CLogindUPowerSyscall::CanHibernate()
+{
+ return m_canHibernate;
+}
+
+bool CLogindUPowerSyscall::CanReboot()
+{
+ return m_canReboot;
+}
+
+bool CLogindUPowerSyscall::HasLogind()
+{
+ // recommended method by systemd devs. The seats directory
+ // doesn't exist unless logind created it and therefore is running.
+ // see also https://mail.gnome.org/archives/desktop-devel-list/2013-March/msg00092.html
+ if (access("/run/systemd/seats/", F_OK) >= 0)
+ return true;
+
+ // on some environments "/run/systemd/seats/" doesn't exist, e.g. on flatpak. Try DBUS instead.
+ CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, "ListSeats");
+ DBusMessage *reply = message.SendSystem();
+ if (!reply)
+ return false;
+
+ DBusMessageIter arrIter;
+ if (dbus_message_iter_init(reply, &arrIter) && dbus_message_iter_get_arg_type(&arrIter) == DBUS_TYPE_ARRAY)
+ {
+ DBusMessageIter structIter;
+ dbus_message_iter_recurse(&arrIter, &structIter);
+ if (dbus_message_iter_get_arg_type(&structIter) == DBUS_TYPE_STRUCT)
+ {
+ DBusMessageIter strIter;
+ dbus_message_iter_recurse(&structIter, &strIter);
+ if (dbus_message_iter_get_arg_type(&strIter) == DBUS_TYPE_STRING)
+ {
+ char *seat;
+ dbus_message_iter_get_basic(&strIter, &seat);
+ if (StringUtils::StartsWith(seat, "seat"))
+ {
+ CLog::Log(LOGDEBUG, "LogindUPowerSyscall::HasLogind - found seat: {}", seat);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool CLogindUPowerSyscall::LogindSetPowerState(const char *state)
+{
+ CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, state);
+ // The user_interaction boolean parameters can be used to control
+ // whether PolicyKit should interactively ask the user for authentication
+ // credentials if it needs to.
+ message.AppendArgument(false);
+ return message.SendSystem() != NULL;
+}
+
+bool CLogindUPowerSyscall::LogindCheckCapability(const char *capability)
+{
+ char *arg;
+ CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, capability);
+ DBusMessage *reply = message.SendSystem();
+ if(reply && dbus_message_get_args(reply, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID))
+ {
+ // Returns one of "yes", "no" or "challenge". If "challenge" is
+ // returned the operation is available, but only after authorization.
+ return (strcmp(arg, "yes") == 0);
+ }
+ return false;
+}
+
+int CLogindUPowerSyscall::BatteryLevel()
+{
+ return m_batteryLevel;
+}
+
+void CLogindUPowerSyscall::UpdateBatteryLevel()
+{
+ char** source = NULL;
+ int length = 0;
+ double batteryLevelSum = 0;
+ int batteryCount = 0;
+
+ CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices");
+ DBusMessage *reply = message.SendSystem();
+
+ if (!reply)
+ return;
+
+ if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &source, &length, DBUS_TYPE_INVALID))
+ {
+ CLog::Log(LOGWARNING, "LogindUPowerSyscall: failed to enumerate devices");
+ return;
+ }
+
+ for (int i = 0; i < length; i++)
+ {
+ CVariant properties = CDBusUtil::GetAll("org.freedesktop.UPower", source[i], "org.freedesktop.UPower.Device");
+ bool isRechargeable = properties["IsRechargeable"].asBoolean();
+
+ if (isRechargeable)
+ {
+ batteryCount++;
+ batteryLevelSum += properties["Percentage"].asDouble();
+ }
+ }
+
+ dbus_free_string_array(source);
+
+ if (batteryCount > 0)
+ {
+ m_batteryLevel = (int)(batteryLevelSum / (double)batteryCount);
+ m_lowBattery = CDBusUtil::GetVariant("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "OnLowBattery").asBoolean();
+ }
+}
+
+bool CLogindUPowerSyscall::PumpPowerEvents(IPowerEventsCallback *callback)
+{
+ bool result = false;
+ bool releaseLockSleep = false;
+
+ if (m_connection)
+ {
+ dbus_connection_read_write(m_connection, 0);
+ DBusMessagePtr msg(dbus_connection_pop_message(m_connection));
+
+ if (msg)
+ {
+ if (dbus_message_is_signal(msg.get(), "org.freedesktop.login1.Manager", "PrepareForSleep"))
+ {
+ dbus_bool_t arg;
+ // the boolean argument defines whether we are going to sleep (true) or just woke up (false)
+ dbus_message_get_args(msg.get(), NULL, DBUS_TYPE_BOOLEAN, &arg, DBUS_TYPE_INVALID);
+ CLog::Log(LOGDEBUG, "LogindUPowerSyscall: Received PrepareForSleep with arg {}", (int)arg);
+ if (arg)
+ {
+ callback->OnSleep();
+ releaseLockSleep = true;
+ }
+ else
+ {
+ callback->OnWake();
+ InhibitDelayLockSleep();
+ }
+
+ result = true;
+ }
+ else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "DeviceChanged"))
+ {
+ bool lowBattery = m_lowBattery;
+ UpdateBatteryLevel();
+ if (m_lowBattery && !lowBattery)
+ callback->OnLowBattery();
+
+ result = true;
+ }
+ else
+ CLog::Log(LOGDEBUG, "LogindUPowerSyscall - Received unknown signal {}",
+ dbus_message_get_member(msg.get()));
+ }
+ }
+
+ if (releaseLockSleep)
+ ReleaseDelayLockSleep();
+
+ return result;
+}
+
+void CLogindUPowerSyscall::InhibitDelayLockSleep()
+{
+ m_delayLockSleepFd = InhibitDelayLock("sleep");
+}
+
+void CLogindUPowerSyscall::InhibitDelayLockShutdown()
+{
+ m_delayLockShutdownFd = InhibitDelayLock("shutdown");
+}
+
+int CLogindUPowerSyscall::InhibitDelayLock(const char *what)
+{
+#ifdef DBUS_TYPE_UNIX_FD
+ CDBusMessage message("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Inhibit");
+ message.AppendArgument(what); // what to inhibit
+ message.AppendArgument("XBMC"); // who
+ message.AppendArgument(""); // reason
+ message.AppendArgument("delay"); // mode
+
+ DBusMessage *reply = message.SendSystem();
+
+ if (!reply)
+ {
+ CLog::Log(LOGWARNING, "LogindUPowerSyscall - failed to inhibit {} delay lock", what);
+ return -1;
+ }
+
+ int delayLockFd;
+ if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_UNIX_FD, &delayLockFd, DBUS_TYPE_INVALID))
+ {
+ CLog::Log(LOGWARNING, "LogindUPowerSyscall - failed to get inhibit file descriptor");
+ return -1;
+ }
+
+ CLog::Log(LOGDEBUG, "LogindUPowerSyscall - inhibit lock taken, fd {}", delayLockFd);
+ return delayLockFd;
+#else
+ CLog::Log(LOGWARNING, "LogindUPowerSyscall - inhibit lock support not compiled in");
+ return -1;
+#endif
+}
+
+void CLogindUPowerSyscall::ReleaseDelayLockSleep()
+{
+ ReleaseDelayLock(m_delayLockSleepFd, "sleep");
+ m_delayLockSleepFd = -1;
+}
+
+void CLogindUPowerSyscall::ReleaseDelayLockShutdown()
+{
+ ReleaseDelayLock(m_delayLockShutdownFd, "shutdown");
+ m_delayLockShutdownFd = -1;
+}
+
+void CLogindUPowerSyscall::ReleaseDelayLock(int lockFd, const char *what)
+{
+ if (lockFd != -1)
+ {
+ close(lockFd);
+ CLog::Log(LOGDEBUG, "LogindUPowerSyscall - delay lock {} released", what);
+ }
+}
diff --git a/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.h b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.h
new file mode 100644
index 0000000..998e706
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 Denis Yantarev
+ * 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 "DBusUtil.h"
+#include "powermanagement/IPowerSyscall.h"
+
+class CLogindUPowerSyscall : public CAbstractPowerSyscall
+{
+public:
+ CLogindUPowerSyscall();
+ ~CLogindUPowerSyscall() override;
+ bool Powerdown() override;
+ bool Suspend() override;
+ bool Hibernate() override;
+ bool Reboot() override;
+ bool CanPowerdown() override;
+ bool CanSuspend() override;
+ bool CanHibernate() override;
+ bool CanReboot() override;
+ int BatteryLevel() override;
+ bool PumpPowerEvents(IPowerEventsCallback *callback) override;
+ // we don't require UPower because everything except the battery level works fine without it
+ static bool HasLogind();
+private:
+ CDBusConnection m_connection;
+ bool m_canPowerdown;
+ bool m_canSuspend;
+ bool m_canHibernate;
+ bool m_canReboot;
+ bool m_hasUPower;
+ bool m_lowBattery;
+ int m_batteryLevel;
+ int m_delayLockSleepFd = -1; // file descriptor for the logind sleep delay lock
+ int m_delayLockShutdownFd = -1; // file descriptor for the logind powerdown delay lock
+ void UpdateBatteryLevel();
+ void InhibitDelayLockSleep();
+ void InhibitDelayLockShutdown();
+ int InhibitDelayLock(const char *what);
+ void ReleaseDelayLockSleep();
+ void ReleaseDelayLockShutdown();
+ void ReleaseDelayLock(int lockFd, const char *what);
+ static bool LogindSetPowerState(const char *state);
+ static bool LogindCheckCapability(const char *capability);
+};
diff --git a/xbmc/platform/linux/powermanagement/UPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/UPowerSyscall.cpp
new file mode 100644
index 0000000..27ed2f4
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/UPowerSyscall.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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 "UPowerSyscall.h"
+
+#include "utils/log.h"
+
+CUPowerSource::CUPowerSource(const char *powerSource)
+{
+ if(powerSource == NULL)
+ m_powerSource = "";
+ else
+ m_powerSource = powerSource;
+
+ CVariant properties = CDBusUtil::GetAll("org.freedesktop.UPower", m_powerSource.c_str(), "org.freedesktop.UPower.Device");
+ m_isRechargeable = properties["IsRechargeable"].asBoolean();
+ Update();
+}
+
+CUPowerSource::~CUPowerSource() = default;
+
+void CUPowerSource::Update()
+{
+ CVariant properties = CDBusUtil::GetAll("org.freedesktop.UPower", m_powerSource.c_str(), "org.freedesktop.UPower.Device");
+ m_batteryLevel = properties["Percentage"].asDouble();
+}
+
+bool CUPowerSource::IsRechargeable()
+{
+ return m_isRechargeable;
+}
+
+double CUPowerSource::BatteryLevel()
+{
+ return m_batteryLevel;
+}
+
+CUPowerSyscall::CUPowerSyscall()
+{
+ CLog::Log(LOGINFO, "Selected UPower as PowerSyscall");
+
+ m_lowBattery = false;
+
+ //! @todo do not use dbus_connection_pop_message() that requires the use of a
+ //! private connection
+ if (m_connection.Connect(DBUS_BUS_SYSTEM, true))
+ {
+ dbus_connection_set_exit_on_disconnect(m_connection, false);
+
+ CDBusError error;
+ dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UPower'", error);
+ dbus_connection_flush(m_connection);
+
+ if (error)
+ {
+ error.Log("UPower: Failed to attach to signal");
+ m_connection.Destroy();
+ }
+ }
+
+ m_CanPowerdown = false;
+ m_CanReboot = false;
+
+ UpdateCapabilities();
+
+ EnumeratePowerSources();
+}
+
+bool CUPowerSyscall::Powerdown()
+{
+ return false;
+}
+
+bool CUPowerSyscall::Suspend()
+{
+ // UPower 0.9.1 does not signal sleeping unless you tell that its about to sleep...
+ CDBusMessage aboutToSleepMessage("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "AboutToSleep");
+ aboutToSleepMessage.SendAsyncSystem();
+
+ CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "Suspend");
+ return message.SendAsyncSystem();
+}
+
+bool CUPowerSyscall::Hibernate()
+{
+ // UPower 0.9.1 does not signal sleeping unless you tell that its about to sleep...
+ CDBusMessage aboutToSleepMessage("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "AboutToSleep");
+ aboutToSleepMessage.SendAsyncSystem();
+
+ CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "Hibernate");
+ return message.SendAsyncSystem();
+}
+
+bool CUPowerSyscall::Reboot()
+{
+ return false;
+}
+
+bool CUPowerSyscall::CanPowerdown()
+{
+ return m_CanPowerdown;
+}
+
+bool CUPowerSyscall::CanSuspend()
+{
+ return m_CanSuspend;
+}
+
+bool CUPowerSyscall::CanHibernate()
+{
+ return m_CanHibernate;
+}
+
+bool CUPowerSyscall::CanReboot()
+{
+ return m_CanReboot;
+}
+
+int CUPowerSyscall::BatteryLevel()
+{
+ unsigned int nBatteryCount = 0;
+ double subCapacity = 0;
+ double batteryLevel = 0;
+
+ for (auto& itr : m_powerSources)
+ {
+ itr.Update();
+ if (itr.IsRechargeable())
+ {
+ nBatteryCount++;
+ subCapacity += itr.BatteryLevel();
+ }
+ }
+
+ if(nBatteryCount)
+ batteryLevel = subCapacity / (double)nBatteryCount;
+
+ return (int) batteryLevel;
+}
+
+void CUPowerSyscall::EnumeratePowerSources()
+{
+ CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices");
+ DBusMessage *reply = message.SendSystem();
+ if (reply)
+ {
+ char** source = NULL;
+ int length = 0;
+
+ if (dbus_message_get_args (reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &source, &length, DBUS_TYPE_INVALID))
+ {
+ for (int i = 0; i < length; i++)
+ {
+ m_powerSources.emplace_back(source[i]);
+ }
+
+ dbus_free_string_array(source);
+ }
+ }
+}
+
+bool CUPowerSyscall::HasUPower()
+{
+ return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, "org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices");
+}
+
+bool CUPowerSyscall::PumpPowerEvents(IPowerEventsCallback *callback)
+{
+ bool result = false;
+
+ if (m_connection)
+ {
+ dbus_connection_read_write(m_connection, 0);
+ DBusMessagePtr msg(dbus_connection_pop_message(m_connection));
+
+ if (msg)
+ {
+ result = true;
+ if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "Sleeping"))
+ callback->OnSleep();
+ else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "Resuming"))
+ callback->OnWake();
+ else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "Changed"))
+ {
+ bool lowBattery = m_lowBattery;
+ UpdateCapabilities();
+ if (m_lowBattery && !lowBattery)
+ callback->OnLowBattery();
+ }
+ else
+ CLog::Log(LOGDEBUG, "UPower: Received an unknown signal {}",
+ dbus_message_get_member(msg.get()));
+ }
+ }
+ return result;
+}
+
+void CUPowerSyscall::UpdateCapabilities()
+{
+ m_CanSuspend = CDBusUtil::GetVariant("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "CanSuspend").asBoolean(false);
+ m_CanHibernate = CDBusUtil::GetVariant("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "CanHibernate").asBoolean(false);
+}
diff --git a/xbmc/platform/linux/powermanagement/UPowerSyscall.h b/xbmc/platform/linux/powermanagement/UPowerSyscall.h
new file mode 100644
index 0000000..aed08d8
--- /dev/null
+++ b/xbmc/platform/linux/powermanagement/UPowerSyscall.h
@@ -0,0 +1,61 @@
+/*
+ * 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 "DBusUtil.h"
+#include "powermanagement/IPowerSyscall.h"
+
+#include <list>
+#include <string>
+
+class CUPowerSource
+{
+public:
+ CUPowerSource(const char *powerSource);
+ ~CUPowerSource();
+
+ void Update();
+ bool IsRechargeable();
+ double BatteryLevel();
+
+private:
+ std::string m_powerSource;
+ bool m_isRechargeable;
+ double m_batteryLevel;
+};
+
+class CUPowerSyscall : public CAbstractPowerSyscall
+{
+public:
+ CUPowerSyscall();
+ bool Powerdown() override;
+ bool Suspend() override;
+ bool Hibernate() override;
+ bool Reboot() override;
+ bool CanPowerdown() override;
+ bool CanSuspend() override;
+ bool CanHibernate() override;
+ bool CanReboot() override;
+ int BatteryLevel() override;
+ bool PumpPowerEvents(IPowerEventsCallback *callback) override;
+ static bool HasUPower();
+protected:
+ bool m_CanPowerdown;
+ bool m_CanSuspend;
+ bool m_CanHibernate;
+ bool m_CanReboot;
+
+ void UpdateCapabilities();
+private:
+ std::list<CUPowerSource> m_powerSources;
+ CDBusConnection m_connection;
+
+ bool m_lowBattery;
+ void EnumeratePowerSources();
+};
diff --git a/xbmc/platform/linux/sse4/CMakeLists.txt b/xbmc/platform/linux/sse4/CMakeLists.txt
new file mode 100644
index 0000000..6ec73f1
--- /dev/null
+++ b/xbmc/platform/linux/sse4/CMakeLists.txt
@@ -0,0 +1,8 @@
+if(HAVE_SSE4_1)
+ set(SOURCES CopyFrame.cpp)
+ set(HEADERS DllLibSSE4.h)
+
+ core_add_shared_library(sse4)
+ set_target_properties(sse4 PROPERTIES FOLDER lib)
+ target_compile_options(sse4 PRIVATE -msse4.1)
+endif()
diff --git a/xbmc/platform/linux/sse4/CopyFrame.cpp b/xbmc/platform/linux/sse4/CopyFrame.cpp
new file mode 100644
index 0000000..2147c3c
--- /dev/null
+++ b/xbmc/platform/linux/sse4/CopyFrame.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <smmintrin.h>
+
+#define CACHED_BUFFER_SIZE 4096
+
+extern "C"
+{
+
+/*
+ * http://software.intel.com/en-us/articles/copying-accelerated-video-decode-frame-buffers
+ * COPIES VIDEO FRAMES FROM USWC MEMORY TO WB SYSTEM MEMORY VIA CACHED BUFFER
+ * ASSUMES PITCH IS A MULTIPLE OF 64B CACHE LINE SIZE, WIDTH MAY NOT BE
+ */
+void copy_frame( void * pSrc, void * pDest, void * pCacheBlock,
+ unsigned int width, unsigned int height, unsigned int pitch )
+{
+ __m128i x0, x1, x2, x3;
+ __m128i *pLoad;
+ __m128i *pStore;
+ __m128i *pCache;
+ unsigned int x, y, yLoad, yStore;
+ unsigned int rowsPerBlock;
+ unsigned int width64;
+ unsigned int extraPitch;
+
+
+ rowsPerBlock = CACHED_BUFFER_SIZE / pitch;
+ width64 = (width + 63) & ~0x03f;
+ extraPitch = (pitch - width64) / 16;
+
+ pLoad = (__m128i *)pSrc;
+ pStore = (__m128i *)pDest;
+
+ // COPY THROUGH 4KB CACHED BUFFER
+ for( y = 0; y < height; y += rowsPerBlock )
+ {
+ // ROWS LEFT TO COPY AT END
+ if( y + rowsPerBlock > height )
+ rowsPerBlock = height - y;
+
+ pCache = (__m128i *)pCacheBlock;
+
+ _mm_mfence();
+
+ // LOAD ROWS OF PITCH WIDTH INTO CACHED BLOCK
+ for( yLoad = 0; yLoad < rowsPerBlock; yLoad++ )
+ {
+ // COPY A ROW, CACHE LINE AT A TIME
+ for( x = 0; x < pitch; x +=64 )
+ {
+ x0 = _mm_stream_load_si128( pLoad +0 );
+ x1 = _mm_stream_load_si128( pLoad +1 );
+ x2 = _mm_stream_load_si128( pLoad +2 );
+ x3 = _mm_stream_load_si128( pLoad +3 );
+
+ _mm_store_si128( pCache +0, x0 );
+ _mm_store_si128( pCache +1, x1 );
+ _mm_store_si128( pCache +2, x2 );
+ _mm_store_si128( pCache +3, x3 );
+
+ pCache += 4;
+ pLoad += 4;
+ }
+ }
+
+ _mm_mfence();
+
+ pCache = (__m128i *)pCacheBlock;
+
+ // STORE ROWS OF FRAME WIDTH FROM CACHED BLOCK
+ for( yStore = 0; yStore < rowsPerBlock; yStore++ )
+ {
+ // copy a row, cache line at a time
+ for( x = 0; x < width64; x +=64 )
+ {
+ x0 = _mm_load_si128( pCache );
+ x1 = _mm_load_si128( pCache +1 );
+ x2 = _mm_load_si128( pCache +2 );
+ x3 = _mm_load_si128( pCache +3 );
+
+ _mm_stream_si128( pStore, x0 );
+ _mm_stream_si128( pStore +1, x1 );
+ _mm_stream_si128( pStore +2, x2 );
+ _mm_stream_si128( pStore +3, x3 );
+
+ pCache += 4;
+ pStore += 4;
+ }
+
+ pCache += extraPitch;
+ pStore += extraPitch;
+ }
+ }
+}
+}
diff --git a/xbmc/platform/linux/sse4/DllLibSSE4.h b/xbmc/platform/linux/sse4/DllLibSSE4.h
new file mode 100644
index 0000000..a89f713
--- /dev/null
+++ b/xbmc/platform/linux/sse4/DllLibSSE4.h
@@ -0,0 +1,32 @@
+/*
+ * 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 "DynamicDll.h"
+
+extern "C" {
+
+}
+
+class DllLibSSE4Interface
+{
+public:
+ virtual ~DllLibSSE4Interface() = default;
+ virtual void copy_frame(void * pSrc, void * pDest, void * pCacheBlock, unsigned int width, unsigned int height, unsigned int pitch) = 0;
+};
+
+class DllLibSSE4 : public DllDynamic, DllLibSSE4Interface
+{
+ DECLARE_DLL_WRAPPER(DllLibSSE4, DLL_PATH_LIBSSE4)
+ DEFINE_METHOD6(void, copy_frame, (void *p1, void *p2, void *p3, unsigned int p4, unsigned int p5, unsigned int p6))
+
+ BEGIN_METHOD_RESOLVE()
+ RESOLVE_METHOD(copy_frame)
+ END_METHOD_RESOLVE()
+};
diff --git a/xbmc/platform/linux/storage/CMakeLists.txt b/xbmc/platform/linux/storage/CMakeLists.txt
new file mode 100644
index 0000000..223655a
--- /dev/null
+++ b/xbmc/platform/linux/storage/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(SOURCES LinuxStorageProvider.cpp)
+
+set(HEADERS LinuxStorageProvider.h)
+
+if(DBUS_FOUND)
+ list(APPEND SOURCES UDisksProvider.cpp
+ UDisks2Provider.cpp)
+ list(APPEND HEADERS UDisksProvider.h
+ UDisks2Provider.h)
+endif()
+
+if(UDEV_FOUND)
+ list(APPEND SOURCES UDevProvider.cpp)
+ list(APPEND HEADERS UDevProvider.h)
+endif()
+
+if(SOURCES)
+ core_add_library(platform_linux_storage)
+endif()
diff --git a/xbmc/platform/linux/storage/LinuxStorageProvider.cpp b/xbmc/platform/linux/storage/LinuxStorageProvider.cpp
new file mode 100644
index 0000000..6206466
--- /dev/null
+++ b/xbmc/platform/linux/storage/LinuxStorageProvider.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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 "LinuxStorageProvider.h"
+#include "guilib/LocalizeStrings.h"
+#include "UDevProvider.h"
+#ifdef HAS_DBUS
+#include "UDisksProvider.h"
+#include "UDisks2Provider.h"
+#endif
+#include "platform/posix/PosixMountProvider.h"
+
+std::unique_ptr<IStorageProvider> IStorageProvider::CreateInstance()
+{
+ return std::make_unique<CLinuxStorageProvider>();
+}
+
+CLinuxStorageProvider::CLinuxStorageProvider()
+{
+ m_instance = NULL;
+
+#ifdef HAS_DBUS
+ if (CUDisks2Provider::HasUDisks2())
+ m_instance = new CUDisks2Provider();
+ else if (CUDisksProvider::HasUDisks())
+ m_instance = new CUDisksProvider();
+#endif
+#ifdef HAVE_LIBUDEV
+ if (m_instance == NULL)
+ m_instance = new CUDevProvider();
+#endif
+
+ if (m_instance == NULL)
+ m_instance = new CPosixMountProvider();
+}
+
+CLinuxStorageProvider::~CLinuxStorageProvider()
+{
+ delete m_instance;
+}
+
+void CLinuxStorageProvider::Initialize()
+{
+ m_instance->Initialize();
+}
+
+void CLinuxStorageProvider::Stop()
+{
+ m_instance->Stop();
+}
+
+void CLinuxStorageProvider::GetLocalDrives(VECSOURCES &localDrives)
+{
+ // Home directory
+ CMediaSource share;
+ share.strPath = getenv("HOME");
+ share.strName = g_localizeStrings.Get(21440);
+ share.m_ignore = true;
+ share.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL;
+ localDrives.push_back(share);
+ share.strPath = "/";
+ share.strName = g_localizeStrings.Get(21453);
+ localDrives.push_back(share);
+
+ m_instance->GetLocalDrives(localDrives);
+}
+
+void CLinuxStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives)
+{
+ m_instance->GetRemovableDrives(removableDrives);
+}
+
+bool CLinuxStorageProvider::Eject(const std::string& mountpath)
+{
+ return m_instance->Eject(mountpath);
+}
+
+std::vector<std::string> CLinuxStorageProvider::GetDiskUsage()
+{
+ return m_instance->GetDiskUsage();
+}
+
+bool CLinuxStorageProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback)
+{
+ return m_instance->PumpDriveChangeEvents(callback);
+}
diff --git a/xbmc/platform/linux/storage/LinuxStorageProvider.h b/xbmc/platform/linux/storage/LinuxStorageProvider.h
new file mode 100644
index 0000000..e1c575f
--- /dev/null
+++ b/xbmc/platform/linux/storage/LinuxStorageProvider.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "storage/IStorageProvider.h"
+
+#include <vector>
+
+class CLinuxStorageProvider : public IStorageProvider
+{
+public:
+ CLinuxStorageProvider();
+ ~CLinuxStorageProvider() override;
+
+ void Initialize() override;
+ void Stop() override;
+ void GetLocalDrives(VECSOURCES &localDrives) override;
+ void GetRemovableDrives(VECSOURCES &removableDrives) override;
+ bool Eject(const std::string& mountpath) override;
+ std::vector<std::string> GetDiskUsage() override;
+ bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override;
+
+private:
+ IStorageProvider *m_instance;
+};
diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp
new file mode 100644
index 0000000..7123e87
--- /dev/null
+++ b/xbmc/platform/linux/storage/UDevProvider.cpp
@@ -0,0 +1,298 @@
+/*
+ * 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 "UDevProvider.h"
+
+#include "platform/posix/PosixMountProvider.h"
+#include "utils/log.h"
+#include "utils/URIUtils.h"
+
+extern "C" {
+#include <libudev.h>
+#include <poll.h>
+}
+
+static const char *get_mountpoint(const char *devnode)
+{
+ static char buf[4096];
+ const char *delim = " ";
+ const char *mountpoint = nullptr;
+ FILE *fp = fopen("/proc/mounts", "r");
+ if (!fp)
+ return nullptr;
+
+ while (fgets(buf, sizeof (buf), fp))
+ {
+ const char *node = strtok(buf, delim);
+ if (strcmp(node, devnode) == 0)
+ {
+ mountpoint = strtok(nullptr, delim);
+ break;
+ }
+ }
+
+ if (mountpoint)
+ {
+ // If mount point contain characters like space, it is converted to
+ // "\040". This situation should be handled.
+ char *c1, *c2;
+ for (c1 = c2 = const_cast<char*>(mountpoint); *c2; ++c1)
+ {
+ if (*c2 == '\\')
+ {
+ *c1 = (((c2[1] - '0') << 6) | ((c2[2] - '0') << 3) | (c2[3] - '0'));
+ c2 += 4;
+ continue;
+ }
+ if (c1 != c2)
+ *c1 = *c2;
+ ++c2;
+ }
+ *c1 = *c2;
+ }
+
+ fclose(fp);
+ return mountpoint;
+}
+
+CUDevProvider::CUDevProvider()
+{
+ m_udev = nullptr;
+ m_udevMon = nullptr;
+}
+
+void CUDevProvider::Initialize()
+{
+ CLog::Log(LOGDEBUG, "Selected UDev as storage provider");
+
+ m_udev = udev_new();
+ if (!m_udev)
+ {
+ CLog::Log(LOGERROR, "{} - failed to allocate udev context", __FUNCTION__);
+ return;
+ }
+ /* set up a devices monitor that listen for any device change */
+ m_udevMon = udev_monitor_new_from_netlink(m_udev, "udev");
+ udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "block", "disk");
+ udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "block", "partition");
+ udev_monitor_enable_receiving(m_udevMon);
+
+ PumpDriveChangeEvents(nullptr);
+}
+
+void CUDevProvider::Stop()
+{
+ udev_monitor_unref(m_udevMon);
+ udev_unref(m_udev);
+}
+
+void CUDevProvider::GetDisks(VECSOURCES& disks, bool removable)
+{
+ // enumerate existing block devices
+ struct udev_enumerate *u_enum = udev_enumerate_new(m_udev);
+ if (!u_enum)
+ {
+ fprintf(stderr, "Error: udev_enumerate_new(udev)\n");
+ return;
+ }
+
+ udev_enumerate_add_match_subsystem(u_enum, "block");
+ udev_enumerate_add_match_property(u_enum, "DEVTYPE", "disk");
+ udev_enumerate_add_match_property(u_enum, "DEVTYPE", "partition");
+ udev_enumerate_scan_devices(u_enum);
+
+ struct udev_list_entry *u_list_ent;
+ struct udev_list_entry *u_first_list_ent;
+ u_first_list_ent = udev_enumerate_get_list_entry(u_enum);
+ udev_list_entry_foreach(u_list_ent, u_first_list_ent)
+ {
+ const char *name = udev_list_entry_get_name(u_list_ent);
+ struct udev *context = udev_enumerate_get_udev(u_enum);
+ struct udev_device *device = udev_device_new_from_syspath(context, name);
+ if (!device)
+ continue;
+
+ // filter out devices without devnode
+ const char* devnode = udev_device_get_devnode(device);
+ if (!devnode)
+ {
+ udev_device_unref(device);
+ continue;
+ }
+
+ // filter out devices that are not mounted
+ const char* mountpoint = get_mountpoint(devnode);
+ if (!mountpoint)
+ {
+ udev_device_unref(device);
+ continue;
+ }
+
+ // filter out root partition
+ if (strcmp(mountpoint, "/") == 0)
+ {
+ udev_device_unref(device);
+ continue;
+ }
+
+ // filter out things mounted on /tmp
+ if (strstr(mountpoint, "/tmp"))
+ {
+ udev_device_unref(device);
+ continue;
+ }
+
+ // look for devices on the usb bus, or mounted on */media/ (sdcards), or optical devices
+ const char *bus = udev_device_get_property_value(device, "ID_BUS");
+ const char *optical = udev_device_get_property_value(device, "ID_CDROM"); // matches also DVD, Blu-ray
+ bool isRemovable = ((bus && strstr(bus, "usb")) ||
+ (optical && strstr(optical,"1")) ||
+ (mountpoint && strstr(mountpoint, "/media/")));
+
+ // filter according to requested device type
+ if (removable != isRemovable)
+ {
+ udev_device_unref(device);
+ continue;
+ }
+
+ const char *udev_label = udev_device_get_property_value(device, "ID_FS_LABEL");
+ std::string label;
+ if (udev_label)
+ label = udev_label;
+ else
+ label = URIUtils::GetFileName(mountpoint);
+
+ CMediaSource share;
+ share.strName = label;
+ share.strPath = mountpoint;
+ share.m_ignore = true;
+ if (isRemovable)
+ {
+ if (optical)
+ share.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD;
+ else
+ share.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE;
+ }
+ else
+ share.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL;
+
+ disks.push_back(share);
+ udev_device_unref(device);
+ }
+ udev_enumerate_unref(u_enum);
+}
+
+void CUDevProvider::GetLocalDrives(VECSOURCES &localDrives)
+{
+ GetDisks(localDrives, false);
+}
+
+void CUDevProvider::GetRemovableDrives(VECSOURCES &removableDrives)
+{
+ GetDisks(removableDrives, true);
+}
+
+bool CUDevProvider::Eject(const std::string& mountpath)
+{
+ // just go ahead and try to umount the disk
+ // if it does umount, life is good, if not, no loss.
+ std::string cmd = "umount \"" + mountpath + "\"";
+ int status = system(cmd.c_str());
+
+ if (status == 0)
+ return true;
+
+ return false;
+}
+
+std::vector<std::string> CUDevProvider::GetDiskUsage()
+{
+ CPosixMountProvider legacy;
+ return legacy.GetDiskUsage();
+}
+
+bool CUDevProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback)
+{
+ bool changed = false;
+
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(udev_monitor_get_fd(m_udevMon), &readfds);
+
+ // non-blocking, check the file descriptor for received data
+ struct timeval tv = {};
+ int count = select(udev_monitor_get_fd(m_udevMon) + 1, &readfds, nullptr, nullptr, &tv);
+ if (count < 0)
+ return false;
+
+ if (FD_ISSET(udev_monitor_get_fd(m_udevMon), &readfds))
+ {
+ struct udev_device* dev = udev_monitor_receive_device(m_udevMon);
+ if (!dev)
+ return false;
+
+ const char* action = udev_device_get_action(dev);
+ const char* devnode = udev_device_get_devnode(dev);
+ if (action && devnode)
+ {
+ MEDIA_DETECT::STORAGE::StorageDevice storageDevice;
+ const char *udev_label = udev_device_get_property_value(dev, "ID_FS_LABEL");
+ const char* mountpoint = get_mountpoint(devnode);
+ if (udev_label)
+ storageDevice.label = udev_label;
+ else if (mountpoint)
+ {
+ storageDevice.label = URIUtils::GetFileName(mountpoint);
+ storageDevice.path = mountpoint;
+ }
+
+ const char *fs_usage = udev_device_get_property_value(dev, "ID_FS_USAGE");
+ if (mountpoint && strcmp(action, "add") == 0 && (fs_usage && strcmp(fs_usage, "filesystem") == 0))
+ {
+ CLog::Log(LOGINFO, "UDev: Added {}", mountpoint);
+ if (callback)
+ callback->OnStorageAdded(storageDevice);
+ changed = true;
+ }
+ if (strcmp(action, "remove") == 0 && (fs_usage && strcmp(fs_usage, "filesystem") == 0))
+ {
+ if (callback)
+ callback->OnStorageSafelyRemoved(storageDevice);
+ changed = true;
+ }
+ // browse disk dialog is not wanted for blu-rays
+ const char *bd = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_BD");
+ if (strcmp(action, "change") == 0 && !(bd && strcmp(bd, "1") == 0))
+ {
+ const char *optical = udev_device_get_property_value(dev, "ID_CDROM");
+ bool isOptical = optical && (strcmp(optical, "1") != 0);
+ storageDevice.type =
+ isOptical ? MEDIA_DETECT::STORAGE::Type::OPTICAL : MEDIA_DETECT::STORAGE::Type::UNKNOWN;
+
+ if (mountpoint && !isOptical)
+ {
+ CLog::Log(LOGINFO, "UDev: Changed / Added {}", mountpoint);
+ if (callback)
+ callback->OnStorageAdded(storageDevice);
+ changed = true;
+ }
+ const char *eject_request = udev_device_get_property_value(dev, "DISK_EJECT_REQUEST");
+ if (eject_request && strcmp(eject_request, "1") == 0)
+ {
+ if (callback)
+ callback->OnStorageSafelyRemoved(storageDevice);
+ changed = true;
+ }
+ }
+ }
+ udev_device_unref(dev);
+ }
+
+ return changed;
+}
diff --git a/xbmc/platform/linux/storage/UDevProvider.h b/xbmc/platform/linux/storage/UDevProvider.h
new file mode 100644
index 0000000..a4c83b3
--- /dev/null
+++ b/xbmc/platform/linux/storage/UDevProvider.h
@@ -0,0 +1,42 @@
+/*
+ * 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 "storage/IStorageProvider.h"
+
+#include <string>
+#include <vector>
+
+struct udev;
+struct udev_monitor;
+
+class CUDevProvider : public IStorageProvider
+{
+public:
+ CUDevProvider();
+ ~CUDevProvider() override = default;
+
+ void Initialize() override;
+ void Stop() override;
+
+ void GetLocalDrives(VECSOURCES &localDrives) override;
+ void GetRemovableDrives(VECSOURCES &removableDrives) override;
+
+ bool Eject(const std::string& mountpath) override;
+
+ std::vector<std::string> GetDiskUsage() override;
+
+ bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override;
+
+private:
+ void GetDisks(VECSOURCES& devices, bool removable);
+
+ struct udev *m_udev;
+ struct udev_monitor *m_udevMon;
+};
diff --git a/xbmc/platform/linux/storage/UDisks2Provider.cpp b/xbmc/platform/linux/storage/UDisks2Provider.cpp
new file mode 100644
index 0000000..b3431c7
--- /dev/null
+++ b/xbmc/platform/linux/storage/UDisks2Provider.cpp
@@ -0,0 +1,861 @@
+/*
+ * 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 "UDisks2Provider.h"
+
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include "platform/posix/PosixMountProvider.h"
+
+#include <algorithm>
+#include <functional>
+
+#define BOOL2SZ(b) ((b) ? "true" : "false")
+
+#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
+
+#define UDISKS2_SERVICE_UDISKS2 "org.freedesktop.UDisks2"
+
+#define UDISKS2_PATH_MANAGER "/org/freedesktop/UDisks2/Manager"
+#define UDISKS2_PATH_UDISKS2 "/org/freedesktop/UDisks2"
+
+#define UDISKS2_INTERFACE_BLOCK "org.freedesktop.UDisks2.Block"
+#define UDISKS2_INTERFACE_DRIVE "org.freedesktop.UDisks2.Drive"
+#define UDISKS2_INTERFACE_FILESYSTEM "org.freedesktop.UDisks2.Filesystem"
+#define UDISKS2_INTERFACE_MANAGER "org.freedesktop.UDisks2.Manager"
+
+#define UDISKS2_MATCH_RULE "type='signal',sender='" UDISKS2_SERVICE_UDISKS2 "',path_namespace='" UDISKS2_PATH_UDISKS2 "'"
+
+CUDisks2Provider::Drive::Drive(const char *object) : m_object(object)
+{
+}
+
+bool CUDisks2Provider::Drive::IsOptical() const
+{
+ return std::any_of(m_mediaCompatibility.begin(), m_mediaCompatibility.end(),
+ [](const std::string& kind) { return kind.compare(0, 7, "optical") == 0; });
+}
+
+std::string CUDisks2Provider::Drive::ToString() const
+{
+ return StringUtils::Format("Drive {}: IsRemovable {} IsOptical {}", m_object,
+ BOOL2SZ(m_isRemovable), BOOL2SZ(IsOptical()));
+}
+
+CUDisks2Provider::Block::Block(const char *object) : m_object(object)
+{
+}
+
+bool CUDisks2Provider::Block::IsReady()
+{
+ return m_drive != nullptr;
+}
+
+std::string CUDisks2Provider::Block::ToString() const
+{
+ return StringUtils::Format("Block device {}: Device {} Label {} IsSystem: {} Drive {}", m_object,
+ m_device, m_label.empty() ? "none" : m_label, BOOL2SZ(m_isSystem),
+ m_driveobject.empty() ? "none" : m_driveobject);
+}
+
+CUDisks2Provider::Filesystem::Filesystem(const char *object) : m_object(object)
+{
+}
+
+std::string CUDisks2Provider::Filesystem::ToString() const
+{
+ return StringUtils::Format("Filesystem {}: IsMounted {} MountPoint {}", m_object,
+ BOOL2SZ(m_isMounted), m_mountPoint.empty() ? "none" : m_mountPoint);
+}
+
+MEDIA_DETECT::STORAGE::StorageDevice CUDisks2Provider::Filesystem::ToStorageDevice() const
+{
+ MEDIA_DETECT::STORAGE::StorageDevice device;
+ device.label = GetDisplayName();
+ device.path = GetMountPoint();
+ device.type = GetStorageType();
+ return device;
+}
+
+bool CUDisks2Provider::Filesystem::IsReady() const
+{
+ return m_block != nullptr && m_block->IsReady();
+}
+
+bool CUDisks2Provider::Filesystem::IsOptical() const
+{
+ return m_block->m_drive->IsOptical();
+}
+
+MEDIA_DETECT::STORAGE::Type CUDisks2Provider::Filesystem::GetStorageType() const
+{
+ if (m_block == nullptr || !m_block->IsReady())
+ return MEDIA_DETECT::STORAGE::Type::UNKNOWN;
+
+ if (IsOptical())
+ return MEDIA_DETECT::STORAGE::Type::OPTICAL;
+
+ return MEDIA_DETECT::STORAGE::Type::UNKNOWN;
+}
+
+std::string CUDisks2Provider::Filesystem::GetMountPoint() const
+{
+ return m_mountPoint;
+}
+
+std::string CUDisks2Provider::Filesystem::GetObject() const
+{
+ return m_object;
+}
+
+void CUDisks2Provider::Filesystem::ResetMountPoint()
+{
+ m_mountPoint.clear();
+ m_isMounted = false;
+}
+
+void CUDisks2Provider::Filesystem::SetMountPoint(const std::string& mountPoint)
+{
+ m_mountPoint = mountPoint;
+ m_isMounted = true;
+}
+
+bool CUDisks2Provider::Filesystem::IsMounted() const
+{
+ return m_isMounted;
+}
+
+bool CUDisks2Provider::Filesystem::Mount()
+{
+ if (m_block->m_isSystem) {
+ CLog::Log(LOGDEBUG, "UDisks2: Skip mount of system device {}", ToString());
+ return false;
+ }
+ else if (m_isMounted)
+ {
+ CLog::Log(LOGDEBUG, "UDisks2: Skip mount of already mounted device {}", ToString());
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "UDisks2: Mounting {}", m_block->m_device);
+ CDBusMessage message(UDISKS2_SERVICE_UDISKS2, m_object, UDISKS2_INTERFACE_FILESYSTEM, "Mount");
+ AppendEmptyOptions(message.GetArgumentIter());
+ DBusMessage *reply = message.SendSystem();
+ return (reply && dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_ERROR);
+ }
+}
+
+bool CUDisks2Provider::Filesystem::Unmount()
+{
+ if (m_block->m_isSystem) {
+ CLog::Log(LOGDEBUG, "UDisks2: Skip unmount of system device {}", ToString());
+ return false;
+ }
+ else if (!m_isMounted)
+ {
+ CLog::Log(LOGDEBUG, "UDisks2: Skip unmount of not mounted device {}", ToString());
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "UDisks2: Unmounting {}", m_block->m_device);
+ CDBusMessage message(UDISKS2_SERVICE_UDISKS2, m_object, UDISKS2_INTERFACE_FILESYSTEM, "Unmount");
+ AppendEmptyOptions(message.GetArgumentIter());
+ DBusMessage *reply = message.SendSystem();
+ return (reply && dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_ERROR);
+ }
+}
+
+std::string CUDisks2Provider::Filesystem::GetDisplayName() const
+{
+ if (m_block->m_label.empty())
+ {
+ std::string strSize = StringUtils::SizeToString(m_block->m_size);
+ return StringUtils::Format("{} {}", strSize, g_localizeStrings.Get(155));
+ }
+ else
+ return m_block->m_label;
+}
+
+CMediaSource CUDisks2Provider::Filesystem::ToMediaShare() const
+{
+ CMediaSource source;
+ source.strPath = m_mountPoint;
+ source.strName = GetDisplayName();
+ if (IsOptical())
+ source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD;
+ else if (m_block->m_isSystem)
+ source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL;
+ else
+ source.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE;
+ source.m_ignore = true;
+ return source;
+}
+
+bool CUDisks2Provider::Filesystem::IsApproved() const
+{
+ return IsReady() &&
+ (m_isMounted && m_mountPoint != "/" && m_mountPoint != "/boot" && m_mountPoint.compare(0, 6, "/boot/") != 0) /*||
+ m_block->m_drive->m_isOptical*/;
+}
+
+CUDisks2Provider::CUDisks2Provider()
+{
+ if (!m_connection.Connect(DBUS_BUS_SYSTEM, true))
+ {
+ return;
+ }
+
+ dbus_connection_set_exit_on_disconnect(m_connection, static_cast<dbus_bool_t>(false));
+
+ CDBusError error;
+ dbus_bus_add_match(m_connection, UDISKS2_MATCH_RULE, error);
+ dbus_connection_flush(m_connection);
+
+ if (error)
+ {
+ error.Log("UDisks2: Failed to attach to signal");
+ m_connection.Destroy();
+ }
+}
+
+CUDisks2Provider::~CUDisks2Provider()
+{
+ for (auto &elt : m_filesystems)
+ {
+ delete elt.second;
+ }
+ m_filesystems.clear();
+
+ for (auto &elt : m_blocks)
+ {
+ delete elt.second;
+ }
+ m_blocks.clear();
+
+ for (auto &elt : m_drives)
+ {
+ delete elt.second;
+ }
+ m_drives.clear();
+}
+
+void CUDisks2Provider::Initialize()
+{
+ CLog::Log(LOGDEBUG, "Selected UDisks2 as storage provider");
+ m_daemonVersion = CDBusUtil::GetVariant(UDISKS2_SERVICE_UDISKS2, UDISKS2_PATH_MANAGER, UDISKS2_INTERFACE_MANAGER,
+ "Version").asString();
+ CLog::Log(LOGDEBUG, "UDisks2: Daemon version {}", m_daemonVersion);
+
+ CLog::Log(LOGDEBUG, "UDisks2: Querying available devices");
+ CDBusMessage message(UDISKS2_SERVICE_UDISKS2, UDISKS2_PATH_UDISKS2, DBUS_INTERFACE_OBJECT_MANAGER,
+ "GetManagedObjects");
+ DBusMessage *reply = message.SendSystem();
+
+ if (reply && dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_ERROR)
+ {
+ HandleManagedObjects(reply);
+ }
+}
+
+bool CUDisks2Provider::PumpDriveChangeEvents(IStorageEventsCallback *callback)
+{
+ if (m_connection)
+ {
+ dbus_connection_read_write(m_connection, 0);
+ DBusMessagePtr msg(dbus_connection_pop_message(m_connection));
+
+ if (msg)
+ {
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Message received (interface: {}, member: {})",
+ dbus_message_get_interface(msg.get()), dbus_message_get_member(msg.get()));
+
+ if (dbus_message_is_signal(msg.get(), DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded"))
+ {
+ HandleInterfacesAdded(msg.get());
+ return false;
+ }
+ else if (dbus_message_is_signal(msg.get(), DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved"))
+ {
+ return HandleInterfacesRemoved(msg.get(), callback);
+ }
+ else if (dbus_message_is_signal(msg.get(), DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"))
+ {
+ return HandlePropertiesChanged(msg.get(), callback);
+ }
+ }
+ }
+ return false;
+}
+
+bool CUDisks2Provider::HasUDisks2()
+{
+ return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, UDISKS2_SERVICE_UDISKS2, UDISKS2_PATH_UDISKS2, DBUS_INTERFACE_PEER,
+ "Ping");
+}
+
+bool CUDisks2Provider::Eject(const std::string &mountpath)
+{
+ std::string path(mountpath);
+ URIUtils::RemoveSlashAtEnd(path);
+
+ for (const auto &elt: m_filesystems)
+ {
+ Filesystem *fs = elt.second;
+ if (fs->GetMountPoint() == path)
+ {
+ return fs->Unmount();
+ }
+ }
+
+ return false;
+}
+
+std::vector<std::string> CUDisks2Provider::GetDiskUsage()
+{
+ CPosixMountProvider legacy;
+ return legacy.GetDiskUsage();
+}
+
+void CUDisks2Provider::GetDisks(VECSOURCES &devices, bool enumerateRemovable)
+{
+ for (const auto &elt: m_filesystems)
+ {
+ Filesystem *fs = elt.second;
+ if (fs->IsApproved() && fs->m_block->m_isSystem != enumerateRemovable)
+ devices.push_back(fs->ToMediaShare());
+ }
+}
+
+void CUDisks2Provider::DriveAdded(Drive *drive)
+{
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Drive added - {}", drive->ToString());
+
+ if (m_drives[drive->m_object])
+ {
+ CLog::Log(LOGWARNING, "UDisks2: Inconsistency found! DriveAdded on an indexed drive");
+ delete m_drives[drive->m_object];
+ }
+
+ m_drives[drive->m_object] = drive;
+
+ for (auto &elt: m_blocks)
+ {
+ auto block = elt.second;
+ if (block->m_driveobject == drive->m_object)
+ {
+ block->m_drive = drive;
+ BlockAdded(block, false);
+ }
+ }
+}
+
+bool CUDisks2Provider::DriveRemoved(const std::string& object)
+{
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Drive removed ({})", object);
+
+ if (m_drives.count(object) > 0)
+ {
+ delete m_drives[object];
+ m_drives.erase(object);
+ }
+
+ for (auto &elt: m_blocks)
+ {
+ auto block = elt.second;
+ if (block->m_driveobject == object)
+ {
+ block->m_drive = nullptr;
+ }
+ }
+
+ return false;
+}
+
+void CUDisks2Provider::BlockAdded(Block *block, bool isNew)
+{
+ if (isNew)
+ {
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Block added - {}", block->ToString());
+
+ if (m_drives.count(block->m_driveobject) > 0)
+ block->m_drive = m_drives[block->m_driveobject];
+
+ if (m_blocks[block->m_object])
+ {
+ CLog::Log(LOGWARNING, "UDisks2: Inconsistency found! BlockAdded on an indexed block device");
+ delete m_blocks[block->m_object];
+ }
+
+ m_blocks[block->m_object] = block;
+ }
+
+
+ if (m_filesystems.count(block->m_object) > 0)
+ {
+ auto fs = m_filesystems[block->m_object];
+ fs->m_block = block;
+ FilesystemAdded(fs, false);
+ }
+}
+
+bool CUDisks2Provider::BlockRemoved(const std::string& object)
+{
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Block removed ({})", object);
+
+ if (m_blocks.count(object) > 0)
+ {
+ delete m_blocks[object];
+ m_blocks.erase(object);
+ }
+
+ if (m_filesystems.count(object) > 0)
+ {
+ m_filesystems[object]->m_block = nullptr;
+ }
+
+ return false;
+}
+
+void CUDisks2Provider::FilesystemAdded(Filesystem *fs, bool isNew)
+{
+ if (isNew)
+ {
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Filesystem added - {}", fs->ToString());
+
+ if (m_blocks.count(fs->GetObject()) > 0)
+ fs->m_block = m_blocks[fs->GetObject()];
+
+ if (m_filesystems[fs->GetObject()])
+ {
+ CLog::Log(LOGWARNING, "UDisks2: Inconsistency found! FilesystemAdded on an indexed filesystem");
+ delete m_filesystems[fs->GetObject()];
+ }
+
+ m_filesystems[fs->GetObject()] = fs;
+ }
+
+ if (fs->IsReady() && !fs->IsMounted())
+ {
+ // optical drives should be always mounted by default unless explicitly disabled by the user
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_handleMounting ||
+ (fs->IsOptical() &&
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_autoMountOpticalMedia))
+ {
+ fs->Mount();
+ }
+ }
+}
+
+bool CUDisks2Provider::FilesystemRemoved(const char *object, IStorageEventsCallback *callback)
+{
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Filesystem removed ({})", object);
+ bool result = false;
+ if (m_filesystems.count(object) > 0)
+ {
+ auto fs = m_filesystems[object];
+ if (fs->IsMounted())
+ {
+ callback->OnStorageUnsafelyRemoved(fs->ToStorageDevice());
+ result = true;
+ }
+ delete m_filesystems[object];
+ m_filesystems.erase(object);
+ }
+ return result;
+}
+
+void CUDisks2Provider::HandleManagedObjects(DBusMessage *msg)
+{
+ DBusMessageIter msgIter, dictIter;
+ dbus_message_iter_init(msg, &msgIter);
+ dbus_message_iter_recurse(&msgIter, &dictIter);
+ while (dbus_message_iter_get_arg_type(&dictIter) == DBUS_TYPE_DICT_ENTRY)
+ {
+ DBusMessageIter objIter;
+ dbus_message_iter_recurse(&dictIter, &objIter);
+ ParseInterfaces(&objIter);
+ dbus_message_iter_next(&dictIter);
+ }
+}
+
+void CUDisks2Provider::HandleInterfacesAdded(DBusMessage *msg)
+{
+ DBusMessageIter msgIter;
+ dbus_message_iter_init(msg, &msgIter);
+ ParseInterfaces(&msgIter);
+}
+
+bool CUDisks2Provider::HandleInterfacesRemoved(DBusMessage *msg, IStorageEventsCallback *callback)
+{
+ DBusMessageIter msgIter, ifaceIter;
+ const char *path, *iface;
+ bool result = false;
+ dbus_message_iter_init(msg, &msgIter);
+ dbus_message_iter_get_basic(&msgIter, &path);
+ dbus_message_iter_next(&msgIter);
+ dbus_message_iter_recurse(&msgIter, &ifaceIter);
+ while (dbus_message_iter_get_arg_type(&ifaceIter) == DBUS_TYPE_STRING)
+ {
+ dbus_message_iter_get_basic(&ifaceIter, &iface);
+ result |= RemoveInterface(path, iface, callback);
+ dbus_message_iter_next(&ifaceIter);
+ }
+ return result;
+}
+
+bool CUDisks2Provider::HandlePropertiesChanged(DBusMessage *msg, IStorageEventsCallback *callback)
+{
+ DBusMessageIter msgIter, propsIter;
+ const char *object = dbus_message_get_path(msg);
+ const char *iface;
+
+ dbus_message_iter_init(msg, &msgIter);
+ dbus_message_iter_get_basic(&msgIter, &iface);
+ dbus_message_iter_next(&msgIter);
+ dbus_message_iter_recurse(&msgIter, &propsIter);
+
+ if (strcmp(iface, UDISKS2_INTERFACE_DRIVE) == 0)
+ {
+ return DrivePropertiesChanged(object, &propsIter);
+ }
+ else if (strcmp(iface, UDISKS2_INTERFACE_BLOCK) == 0)
+ {
+ return BlockPropertiesChanged(object, &propsIter);
+ }
+ else if (strcmp(iface, UDISKS2_INTERFACE_FILESYSTEM) == 0)
+ {
+ return FilesystemPropertiesChanged(object, &propsIter, callback);
+ }
+
+ return false;
+}
+
+bool CUDisks2Provider::DrivePropertiesChanged(const char *object, DBusMessageIter *propsIter)
+{
+ if (m_drives.count(object) > 0)
+ {
+ auto drive = m_drives[object];
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Before update: {}", drive->ToString());
+ auto ParseDriveProperty = std::bind(&CUDisks2Provider::ParseDriveProperty, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3);
+ ParseProperties(drive, propsIter, ParseDriveProperty);
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: After update: {}", drive->ToString());
+ }
+ return false;
+}
+
+bool CUDisks2Provider::BlockPropertiesChanged(const char *object, DBusMessageIter *propsIter)
+{
+ if (m_blocks.count(object) > 0)
+ {
+ auto block = m_blocks[object];
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Before update: {}", block->ToString());
+ auto ParseBlockProperty = std::bind(&CUDisks2Provider::ParseBlockProperty, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3);
+ ParseProperties(block, propsIter, ParseBlockProperty);
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: After update: {}", block->ToString());
+ }
+ return false;
+}
+
+bool CUDisks2Provider::FilesystemPropertiesChanged(const char *object, DBusMessageIter *propsIter, IStorageEventsCallback *callback)
+{
+ if (m_filesystems.count(object) > 0)
+ {
+ auto fs = m_filesystems[object];
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Before update: {}", fs->ToString());
+ bool wasMounted = fs->IsMounted();
+ auto ParseFilesystemProperty = std::bind(&CUDisks2Provider::ParseFilesystemProperty, this,
+ std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3);
+ ParseProperties(fs, propsIter, ParseFilesystemProperty);
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: After update: {}", fs->ToString());
+
+ if (!wasMounted && fs->IsMounted() && fs->IsApproved())
+ {
+ CLog::Log(LOGINFO, "UDisks2: Added {}", fs->GetMountPoint());
+ if (callback)
+ callback->OnStorageAdded(fs->ToStorageDevice());
+ return true;
+ }
+ else if (wasMounted && !fs->IsMounted())
+ {
+ CLog::Log(LOGINFO, "UDisks2: Removed {}", fs->m_block->m_device);
+ if (callback)
+ callback->OnStorageSafelyRemoved(fs->ToStorageDevice());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CUDisks2Provider::RemoveInterface(const char *path, const char *iface, IStorageEventsCallback *callback)
+{
+ if (strcmp(iface, UDISKS2_INTERFACE_DRIVE) == 0)
+ {
+ return DriveRemoved(path);
+ }
+ else if (strcmp(iface, UDISKS2_INTERFACE_BLOCK) == 0)
+ {
+ return BlockRemoved(path);
+ }
+ else if (strcmp(iface, UDISKS2_INTERFACE_FILESYSTEM) == 0)
+ {
+ return FilesystemRemoved(path, callback);
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+void CUDisks2Provider::ParseInterfaces(DBusMessageIter *objIter)
+{
+ DBusMessageIter dictIter;
+ const char *object;
+ dbus_message_iter_get_basic(objIter, &object);
+ dbus_message_iter_next(objIter);
+ dbus_message_iter_recurse(objIter, &dictIter);
+ while (dbus_message_iter_get_arg_type(&dictIter) == DBUS_TYPE_DICT_ENTRY)
+ {
+ DBusMessageIter ifaceIter, propsIter;
+ const char *iface;
+ dbus_message_iter_recurse(&dictIter, &ifaceIter);
+ dbus_message_iter_get_basic(&ifaceIter, &iface);
+ dbus_message_iter_next(&ifaceIter);
+ dbus_message_iter_recurse(&ifaceIter, &propsIter);
+ ParseInterface(object, iface, &propsIter/*, &discovery*/);
+ dbus_message_iter_next(&dictIter);
+ }
+}
+
+void CUDisks2Provider::ParseInterface(const char *object, const char *iface, DBusMessageIter *propsIter)
+{
+ if (strcmp(iface, UDISKS2_INTERFACE_DRIVE) == 0)
+ {
+ auto *drive = new Drive(object);
+ auto f = std::bind(&CUDisks2Provider::ParseDriveProperty, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3);
+ ParseProperties(drive, propsIter, f);
+ DriveAdded(drive);
+ }
+ else if (strcmp(iface, UDISKS2_INTERFACE_BLOCK) == 0)
+ {
+ auto *block = new Block(object);
+ auto f = std::bind(&CUDisks2Provider::ParseBlockProperty, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3);
+ ParseProperties(block, propsIter, f);
+ BlockAdded(block);
+ }
+ else if (strcmp(iface, UDISKS2_INTERFACE_FILESYSTEM) == 0)
+ {
+ auto *fs = new Filesystem(object);
+ auto f = std::bind(&CUDisks2Provider::ParseFilesystemProperty, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3);
+ ParseProperties(fs, propsIter, f);
+ FilesystemAdded(fs);
+ }
+}
+
+
+template<class Object, class Function>
+void CUDisks2Provider::ParseProperties(Object *ref, DBusMessageIter *propsIter, Function f)
+{
+ while (dbus_message_iter_get_arg_type(propsIter) == DBUS_TYPE_DICT_ENTRY)
+ {
+ DBusMessageIter propIter, varIter;
+ const char *key;
+
+ dbus_message_iter_recurse(propsIter, &propIter);
+
+ dbus_message_iter_get_basic(&propIter, &key);
+ dbus_message_iter_next(&propIter);
+
+ dbus_message_iter_recurse(&propIter, &varIter);
+
+ f(ref, key, &varIter);
+
+ dbus_message_iter_next(propsIter);
+ }
+
+}
+
+void CUDisks2Provider::ParseDriveProperty(Drive *drive, const char *key, DBusMessageIter *varIter)
+{
+ switch (dbus_message_iter_get_arg_type(varIter))
+ {
+ case DBUS_TYPE_BOOLEAN:
+ {
+ dbus_bool_t value;
+
+ if (strcmp(key, "Removable") == 0)
+ {
+ dbus_message_iter_get_basic(varIter, &value);
+ drive->m_isRemovable = static_cast<bool>(value);
+ }
+
+ break;
+ }
+ case DBUS_TYPE_ARRAY:
+ {
+ DBusMessageIter arrIter;
+
+ if (strcmp(key, "MediaCompatibility") == 0)
+ {
+ dbus_message_iter_recurse(varIter, &arrIter);
+ while (dbus_message_iter_get_arg_type(&arrIter) == DBUS_TYPE_STRING)
+ {
+ const char *compatibility;
+ dbus_message_iter_get_basic(&arrIter, &compatibility);
+ drive->m_mediaCompatibility.emplace_back(compatibility);
+ dbus_message_iter_next(&arrIter);
+ }
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+
+void CUDisks2Provider::ParseBlockProperty(Block *block, const char *key, DBusMessageIter *varIter)
+{
+ switch (dbus_message_iter_get_arg_type(varIter))
+ {
+ case DBUS_TYPE_OBJECT_PATH:
+ {
+ const char *value;
+
+ if (strcmp(key, "Drive") == 0)
+ {
+ dbus_message_iter_get_basic(varIter, &value);
+ block->m_driveobject.assign(value);
+ }
+
+ break;
+ }
+ case DBUS_TYPE_STRING:
+ {
+ const char *value;
+
+ if (strcmp(key, "IdLabel") == 0)
+ {
+ dbus_message_iter_get_basic(varIter, &value);
+ block->m_label.assign(value);
+ }
+
+ break;
+ }
+ case DBUS_TYPE_BOOLEAN:
+ {
+ dbus_bool_t value;
+
+ if (strcmp(key, "HintSystem") == 0)
+ {
+ dbus_message_iter_get_basic(varIter, &value);
+ block->m_isSystem = static_cast<bool>(value);
+ }
+
+ break;
+ }
+ case DBUS_TYPE_UINT64:
+ {
+ dbus_uint64_t value;
+
+ if (strcmp(key, "Size") == 0)
+ {
+ dbus_message_iter_get_basic(varIter, &value);
+ block->m_size = value;
+ }
+
+ break;
+ }
+ case DBUS_TYPE_ARRAY:
+ {
+ DBusMessageIter arrIter;
+
+ if (strcmp(key, "PreferredDevice") == 0)
+ {
+ dbus_message_iter_recurse(varIter, &arrIter);
+ block->m_device.assign(ParseByteArray(&arrIter));
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void CUDisks2Provider::ParseFilesystemProperty(Filesystem *fs, const char *key, DBusMessageIter *varIter)
+{
+ switch (dbus_message_iter_get_arg_type(varIter))
+ {
+ case DBUS_TYPE_ARRAY:
+ {
+ DBusMessageIter arrIter;
+
+ if (strcmp(key, "MountPoints") == 0)
+ {
+ dbus_message_iter_recurse(varIter, &arrIter);
+
+ if (dbus_message_iter_get_arg_type(&arrIter) == DBUS_TYPE_ARRAY)
+ {
+ DBusMessageIter valIter;
+
+ dbus_message_iter_recurse(&arrIter, &valIter);
+ fs->SetMountPoint(ParseByteArray(&valIter));
+
+ dbus_message_iter_next(&arrIter);
+ }
+ else
+ {
+ fs->ResetMountPoint();
+ }
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+std::string CUDisks2Provider::ParseByteArray(DBusMessageIter *arrIter)
+{
+ std::ostringstream strStream;
+
+ while (dbus_message_iter_get_arg_type(arrIter) == DBUS_TYPE_BYTE)
+ {
+ dbus_int16_t value = 0;
+ dbus_message_iter_get_basic(arrIter, &value);
+ if (value == 0)
+ break;
+ strStream << static_cast<char>(value);
+ dbus_message_iter_next(arrIter);
+ }
+
+ return strStream.str();
+}
+
+void CUDisks2Provider::AppendEmptyOptions(DBusMessageIter *argsIter)
+{
+ DBusMessageIter dictIter;
+ dbus_message_iter_open_container(argsIter, DBUS_TYPE_ARRAY, "{sv}", &dictIter);
+ dbus_message_iter_close_container(argsIter, &dictIter);
+}
diff --git a/xbmc/platform/linux/storage/UDisks2Provider.h b/xbmc/platform/linux/storage/UDisks2Provider.h
new file mode 100644
index 0000000..b4c85ee
--- /dev/null
+++ b/xbmc/platform/linux/storage/UDisks2Provider.h
@@ -0,0 +1,210 @@
+/*
+ * 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 "DBusUtil.h"
+#include "storage/IStorageProvider.h"
+
+#include <string>
+#include <vector>
+
+class CUDisks2Provider : public IStorageProvider
+{
+ class Drive
+ {
+ public:
+ std::string m_object;
+ bool m_isRemovable = false;
+ std::vector<std::string> m_mediaCompatibility;
+
+ explicit Drive(const char *object);
+ ~Drive() = default;
+
+ /*! \brief Check if the drive is optical
+ * @return true if the drive is optical, false otherwise
+ */
+ bool IsOptical() const;
+
+ /*! \brief Get a representation of the drive as a readable string
+ * @return drive as a string
+ */
+ std::string ToString() const;
+ };
+
+ class Block
+ {
+ public:
+ Drive *m_drive = nullptr;
+ std::string m_object;
+ std::string m_driveobject;
+ std::string m_label;
+ std::string m_device;
+ bool m_isSystem = false;
+ u_int64_t m_size = 0;
+
+ explicit Block(const char *object);
+ ~Block() = default;
+
+ bool IsReady();
+
+ /*! \brief Get a representation of the block as a readable string
+ * @return block as a string
+ */
+ std::string ToString() const;
+ };
+
+ class Filesystem
+ {
+ public:
+ Block *m_block = nullptr;
+
+ explicit Filesystem(const char *object);
+ ~Filesystem() = default;
+
+ bool Mount();
+ bool Unmount();
+
+ /*! \brief Get the device display name/label
+ * @return the device display name/label
+ */
+ std::string GetDisplayName() const;
+
+ /*! \brief Check if the device is approved/whitelisted
+ * @return true if the device is approved/whitelisted, false otherwise
+ */
+ bool IsApproved() const;
+
+ /*! \brief Check if the device is mounted
+ * @return true if the device is mounted, false otherwise
+ */
+ bool IsMounted() const;
+
+ /*! \brief Check if the device is ready
+ * @return true if the device is ready, false otherwise
+ */
+ bool IsReady() const;
+
+ /*! \brief Check if the device is optical
+ * @return true if the device is optical, false otherwise
+ */
+ bool IsOptical() const;
+
+ /*! \brief Get the storage type of this device
+ * @return the storage type (e.g. OPTICAL) or UNKNOWN if
+ * the type couldn't be detected
+ */
+ MEDIA_DETECT::STORAGE::Type GetStorageType() const;
+
+ /*! \brief Get the device mount point
+ * @return the device mount point
+ */
+ std::string GetMountPoint() const;
+
+ /*! \brief Reset the device mount point
+ */
+ void ResetMountPoint();
+
+ /*! \brief Set the device mount point
+ * @param mountPoint the device mount point
+ */
+ void SetMountPoint(const std::string& mountPoint);
+
+ /*! \brief Get the device dbus object
+ * @return the device dbus object
+ */
+ std::string GetObject() const;
+
+ /*! \brief Get a representation of the device as a readable string
+ * @return device as a string
+ */
+ std::string ToString() const;
+
+ /*! \brief Get a representation of the device as a media share
+ * @return the media share
+ */
+ CMediaSource ToMediaShare() const;
+
+ /*! \brief Get a representation of the device as a storage device abstraction
+ * @return the storage device abstraction of the device
+ */
+ MEDIA_DETECT::STORAGE::StorageDevice ToStorageDevice() const;
+
+ private:
+ bool m_isMounted = false;
+ std::string m_object;
+ std::string m_mountPoint;
+ };
+
+ typedef std::map<std::string, Drive *> DriveMap;
+ typedef std::map<std::string, Block *> BlockMap;
+ typedef std::map<std::string, Filesystem *> FilesystemMap;
+
+public:
+ CUDisks2Provider();
+ ~CUDisks2Provider() override;
+
+ void Initialize() override;
+
+ bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override;
+
+ static bool HasUDisks2();
+
+ bool Eject(const std::string &mountpath) override;
+
+ std::vector<std::string> GetDiskUsage() override;
+
+ void GetLocalDrives(VECSOURCES &localDrives) override
+ { GetDisks(localDrives, false); }
+
+ void GetRemovableDrives(VECSOURCES &removableDrives) override
+ { GetDisks(removableDrives, true); }
+
+ void Stop() override
+ {}
+
+private:
+ CDBusConnection m_connection;
+
+ DriveMap m_drives;
+ BlockMap m_blocks;
+ FilesystemMap m_filesystems;
+
+ std::string m_daemonVersion;
+
+ void GetDisks(VECSOURCES &devices, bool enumerateRemovable);
+
+ void DriveAdded(Drive *drive);
+ bool DriveRemoved(const std::string& object);
+ void BlockAdded(Block *block, bool isNew = true);
+ bool BlockRemoved(const std::string& object);
+ void FilesystemAdded(Filesystem *fs, bool isNew = true);
+ bool FilesystemRemoved(const char *object, IStorageEventsCallback *callback);
+
+ bool HandleInterfacesRemoved(DBusMessage *msg, IStorageEventsCallback *callback);
+ void HandleInterfacesAdded(DBusMessage *msg);
+ bool HandlePropertiesChanged(DBusMessage *msg, IStorageEventsCallback *callback);
+
+ bool DrivePropertiesChanged(const char *object, DBusMessageIter *propsIter);
+ bool BlockPropertiesChanged(const char *object, DBusMessageIter *propsIter);
+ bool FilesystemPropertiesChanged(const char *object, DBusMessageIter *propsIter, IStorageEventsCallback *callback);
+
+ bool RemoveInterface(const char *path, const char *iface, IStorageEventsCallback *callback);
+
+ template<class Object, class Function>
+ void ParseProperties(Object *ref, DBusMessageIter *dictIter, Function f);
+ void ParseInterfaces(DBusMessageIter *dictIter);
+ void ParseDriveProperty(Drive *drive, const char *key, DBusMessageIter *varIter);
+ void ParseBlockProperty(Block *block, const char *key, DBusMessageIter *varIter);
+ void ParseFilesystemProperty(Filesystem *fs, const char *key, DBusMessageIter *varIter);
+ std::string ParseByteArray(DBusMessageIter *arrIter);
+ void HandleManagedObjects(DBusMessage *msg);
+ void ParseInterface(const char *object, const char *iface, DBusMessageIter *propsIter);
+
+ static void AppendEmptyOptions(DBusMessageIter *argsIter);
+};
diff --git a/xbmc/platform/linux/storage/UDisksProvider.cpp b/xbmc/platform/linux/storage/UDisksProvider.cpp
new file mode 100644
index 0000000..7a2a034
--- /dev/null
+++ b/xbmc/platform/linux/storage/UDisksProvider.cpp
@@ -0,0 +1,412 @@
+/*
+ * 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 "UDisksProvider.h"
+
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include "platform/posix/PosixMountProvider.h"
+
+CUDiskDevice::CUDiskDevice(const char *DeviceKitUDI):
+ m_DeviceKitUDI(DeviceKitUDI)
+{
+ m_isMounted = false;
+ m_isMountedByUs = false;
+ m_isRemovable = false;
+ m_isPartition = false;
+ m_isFileSystem = false;
+ m_isSystemInternal = false;
+ m_isOptical = false;
+ m_PartitionSize = 0;
+ Update();
+}
+
+void CUDiskDevice::Update()
+{
+ CVariant properties = CDBusUtil::GetAll("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device");
+
+ m_isFileSystem = properties["IdUsage"].asString() == "filesystem";
+ if (m_isFileSystem)
+ {
+ m_UDI = properties["IdUuid"].asString();
+ m_Label = properties["IdLabel"].asString();
+ m_FileSystem = properties["IdType"].asString();
+ }
+ else
+ {
+ m_UDI.clear();
+ m_Label.clear();
+ m_FileSystem.clear();
+ }
+
+ m_isMounted = properties["DeviceIsMounted"].asBoolean();
+ if (m_isMounted && !properties["DeviceMountPaths"].empty())
+ m_MountPath = properties["DeviceMountPaths"][0].asString();
+ else
+ m_MountPath.clear();
+
+ m_PartitionSize = properties["PartitionSize"].asUnsignedInteger();
+ m_isPartition = properties["DeviceIsPartition"].asBoolean();
+ m_isSystemInternal = properties["DeviceIsSystemInternal"].asBoolean();
+ m_isOptical = properties["DeviceIsOpticalDisc"].asBoolean();
+ if (m_isPartition)
+ {
+ CVariant isRemovable = CDBusUtil::GetVariant("org.freedesktop.UDisks", properties["PartitionSlave"].asString().c_str(), "org.freedesktop.UDisks.Device", "DeviceIsRemovable");
+
+ if ( !isRemovable.isNull() )
+ m_isRemovable = isRemovable.asBoolean();
+ else
+ m_isRemovable = false;
+ }
+ else
+ m_isRemovable = properties["DeviceIsRemovable"].asBoolean();
+}
+
+bool CUDiskDevice::Mount()
+{
+ if (!m_isMounted && !m_isSystemInternal && m_isFileSystem)
+ {
+ CLog::Log(LOGDEBUG, "UDisks: Mounting {}", m_DeviceKitUDI);
+ CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemMount");
+ message.AppendArgument("");
+ const char *array[] = {};
+ message.AppendArgument(array, 0);
+
+ DBusMessage *reply = message.SendSystem();
+ if (reply)
+ {
+ char *mountPoint;
+ if (dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &mountPoint, DBUS_TYPE_INVALID))
+ {
+ m_MountPath = mountPoint;
+ CLog::Log(LOGDEBUG, "UDisks: Successfully mounted {} on {}", m_DeviceKitUDI, mountPoint);
+ m_isMountedByUs = m_isMounted = true;
+ }
+ }
+
+ return m_isMounted;
+ }
+ else
+ CLog::Log(LOGDEBUG, "UDisks: Is not able to mount {}", ToString());
+
+ return false;
+}
+
+bool CUDiskDevice::UnMount()
+{
+ if (m_isMounted && !m_isSystemInternal && m_isFileSystem)
+ {
+ CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemUnmount");
+
+ const char *array[1];
+ message.AppendArgument(array, 0);
+
+ DBusMessage *reply = message.SendSystem();
+ if (reply)
+ m_isMountedByUs = m_isMounted = false;
+
+ return !m_isMounted;
+ }
+ else
+ CLog::Log(LOGDEBUG, "UDisks: Is not able to unmount {}", ToString());
+
+ return false;
+}
+
+CMediaSource CUDiskDevice::ToMediaShare() const
+{
+ CMediaSource source;
+ source.strPath = m_MountPath;
+ if (m_Label.empty())
+ {
+ std::string strSize = StringUtils::SizeToString(m_PartitionSize);
+ source.strName = StringUtils::Format("{} {}", strSize, g_localizeStrings.Get(155));
+ }
+ else
+ source.strName = m_Label;
+ if (m_isOptical)
+ source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD;
+ else if (m_isSystemInternal)
+ source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL;
+ else
+ source.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE;
+ source.m_ignore = true;
+ return source;
+}
+
+bool CUDiskDevice::IsApproved() const
+{
+ return (m_isFileSystem && m_isMounted && m_UDI.length() > 0 && (m_FileSystem.length() > 0 && m_FileSystem != "swap")
+ && m_MountPath != "/" && m_MountPath != "/boot") || m_isOptical;
+}
+
+bool CUDiskDevice::IsOptical() const
+{
+ return m_isOptical;
+}
+
+MEDIA_DETECT::STORAGE::Type CUDiskDevice::GetStorageType() const
+{
+ if (IsOptical())
+ return MEDIA_DETECT::STORAGE::Type::OPTICAL;
+
+ return MEDIA_DETECT::STORAGE::Type::UNKNOWN;
+}
+
+bool CUDiskDevice::IsMounted() const
+{
+ return m_isMounted;
+}
+
+std::string CUDiskDevice::GetDisplayName() const
+{
+ return m_Label;
+}
+
+std::string CUDiskDevice::GetMountPoint() const
+{
+ return m_MountPath;
+}
+
+bool CUDiskDevice::IsSystemInternal() const
+{
+ return m_isSystemInternal;
+}
+
+MEDIA_DETECT::STORAGE::StorageDevice CUDiskDevice::ToStorageDevice() const
+{
+ MEDIA_DETECT::STORAGE::StorageDevice device;
+ device.label = GetDisplayName();
+ device.path = GetMountPoint();
+ device.type = GetStorageType();
+ return device;
+}
+
+#define BOOL2SZ(b) ((b) ? "true" : "false")
+
+std::string CUDiskDevice::ToString() const
+{
+ return StringUtils::Format("DeviceUDI {}: IsFileSystem {} HasFileSystem {} "
+ "IsSystemInternal {} IsMounted {} IsRemovable {} IsPartition {} "
+ "IsOptical {}",
+ m_DeviceKitUDI, BOOL2SZ(m_isFileSystem), m_FileSystem,
+ BOOL2SZ(m_isSystemInternal), BOOL2SZ(m_isMounted),
+ BOOL2SZ(m_isRemovable), BOOL2SZ(m_isPartition), BOOL2SZ(m_isOptical));
+}
+
+CUDisksProvider::CUDisksProvider()
+{
+ //! @todo do not use dbus_connection_pop_message() that requires the use of a
+ //! private connection
+ if (!m_connection.Connect(DBUS_BUS_SYSTEM, true))
+ {
+ return;
+ }
+
+ dbus_connection_set_exit_on_disconnect(m_connection, false);
+
+ CDBusError error;
+ dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UDisks'", error);
+ dbus_connection_flush(m_connection);
+
+ if (error)
+ {
+ error.Log("UDisks: Failed to attach to signal");
+ m_connection.Destroy();
+ }
+}
+
+CUDisksProvider::~CUDisksProvider()
+{
+ for (auto& itr : m_AvailableDevices)
+ delete itr.second;
+
+ m_AvailableDevices.clear();
+}
+
+void CUDisksProvider::Initialize()
+{
+ CLog::Log(LOGDEBUG, "Selected UDisks as storage provider");
+ m_DaemonVersion = atoi(CDBusUtil::GetVariant("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "DaemonVersion").asString().c_str());
+ CLog::Log(LOGDEBUG, "UDisks: DaemonVersion {}", m_DaemonVersion);
+
+ CLog::Log(LOGDEBUG, "UDisks: Querying available devices");
+ std::vector<std::string> devices = EnumerateDisks();
+ for (unsigned int i = 0; i < devices.size(); i++)
+ DeviceAdded(devices[i].c_str(), NULL);
+}
+
+bool CUDisksProvider::Eject(const std::string& mountpath)
+{
+ std::string path(mountpath);
+ URIUtils::RemoveSlashAtEnd(path);
+
+ for (auto& itr : m_AvailableDevices)
+ {
+ CUDiskDevice* device = itr.second;
+ if (device->GetMountPoint() == path)
+ return device->UnMount();
+ }
+
+ return false;
+}
+
+std::vector<std::string> CUDisksProvider::GetDiskUsage()
+{
+ CPosixMountProvider legacy;
+ return legacy.GetDiskUsage();
+}
+
+bool CUDisksProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback)
+{
+ bool result = false;
+ if (m_connection)
+ {
+ dbus_connection_read_write(m_connection, 0);
+ DBusMessagePtr msg(dbus_connection_pop_message(m_connection));
+
+ if (msg)
+ {
+ char *object;
+ if (dbus_message_get_args (msg.get(), NULL, DBUS_TYPE_OBJECT_PATH, &object, DBUS_TYPE_INVALID))
+ {
+ result = true;
+ if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceAdded"))
+ DeviceAdded(object, callback);
+ else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceRemoved"))
+ DeviceRemoved(object, callback);
+ else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceChanged"))
+ DeviceChanged(object, callback);
+ }
+ }
+ }
+ return result;
+}
+
+bool CUDisksProvider::HasUDisks()
+{
+ return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, "org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices");
+}
+
+void CUDisksProvider::DeviceAdded(const char *object, IStorageEventsCallback *callback)
+{
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceAdded ({})", object);
+
+ if (m_AvailableDevices[object])
+ {
+ CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceAdded on an indexed disk");
+ delete m_AvailableDevices[object];
+ }
+
+ CUDiskDevice *device = NULL;
+ device = new CUDiskDevice(object);
+ m_AvailableDevices[object] = device;
+
+ // optical drives should be always mounted by default unless explicitly disabled by the user
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_handleMounting ||
+ (device->IsOptical() &&
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_autoMountOpticalMedia))
+ {
+ device->Mount();
+ }
+
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceAdded - {}", device->ToString());
+
+ if (device->IsMounted() && device->IsApproved())
+ {
+ CLog::Log(LOGINFO, "UDisks: Added {}", device->GetMountPoint());
+ if (callback)
+ callback->OnStorageAdded(device->ToStorageDevice());
+ }
+}
+
+void CUDisksProvider::DeviceRemoved(const char *object, IStorageEventsCallback *callback)
+{
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceRemoved ({})", object);
+
+ CUDiskDevice *device = m_AvailableDevices[object];
+ if (device)
+ {
+ if (device->IsMounted() && callback)
+ callback->OnStorageUnsafelyRemoved(device->ToStorageDevice());
+
+ delete m_AvailableDevices[object];
+ m_AvailableDevices.erase(object);
+ }
+}
+
+void CUDisksProvider::DeviceChanged(const char *object, IStorageEventsCallback *callback)
+{
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceChanged ({})", object);
+
+ CUDiskDevice *device = m_AvailableDevices[object];
+ if (device == NULL)
+ {
+ CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceChanged on an unindexed disk");
+ DeviceAdded(object, callback);
+ }
+ else
+ {
+ bool mounted = device->IsMounted();
+ // make sure to not silently remount ejected usb thumb drives that user wants to eject
+ // optical drives should be always mounted by default unless explicitly disabled by the user
+ const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ if (!mounted && device->IsOptical())
+ {
+ if (advancedSettings->m_handleMounting || advancedSettings->m_autoMountOpticalMedia)
+ {
+ device->Mount();
+ }
+ }
+
+ device->Update();
+ if (!mounted && device->IsMounted() && callback)
+ callback->OnStorageAdded(device->ToStorageDevice());
+ else if (mounted && !device->IsMounted() && callback)
+ callback->OnStorageSafelyRemoved(device->ToStorageDevice());
+
+ CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceChanged - {}", device->ToString());
+ }
+}
+
+std::vector<std::string> CUDisksProvider::EnumerateDisks()
+{
+ std::vector<std::string> devices;
+ CDBusMessage message("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices");
+ DBusMessage *reply = message.SendSystem();
+ if (reply)
+ {
+ char** disks = NULL;
+ int length = 0;
+
+ if (dbus_message_get_args (reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &disks, &length, DBUS_TYPE_INVALID))
+ {
+ for (int i = 0; i < length; i++)
+ devices.emplace_back(disks[i]);
+
+ dbus_free_string_array(disks);
+ }
+ }
+
+ return devices;
+}
+
+void CUDisksProvider::GetDisks(VECSOURCES& devices, bool EnumerateRemovable)
+{
+ for (auto& itr : m_AvailableDevices)
+ {
+ CUDiskDevice* device = itr.second;
+ if (device && device->IsApproved() && device->IsSystemInternal() != EnumerateRemovable)
+ devices.push_back(device->ToMediaShare());
+ }
+}
diff --git a/xbmc/platform/linux/storage/UDisksProvider.h b/xbmc/platform/linux/storage/UDisksProvider.h
new file mode 100644
index 0000000..c5372f8
--- /dev/null
+++ b/xbmc/platform/linux/storage/UDisksProvider.h
@@ -0,0 +1,131 @@
+/*
+ * 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 "DBusUtil.h"
+#include "storage/IStorageProvider.h"
+
+#include <string>
+#include <vector>
+
+class CUDiskDevice
+{
+public:
+ CUDiskDevice(const char *DeviceKitUDI);
+ ~CUDiskDevice() = default;
+
+ void Update();
+
+ bool Mount();
+ bool UnMount();
+
+ /*! \brief Check if the device is approved/whitelisted
+ * @return true if the device is approved/whitelisted, false otherwise
+ */
+ bool IsApproved() const;
+
+ /*! \brief Get the storage type of this device
+ * @return the storage type (e.g. OPTICAL) or UNKNOWN if
+ * the type couldn't be detected
+ */
+ MEDIA_DETECT::STORAGE::Type GetStorageType() const;
+
+ /*! \brief Check if the device is optical
+ * @return true if the device is optical, false otherwise
+ */
+ bool IsOptical() const;
+
+ /*! \brief Check if the device is mounted
+ * @return true if the device is mounted, false otherwise
+ */
+ bool IsMounted() const;
+
+ /*! \brief Check if the device is internal to the system
+ * @return true if the device is internal to the system, false otherwise
+ */
+ bool IsSystemInternal() const;
+
+ /*! \brief Get the device display name/label
+ * @return the device display name/label
+ */
+ std::string GetDisplayName() const;
+
+ /*! \brief Get the device mount point
+ * @return the device mount point
+ */
+ std::string GetMountPoint() const;
+
+ /*! \brief Get a representation of the device as a readable string
+ * @return device as a string
+ */
+ std::string ToString() const;
+
+ /*! \brief Get a representation of the device as a media share
+ * @return the media share
+ */
+ CMediaSource ToMediaShare() const;
+
+ /*! \brief Get a representation of the device as a storage device abstraction
+ * @return the storage device abstraction of the device
+ */
+ MEDIA_DETECT::STORAGE::StorageDevice ToStorageDevice() const;
+
+private:
+ std::string m_UDI;
+ std::string m_DeviceKitUDI;
+ std::string m_MountPath;
+ std::string m_FileSystem;
+ std::string m_Label;
+ bool m_isMounted;
+ bool m_isMountedByUs;
+ bool m_isRemovable;
+ bool m_isPartition;
+ bool m_isFileSystem;
+ bool m_isSystemInternal;
+ bool m_isOptical;
+ int64_t m_PartitionSize;
+};
+
+class CUDisksProvider : public IStorageProvider
+{
+public:
+ CUDisksProvider();
+ ~CUDisksProvider() override;
+
+ void Initialize() override;
+ void Stop() override { }
+
+ void GetLocalDrives(VECSOURCES &localDrives) override { GetDisks(localDrives, false); }
+ void GetRemovableDrives(VECSOURCES &removableDrives) override { GetDisks(removableDrives, true); }
+
+ bool Eject(const std::string& mountpath) override;
+
+ std::vector<std::string> GetDiskUsage() override;
+
+ bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override;
+
+ static bool HasUDisks();
+private:
+ typedef std::map<std::string, CUDiskDevice *> DeviceMap;
+ typedef std::pair<std::string, CUDiskDevice *> DevicePair;
+
+ void DeviceAdded(const char *object, IStorageEventsCallback *callback);
+ void DeviceRemoved(const char *object, IStorageEventsCallback *callback);
+ void DeviceChanged(const char *object, IStorageEventsCallback *callback);
+
+ std::vector<std::string> EnumerateDisks();
+
+ void GetDisks(VECSOURCES& devices, bool EnumerateRemovable);
+
+ int m_DaemonVersion;
+
+ DeviceMap m_AvailableDevices;
+
+ CDBusConnection m_connection;
+};
diff --git a/xbmc/platform/linux/test/CMakeLists.txt b/xbmc/platform/linux/test/CMakeLists.txt
new file mode 100644
index 0000000..1fb0281
--- /dev/null
+++ b/xbmc/platform/linux/test/CMakeLists.txt
@@ -0,0 +1,3 @@
+list(APPEND SOURCES TestSysfsPath.cpp)
+
+core_add_test_library(linux_test)
diff --git a/xbmc/platform/linux/test/TestSysfsPath.cpp b/xbmc/platform/linux/test/TestSysfsPath.cpp
new file mode 100644
index 0000000..91e7763
--- /dev/null
+++ b/xbmc/platform/linux/test/TestSysfsPath.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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 "platform/linux/SysfsPath.h"
+#include "utils/StringUtils.h"
+
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+struct TestSysfsPath : public ::testing::Test
+{
+ std::string GetTestFilePath()
+ {
+ std::string tmpdir{"/tmp"};
+ const char *test_tmpdir = getenv("TMPDIR");
+
+ if (test_tmpdir && test_tmpdir[0] != '\0') {
+ tmpdir.assign(test_tmpdir);
+ }
+
+ return tmpdir + "/kodi-test-" + StringUtils::CreateUUID();
+ }
+};
+
+TEST_F(TestSysfsPath, SysfsPathTestInt)
+{
+ std::string filepath = GetTestFilePath();
+ std::ofstream m_output(filepath);
+
+ int temp{1234};
+ m_output << temp;
+ m_output.close();
+
+ CSysfsPath path(filepath);
+ ASSERT_TRUE(path.Exists());
+ EXPECT_EQ(path.Get<int>(), 1234);
+ EXPECT_EQ(path.Get<float>(), 1234);
+ EXPECT_EQ(path.Get<double>(), 1234);
+ EXPECT_EQ(path.Get<uint64_t>(), 1234);
+ EXPECT_EQ(path.Get<uint16_t>(), 1234);
+ EXPECT_EQ(path.Get<unsigned int>(), 1234);
+ EXPECT_EQ(path.Get<unsigned long int>(), 1234);
+
+ std::remove(filepath.c_str());
+}
+
+TEST_F(TestSysfsPath, SysfsPathTestString)
+{
+ std::string filepath = GetTestFilePath();
+ std::ofstream m_output{filepath};
+
+ std::string temp{"test"};
+ m_output << temp;
+ m_output.close();
+
+ CSysfsPath path(filepath);
+ ASSERT_TRUE(path.Exists());
+ EXPECT_EQ(path.Get<std::string>(), "test");
+
+ std::remove(filepath.c_str());
+}
+
+TEST_F(TestSysfsPath, SysfsPathTestLongString)
+{
+ std::string filepath = GetTestFilePath().append("-long");
+ std::ofstream m_output{filepath};
+
+ std::string temp{"test with spaces"};
+ m_output << temp;
+ m_output.close();
+
+ CSysfsPath path(filepath);
+ ASSERT_TRUE(path.Exists());
+ EXPECT_EQ(path.Get<std::string>(), "test with spaces");
+
+ std::remove(filepath.c_str());
+}
+
+TEST_F(TestSysfsPath, SysfsPathTestPathDoesNotExist)
+{
+ CSysfsPath otherPath{"/thispathdoesnotexist"};
+ ASSERT_FALSE(otherPath.Exists());
+}
diff --git a/xbmc/platform/linux/threads/CMakeLists.txt b/xbmc/platform/linux/threads/CMakeLists.txt
new file mode 100644
index 0000000..b90d55e
--- /dev/null
+++ b/xbmc/platform/linux/threads/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES ThreadImplLinux.cpp)
+set(HEADERS ThreadImplLinux.h)
+
+core_add_library(platform_linux_threads)
diff --git a/xbmc/platform/linux/threads/ThreadImplLinux.cpp b/xbmc/platform/linux/threads/ThreadImplLinux.cpp
new file mode 100644
index 0000000..f4810cf
--- /dev/null
+++ b/xbmc/platform/linux/threads/ThreadImplLinux.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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 "ThreadImplLinux.h"
+
+#include "utils/Map.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <array>
+#include <limits.h>
+
+#include <sys/resource.h>
+#include <unistd.h>
+
+#if !defined(TARGET_ANDROID) && (defined(__GLIBC__) || defined(__UCLIBC__))
+#if defined(__UCLIBC__) || !__GLIBC_PREREQ(2, 30)
+#include <sys/syscall.h>
+#endif
+#endif
+
+namespace
+{
+
+constexpr auto nativeThreadPriorityMap = make_map<ThreadPriority, int>({
+ {ThreadPriority::LOWEST, -1},
+ {ThreadPriority::BELOW_NORMAL, -1},
+ {ThreadPriority::NORMAL, 0},
+ {ThreadPriority::ABOVE_NORMAL, 1},
+ {ThreadPriority::HIGHEST, 1},
+});
+
+static_assert(static_cast<size_t>(ThreadPriority::PRIORITY_COUNT) == nativeThreadPriorityMap.size(),
+ "nativeThreadPriorityMap doesn't match the size of ThreadPriority, did you forget to "
+ "add/remove a mapping?");
+
+constexpr int ThreadPriorityToNativePriority(const ThreadPriority& priority)
+{
+ const auto it = nativeThreadPriorityMap.find(priority);
+ if (it != nativeThreadPriorityMap.cend())
+ {
+ return it->second;
+ }
+ else
+ {
+ throw std::range_error("Priority not found");
+ }
+}
+
+#if !defined(TARGET_ANDROID) && (defined(__GLIBC__) || defined(__UCLIBC__))
+#if defined(__UCLIBC__) || !__GLIBC_PREREQ(2, 30)
+static pid_t gettid()
+{
+ return syscall(__NR_gettid);
+}
+#endif
+#endif
+
+} // namespace
+
+static int s_maxPriority;
+static bool s_maxPriorityIsSet{false};
+
+// We need to return what the best number than can be passed
+// to SetPriority is. It will basically be relative to the
+// the main thread's nice level, inverted (since "higher" priority
+// nice levels are actually lower numbers).
+static int GetUserMaxPriority(int maxPriority)
+{
+ if (s_maxPriorityIsSet)
+ return s_maxPriority;
+
+ // if we're root, then we can do anything. So we'll allow
+ // max priority.
+ if (geteuid() == 0)
+ return maxPriority;
+
+ // get user max prio
+ struct rlimit limit;
+ if (getrlimit(RLIMIT_NICE, &limit) != 0)
+ {
+ // If we fail getting the limit for nice we just assume we can't raise the priority
+ return 0;
+ }
+
+ const int appNice = getpriority(PRIO_PROCESS, getpid());
+ const int rlimVal = limit.rlim_cur;
+
+ // according to the docs, limit.rlim_cur shouldn't be zero, yet, here we are.
+ // if a user has no entry in limits.conf rlim_cur is zero. In this case the best
+ // nice value we can hope to achieve is '0' as a regular user
+ const int userBestNiceValue = (rlimVal == 0) ? 0 : (20 - rlimVal);
+
+ // running the app with nice -n 10 ->
+ // e.g. +10 10 - 0 // default non-root user.
+ // e.g. +30 10 - -20 // if root with rlimits set.
+ // running the app default ->
+ // e.g. 0 0 - 0 // default non-root user.
+ // e.g. +20 0 - -20 // if root with rlimits set.
+ const int bestUserSetPriority = appNice - userBestNiceValue; // nice is inverted from prio.
+
+ // static because we only need to check this once.
+ // we shouldn't expect a user to change RLIMIT_NICE while running
+ // and it won't work anyway for threads that already set their priority.
+ s_maxPriority = std::min(maxPriority, bestUserSetPriority);
+ s_maxPriorityIsSet = true;
+
+ return s_maxPriority;
+}
+
+std::unique_ptr<IThreadImpl> IThreadImpl::CreateThreadImpl(std::thread::native_handle_type handle)
+{
+ return std::make_unique<CThreadImplLinux>(handle);
+}
+
+CThreadImplLinux::CThreadImplLinux(std::thread::native_handle_type handle)
+ : IThreadImpl(handle), m_threadID(gettid())
+{
+}
+
+void CThreadImplLinux::SetThreadInfo(const std::string& name)
+{
+#if defined(__GLIBC__)
+ pthread_setname_np(m_handle, name.c_str());
+#endif
+
+ // get user max prio
+ const int maxPrio = ThreadPriorityToNativePriority(ThreadPriority::HIGHEST);
+ const int userMaxPrio = GetUserMaxPriority(maxPrio);
+
+ // if the user does not have an entry in limits.conf the following
+ // call will fail
+ if (userMaxPrio > 0)
+ {
+ // start thread with nice level of application
+ const int appNice = getpriority(PRIO_PROCESS, getpid());
+ if (setpriority(PRIO_PROCESS, m_threadID, appNice) != 0)
+ CLog::Log(LOGERROR, "[threads] failed to set priority: {}", strerror(errno));
+ }
+}
+
+bool CThreadImplLinux::SetPriority(const ThreadPriority& priority)
+{
+ // keep priority in bounds
+ const int prio = ThreadPriorityToNativePriority(priority);
+ const int maxPrio = ThreadPriorityToNativePriority(ThreadPriority::HIGHEST);
+ const int minPrio = ThreadPriorityToNativePriority(ThreadPriority::LOWEST);
+
+ // get user max prio given max prio (will take the min)
+ const int userMaxPrio = GetUserMaxPriority(maxPrio);
+
+ // clamp to min and max priorities
+ const int adjustedPrio = std::clamp(prio, minPrio, userMaxPrio);
+
+ // nice level of application
+ const int appNice = getpriority(PRIO_PROCESS, getpid());
+ const int newNice = appNice - adjustedPrio;
+
+ if (setpriority(PRIO_PROCESS, m_threadID, newNice) != 0)
+ {
+ CLog::Log(LOGERROR, "[threads] failed to set priority: {}", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
diff --git a/xbmc/platform/linux/threads/ThreadImplLinux.h b/xbmc/platform/linux/threads/ThreadImplLinux.h
new file mode 100644
index 0000000..986ffe5
--- /dev/null
+++ b/xbmc/platform/linux/threads/ThreadImplLinux.h
@@ -0,0 +1,26 @@
+/*
+ * 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 "threads/IThreadImpl.h"
+
+class CThreadImplLinux : public IThreadImpl
+{
+public:
+ CThreadImplLinux(std::thread::native_handle_type handle);
+
+ ~CThreadImplLinux() override = default;
+
+ void SetThreadInfo(const std::string& name) override;
+
+ bool SetPriority(const ThreadPriority& priority) override;
+
+private:
+ pid_t m_threadID;
+};