summaryrefslogtreecommitdiffstats
path: root/xbmc/platform
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/platform')
-rw-r--r--xbmc/platform/CMakeLists.txt10
-rw-r--r--xbmc/platform/Environment.cpp75
-rw-r--r--xbmc/platform/Environment.h98
-rw-r--r--xbmc/platform/Filesystem.h33
-rw-r--r--xbmc/platform/MessagePrinter.h43
-rw-r--r--xbmc/platform/Platform.h101
-rw-r--r--xbmc/platform/common/speech/CMakeLists.txt5
-rw-r--r--xbmc/platform/common/speech/SpeechRecognitionStub.cpp15
-rw-r--r--xbmc/platform/common/speech/SpeechRecognitionStub.h22
-rw-r--r--xbmc/platform/freebsd/CMakeLists.txt28
-rw-r--r--xbmc/platform/freebsd/CPUInfoFreebsd.cpp268
-rw-r--r--xbmc/platform/freebsd/CPUInfoFreebsd.h24
-rw-r--r--xbmc/platform/freebsd/MemUtils.cpp83
-rw-r--r--xbmc/platform/freebsd/OptionalsReg.cpp23
-rw-r--r--xbmc/platform/freebsd/OptionalsReg.h18
-rw-r--r--xbmc/platform/freebsd/PlatformFreebsd.cpp124
-rw-r--r--xbmc/platform/freebsd/PlatformFreebsd.h26
-rw-r--r--xbmc/platform/freebsd/network/CMakeLists.txt4
-rw-r--r--xbmc/platform/freebsd/network/NetworkFreebsd.cpp247
-rw-r--r--xbmc/platform/freebsd/network/NetworkFreebsd.h40
-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
-rw-r--r--xbmc/platform/posix/CMakeLists.txt21
-rw-r--r--xbmc/platform/posix/CPUInfoPosix.cpp60
-rw-r--r--xbmc/platform/posix/CPUInfoPosix.h23
-rw-r--r--xbmc/platform/posix/ConvUtils.cpp23
-rw-r--r--xbmc/platform/posix/ConvUtils.h15
-rw-r--r--xbmc/platform/posix/Filesystem.cpp128
-rw-r--r--xbmc/platform/posix/MessagePrinter.cpp40
-rw-r--r--xbmc/platform/posix/PlatformDefs.h153
-rw-r--r--xbmc/platform/posix/PlatformPosix.cpp63
-rw-r--r--xbmc/platform/posix/PlatformPosix.h25
-rw-r--r--xbmc/platform/posix/PosixMountProvider.cpp144
-rw-r--r--xbmc/platform/posix/PosixMountProvider.h37
-rw-r--r--xbmc/platform/posix/PosixResourceCounter.cpp71
-rw-r--r--xbmc/platform/posix/PosixResourceCounter.h31
-rw-r--r--xbmc/platform/posix/PosixTimezone.cpp272
-rw-r--r--xbmc/platform/posix/PosixTimezone.h58
-rw-r--r--xbmc/platform/posix/XHandle.cpp138
-rw-r--r--xbmc/platform/posix/XHandle.h59
-rw-r--r--xbmc/platform/posix/XHandlePublic.h19
-rw-r--r--xbmc/platform/posix/XTimeUtils.cpp228
-rw-r--r--xbmc/platform/posix/filesystem/CMakeLists.txt18
-rw-r--r--xbmc/platform/posix/filesystem/PosixDirectory.cpp207
-rw-r--r--xbmc/platform/posix/filesystem/PosixDirectory.h29
-rw-r--r--xbmc/platform/posix/filesystem/PosixFile.cpp388
-rw-r--r--xbmc/platform/posix/filesystem/PosixFile.h47
-rw-r--r--xbmc/platform/posix/filesystem/SMBDirectory.cpp372
-rw-r--r--xbmc/platform/posix/filesystem/SMBDirectory.h33
-rw-r--r--xbmc/platform/posix/filesystem/SMBFile.cpp721
-rw-r--r--xbmc/platform/posix/filesystem/SMBFile.h93
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp126
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscovery.h107
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp608
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h128
-rw-r--r--xbmc/platform/posix/main.cpp75
-rw-r--r--xbmc/platform/posix/network/CMakeLists.txt5
-rw-r--r--xbmc/platform/posix/network/NetworkPosix.cpp150
-rw-r--r--xbmc/platform/posix/network/NetworkPosix.h61
-rw-r--r--xbmc/platform/posix/storage/discs/CMakeLists.txt7
-rw-r--r--xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp152
-rw-r--r--xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h58
-rw-r--r--xbmc/platform/posix/threads/CMakeLists.txt9
-rw-r--r--xbmc/platform/posix/threads/RecursiveMutex.cpp43
-rw-r--r--xbmc/platform/posix/threads/RecursiveMutex.h48
-rw-r--r--xbmc/platform/posix/threads/ThreadImplPosix.cpp35
-rw-r--r--xbmc/platform/posix/threads/ThreadImplPosix.h23
-rw-r--r--xbmc/platform/posix/utils/CMakeLists.txt10
-rw-r--r--xbmc/platform/posix/utils/FileHandle.h31
-rw-r--r--xbmc/platform/posix/utils/Mmap.cpp27
-rw-r--r--xbmc/platform/posix/utils/Mmap.h53
-rw-r--r--xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp29
-rw-r--r--xbmc/platform/posix/utils/PosixInterfaceForCLog.h22
-rw-r--r--xbmc/platform/posix/utils/SharedMemory.cpp118
-rw-r--r--xbmc/platform/posix/utils/SharedMemory.h62
-rw-r--r--xbmc/platform/xbmc.cpp79
-rw-r--r--xbmc/platform/xbmc.h15
148 files changed, 15759 insertions, 0 deletions
diff --git a/xbmc/platform/CMakeLists.txt b/xbmc/platform/CMakeLists.txt
new file mode 100644
index 0000000..4df5326
--- /dev/null
+++ b/xbmc/platform/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES Environment.cpp
+ xbmc.cpp)
+
+set(HEADERS Environment.h
+ Filesystem.h
+ MessagePrinter.h
+ Platform.h
+ xbmc.h)
+
+core_add_library(platform_common)
diff --git a/xbmc/platform/Environment.cpp b/xbmc/platform/Environment.cpp
new file mode 100644
index 0000000..4655dcc
--- /dev/null
+++ b/xbmc/platform/Environment.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013-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.
+ */
+
+/**
+ * \file platform\Environment.cpp
+ * \brief Implements CEnvironment class functions.
+ *
+ * Some ideas were inspired by PostgreSQL's pgwin32_putenv function.
+ * Refined, updated, enhanced and modified for XBMC by Karlson2k.
+ */
+
+#include "Environment.h"
+
+#include <stdlib.h>
+
+
+// --------------------- Main Functions ---------------------
+
+int CEnvironment::setenv(const std::string &name, const std::string &value, int overwrite /*= 1*/)
+{
+#ifdef TARGET_WINDOWS
+ return (win_setenv(name, value, overwrite ? autoDetect : addOnly) == 0) ? 0 : -1;
+#else
+ if (value.empty() && overwrite != 0)
+ return ::unsetenv(name.c_str());
+ return ::setenv(name.c_str(), value.c_str(), overwrite);
+#endif
+}
+
+std::string CEnvironment::getenv(const std::string &name)
+{
+#ifdef TARGET_WINDOWS
+ return win_getenv(name);
+#else
+ char * str = ::getenv(name.c_str());
+ if (str == NULL)
+ return "";
+ return str;
+#endif
+}
+
+int CEnvironment::unsetenv(const std::string &name)
+{
+#ifdef TARGET_WINDOWS
+ return (win_setenv(name, "", deleteVariable)) == 0 ? 0 : -1;
+#else
+ return ::unsetenv(name.c_str());
+#endif
+}
+
+int CEnvironment::putenv(const std::string &envstring)
+{
+ if (envstring.empty())
+ return 0;
+ size_t pos = envstring.find('=');
+ if (pos == 0) // '=' is the first character
+ return -1;
+ if (pos == std::string::npos)
+ return unsetenv(envstring);
+ if (pos == envstring.length()-1) // '=' is in last position
+ {
+ std::string name(envstring);
+ name.erase(name.length()-1, 1);
+ return unsetenv(name);
+ }
+ std::string name(envstring, 0, pos), value(envstring, pos+1);
+
+ return setenv(name, value);
+}
+
diff --git a/xbmc/platform/Environment.h b/xbmc/platform/Environment.h
new file mode 100644
index 0000000..a05974c
--- /dev/null
+++ b/xbmc/platform/Environment.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2013-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
+
+/**
+ * \file utils\Environment.h
+ * \brief Declares CEnvironment class for platform-independent environment variables manipulations.
+ *
+ */
+#include <string>
+
+/**
+ * @class CEnvironment
+ *
+ * @brief Platform-independent environment variables manipulations.
+ *
+ * Provide analog for POSIX functions:
+ * + setenv
+ * + unsetenv
+ * + putenv
+ * + getenv
+ *
+ * You can generally use the functions as you would normally in POSIX-style.
+ * The differences below are just to make things more convenient through use of std::string (2,3),
+ * and to also allow the Win32-style of unsetting variables (4,5) if wanted.
+ * 1. CEnvironment::setenv parameter 'overwrite' is optional, set by default to 1 (allow overwrite).
+ * 2. CEnvironment::putenv uses copy of provided string (rather than string itself) to change environment,
+ * so you can free parameter variable right after call of function.
+ * 3. CEnvironment::getenv returns a copy of environment variable value instead of pointer to value.
+ * 4. CEnvironment::setenv can be used to unset variables. Just pass empty string for 'value' parameter.
+ * 5. CEnvironment::putenv can be used to unset variables. Set parameter to 'var=' (Windows style) or
+ * just 'var' (POSIX style), and 'var' will be unset.
+ *
+ * All 'std::string' types are supposed to be in UTF-8 encoding.
+ * All functions work on all platforms. Special care is taken on Windows platform where Environment is changed for process itself,
+ * for process runtime library and for all runtime libraries (MSVCRT) loaded by third-party modules.
+ * Functions internally make all necessary UTF-8 <-> wide conversions.*
+ */
+
+class CEnvironment
+{
+public:
+ /**
+ * \fn static int CEnvironment::setenv(const std::string &name, const std::string &value,
+ * int overwrite = 1);
+ * \brief Sets or unsets environment variable.
+ * \param name The environment variable name to add/modify/delete.
+ * \param value The environment variable new value. If set to empty string, variable will be
+ * deleted from the environment.
+ * \param overwrite (optional) If set to non-zero, existing variable will be overwritten. If set to zero and
+ * variable is already present, then variable will be unchanged and function returns success.
+ * \return Zero on success, non-zero on error.
+ */
+ static int setenv(const std::string &name, const std::string &value, int overwrite = 1);
+ /**
+ * \fn static int CEnvironment::unsetenv(const std::string &name);
+ * \brief Deletes environment variable.
+ * \param name The environment variable name to delete.
+ * \return Zero on success, non-zero on error.
+ */
+ static int unsetenv(const std::string &name);
+
+ /**
+ * \fn static int CEnvironment::putenv(const std::string &envstring);
+ * \brief Adds/modifies/deletes environment variable.
+ * \param envstring The variable-value string in form 'var=value'. If set to 'var=' or 'var', then variable
+ * will be deleted from the environment.
+ * \return Zero on success, non-zero on error.
+ */
+ static int putenv(const std::string &envstring);
+ /**
+ * \fn static std::string CEnvironment::getenv(const std::string &name);
+ * \brief Gets value of environment variable in UTF-8 encoding.
+ * \param name The name of environment variable.
+ * \return Copy of of environment variable value or empty string if variable in not present in environment.
+ * \sa xbmc_getenvUtf8, xbmc_getenvW
+ */
+ static std::string getenv(const std::string &name);
+private:
+#ifdef TARGET_WINDOWS
+ enum updateAction:int
+ {
+ addOrUpdateOnly = -2,
+ deleteVariable = -1,
+ addOnly = 0,
+ autoDetect = 1
+ };
+ static int win_setenv(const std::string &name, const std::string &value = "", updateAction action = autoDetect);
+ static std::string win_getenv(const std::string &name);
+#endif // TARGET_WINDOWS
+};
+
diff --git a/xbmc/platform/Filesystem.h b/xbmc/platform/Filesystem.h
new file mode 100644
index 0000000..732be92
--- /dev/null
+++ b/xbmc/platform/Filesystem.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <system_error>
+namespace KODI
+{
+namespace PLATFORM
+{
+namespace FILESYSTEM
+{
+struct space_info {
+ std::uintmax_t capacity;
+ std::uintmax_t free;
+ std::uintmax_t available;
+};
+
+space_info space(const std::string &path, std::error_code &ec);
+
+std::string temp_directory_path(std::error_code &ec);
+std::string create_temp_directory(std::error_code &ec);
+std::string temp_file_path(const std::string& suffix, std::error_code& ec);
+}
+}
+}
diff --git a/xbmc/platform/MessagePrinter.h b/xbmc/platform/MessagePrinter.h
new file mode 100644
index 0000000..f0aa08e
--- /dev/null
+++ b/xbmc/platform/MessagePrinter.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 <string>
+#include <utility>
+#include <vector>
+
+class CMessagePrinter
+{
+public:
+
+ /*! \brief Display a normal message to the user during startup
+ *
+ * \param[in] message message to display
+ */
+ static void DisplayMessage(const std::string& message);
+
+ /*! \brief Display a warning message to the user during startup
+ *
+ * \param[in] warning warning to display
+ */
+ static void DisplayWarning(const std::string& warning);
+
+ /*! \brief Display an error message to the user during startup
+ *
+ * \param[in] error error to display
+ */
+ static void DisplayError(const std::string& error);
+
+ /*! \brief Display the help message with command line options available
+ *
+ * \param[in] help List of commands and explanations,
+ help.push_back(std::make_pair("--help", "this displays the help))
+ */
+ static void DisplayHelpMessage(const std::vector<std::pair<std::string, std::string>>& help);
+};
diff --git a/xbmc/platform/Platform.h b/xbmc/platform/Platform.h
new file mode 100644
index 0000000..ebb441c
--- /dev/null
+++ b/xbmc/platform/Platform.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/ComponentContainer.h"
+
+//! \brief Base class for services.
+class IPlatformService
+{
+public:
+ virtual ~IPlatformService() = default;
+};
+
+/**\brief Class for the Platform object
+ *
+ * Contains methods to retrieve platform specific information
+ * and methods for doing platform specific environment preparation/initialisation
+ */
+class CPlatform : public CComponentContainer<IPlatformService>
+{
+public:
+ /**\brief Creates the Platform object
+ *
+ *@return the platform object
+ */
+ static CPlatform *CreateInstance();
+
+ /**\brief C'tor */
+ CPlatform() = default;
+
+ /**\brief D'tor */
+ virtual ~CPlatform() = default;
+
+ /**\brief Called at an early stage of application startup
+ *
+ * This method can be used to do platform specific environment preparation
+ * or initialisation (like setting environment variables for example)
+ */
+ virtual bool InitStageOne() { return true; }
+
+ /**\brief Called at a middle stage of application startup
+ *
+ * This method can be used for starting platform specific services that
+ * do not depend on windowing/gui. (eg macos XBMCHelper)
+ */
+ virtual bool InitStageTwo() { return true; }
+
+ /**\brief Called at a late stage of application startup
+ *
+ * This method can be used for starting platform specific Window/GUI related
+ * services/components. (eg , WS-Discovery Daemons)
+ */
+ virtual bool InitStageThree() { return true; }
+
+ /**\brief Called at a late stage of application shutdown
+ *
+ * This method should be used to cleanup resources allocated in InitStageOne
+ */
+ virtual void DeinitStageOne() {}
+
+ /**\brief Called at a middle stage of application shutdown
+ *
+ * This method should be used to cleanup resources allocated in InitStageTwo
+ */
+ virtual void DeinitStageTwo() {}
+
+ /**\brief Called at an early stage of application shutdown
+ *
+ * This method should be used to cleanup resources allocated in InitStageThree
+ */
+ virtual void DeinitStageThree() {}
+
+ /**\brief Flag whether disabled add-ons - installed via packagemanager or manually - should be
+ * offered for configuration and activation on kodi startup for this platform
+ */
+ virtual bool IsConfigureAddonsAtStartupEnabled() { return false; }
+
+ /**\brief Flag whether this platform supports user installation of binary add-ons.
+ */
+ virtual bool SupportsUserInstalledBinaryAddons() { return true; }
+
+ /**\brief Print platform specific info to log
+ *
+ * Logs platform specific system info during application creation startup
+ */
+ virtual void PlatformSyslog() {}
+
+ /**\brief Get a platform service instance.
+ */
+ template<class T>
+ std::shared_ptr<T> GetService()
+ {
+ return this->GetComponent<T>();
+ }
+};
diff --git a/xbmc/platform/common/speech/CMakeLists.txt b/xbmc/platform/common/speech/CMakeLists.txt
new file mode 100644
index 0000000..ac9752b
--- /dev/null
+++ b/xbmc/platform/common/speech/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES SpeechRecognitionStub.cpp)
+
+set(HEADERS SpeechRecognitionStub.h)
+
+core_add_library(platform_common_speech)
diff --git a/xbmc/platform/common/speech/SpeechRecognitionStub.cpp b/xbmc/platform/common/speech/SpeechRecognitionStub.cpp
new file mode 100644
index 0000000..2f76863
--- /dev/null
+++ b/xbmc/platform/common/speech/SpeechRecognitionStub.cpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2012-2022 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 "SpeechRecognitionStub.h"
+
+std::shared_ptr<speech::ISpeechRecognition> speech::ISpeechRecognition::CreateInstance()
+{
+ // speech recognition not implemented
+ return {};
+}
diff --git a/xbmc/platform/common/speech/SpeechRecognitionStub.h b/xbmc/platform/common/speech/SpeechRecognitionStub.h
new file mode 100644
index 0000000..ec44bc5
--- /dev/null
+++ b/xbmc/platform/common/speech/SpeechRecognitionStub.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012-2022 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 "speech/ISpeechRecognition.h"
+
+// This is a stub for all platforms without a speech recognition implementation
+// Saves us from feature/platform ifdeffery.
+class CSpeechRecognitionStub : public speech::ISpeechRecognition
+{
+public:
+ void StartSpeechRecognition(
+ const std::shared_ptr<speech::ISpeechRecognitionListener>& listener) override
+ {
+ }
+};
diff --git a/xbmc/platform/freebsd/CMakeLists.txt b/xbmc/platform/freebsd/CMakeLists.txt
new file mode 100644
index 0000000..9b8782f
--- /dev/null
+++ b/xbmc/platform/freebsd/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(SOURCES ../linux/AppParamParserLinux.cpp
+ CPUInfoFreebsd.cpp
+ OptionalsReg.cpp
+ ../linux/OptionalsReg.cpp
+ ../linux/TimeUtils.cpp
+ MemUtils.cpp
+ PlatformFreebsd.cpp)
+
+set(HEADERS ../linux/AppParamParserLinux.cpp
+ CPUInfoFreebsd.h
+ OptionalsReg.h
+ ../linux/OptionalsReg.h
+ ../linux/TimeUtils.h
+ PlatformFreebsd.h)
+
+if(ALSA_FOUND)
+ list(APPEND SOURCES ../linux/FDEventMonitor.cpp)
+ list(APPEND HEADERS ../linux/FDEventMonitor.h)
+endif()
+
+if(DBUS_FOUND)
+ list(APPEND SOURCES ../linux/DBusMessage.cpp
+ ../linux/DBusUtil.cpp)
+ list(APPEND HEADERS ../linux/DBusMessage.h
+ ../linux/DBusUtil.h)
+endif()
+
+core_add_library(freebsdsupport)
diff --git a/xbmc/platform/freebsd/CPUInfoFreebsd.cpp b/xbmc/platform/freebsd/CPUInfoFreebsd.cpp
new file mode 100644
index 0000000..5128a72
--- /dev/null
+++ b/xbmc/platform/freebsd/CPUInfoFreebsd.cpp
@@ -0,0 +1,268 @@
+/*
+ * 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 "CPUInfoFreebsd.h"
+
+#include "utils/Temperature.h"
+#include "utils/log.h"
+
+#include <array>
+#include <vector>
+
+// clang-format off
+/* sys/types.h must be included early, esp. before sysy/systl.h, otherwise:
+ /usr/include/sys/sysctl.h:1117:25: error: unknown type name 'u_int' */
+
+#include <sys/types.h>
+// clang-format on
+
+#if defined(__i386__) || defined(__x86_64__)
+#include <cpuid.h>
+#elif __has_include(<sys/auxv.h>)
+#include <sys/auxv.h>
+#endif
+
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+
+namespace
+{
+
+struct CpuData
+{
+public:
+ std::size_t GetActiveTime() const { return state[CP_USER] + state[CP_NICE] + state[CP_SYS]; }
+
+ std::size_t GetIdleTime() const { return state[CP_INTR] + state[CP_IDLE]; }
+
+ std::size_t GetTotalTime() const { return GetActiveTime() + GetIdleTime(); }
+
+ std::size_t state[CPUSTATES];
+};
+
+} // namespace
+
+std::shared_ptr<CCPUInfo> CCPUInfo::GetCPUInfo()
+{
+ return std::make_shared<CCPUInfoFreebsd>();
+}
+
+CCPUInfoFreebsd::CCPUInfoFreebsd()
+{
+ int count = 0;
+ size_t countLength = sizeof(count);
+ if (sysctlbyname("hw.ncpu", &count, &countLength, nullptr, 0) == 0)
+ m_cpuCount = count;
+ else
+ m_cpuCount = 1;
+
+ std::array<char, 512> cpuModel;
+ size_t length = cpuModel.size();
+ if (sysctlbyname("hw.model", cpuModel.data(), &length, nullptr, 0) == 0)
+ m_cpuModel = cpuModel.data();
+
+ for (int i = 0; i < m_cpuCount; i++)
+ {
+ CoreInfo core;
+ core.m_id = i;
+ m_cores.emplace_back(core);
+ }
+#if defined(__i386__) || defined(__x86_64__)
+ uint32_t eax, ebx, ecx, 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);
+ }
+ }
+ }
+
+ m_cpuModel = m_cpuModel.substr(0, m_cpuModel.find(char(0))); // remove extra null terminations
+
+ if (__get_cpuid(CPUID_INFOTYPE_STANDARD, &eax, &eax, &ecx, &edx))
+ {
+ if (edx & CPUID_00000001_EDX_MMX)
+ m_cpuFeatures |= CPU_FEATURE_MMX;
+
+ // Set MMX2 when SSE is present as SSE is a superset of MMX2 and Intel doesn't set the MMX2 cap
+ if (edx & CPUID_00000001_EDX_SSE)
+ m_cpuFeatures |= (CPU_FEATURE_SSE | CPU_FEATURE_MMX2);
+
+ 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;
+ }
+ }
+#endif
+
+#if defined(HAS_NEON)
+#if defined(__ARM_NEON)
+ m_cpuFeatures |= CPU_FEATURE_NEON;
+#elif __has_include(<sys/auxv.h>)
+ unsigned long hwcap = 0;
+ elf_aux_info(AT_HWCAP, &hwcap, sizeof(hwcap));
+ if (hwcap & HWCAP_NEON)
+ m_cpuFeatures |= CPU_FEATURE_NEON;
+#endif
+#endif
+}
+
+int CCPUInfoFreebsd::GetUsedPercentage()
+{
+ if (!m_nextUsedReadTime.IsTimePast())
+ return m_lastUsedPercentage;
+
+ size_t len = sizeof(long);
+
+ if (sysctlbyname("kern.cp_times", nullptr, &len, nullptr, 0) != 0)
+ return false;
+
+ std::vector<long> cptimes(len);
+ size_t cptimesLength = cptimes.size();
+ if (sysctlbyname("kern.cp_times", cptimes.data(), &cptimesLength, nullptr, 0) != 0)
+ return false;
+
+ size_t activeTime{0};
+ size_t idleTime{0};
+ size_t totalTime{0};
+
+ std::vector<CpuData> cpuData;
+
+ for (int i = 0; i < m_cpuCount; i++)
+ {
+ CpuData info;
+
+ for (size_t state = 0; state < CPUSTATES; state++)
+ {
+ info.state[state] = cptimes[i * CPUSTATES + state];
+ }
+
+ activeTime += info.GetActiveTime();
+ idleTime += info.GetIdleTime();
+ totalTime += info.GetTotalTime();
+
+ cpuData.emplace_back(info);
+ }
+
+ activeTime -= m_activeTime;
+ idleTime -= m_idleTime;
+ totalTime -= m_totalTime;
+
+ m_activeTime += activeTime;
+ m_idleTime += idleTime;
+ m_totalTime += totalTime;
+
+ m_lastUsedPercentage = activeTime * 100.0f / totalTime;
+ m_nextUsedReadTime.Set(MINIMUM_TIME_BETWEEN_READS);
+
+ for (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 CCPUInfoFreebsd::GetCPUFrequency()
+{
+ int hz = 0;
+ size_t len = sizeof(hz);
+ if (sysctlbyname("dev.cpu.0.freq", &hz, &len, nullptr, 0) != 0)
+ hz = 0;
+
+ return static_cast<float>(hz);
+}
+
+
+bool CCPUInfoFreebsd::GetTemperature(CTemperature& temperature)
+{
+ if (CheckUserTemperatureCommand(temperature))
+ return true;
+
+ int value;
+ size_t len = sizeof(value);
+
+ /* Temperature is in Kelvin * 10 */
+ if (sysctlbyname("dev.cpu.0.temperature", &value, &len, nullptr, 0) != 0)
+ return false;
+
+ temperature = CTemperature::CreateFromKelvin(static_cast<double>(value) / 10.0);
+ temperature.SetValid(true);
+
+ return true;
+}
diff --git a/xbmc/platform/freebsd/CPUInfoFreebsd.h b/xbmc/platform/freebsd/CPUInfoFreebsd.h
new file mode 100644
index 0000000..f1743ce
--- /dev/null
+++ b/xbmc/platform/freebsd/CPUInfoFreebsd.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/Temperature.h"
+
+#include "platform/posix/CPUInfoPosix.h"
+
+class CCPUInfoFreebsd : public CCPUInfoPosix
+{
+public:
+ CCPUInfoFreebsd();
+ ~CCPUInfoFreebsd() = default;
+
+ int GetUsedPercentage() override;
+ float GetCPUFrequency() override;
+ bool GetTemperature(CTemperature& temperature) override;
+};
diff --git a/xbmc/platform/freebsd/MemUtils.cpp b/xbmc/platform/freebsd/MemUtils.cpp
new file mode 100644
index 0000000..de3e42c
--- /dev/null
+++ b/xbmc/platform/freebsd/MemUtils.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 <array>
+#include <cstdlib>
+#include <cstring>
+#include <stdio.h>
+
+#include <unistd.h> /* FreeBSD can't write standalone headers */
+#include <sys/sysctl.h> /* FreeBSD can't write standalone headers */
+#include <sys/types.h>
+
+namespace KODI
+{
+namespace MEMORY
+{
+
+void* AlignedMalloc(size_t s, size_t alignTo)
+{
+ void* p;
+ posix_memalign(&p, alignTo, s);
+
+ return p;
+}
+
+void AlignedFree(void* p)
+{
+ free(p);
+}
+
+void GetMemoryStatus(MemoryStatus* buffer)
+{
+ if (!buffer)
+ return;
+
+ /* sysctl hw.physmem */
+ size_t len = 0;
+
+ /* physmem */
+ size_t physmem = 0;
+ len = sizeof(physmem);
+ if (sysctlbyname("hw.physmem", &physmem, &len, NULL, 0) == 0)
+ {
+ buffer->totalPhys = physmem;
+ }
+
+ /* pagesize */
+ size_t pagesize = 0;
+ len = sizeof(pagesize);
+ if (sysctlbyname("hw.pagesize", &pagesize, &len, NULL, 0) != 0)
+ pagesize = 4096;
+
+ /* mem_inactive */
+ size_t mem_inactive = 0;
+ len = sizeof(mem_inactive);
+ if (sysctlbyname("vm.stats.vm.v_inactive_count", &mem_inactive, &len, NULL, 0) == 0)
+ mem_inactive *= pagesize;
+
+ /* mem_cache */
+ size_t mem_cache = 0;
+ len = sizeof(mem_cache);
+ if (sysctlbyname("vm.stats.vm.v_cache_count", &mem_cache, &len, NULL, 0) == 0)
+ mem_cache *= pagesize;
+
+ /* mem_free */
+ size_t mem_free = 0;
+ len = sizeof(mem_free);
+ if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &len, NULL, 0) == 0)
+ mem_free *= pagesize;
+
+ /* mem_avail = mem_inactive + mem_cache + mem_free */
+ buffer->availPhys = mem_inactive + mem_cache + mem_free;
+}
+
+}
+}
diff --git a/xbmc/platform/freebsd/OptionalsReg.cpp b/xbmc/platform/freebsd/OptionalsReg.cpp
new file mode 100644
index 0000000..ed414f1
--- /dev/null
+++ b/xbmc/platform/freebsd/OptionalsReg.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2019 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"
+
+
+//-----------------------------------------------------------------------------
+// OSS
+//-----------------------------------------------------------------------------
+
+#ifdef HAS_OSS
+#include "cores/AudioEngine/Sinks/AESinkOSS.h"
+bool OPTIONALS::OSSRegister()
+{
+ CAESinkOSS::Register();
+ return true;
+}
+#endif
diff --git a/xbmc/platform/freebsd/OptionalsReg.h b/xbmc/platform/freebsd/OptionalsReg.h
new file mode 100644
index 0000000..36837d0
--- /dev/null
+++ b/xbmc/platform/freebsd/OptionalsReg.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2005-2019 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
+
+//-----------------------------------------------------------------------------
+// OSS
+//-----------------------------------------------------------------------------
+
+namespace OPTIONALS
+{
+bool OSSRegister();
+}
diff --git a/xbmc/platform/freebsd/PlatformFreebsd.cpp b/xbmc/platform/freebsd/PlatformFreebsd.cpp
new file mode 100644
index 0000000..b65501b
--- /dev/null
+++ b/xbmc/platform/freebsd/PlatformFreebsd.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PlatformFreebsd.h"
+
+#include "utils/StringUtils.h"
+
+#include "platform/freebsd/OptionalsReg.h"
+#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 CPlatformFreebsd();
+}
+
+bool CPlatformFreebsd::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, "OSS"))
+ {
+ OPTIONALS::OSSRegister();
+ }
+ 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::ALSARegister())
+ {
+ if (!OPTIONALS::SndioRegister())
+ {
+ OPTIONALS::OSSRegister();
+ }
+ }
+ }
+ }
+
+ m_lirc.reset(OPTIONALS::LircRegister());
+
+ return true;
+}
diff --git a/xbmc/platform/freebsd/PlatformFreebsd.h b/xbmc/platform/freebsd/PlatformFreebsd.h
new file mode 100644
index 0000000..48a4151
--- /dev/null
+++ b/xbmc/platform/freebsd/PlatformFreebsd.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "platform/linux/OptionalsReg.h"
+#include "platform/posix/PlatformPosix.h"
+
+#include <memory>
+
+class CPlatformFreebsd : public CPlatformPosix
+{
+public:
+ CPlatformFreebsd() = default;
+ ~CPlatformFreebsd() override = default;
+
+ bool InitStageOne() override;
+
+private:
+ std::unique_ptr<OPTIONALS::CLircContainer, OPTIONALS::delete_CLircContainer> m_lirc;
+};
diff --git a/xbmc/platform/freebsd/network/CMakeLists.txt b/xbmc/platform/freebsd/network/CMakeLists.txt
new file mode 100644
index 0000000..25d8ee8
--- /dev/null
+++ b/xbmc/platform/freebsd/network/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES NetworkFreebsd.cpp)
+set(HEADERS NetworkFreebsd.h)
+
+core_add_library(platform_freebsd_network)
diff --git a/xbmc/platform/freebsd/network/NetworkFreebsd.cpp b/xbmc/platform/freebsd/network/NetworkFreebsd.cpp
new file mode 100644
index 0000000..94a0a41
--- /dev/null
+++ b/xbmc/platform/freebsd/network/NetworkFreebsd.cpp
@@ -0,0 +1,247 @@
+/*
+ * 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 "NetworkFreebsd.h"
+
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <array>
+#include <errno.h>
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <sys/sockio.h>
+#include <sys/wait.h>
+
+CNetworkInterfaceFreebsd::CNetworkInterfaceFreebsd(CNetworkPosix* network,
+ std::string interfaceName,
+ char interfaceMacAddrRaw[6])
+ : CNetworkInterfacePosix(network, interfaceName, interfaceMacAddrRaw)
+{
+}
+
+std::string CNetworkInterfaceFreebsd::GetCurrentDefaultGateway() const
+{
+ std::string result;
+
+ size_t needed;
+ int mib[6];
+ char *buf, *next, *lim;
+ char line[16];
+ struct rt_msghdr* rtm;
+ struct sockaddr* sa;
+ struct sockaddr_in* sockin;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
+ return result;
+
+ if ((buf = (char*)malloc(needed)) == NULL)
+ return result;
+
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
+ {
+ free(buf);
+ return result;
+ }
+
+ lim = buf + needed;
+ for (next = buf; next < lim; next += rtm->rtm_msglen)
+ {
+ rtm = (struct rt_msghdr*)next;
+ sa = (struct sockaddr*)(rtm + 1);
+ sa = (struct sockaddr*)(SA_SIZE(sa) + (char*)sa);
+ sockin = (struct sockaddr_in*)sa;
+ if (inet_ntop(AF_INET, &sockin->sin_addr.s_addr, line, sizeof(line)) == NULL)
+ {
+ free(buf);
+ return result;
+ }
+ result = line;
+ break;
+ }
+ free(buf);
+
+ return result;
+}
+
+bool CNetworkInterfaceFreebsd::GetHostMacAddress(unsigned long host_ip, std::string& mac) const
+{
+ bool ret = false;
+ size_t needed;
+ char *buf, *next;
+ struct rt_msghdr* rtm;
+ struct sockaddr_inarp* sin;
+ struct sockaddr_dl* sdl;
+ constexpr std::array<int, 6> mib = {
+ CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_LLINFO,
+ };
+
+ mac = "";
+
+ if (sysctl(mib.data(), mib.size(), nullptr, &needed, nullptr, 0) == 0)
+ {
+ buf = (char*)malloc(needed);
+ if (buf)
+ {
+ if (sysctl(mib.data(), mib.size(), buf, &needed, nullptr, 0) == 0)
+ {
+ for (next = buf; next < buf + needed; next += rtm->rtm_msglen)
+ {
+
+ rtm = (struct rt_msghdr*)next;
+ sin = (struct sockaddr_inarp*)(rtm + 1);
+ sdl = (struct sockaddr_dl*)(sin + 1);
+
+ if (host_ip != sin->sin_addr.s_addr || sdl->sdl_alen < 6)
+ continue;
+
+ u_char* cp = (u_char*)LLADDR(sdl);
+
+ mac = StringUtils::Format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", cp[0], cp[1],
+ cp[2], cp[3], cp[4], cp[5]);
+ ret = true;
+ break;
+ }
+ }
+ free(buf);
+ }
+ }
+ return ret;
+}
+
+std::unique_ptr<CNetworkBase> CNetworkBase::GetNetwork()
+{
+ return std::make_unique<CNetworkFreebsd>();
+}
+
+CNetworkFreebsd::CNetworkFreebsd() : CNetworkPosix()
+{
+ queryInterfaceList();
+}
+
+void CNetworkFreebsd::GetMacAddress(const std::string& interfaceName, char rawMac[6])
+{
+ memset(rawMac, 0, 6);
+
+#if !defined(IFT_ETHER)
+#define IFT_ETHER 0x6 /* Ethernet CSMACD */
+#endif
+ const struct sockaddr_dl* dlAddr = NULL;
+ const uint8_t* base = NULL;
+ // Query the list of interfaces.
+ struct ifaddrs* list;
+ struct ifaddrs* interface;
+
+ if (getifaddrs(&list) < 0)
+ {
+ return;
+ }
+
+ for (interface = list; interface != NULL; interface = interface->ifa_next)
+ {
+ if (interfaceName == interface->ifa_name)
+ {
+ if ((interface->ifa_addr->sa_family == AF_LINK) &&
+ (((const struct sockaddr_dl*)interface->ifa_addr)->sdl_type == IFT_ETHER))
+ {
+ dlAddr = (const struct sockaddr_dl*)interface->ifa_addr;
+ base = (const uint8_t*)&dlAddr->sdl_data[dlAddr->sdl_nlen];
+
+ if (dlAddr->sdl_alen > 5)
+ {
+ memcpy(rawMac, base, 6);
+ }
+ }
+ break;
+ }
+ }
+
+ freeifaddrs(list);
+}
+
+void CNetworkFreebsd::queryInterfaceList()
+{
+ char macAddrRaw[6];
+ m_interfaces.clear();
+
+ // Query the list of interfaces.
+ struct ifaddrs* list;
+ if (getifaddrs(&list) < 0)
+ return;
+
+ struct ifaddrs* cur;
+ for (cur = list; cur != NULL; cur = cur->ifa_next)
+ {
+ if (cur->ifa_addr->sa_family != AF_INET)
+ continue;
+
+ GetMacAddress(cur->ifa_name, macAddrRaw);
+
+ // only add interfaces with non-zero mac addresses
+ if (macAddrRaw[0] || macAddrRaw[1] || macAddrRaw[2] || macAddrRaw[3] || macAddrRaw[4] ||
+ macAddrRaw[5])
+ // Add the interface.
+ m_interfaces.push_back(new CNetworkInterfaceFreebsd(this, cur->ifa_name, macAddrRaw));
+ }
+
+ freeifaddrs(list);
+}
+
+std::vector<std::string> CNetworkFreebsd::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 CNetworkFreebsd::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 -t %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/freebsd/network/NetworkFreebsd.h b/xbmc/platform/freebsd/network/NetworkFreebsd.h
new file mode 100644
index 0000000..92b00d7
--- /dev/null
+++ b/xbmc/platform/freebsd/network/NetworkFreebsd.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 CNetworkInterfaceFreebsd : public CNetworkInterfacePosix
+{
+public:
+ CNetworkInterfaceFreebsd(CNetworkPosix* network,
+ std::string interfaceName,
+ char interfaceMacAddrRaw[6]);
+ ~CNetworkInterfaceFreebsd() override = default;
+
+ std::string GetCurrentDefaultGateway() const override;
+ bool GetHostMacAddress(unsigned long host, std::string& mac) const override;
+};
+
+class CNetworkFreebsd : public CNetworkPosix
+{
+public:
+ CNetworkFreebsd();
+ ~CNetworkFreebsd() override = default;
+
+ bool PingHost(unsigned long host, unsigned int timeout_ms = 2000) override;
+ std::vector<std::string> GetNameServers() override;
+
+private:
+ void GetMacAddress(const std::string& interfaceName, char macAddrRaw[6]) override;
+ void queryInterfaceList() override;
+};
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;
+};
diff --git a/xbmc/platform/posix/CMakeLists.txt b/xbmc/platform/posix/CMakeLists.txt
new file mode 100644
index 0000000..2d8d4db
--- /dev/null
+++ b/xbmc/platform/posix/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(SOURCES ConvUtils.cpp
+ CPUInfoPosix.cpp
+ Filesystem.cpp
+ MessagePrinter.cpp
+ PlatformPosix.cpp
+ PosixMountProvider.cpp
+ PosixResourceCounter.cpp
+ PosixTimezone.cpp
+ XHandle.cpp
+ XTimeUtils.cpp)
+
+set(HEADERS ConvUtils.h
+ CPUInfoPosix.h
+ PlatformDefs.h
+ PlatformPosix.h
+ PosixMountProvider.h
+ PosixResourceCounter.h
+ PosixTimezone.h
+ XHandle.h)
+
+core_add_library(platform_posix)
diff --git a/xbmc/platform/posix/CPUInfoPosix.cpp b/xbmc/platform/posix/CPUInfoPosix.cpp
new file mode 100644
index 0000000..9d158d0
--- /dev/null
+++ b/xbmc/platform/posix/CPUInfoPosix.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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 "CPUInfoPosix.h"
+
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+
+bool CCPUInfoPosix::GetTemperature(CTemperature& temperature)
+{
+ return CheckUserTemperatureCommand(temperature);
+}
+
+bool CCPUInfoPosix::CheckUserTemperatureCommand(CTemperature& temperature)
+{
+ temperature.SetValid(false);
+
+ auto settingComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingComponent)
+ return false;
+
+ auto advancedSettings = settingComponent->GetAdvancedSettings();
+ if (!advancedSettings)
+ return false;
+
+ std::string cmd = advancedSettings->m_cpuTempCmd;
+
+ if (cmd.empty())
+ return false;
+
+ int value = {};
+ char scale = {};
+
+ auto p = popen(cmd.c_str(), "r");
+ if (p)
+ {
+ int ret = fscanf(p, "%d %c", &value, &scale);
+ pclose(p);
+
+ if (ret < 2)
+ return false;
+ }
+
+ if (scale == 'C' || scale == 'c')
+ temperature = CTemperature::CreateFromCelsius(value);
+ else if (scale == 'F' || scale == 'f')
+ temperature = CTemperature::CreateFromFahrenheit(value);
+ else
+ return false;
+
+ temperature.SetValid(true);
+
+ return true;
+}
diff --git a/xbmc/platform/posix/CPUInfoPosix.h b/xbmc/platform/posix/CPUInfoPosix.h
new file mode 100644
index 0000000..af94b1b
--- /dev/null
+++ b/xbmc/platform/posix/CPUInfoPosix.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 "utils/CPUInfo.h"
+
+class CCPUInfoPosix : public CCPUInfo
+{
+public:
+ virtual bool GetTemperature(CTemperature& temperature) override;
+
+protected:
+ CCPUInfoPosix() = default;
+ virtual ~CCPUInfoPosix() = default;
+
+ bool CheckUserTemperatureCommand(CTemperature& temperature);
+};
diff --git a/xbmc/platform/posix/ConvUtils.cpp b/xbmc/platform/posix/ConvUtils.cpp
new file mode 100644
index 0000000..6ad3da2
--- /dev/null
+++ b/xbmc/platform/posix/ConvUtils.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "PlatformDefs.h"
+
+DWORD GetLastError()
+{
+ return errno;
+}
+
+void SetLastError(DWORD dwErrCode)
+{
+ errno = dwErrCode;
+}
diff --git a/xbmc/platform/posix/ConvUtils.h b/xbmc/platform/posix/ConvUtils.h
new file mode 100644
index 0000000..2e7c16a
--- /dev/null
+++ b/xbmc/platform/posix/ConvUtils.h
@@ -0,0 +1,15 @@
+/*
+ * 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 "PlatformDefs.h" // DWORD ...
+
+DWORD GetLastError();
+void SetLastError(DWORD dwErrCode);
+
diff --git a/xbmc/platform/posix/Filesystem.cpp b/xbmc/platform/posix/Filesystem.cpp
new file mode 100644
index 0000000..0db3af5
--- /dev/null
+++ b/xbmc/platform/posix/Filesystem.cpp
@@ -0,0 +1,128 @@
+/*
+ * 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/Filesystem.h"
+#include "filesystem/SpecialProtocol.h"
+#include "utils/URIUtils.h"
+
+#if defined(TARGET_LINUX)
+#include <sys/statvfs.h>
+#elif defined(TARGET_DARWIN) || defined(TARGET_FREEBSD)
+#include <sys/param.h>
+#include <sys/mount.h>
+#elif defined(TARGET_ANDROID)
+#include <sys/statfs.h>
+#endif
+
+#include <cstdint>
+#include <cstdlib>
+#include <limits.h>
+#include <string.h>
+
+#include <unistd.h>
+
+namespace KODI
+{
+namespace PLATFORM
+{
+namespace FILESYSTEM
+{
+
+space_info space(const std::string& path, std::error_code& ec)
+{
+ ec.clear();
+ space_info sp;
+#if defined(TARGET_LINUX)
+ struct statvfs64 fsInfo;
+ auto result = statvfs64(CSpecialProtocol::TranslatePath(path).c_str(), &fsInfo);
+#else
+ struct statfs fsInfo;
+ // is 64-bit on android and darwin (10.6SDK + any iOS)
+ auto result = statfs(CSpecialProtocol::TranslatePath(path).c_str(), &fsInfo);
+#endif
+
+ if (result != 0)
+ {
+ ec.assign(result, std::system_category());
+ sp.available = static_cast<uintmax_t>(-1);
+ sp.capacity = static_cast<uintmax_t>(-1);
+ sp.free = static_cast<uintmax_t>(-1);
+ return sp;
+ }
+ sp.available = static_cast<uintmax_t>(fsInfo.f_bavail * fsInfo.f_bsize);
+ sp.capacity = static_cast<uintmax_t>(fsInfo.f_blocks * fsInfo.f_bsize);
+ sp.free = static_cast<uintmax_t>(fsInfo.f_bfree * fsInfo.f_bsize);
+
+ return sp;
+}
+
+std::string temp_directory_path(std::error_code &ec)
+{
+ ec.clear();
+
+ auto result = getenv("TMPDIR");
+ if (result)
+ return URIUtils::AppendSlash(result);
+
+ return "/tmp/";
+}
+
+std::string create_temp_directory(std::error_code &ec)
+{
+ char buf[PATH_MAX];
+
+ auto path = temp_directory_path(ec);
+
+ strncpy(buf, (path + "xbmctempXXXXXX").c_str(), sizeof(buf) - 1);
+ buf[sizeof(buf) - 1] = '\0';
+
+ auto tmp = mkdtemp(buf);
+ if (!tmp)
+ {
+ ec.assign(errno, std::system_category());
+ return std::string();
+ }
+
+ ec.clear();
+ return std::string(tmp);
+}
+
+std::string temp_file_path(const std::string& suffix, std::error_code& ec)
+{
+ char tmp[PATH_MAX];
+
+ auto tempPath = create_temp_directory(ec);
+ if (ec)
+ return std::string();
+
+ tempPath = URIUtils::AddFileToFolder(tempPath, "xbmctempfileXXXXXX" + suffix);
+ if (tempPath.length() >= PATH_MAX)
+ {
+ ec.assign(EOVERFLOW, std::system_category());
+ return std::string();
+ }
+
+ strncpy(tmp, tempPath.c_str(), sizeof(tmp) - 1);
+ tmp[sizeof(tmp) - 1] = '\0';
+
+ auto fd = mkstemps(tmp, suffix.length());
+ if (fd < 0)
+ {
+ ec.assign(errno, std::system_category());
+ return std::string();
+ }
+
+ close(fd);
+
+ ec.clear();
+ return std::string(tmp);
+}
+
+}
+}
+}
diff --git a/xbmc/platform/posix/MessagePrinter.cpp b/xbmc/platform/posix/MessagePrinter.cpp
new file mode 100644
index 0000000..6621740
--- /dev/null
+++ b/xbmc/platform/posix/MessagePrinter.cpp
@@ -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.
+ */
+
+#include "platform/MessagePrinter.h"
+
+#include "CompileInfo.h"
+
+#include <stdio.h>
+
+void CMessagePrinter::DisplayMessage(const std::string& message)
+{
+ fprintf(stdout, "%s\n", message.c_str());
+}
+
+void CMessagePrinter::DisplayWarning(const std::string& warning)
+{
+ fprintf(stderr, "%s\n", warning.c_str());
+}
+
+void CMessagePrinter::DisplayError(const std::string& error)
+{
+ fprintf(stderr,"%s\n", error.c_str());
+}
+
+void CMessagePrinter::DisplayHelpMessage(const std::vector<std::pair<std::string, std::string>>& help)
+{
+ //very crude implementation, pretty it up when possible
+ std::string message;
+ for (const auto& line : help)
+ {
+ message.append(line.first + "\t" + line.second + "\n");
+ }
+
+ fprintf(stdout, "%s\n", message.c_str());
+}
diff --git a/xbmc/platform/posix/PlatformDefs.h b/xbmc/platform/posix/PlatformDefs.h
new file mode 100644
index 0000000..e6e59fe
--- /dev/null
+++ b/xbmc/platform/posix/PlatformDefs.h
@@ -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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <string.h>
+#if defined(TARGET_DARWIN)
+#include <stdio.h>
+#include <sched.h>
+#include <AvailabilityMacros.h>
+#ifndef __STDC_FORMAT_MACROS
+ #define __STDC_FORMAT_MACROS
+#endif
+#include <sys/sysctl.h>
+#include <mach/mach.h>
+#if defined(TARGET_DARWIN_OSX)
+#include <libkern/OSTypes.h>
+#endif
+
+#elif defined(TARGET_FREEBSD)
+#include <stdio.h>
+#include <sys/sysctl.h>
+#else
+#include <sys/sysinfo.h>
+#endif
+
+#include <sys/time.h>
+#include <time.h>
+
+#if defined(__ppc__) || defined(__powerpc__)
+#define PIXEL_ASHIFT 0
+#define PIXEL_RSHIFT 8
+#define PIXEL_GSHIFT 16
+#define PIXEL_BSHIFT 24
+#else
+#define PIXEL_ASHIFT 24
+#define PIXEL_RSHIFT 16
+#define PIXEL_GSHIFT 8
+#define PIXEL_BSHIFT 0
+#endif
+
+#include <stdint.h>
+
+#define _fdopen fdopen
+#define _vsnprintf vsnprintf
+
+#define __stdcall
+#define __cdecl
+#define WINAPI __stdcall
+#undef APIENTRY
+struct CXHandle; // forward declaration
+typedef CXHandle* HANDLE;
+
+typedef void* HINSTANCE;
+typedef void* HMODULE;
+
+typedef unsigned int DWORD;
+#define INVALID_HANDLE_VALUE ((HANDLE)~0U)
+
+#define MAXWORD 0xffff
+
+typedef union _LARGE_INTEGER
+{
+ struct {
+ DWORD LowPart;
+ int32_t HighPart;
+ } u;
+ long long QuadPart;
+} LARGE_INTEGER, *PLARGE_INTEGER;
+
+ typedef union _ULARGE_INTEGER {
+ struct {
+ DWORD LowPart;
+ DWORD HighPart;
+ } u;
+ unsigned long long QuadPart;
+} ULARGE_INTEGER;
+
+// Network
+#define SOCKET_ERROR (-1)
+#define INVALID_SOCKET (~0)
+#define closesocket(s) close(s)
+#define ioctlsocket(s, f, v) ioctl(s, f, v)
+#define WSAGetLastError() (errno)
+#define WSAECONNRESET ECONNRESET
+
+typedef int SOCKET;
+
+// Thread
+typedef int (*LPTHREAD_START_ROUTINE)(void *);
+
+// File
+#define O_BINARY 0
+#define _O_TRUNC O_TRUNC
+#define _O_RDONLY O_RDONLY
+#define _O_WRONLY O_WRONLY
+
+#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD)
+ #define stat64 stat
+ #define __stat64 stat
+ #define fstat64 fstat
+ typedef int64_t off64_t;
+ #if defined(TARGET_FREEBSD)
+ #define statfs64 statfs
+ #endif
+#else
+ #define __stat64 stat64
+#endif
+
+struct _stati64 {
+ dev_t st_dev;
+ ino_t st_ino;
+ unsigned short st_mode;
+ short st_nlink;
+ short st_uid;
+ short st_gid;
+ dev_t st_rdev;
+ long long st_size;
+ time_t _st_atime;
+ time_t _st_mtime;
+ time_t _st_ctime;
+};
+
+#define FILE_BEGIN 0
+#define FILE_CURRENT 1
+#define FILE_END 2
+
+#define _S_IFREG S_IFREG
+#define _S_IFDIR S_IFDIR
+#define MAX_PATH PATH_MAX
+
+// CreateFile defines
+#define FILE_FLAG_NO_BUFFERING 0x20000000
+#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000
+
+#define CREATE_NEW 1
+#define CREATE_ALWAYS 2
+#define OPEN_EXISTING 3
+#define OPEN_ALWAYS 4
+#define TRUNCATE_EXISTING 5
+
+#define FILE_READ_DATA ( 0x0001 )
+#define FILE_WRITE_DATA ( 0x0002 )
diff --git a/xbmc/platform/posix/PlatformPosix.cpp b/xbmc/platform/posix/PlatformPosix.cpp
new file mode 100644
index 0000000..b1caa26
--- /dev/null
+++ b/xbmc/platform/posix/PlatformPosix.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 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 "PlatformPosix.h"
+
+#include "filesystem/SpecialProtocol.h"
+
+#include <cstdlib>
+#include <time.h>
+
+#ifdef HAS_DBUS
+#include <dbus/dbus.h>
+#endif
+
+std::atomic_flag CPlatformPosix::ms_signalFlag;
+
+bool CPlatformPosix::InitStageOne()
+{
+
+ if (!CPlatform::InitStageOne())
+ return false;
+
+ // Initialize to "set" state
+ ms_signalFlag.test_and_set();
+
+ // Initialize timezone information variables
+ tzset();
+
+ // set special://envhome
+ if (getenv("HOME"))
+ {
+ CSpecialProtocol::SetEnvHomePath(getenv("HOME"));
+ }
+ else
+ {
+ fprintf(stderr, "The HOME environment variable is not set!\n");
+ return false;
+ }
+
+#ifdef HAS_DBUS
+ // call 'dbus_threads_init_default' before any other dbus calls in order to
+ // avoid race conditions with other threads using dbus connections
+ dbus_threads_init_default();
+#endif
+
+ return true;
+}
+
+bool CPlatformPosix::TestQuitFlag()
+{
+ // Keep set, return true when it was cleared before
+ return !ms_signalFlag.test_and_set();
+}
+
+void CPlatformPosix::RequestQuit()
+{
+ ms_signalFlag.clear();
+}
diff --git a/xbmc/platform/posix/PlatformPosix.h b/xbmc/platform/posix/PlatformPosix.h
new file mode 100644
index 0000000..08513b9
--- /dev/null
+++ b/xbmc/platform/posix/PlatformPosix.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 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 <atomic>
+
+class CPlatformPosix : public CPlatform
+{
+public:
+ bool InitStageOne() override;
+
+ static bool TestQuitFlag();
+ static void RequestQuit();
+
+private:
+ static std::atomic_flag ms_signalFlag;
+};
diff --git a/xbmc/platform/posix/PosixMountProvider.cpp b/xbmc/platform/posix/PosixMountProvider.cpp
new file mode 100644
index 0000000..d139be1
--- /dev/null
+++ b/xbmc/platform/posix/PosixMountProvider.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 "PosixMountProvider.h"
+
+#include "utils/RegExp.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <cstdlib>
+
+CPosixMountProvider::CPosixMountProvider()
+{
+ m_removableLength = 0;
+ PumpDriveChangeEvents(NULL);
+}
+
+void CPosixMountProvider::Initialize()
+{
+ CLog::Log(LOGDEBUG, "Selected Posix mount as storage provider");
+}
+
+void CPosixMountProvider::GetDrives(VECSOURCES &drives)
+{
+ std::vector<std::string> result;
+
+ CRegExp reMount;
+#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD)
+ reMount.RegComp("on (.+) \\(([^,]+)");
+#else
+ reMount.RegComp("on (.+) type ([^ ]+)");
+#endif
+ char line[1024];
+
+ FILE* pipe = popen("mount", "r");
+
+ if (pipe)
+ {
+ while (fgets(line, sizeof(line) - 1, pipe))
+ {
+ if (reMount.RegFind(line) != -1)
+ {
+ bool accepted = false;
+ std::string mountStr = reMount.GetReplaceString("\\1");
+ std::string fsStr = reMount.GetReplaceString("\\2");
+ const char* mount = mountStr.c_str();
+ const char* fs = fsStr.c_str();
+
+ // Here we choose which filesystems are approved
+ if (strcmp(fs, "fuseblk") == 0 || strcmp(fs, "vfat") == 0
+ || strcmp(fs, "ext2") == 0 || strcmp(fs, "ext3") == 0
+ || strcmp(fs, "reiserfs") == 0 || strcmp(fs, "xfs") == 0
+ || strcmp(fs, "ntfs-3g") == 0 || strcmp(fs, "iso9660") == 0
+ || strcmp(fs, "exfat") == 0
+ || strcmp(fs, "fusefs") == 0 || strcmp(fs, "hfs") == 0)
+ accepted = true;
+
+ // Ignore root
+ if (strcmp(mount, "/") == 0)
+ accepted = false;
+
+ if(accepted)
+ result.emplace_back(mount);
+ }
+ }
+ pclose(pipe);
+ }
+
+ for (unsigned int i = 0; i < result.size(); i++)
+ {
+ CMediaSource share;
+ share.strPath = result[i];
+ share.strName = URIUtils::GetFileName(result[i]);
+ share.m_ignore = true;
+ drives.push_back(share);
+ }
+}
+
+std::vector<std::string> CPosixMountProvider::GetDiskUsage()
+{
+ std::vector<std::string> result;
+ char line[1024];
+
+#if defined(TARGET_DARWIN)
+ FILE* pipe = popen("df -hT ufs,cd9660,hfs,udf", "r");
+#elif defined(TARGET_FREEBSD)
+ FILE* pipe = popen("df -h -t ufs,cd9660,hfs,udf,zfs", "r");
+#else
+ FILE* pipe = popen("df -h", "r");
+#endif
+
+ static const char* excludes[] = {"rootfs","devtmpfs","tmpfs","none","/dev/loop", "udev", NULL};
+
+ if (pipe)
+ {
+ while (fgets(line, sizeof(line) - 1, pipe))
+ {
+ bool ok=true;
+ for (int i=0;excludes[i];++i)
+ {
+ if (strstr(line,excludes[i]))
+ {
+ ok=false;
+ break;
+ }
+ }
+ if (ok)
+ result.emplace_back(line);
+ }
+ pclose(pipe);
+ }
+
+ return result;
+}
+
+bool CPosixMountProvider::Eject(const std::string& mountpath)
+{
+
+#if !defined(TARGET_DARWIN_EMBEDDED)
+ // 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;
+#endif
+
+ return false;
+}
+
+bool CPosixMountProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback)
+{
+ VECSOURCES drives;
+ GetRemovableDrives(drives);
+ bool changed = drives.size() != m_removableLength;
+ m_removableLength = drives.size();
+ return changed;
+}
diff --git a/xbmc/platform/posix/PosixMountProvider.h b/xbmc/platform/posix/PosixMountProvider.h
new file mode 100644
index 0000000..170c644
--- /dev/null
+++ b/xbmc/platform/posix/PosixMountProvider.h
@@ -0,0 +1,37 @@
+/*
+ * 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 <string>
+#include <vector>
+
+class CPosixMountProvider : public IStorageProvider
+{
+public:
+ CPosixMountProvider();
+ ~CPosixMountProvider() override = default;
+
+ void Initialize() override;
+ void Stop() override { }
+
+ void GetLocalDrives(VECSOURCES &localDrives) override { GetDrives(localDrives); }
+ void GetRemovableDrives(VECSOURCES &removableDrives) override { /*GetDrives(removableDrives);*/ }
+
+ std::vector<std::string> GetDiskUsage() override;
+
+ bool Eject(const std::string& mountpath) override;
+
+ bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override;
+private:
+ void GetDrives(VECSOURCES &drives);
+
+ unsigned int m_removableLength;
+};
diff --git a/xbmc/platform/posix/PosixResourceCounter.cpp b/xbmc/platform/posix/PosixResourceCounter.cpp
new file mode 100644
index 0000000..d82ef72
--- /dev/null
+++ b/xbmc/platform/posix/PosixResourceCounter.cpp
@@ -0,0 +1,71 @@
+/*
+ * 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 "PosixResourceCounter.h"
+
+#include "utils/log.h"
+
+#include <errno.h>
+
+#include "PlatformDefs.h"
+
+CPosixResourceCounter::CPosixResourceCounter()
+{
+ Reset();
+}
+
+CPosixResourceCounter::~CPosixResourceCounter() = default;
+
+double CPosixResourceCounter::GetCPUUsage()
+{
+ struct timeval tmNow;
+ if (gettimeofday(&tmNow, NULL) == -1)
+ CLog::Log(LOGERROR, "error {} in gettimeofday", errno);
+ else
+ {
+ double dElapsed = ( ((double)tmNow.tv_sec + (double)tmNow.tv_usec / 1000000.0) -
+ ((double)m_tmLastCheck.tv_sec + (double)m_tmLastCheck.tv_usec / 1000000.0) );
+
+ if (dElapsed >= 3.0)
+ {
+ struct rusage usage;
+ if (getrusage(RUSAGE_SELF, &usage) == -1)
+ CLog::Log(LOGERROR, "error {} in getrusage", errno);
+ else
+ {
+ double dUser = ( ((double)usage.ru_utime.tv_sec + (double)usage.ru_utime.tv_usec / 1000000.0) -
+ ((double)m_usage.ru_utime.tv_sec + (double)m_usage.ru_utime.tv_usec / 1000000.0) );
+ double dSys = ( ((double)usage.ru_stime.tv_sec + (double)usage.ru_stime.tv_usec / 1000000.0) -
+ ((double)m_usage.ru_stime.tv_sec + (double)m_usage.ru_stime.tv_usec / 1000000.0) );
+
+ m_tmLastCheck = tmNow;
+ m_usage = usage;
+ m_dLastUsage = ((dUser+dSys) / dElapsed) * 100.0;
+ return m_dLastUsage;
+ }
+ }
+ }
+
+ return m_dLastUsage;
+}
+
+void CPosixResourceCounter::Reset()
+{
+ if (gettimeofday(&m_tmLastCheck, NULL) == -1)
+ CLog::Log(LOGERROR, "error {} in gettimeofday", errno);
+
+ if (getrusage(RUSAGE_SELF, &m_usage) == -1)
+ CLog::Log(LOGERROR, "error {} in getrusage", errno);
+
+ m_dLastUsage = 0.0;
+}
+
+
+
+
+
diff --git a/xbmc/platform/posix/PosixResourceCounter.h b/xbmc/platform/posix/PosixResourceCounter.h
new file mode 100644
index 0000000..ef61b15
--- /dev/null
+++ b/xbmc/platform/posix/PosixResourceCounter.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 <time.h>
+
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/times.h>
+
+class CPosixResourceCounter
+{
+public:
+ CPosixResourceCounter();
+ virtual ~CPosixResourceCounter();
+
+ double GetCPUUsage();
+ void Reset();
+
+protected:
+ struct rusage m_usage;
+ struct timeval m_tmLastCheck;
+ double m_dLastUsage;
+};
+
diff --git a/xbmc/platform/posix/PosixTimezone.cpp b/xbmc/platform/posix/PosixTimezone.cpp
new file mode 100644
index 0000000..6f276a3
--- /dev/null
+++ b/xbmc/platform/posix/PosixTimezone.cpp
@@ -0,0 +1,272 @@
+/*
+ * 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 <time.h>
+#ifdef TARGET_ANDROID
+#include "platform/android/bionic_supplement/bionic_supplement.h"
+#endif
+#include "PlatformDefs.h"
+#include "PosixTimezone.h"
+#include "utils/SystemInfo.h"
+
+#include "ServiceBroker.h"
+#include "utils/StringUtils.h"
+#include "XBDateTime.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include <stdlib.h>
+
+#include <algorithm>
+
+CPosixTimezone::CPosixTimezone()
+{
+ char* line = NULL;
+ size_t linelen = 0;
+ int nameonfourthfield = 0;
+ std::string s;
+ std::vector<std::string> tokens;
+
+ // Load timezones
+ FILE* fp = fopen("/usr/share/zoneinfo/zone.tab", "r");
+ if (fp)
+ {
+ std::string countryCode;
+ std::string timezoneName;
+
+ while (getdelim(&line, &linelen, '\n', fp) > 0)
+ {
+ tokens.clear();
+ s = line;
+ StringUtils::Trim(s);
+
+ if (s.length() == 0)
+ continue;
+
+ if (s[0] == '#')
+ continue;
+
+ StringUtils::Tokenize(s, tokens, " \t");
+ if (tokens.size() < 3)
+ continue;
+
+ countryCode = tokens[0];
+ timezoneName = tokens[2];
+
+ if (m_timezonesByCountryCode.count(countryCode) == 0)
+ {
+ std::vector<std::string> timezones;
+ timezones.push_back(timezoneName);
+ m_timezonesByCountryCode[countryCode] = timezones;
+ }
+ else
+ {
+ std::vector<std::string>& timezones = m_timezonesByCountryCode[countryCode];
+ timezones.push_back(timezoneName);
+ }
+
+ m_countriesByTimezoneName[timezoneName] = countryCode;
+ }
+ fclose(fp);
+ }
+
+ if (line)
+ {
+ free(line);
+ line = NULL;
+ linelen = 0;
+ }
+
+ // Load countries
+ fp = fopen("/usr/share/zoneinfo/iso3166.tab", "r");
+ if (!fp)
+ {
+ fp = fopen("/usr/share/misc/iso3166", "r");
+ nameonfourthfield = 1;
+ }
+ if (fp)
+ {
+ std::string countryCode;
+ std::string countryName;
+
+ while (getdelim(&line, &linelen, '\n', fp) > 0)
+ {
+ s = line;
+ StringUtils::Trim(s);
+
+ //! @todo STRING_CLEANUP
+ if (s.length() == 0)
+ continue;
+
+ if (s[0] == '#')
+ continue;
+
+ // Search for the first non space from the 2nd character and on
+ int i = 2;
+ while (s[i] == ' ' || s[i] == '\t') i++;
+
+ if (nameonfourthfield)
+ {
+ // skip three letter
+ while (s[i] != ' ' && s[i] != '\t') i++;
+ while (s[i] == ' ' || s[i] == '\t') i++;
+ // skip number
+ while (s[i] != ' ' && s[i] != '\t') i++;
+ while (s[i] == ' ' || s[i] == '\t') i++;
+ }
+
+ countryCode = s.substr(0, 2);
+ countryName = s.substr(i);
+
+ m_counties.push_back(countryName);
+ m_countryByCode[countryCode] = countryName;
+ m_countryByName[countryName] = countryCode;
+ }
+ sort(m_counties.begin(), m_counties.end(), sortstringbyname());
+ fclose(fp);
+ }
+ free(line);
+}
+
+void CPosixTimezone::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == NULL)
+ return;
+
+ const std::string &settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_LOCALE_TIMEZONE)
+ {
+ SetTimezone(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
+
+ CDateTime::ResetTimezoneBias();
+ }
+ else if (settingId == CSettings::SETTING_LOCALE_TIMEZONECOUNTRY)
+ {
+ // nothing to do here. Changing locale.timezonecountry will trigger an
+ // update of locale.timezone and automatically adjust its value
+ // and execute OnSettingChanged() for it as well (see above)
+ }
+}
+
+void CPosixTimezone::OnSettingsLoaded()
+{
+ SetTimezone(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_TIMEZONE));
+ CDateTime::ResetTimezoneBias();
+}
+
+std::vector<std::string> CPosixTimezone::GetCounties()
+{
+ return m_counties;
+}
+
+std::vector<std::string> CPosixTimezone::GetTimezonesByCountry(const std::string& country)
+{
+ return m_timezonesByCountryCode[m_countryByName[country]];
+}
+
+std::string CPosixTimezone::GetCountryByTimezone(const std::string& timezone)
+{
+#if defined(TARGET_DARWIN)
+ return "?";
+#else
+ return m_countryByCode[m_countriesByTimezoneName[timezone]];
+#endif
+}
+
+void CPosixTimezone::SetTimezone(const std::string& timezoneName)
+{
+#if !defined(TARGET_DARWIN)
+ bool use_timezone = true;
+#else
+ bool use_timezone = false;
+#endif
+
+ if (use_timezone)
+ {
+ static char env_var[255];
+ sprintf(env_var, "TZ=:%s", timezoneName.c_str());
+ putenv(env_var);
+ tzset();
+ }
+}
+
+std::string CPosixTimezone::GetOSConfiguredTimezone()
+{
+ char timezoneName[255];
+
+ // try Slackware approach first
+ ssize_t rlrc = readlink("/etc/localtime-copied-from"
+ , timezoneName, sizeof(timezoneName)-1);
+
+ // RHEL and maybe other distros make /etc/localtime a symlink
+ if (rlrc == -1)
+ rlrc = readlink("/etc/localtime", timezoneName, sizeof(timezoneName)-1);
+
+ if (rlrc != -1)
+ {
+ timezoneName[rlrc] = '\0';
+
+ char* p = strrchr(timezoneName,'/');
+ if (p)
+ { // we want the previous '/'
+ char* q = p;
+ *q = 0;
+ p = strrchr(timezoneName,'/');
+ *q = '/';
+ if (p)
+ p++;
+ }
+ return p;
+ }
+
+ // now try Debian approach
+ timezoneName[0] = 0;
+ FILE* fp = fopen("/etc/timezone", "r");
+ if (fp)
+ {
+ if (fgets(timezoneName, sizeof(timezoneName), fp))
+ timezoneName[strlen(timezoneName)-1] = '\0';
+ fclose(fp);
+ }
+
+ return timezoneName;
+}
+
+void CPosixTimezone::SettingOptionsTimezoneCountriesFiller(
+ const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ std::vector<std::string> countries = g_timezone.GetCounties();
+ for (unsigned int i = 0; i < countries.size(); i++)
+ list.emplace_back(countries[i], countries[i]);
+}
+
+void CPosixTimezone::SettingOptionsTimezonesFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ current = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
+ bool found = false;
+ std::vector<std::string> timezones = g_timezone.GetTimezonesByCountry(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_TIMEZONECOUNTRY));
+ for (unsigned int i = 0; i < timezones.size(); i++)
+ {
+ if (!found && StringUtils::EqualsNoCase(timezones[i], current))
+ found = true;
+
+ list.emplace_back(timezones[i], timezones[i]);
+ }
+
+ if (!found && !timezones.empty())
+ current = timezones[0];
+}
+
+CPosixTimezone g_timezone;
diff --git a/xbmc/platform/posix/PosixTimezone.h b/xbmc/platform/posix/PosixTimezone.h
new file mode 100644
index 0000000..076e87f
--- /dev/null
+++ b/xbmc/platform/posix/PosixTimezone.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "settings/lib/ISettingCallback.h"
+#include "settings/lib/ISettingsHandler.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+class CSetting;
+struct StringSettingOption;
+
+class CPosixTimezone : public ISettingCallback, public ISettingsHandler
+{
+public:
+ CPosixTimezone();
+
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+
+ void OnSettingsLoaded() override;
+
+ std::string GetOSConfiguredTimezone();
+
+ std::vector<std::string> GetCounties();
+ std::vector<std::string> GetTimezonesByCountry(const std::string& country);
+ std::string GetCountryByTimezone(const std::string& timezone);
+
+ void SetTimezone(const std::string& timezone);
+ int m_IsDST = 0;
+
+ static void SettingOptionsTimezoneCountriesFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
+ static void SettingOptionsTimezonesFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
+
+private:
+ std::vector<std::string> m_counties;
+ std::map<std::string, std::string> m_countryByCode;
+ std::map<std::string, std::string> m_countryByName;
+
+ std::map<std::string, std::vector<std::string> > m_timezonesByCountryCode;
+ std::map<std::string, std::string> m_countriesByTimezoneName;
+};
+
+extern CPosixTimezone g_timezone;
+
diff --git a/xbmc/platform/posix/XHandle.cpp b/xbmc/platform/posix/XHandle.cpp
new file mode 100644
index 0000000..be7a78b
--- /dev/null
+++ b/xbmc/platform/posix/XHandle.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 "XHandle.h"
+
+#include "utils/log.h"
+
+#include <cassert>
+#include <mutex>
+
+int CXHandle::m_objectTracker[10] = {};
+
+HANDLE WINAPI GetCurrentProcess(void) {
+ return (HANDLE)-1; // -1 a special value - pseudo handle
+}
+
+CXHandle::CXHandle()
+{
+ Init();
+ m_objectTracker[m_type]++;
+}
+
+CXHandle::CXHandle(HandleType nType)
+{
+ Init();
+ m_type=nType;
+ m_objectTracker[m_type]++;
+}
+
+CXHandle::CXHandle(const CXHandle &src)
+{
+ // we shouldn't get here EVER. however, if we do - try to make the best. copy what we can
+ // and most importantly - not share any pointer.
+ CLog::Log(LOGWARNING, "{}, copy handle.", __FUNCTION__);
+
+ Init();
+
+ if (src.m_hMutex)
+ m_hMutex = new CCriticalSection();
+
+ fd = src.fd;
+ m_bManualEvent = src.m_bManualEvent;
+ m_tmCreation = time(NULL);
+ m_FindFileResults = src.m_FindFileResults;
+ m_nFindFileIterator = src.m_nFindFileIterator;
+ m_FindFileDir = src.m_FindFileDir;
+ m_iOffset = src.m_iOffset;
+ m_bCDROM = src.m_bCDROM;
+ m_objectTracker[m_type]++;
+}
+
+CXHandle::~CXHandle()
+{
+
+ m_objectTracker[m_type]--;
+
+ if (RecursionCount > 0) {
+ CLog::Log(LOGERROR, "{}, destroying handle with recursion count {}", __FUNCTION__,
+ RecursionCount);
+ assert(false);
+ }
+
+ if (m_nRefCount > 1) {
+ CLog::Log(LOGERROR, "{}, destroying handle with ref count {}", __FUNCTION__, m_nRefCount);
+ assert(false);
+ }
+
+ if (m_hMutex) {
+ delete m_hMutex;
+ }
+
+ if (m_internalLock) {
+ delete m_internalLock;
+ }
+
+ if (m_hCond) {
+ delete m_hCond;
+ }
+
+ if ( fd != 0 ) {
+ close(fd);
+ }
+
+}
+
+void CXHandle::Init()
+{
+ fd=0;
+ m_hMutex=NULL;
+ m_hCond=NULL;
+ m_type = HND_NULL;
+ RecursionCount=0;
+ m_bManualEvent=false;
+ m_bEventSet=false;
+ m_nFindFileIterator=0 ;
+ m_nRefCount=1;
+ m_tmCreation = time(NULL);
+ m_internalLock = new CCriticalSection();
+}
+
+void CXHandle::ChangeType(HandleType newType) {
+ m_objectTracker[m_type]--;
+ m_type = newType;
+ m_objectTracker[m_type]++;
+}
+
+void CXHandle::DumpObjectTracker() {
+ for (int i=0; i< 10; i++) {
+ CLog::Log(LOGDEBUG, "object {} --> {} instances", i, m_objectTracker[i]);
+ }
+}
+
+bool CloseHandle(HANDLE hObject) {
+ if (!hObject)
+ return false;
+
+ if (hObject == INVALID_HANDLE_VALUE || hObject == (HANDLE)-1)
+ return true;
+
+ bool bDelete = false;
+ {
+ std::unique_lock<CCriticalSection> lock((*hObject->m_internalLock));
+ if (--hObject->m_nRefCount == 0)
+ bDelete = true;
+ }
+
+ if (bDelete)
+ delete hObject;
+
+ return true;
+}
+
+
diff --git a/xbmc/platform/posix/XHandle.h b/xbmc/platform/posix/XHandle.h
new file mode 100644
index 0000000..158817e
--- /dev/null
+++ b/xbmc/platform/posix/XHandle.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
+
+#include "XHandlePublic.h"
+#include "threads/Condition.h"
+#include "threads/CriticalSection.h"
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "PlatformDefs.h"
+
+struct CXHandle {
+
+public:
+ typedef enum { HND_NULL = 0, HND_FILE, HND_EVENT, HND_MUTEX, HND_FIND_FILE } HandleType;
+
+ CXHandle();
+ explicit CXHandle(HandleType nType);
+ CXHandle(const CXHandle &src);
+
+ virtual ~CXHandle();
+ void Init();
+ inline HandleType GetType() { return m_type; }
+ void ChangeType(HandleType newType);
+
+ XbmcThreads::ConditionVariable *m_hCond;
+ std::list<CXHandle*> m_hParents;
+
+ // simulate mutex and critical section
+ CCriticalSection *m_hMutex;
+ int RecursionCount; // for mutex - for compatibility with TARGET_WINDOWS critical section
+ int fd;
+ bool m_bManualEvent;
+ time_t m_tmCreation;
+ std::vector<std::string> m_FindFileResults;
+ int m_nFindFileIterator;
+ std::string m_FindFileDir;
+ off64_t m_iOffset;
+ bool m_bCDROM;
+ bool m_bEventSet;
+ int m_nRefCount;
+ CCriticalSection *m_internalLock;
+
+ static void DumpObjectTracker();
+
+protected:
+ HandleType m_type;
+ static int m_objectTracker[10];
+
+};
diff --git a/xbmc/platform/posix/XHandlePublic.h b/xbmc/platform/posix/XHandlePublic.h
new file mode 100644
index 0000000..6d1f840
--- /dev/null
+++ b/xbmc/platform/posix/XHandlePublic.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+struct CXHandle;
+typedef CXHandle* HANDLE;
+typedef HANDLE* LPHANDLE;
+
+bool CloseHandle(HANDLE hObject);
+
+#define DUPLICATE_CLOSE_SOURCE 0x00000001
+#define DUPLICATE_SAME_ACCESS 0x00000002
+
diff --git a/xbmc/platform/posix/XTimeUtils.cpp b/xbmc/platform/posix/XTimeUtils.cpp
new file mode 100644
index 0000000..e78e5cf
--- /dev/null
+++ b/xbmc/platform/posix/XTimeUtils.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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/XTimeUtils.h"
+
+#include "PosixTimezone.h"
+
+#include <errno.h>
+#include <mutex>
+#include <time.h>
+
+#include <sys/times.h>
+
+#if defined(TARGET_ANDROID) && !defined(__LP64__)
+#include <time64.h>
+#endif
+
+#define WIN32_TIME_OFFSET ((unsigned long long)(369 * 365 + 89) * 24 * 3600 * 10000000)
+
+namespace KODI
+{
+namespace TIME
+{
+
+/*
+ * A Leap year is any year that is divisible by four, but not by 100 unless also
+ * divisible by 400
+ */
+#define IsLeapYear(y) ((!(y % 4)) ? (((!(y % 400)) && (y % 100)) ? 1 : 0) : 0)
+
+uint32_t GetTimeZoneInformation(TimeZoneInformation* timeZoneInformation)
+{
+ if (!timeZoneInformation)
+ return KODI_TIME_ZONE_ID_INVALID;
+
+ struct tm t;
+ time_t tt = time(NULL);
+ if (localtime_r(&tt, &t))
+ timeZoneInformation->bias = -t.tm_gmtoff / 60;
+
+ timeZoneInformation->standardName = tzname[0];
+ timeZoneInformation->daylightName = tzname[1];
+
+ return KODI_TIME_ZONE_ID_UNKNOWN;
+}
+
+void GetLocalTime(SystemTime* systemTime)
+{
+ const time_t t = time(NULL);
+ struct tm now;
+
+ localtime_r(&t, &now);
+ systemTime->year = now.tm_year + 1900;
+ systemTime->month = now.tm_mon + 1;
+ systemTime->dayOfWeek = now.tm_wday;
+ systemTime->day = now.tm_mday;
+ systemTime->hour = now.tm_hour;
+ systemTime->minute = now.tm_min;
+ systemTime->second = now.tm_sec;
+ systemTime->milliseconds = 0;
+ // NOTE: localtime_r() is not required to set this, but we Assume that it's set here.
+ g_timezone.m_IsDST = now.tm_isdst;
+}
+
+int FileTimeToLocalFileTime(const FileTime* fileTime, FileTime* localFileTime)
+{
+ ULARGE_INTEGER l;
+ l.u.LowPart = fileTime->lowDateTime;
+ l.u.HighPart = fileTime->highDateTime;
+
+ time_t ft;
+ struct tm tm_ft;
+ FileTimeToTimeT(fileTime, &ft);
+ localtime_r(&ft, &tm_ft);
+
+ l.QuadPart += static_cast<unsigned long long>(tm_ft.tm_gmtoff) * 10000000;
+
+ localFileTime->lowDateTime = l.u.LowPart;
+ localFileTime->highDateTime = l.u.HighPart;
+ return 1;
+}
+
+int SystemTimeToFileTime(const SystemTime* systemTime, FileTime* fileTime)
+{
+ static const int dayoffset[12] = {0, 31, 59, 90, 120, 151, 182, 212, 243, 273, 304, 334};
+#if defined(TARGET_DARWIN)
+ static std::mutex timegm_lock;
+#endif
+
+ struct tm sysTime = {};
+ sysTime.tm_year = systemTime->year - 1900;
+ sysTime.tm_mon = systemTime->month - 1;
+ sysTime.tm_wday = systemTime->dayOfWeek;
+ sysTime.tm_mday = systemTime->day;
+ sysTime.tm_hour = systemTime->hour;
+ sysTime.tm_min = systemTime->minute;
+ sysTime.tm_sec = systemTime->second;
+ sysTime.tm_yday = dayoffset[sysTime.tm_mon] + (sysTime.tm_mday - 1);
+ sysTime.tm_isdst = g_timezone.m_IsDST;
+
+ // If this is a leap year, and we're past the 28th of Feb, increment tm_yday.
+ if (IsLeapYear(systemTime->year) && (sysTime.tm_yday > 58))
+ sysTime.tm_yday++;
+
+#if defined(TARGET_DARWIN)
+ std::lock_guard<std::mutex> lock(timegm_lock);
+#endif
+
+#if defined(TARGET_ANDROID) && !defined(__LP64__)
+ time64_t t = timegm64(&sysTime);
+#else
+ time_t t = timegm(&sysTime);
+#endif
+
+ LARGE_INTEGER result;
+ result.QuadPart = (long long)t * 10000000 + (long long)systemTime->milliseconds * 10000;
+ result.QuadPart += WIN32_TIME_OFFSET;
+
+ fileTime->lowDateTime = result.u.LowPart;
+ fileTime->highDateTime = result.u.HighPart;
+
+ return 1;
+}
+
+long CompareFileTime(const FileTime* fileTime1, const FileTime* fileTime2)
+{
+ ULARGE_INTEGER t1;
+ t1.u.LowPart = fileTime1->lowDateTime;
+ t1.u.HighPart = fileTime1->highDateTime;
+
+ ULARGE_INTEGER t2;
+ t2.u.LowPart = fileTime2->lowDateTime;
+ t2.u.HighPart = fileTime2->highDateTime;
+
+ if (t1.QuadPart == t2.QuadPart)
+ return 0;
+ else if (t1.QuadPart < t2.QuadPart)
+ return -1;
+ else
+ return 1;
+}
+
+int FileTimeToSystemTime(const FileTime* fileTime, SystemTime* systemTime)
+{
+ LARGE_INTEGER file;
+ file.u.LowPart = fileTime->lowDateTime;
+ file.u.HighPart = fileTime->highDateTime;
+
+ file.QuadPart -= WIN32_TIME_OFFSET;
+ file.QuadPart /= 10000; /* to milliseconds */
+ systemTime->milliseconds = file.QuadPart % 1000;
+ file.QuadPart /= 1000; /* to seconds */
+
+ time_t ft = file.QuadPart;
+
+ struct tm tm_ft;
+ gmtime_r(&ft, &tm_ft);
+
+ systemTime->year = tm_ft.tm_year + 1900;
+ systemTime->month = tm_ft.tm_mon + 1;
+ systemTime->dayOfWeek = tm_ft.tm_wday;
+ systemTime->day = tm_ft.tm_mday;
+ systemTime->hour = tm_ft.tm_hour;
+ systemTime->minute = tm_ft.tm_min;
+ systemTime->second = tm_ft.tm_sec;
+
+ return 1;
+}
+
+int LocalFileTimeToFileTime(const FileTime* localFileTime, FileTime* fileTime)
+{
+ ULARGE_INTEGER l;
+ l.u.LowPart = localFileTime->lowDateTime;
+ l.u.HighPart = localFileTime->highDateTime;
+
+ l.QuadPart += (unsigned long long) timezone * 10000000;
+
+ fileTime->lowDateTime = l.u.LowPart;
+ fileTime->highDateTime = l.u.HighPart;
+
+ return 1;
+}
+
+
+int FileTimeToTimeT(const FileTime* localFileTime, time_t* pTimeT)
+{
+ if (!localFileTime || !pTimeT)
+ return false;
+
+ ULARGE_INTEGER fileTime;
+ fileTime.u.LowPart = localFileTime->lowDateTime;
+ fileTime.u.HighPart = localFileTime->highDateTime;
+
+ fileTime.QuadPart -= WIN32_TIME_OFFSET;
+ fileTime.QuadPart /= 10000; /* to milliseconds */
+ fileTime.QuadPart /= 1000; /* to seconds */
+
+ time_t ft = fileTime.QuadPart;
+
+ struct tm tm_ft;
+ localtime_r(&ft, &tm_ft);
+
+ *pTimeT = mktime(&tm_ft);
+ return 1;
+}
+
+int TimeTToFileTime(time_t timeT, FileTime* localFileTime)
+{
+ if (!localFileTime)
+ return false;
+
+ ULARGE_INTEGER result;
+ result.QuadPart = (unsigned long long) timeT * 10000000;
+ result.QuadPart += WIN32_TIME_OFFSET;
+
+ localFileTime->lowDateTime = result.u.LowPart;
+ localFileTime->highDateTime = result.u.HighPart;
+
+ return 1;
+}
+
+} // namespace TIME
+} // namespace KODI
diff --git a/xbmc/platform/posix/filesystem/CMakeLists.txt b/xbmc/platform/posix/filesystem/CMakeLists.txt
new file mode 100644
index 0000000..badb1a8
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(SOURCES PosixDirectory.cpp
+ PosixFile.cpp)
+
+set(HEADERS PosixDirectory.h
+ PosixFile.h)
+
+if(SMBCLIENT_FOUND)
+ list(APPEND SOURCES SMBDirectory.cpp
+ SMBFile.cpp
+ SMBWSDiscovery.cpp
+ SMBWSDiscoveryListener.cpp)
+ list(APPEND HEADERS SMBDirectory.h
+ SMBFile.h
+ SMBWSDiscovery.h
+ SMBWSDiscoveryListener.h)
+endif()
+
+core_add_library(platform_posix_filesystem)
diff --git a/xbmc/platform/posix/filesystem/PosixDirectory.cpp b/xbmc/platform/posix/filesystem/PosixDirectory.cpp
new file mode 100644
index 0000000..6685c0e
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/PosixDirectory.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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 "PosixDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "utils/AliasShortcutUtils.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+using namespace XFILE;
+
+CPosixDirectory::CPosixDirectory(void) = default;
+
+CPosixDirectory::~CPosixDirectory(void) = default;
+
+bool CPosixDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ std::string root = url.Get();
+
+ if (IsAliasShortcut(root, true))
+ TranslateAliasShortcut(root);
+
+ DIR *dir = opendir(root.c_str());
+ if (!dir)
+ return false;
+
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL)
+ {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ std::string itemLabel(entry->d_name);
+ CCharsetConverter::unknownToUTF8(itemLabel);
+ CFileItemPtr pItem(new CFileItem(itemLabel));
+ std::string itemPath(URIUtils::AddFileToFolder(root, entry->d_name));
+
+ bool bStat = false;
+ struct stat buffer;
+
+ // Unix-based readdir implementations may return an incorrect dirent.d_ino value that
+ // is not equal to the (correct) stat() obtained one. In this case the file type
+ // could not be determined and the value of dirent.d_type is set to DT_UNKNOWN.
+ // In order to get a correct value we have to incur the cost of calling stat.
+ if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)
+ {
+ if (stat(itemPath.c_str(), &buffer) == 0)
+ bStat = true;
+ }
+
+ if (entry->d_type == DT_DIR || (bStat && S_ISDIR(buffer.st_mode)))
+ {
+ pItem->m_bIsFolder = true;
+ URIUtils::AddSlashAtEnd(itemPath);
+ }
+ else
+ {
+ pItem->m_bIsFolder = false;
+ }
+
+ if (StringUtils::StartsWith(entry->d_name, "."))
+ pItem->SetProperty("file:hidden", true);
+
+ pItem->SetPath(itemPath);
+
+ if (!(m_flags & DIR_FLAG_NO_FILE_INFO))
+ {
+ if (bStat || stat(pItem->GetPath().c_str(), &buffer) == 0)
+ {
+ KODI::TIME::FileTime fileTime, localTime;
+ KODI::TIME::TimeTToFileTime(buffer.st_mtime, &fileTime);
+ KODI::TIME::FileTimeToLocalFileTime(&fileTime, &localTime);
+ pItem->m_dateTime = localTime;
+
+ if (!pItem->m_bIsFolder)
+ pItem->m_dwSize = buffer.st_size;
+ }
+ }
+ items.Add(pItem);
+ }
+ closedir(dir);
+ return true;
+}
+
+bool CPosixDirectory::Create(const CURL& url)
+{
+ if (!Create(url.Get()))
+ return Exists(url);
+
+ return true;
+}
+
+bool CPosixDirectory::Create(const std::string& path)
+{
+ if (mkdir(path.c_str(), 0755) != 0)
+ {
+ if (errno == ENOENT)
+ {
+ auto sep = path.rfind('/');
+ if (sep == std::string::npos)
+ return false;
+
+ if (Create(path.substr(0, sep)))
+ return Create(path);
+ }
+
+ return false;
+ }
+ return true;
+}
+
+bool CPosixDirectory::Remove(const CURL& url)
+{
+ if (rmdir(url.Get().c_str()) == 0)
+ return true;
+
+ return !Exists(url);
+}
+
+bool CPosixDirectory::RemoveRecursive(const CURL& url)
+{
+ std::string root = url.Get();
+
+ if (IsAliasShortcut(root, true))
+ TranslateAliasShortcut(root);
+
+ DIR *dir = opendir(root.c_str());
+ if (!dir)
+ return false;
+
+ bool success(true);
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL)
+ {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ std::string itemLabel(entry->d_name);
+ CCharsetConverter::unknownToUTF8(itemLabel);
+ std::string itemPath(URIUtils::AddFileToFolder(root, entry->d_name));
+
+ bool bStat = false;
+ struct stat buffer;
+
+ // Unix-based readdir implementations may return an incorrect dirent.d_ino value that
+ // is not equal to the (correct) stat() obtained one. In this case the file type
+ // could not be determined and the value of dirent.d_type is set to DT_UNKNOWN.
+ // In order to get a correct value we have to incur the cost of calling stat.
+ if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)
+ {
+ if (stat(itemPath.c_str(), &buffer) == 0)
+ bStat = true;
+ }
+
+ if (entry->d_type == DT_DIR || (bStat && S_ISDIR(buffer.st_mode)))
+ {
+ if (!RemoveRecursive(CURL{ itemPath }))
+ {
+ success = false;
+ break;
+ }
+ }
+ else
+ {
+ if (unlink(itemPath.c_str()) != 0)
+ {
+ success = false;
+ break;
+ }
+ }
+ }
+
+ closedir(dir);
+
+ if (success)
+ {
+ if (rmdir(root.c_str()) != 0)
+ success = false;
+ }
+
+ return success;
+}
+
+bool CPosixDirectory::Exists(const CURL& url)
+{
+ std::string path = url.Get();
+
+ if (IsAliasShortcut(path, true))
+ TranslateAliasShortcut(path);
+
+ struct stat buffer;
+ if (stat(path.c_str(), &buffer) != 0)
+ return false;
+ return S_ISDIR(buffer.st_mode) ? true : false;
+}
diff --git a/xbmc/platform/posix/filesystem/PosixDirectory.h b/xbmc/platform/posix/filesystem/PosixDirectory.h
new file mode 100644
index 0000000..eb323b6
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/PosixDirectory.h
@@ -0,0 +1,29 @@
+/*
+ * 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 "filesystem/IDirectory.h"
+
+namespace XFILE
+{
+
+class CPosixDirectory : public IDirectory
+{
+public:
+ CPosixDirectory(void);
+ ~CPosixDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Create(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+ bool RemoveRecursive(const CURL& url) override;
+private:
+ bool Create(const std::string& path);
+};
+}
diff --git a/xbmc/platform/posix/filesystem/PosixFile.cpp b/xbmc/platform/posix/filesystem/PosixFile.cpp
new file mode 100644
index 0000000..3010136
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/PosixFile.cpp
@@ -0,0 +1,388 @@
+/*
+ * 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 "PosixFile.h"
+
+#include "URL.h"
+#include "filesystem/File.h"
+#include "utils/AliasShortcutUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <string>
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#if defined(HAVE_STATX) // use statx if available to get file birth date
+#include <sys/sysmacros.h>
+#endif
+#include <unistd.h>
+
+using namespace XFILE;
+
+CPosixFile::~CPosixFile()
+{
+ if (m_fd >= 0)
+ close(m_fd);
+}
+
+// local helper
+static std::string getFilename(const CURL& url)
+{
+ std::string filename(url.GetFileName());
+ if (IsAliasShortcut(filename, false))
+ TranslateAliasShortcut(filename);
+
+ return filename;
+}
+
+
+bool CPosixFile::Open(const CURL& url)
+{
+ if (m_fd >= 0)
+ return false;
+
+ const std::string filename(getFilename(url));
+ if (filename.empty())
+ return false;
+
+ m_fd = open(filename.c_str(), O_RDONLY, S_IRUSR | S_IRGRP | S_IROTH);
+ m_filePos = 0;
+
+ return m_fd != -1;
+}
+
+bool CPosixFile::OpenForWrite(const CURL& url, bool bOverWrite /* = false*/ )
+{
+ if (m_fd >= 0)
+ return false;
+
+ const std::string filename(getFilename(url));
+ if (filename.empty())
+ return false;
+
+ m_fd = open(filename.c_str(), O_RDWR | O_CREAT | (bOverWrite ? O_TRUNC : 0), S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH);
+ if (m_fd < 0)
+ return false;
+
+ m_filePos = 0;
+ m_allowWrite = true;
+
+ return true;
+}
+
+void CPosixFile::Close()
+{
+ if (m_fd >= 0)
+ {
+ close(m_fd);
+ m_fd = -1;
+ m_filePos = -1;
+ m_lastDropPos = -1;
+ m_allowWrite = false;
+ }
+}
+
+
+ssize_t CPosixFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ if (m_fd < 0)
+ return -1;
+
+ assert(lpBuf != NULL || uiBufSize == 0);
+ if (lpBuf == NULL && uiBufSize != 0)
+ return -1;
+
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ const ssize_t res = read(m_fd, lpBuf, uiBufSize);
+ if (res < 0)
+ {
+ Seek(0, SEEK_CUR); // force update file position
+ return -1;
+ }
+
+ if (m_filePos >= 0)
+ {
+ m_filePos += res; // if m_filePos was known - update it
+#if defined(HAVE_POSIX_FADVISE)
+ // Drop the cache between then last drop and 16 MB behind where we
+ // are now, to make sure the file doesn't displace everything else.
+ // However, never throw out the first 16 MB of the file, as it might
+ // be the header etc., and never ask the OS to drop in chunks of
+ // less than 1 MB.
+ const int64_t end_drop = m_filePos - 16 * 1024 * 1024;
+ if (end_drop >= 17 * 1024 * 1024)
+ {
+ const int64_t start_drop = std::max<int64_t>(m_lastDropPos, 16 * 1024 * 1024);
+ if (end_drop - start_drop >= 1 * 1024 * 1024 &&
+ posix_fadvise(m_fd, start_drop, end_drop - start_drop, POSIX_FADV_DONTNEED) == 0)
+ m_lastDropPos = end_drop;
+ }
+#endif
+ }
+
+ return res;
+}
+
+ssize_t CPosixFile::Write(const void* lpBuf, size_t uiBufSize)
+{
+ if (m_fd < 0)
+ return -1;
+
+ assert(lpBuf != NULL || uiBufSize == 0);
+ if ((lpBuf == NULL && uiBufSize != 0) || !m_allowWrite)
+ return -1;
+
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ const ssize_t res = write(m_fd, lpBuf, uiBufSize);
+ if (res < 0)
+ {
+ Seek(0, SEEK_CUR); // force update file position
+ return -1;
+ }
+
+ if (m_filePos >= 0)
+ m_filePos += res; // if m_filePos was known - update it
+
+ return res;
+}
+
+int64_t CPosixFile::Seek(int64_t iFilePosition, int iWhence /* = SEEK_SET*/)
+{
+ if (m_fd < 0)
+ return -1;
+
+#ifdef TARGET_ANDROID
+ //! @todo properly support with detection in configure
+ //! Android special case: Android doesn't substitute off64_t for off_t and similar functions
+ m_filePos = lseek64(m_fd, (off64_t)iFilePosition, iWhence);
+#else // !TARGET_ANDROID
+ const off_t filePosOffT = (off_t) iFilePosition;
+ // check for parameter overflow
+ if (sizeof(int64_t) != sizeof(off_t) && iFilePosition != filePosOffT)
+ return -1;
+
+ m_filePos = lseek(m_fd, filePosOffT, iWhence);
+#endif // !TARGET_ANDROID
+
+ return m_filePos;
+}
+
+int CPosixFile::Truncate(int64_t size)
+{
+ if (m_fd < 0)
+ return -1;
+
+ const off_t sizeOffT = (off_t) size;
+ // check for parameter overflow
+ if (sizeof(int64_t) != sizeof(off_t) && size != sizeOffT)
+ return -1;
+
+ return ftruncate(m_fd, sizeOffT);
+}
+
+int64_t CPosixFile::GetPosition()
+{
+ if (m_fd < 0)
+ return -1;
+
+ if (m_filePos < 0)
+ m_filePos = lseek(m_fd, 0, SEEK_CUR);
+
+ return m_filePos;
+}
+
+int64_t CPosixFile::GetLength()
+{
+ if (m_fd < 0)
+ return -1;
+
+ struct stat64 st;
+ if (fstat64(m_fd, &st) != 0)
+ return -1;
+
+ return st.st_size;
+}
+
+void CPosixFile::Flush()
+{
+ if (m_fd >= 0)
+ fsync(m_fd);
+}
+
+int CPosixFile::IoControl(EIoControl request, void* param)
+{
+ if (m_fd < 0)
+ return -1;
+
+ if (request == IOCTRL_NATIVE)
+ {
+ if(!param)
+ return -1;
+ return ioctl(m_fd, ((SNativeIoControl*)param)->request, ((SNativeIoControl*)param)->param);
+ }
+ else if (request == IOCTRL_SEEK_POSSIBLE)
+ {
+ if (GetPosition() < 0)
+ return -1; // current position is unknown, can't test seeking
+ else if (m_filePos > 0)
+ {
+ const int64_t orgPos = m_filePos;
+ // try to seek one byte back
+ const bool seekPossible = (Seek(orgPos - 1, SEEK_SET) == (orgPos - 1));
+ // restore file position
+ if (Seek(orgPos, SEEK_SET) != orgPos)
+ return 0; // seeking is not possible
+
+ return seekPossible ? 1 : 0;
+ }
+ else
+ { // m_filePos == 0
+ // try to seek one byte forward
+ const bool seekPossible = (Seek(1, SEEK_SET) == 1);
+ // restore file position
+ if (Seek(0, SEEK_SET) != 0)
+ return 0; // seeking is not possible
+
+ if (seekPossible)
+ return 1;
+
+ if (GetLength() <= 0)
+ return -1; // size of file is zero or can be zero, can't test seeking
+ else
+ return 0; // size of file is 1 byte or more and seeking not possible
+ }
+ }
+
+ return -1;
+}
+
+
+bool CPosixFile::Delete(const CURL& url)
+{
+ const std::string filename(getFilename(url));
+ if (filename.empty())
+ return false;
+
+ if (unlink(filename.c_str()) == 0)
+ return true;
+
+ if (errno == EACCES || errno == EPERM)
+ CLog::LogF(LOGWARNING, "Can't access file \"{}\"", filename);
+
+ return false;
+}
+
+bool CPosixFile::Rename(const CURL& url, const CURL& urlnew)
+{
+ const std::string name(getFilename(url)), newName(getFilename(urlnew));
+ if (name.empty() || newName.empty())
+ return false;
+
+ if (name == newName)
+ return true;
+
+ if (rename(name.c_str(), newName.c_str()) == 0)
+ return true;
+
+ if (errno == EACCES || errno == EPERM)
+ CLog::LogF(LOGWARNING, "Can't access file \"{}\" for rename to \"{}\"", name, newName);
+
+ // rename across mount points - need to copy/delete
+ if (errno == EXDEV)
+ {
+ CLog::LogF(LOGDEBUG,
+ "Source file \"{}\" and target file \"{}\" are located on different filesystems, "
+ "copy&delete will be used instead of rename",
+ name, newName);
+ if (XFILE::CFile::Copy(name, newName))
+ {
+ if (XFILE::CFile::Delete(name))
+ return true;
+ else
+ XFILE::CFile::Delete(newName);
+ }
+ }
+
+ return false;
+}
+
+bool CPosixFile::Exists(const CURL& url)
+{
+ const std::string filename(getFilename(url));
+ if (filename.empty())
+ return false;
+
+ struct stat64 st;
+ return stat64(filename.c_str(), &st) == 0 && !S_ISDIR(st.st_mode);
+}
+
+int CPosixFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ assert(buffer != NULL);
+ const std::string filename(getFilename(url));
+ if (filename.empty() || !buffer)
+ return -1;
+
+// Use statx to get file creation date (btime) which isn't available with just stat. This fills the
+// buffer with the same data as the Windows implementation. Useful for the music library so that
+// tags can be updated without changing the date they were added to the library (as m/ctime does)
+
+#if defined(HAVE_STATX)
+ int dirfd = AT_FDCWD;
+ int flags = AT_STATX_SYNC_AS_STAT;
+ unsigned int mask = STATX_BASIC_STATS | STATX_BTIME;
+ struct statx stxbuf = {};
+ long ret = 0;
+ ret = statx(dirfd, filename.c_str(), flags, mask, &stxbuf);
+ if (ret == 0)
+ {
+ *buffer = {};
+ buffer->st_mtime = stxbuf.stx_mtime.tv_sec; // modification time
+ if (stxbuf.stx_btime.tv_sec != 0)
+ buffer->st_ctime = stxbuf.stx_btime.tv_sec; // birth (creation) time
+ else
+ buffer->st_ctime = stxbuf.stx_ctime.tv_sec; // change time (of metadata or file)
+ // fill everything else we might need (statx buffer is slightly different to stat buffer so
+ // can't just return the statx buffer) Note we might not need all this but lets fill it for
+ // completeness
+ buffer->st_atime = stxbuf.stx_atime.tv_sec;
+ buffer->st_size = stxbuf.stx_size;
+ buffer->st_blksize = stxbuf.stx_blksize;
+ buffer->st_blocks = stxbuf.stx_blocks;
+ buffer->st_ino = stxbuf.stx_ino;
+ buffer->st_nlink = stxbuf.stx_nlink;
+ buffer->st_uid = stxbuf.stx_uid;
+ buffer->st_gid = stxbuf.stx_gid;
+ buffer->st_mode = stxbuf.stx_mode;
+ buffer->st_rdev = makedev(stxbuf.stx_rdev_major, stxbuf.stx_rdev_minor);
+ buffer->st_dev = makedev(stxbuf.stx_dev_major, stxbuf.stx_dev_minor);
+ }
+ return ret;
+#else
+ return stat64(filename.c_str(), buffer);
+#endif
+}
+
+int CPosixFile::Stat(struct __stat64* buffer)
+{
+ assert(buffer != NULL);
+ if (m_fd < 0 || !buffer)
+ return -1;
+
+ return fstat64(m_fd, buffer);
+}
diff --git a/xbmc/platform/posix/filesystem/PosixFile.h b/xbmc/platform/posix/filesystem/PosixFile.h
new file mode 100644
index 0000000..aa5e0a0
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/PosixFile.h
@@ -0,0 +1,47 @@
+/*
+ * 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 "filesystem/IFile.h"
+
+namespace XFILE
+{
+
+ class CPosixFile : public IFile
+ {
+ public:
+ ~CPosixFile() override;
+
+ bool Open(const CURL& url) override;
+ bool OpenForWrite(const CURL& url, bool bOverWrite = false) override;
+ void Close() override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ ssize_t Write(const void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ int Truncate(int64_t size) override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+ void Flush() override;
+ int IoControl(EIoControl request, void* param) override;
+
+ bool Delete(const CURL& url) override;
+ bool Rename(const CURL& url, const CURL& urlnew) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ int Stat(struct __stat64* buffer) override;
+
+ protected:
+ int m_fd = -1;
+ int64_t m_filePos = -1;
+ int64_t m_lastDropPos = -1;
+ bool m_allowWrite = false;
+ };
+
+}
diff --git a/xbmc/platform/posix/filesystem/SMBDirectory.cpp b/xbmc/platform/posix/filesystem/SMBDirectory.cpp
new file mode 100644
index 0000000..e2305af
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBDirectory.cpp
@@ -0,0 +1,372 @@
+/*
+ * 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.
+ */
+
+/*
+* know bugs:
+* - when opening a server for the first time with ip address and the second time
+* with server name, access to the server is denied.
+* - when browsing entire network, user can't go back one step
+* share = smb://, user selects a workgroup, user selects a server.
+* doing ".." will go back to smb:// (entire network) and not to workgroup list.
+*
+* debugging is set to a max of 10 for release builds (see local.h)
+*/
+
+#include "SMBDirectory.h"
+
+#include "FileItem.h"
+#include "PasswordManager.h"
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include "platform/posix/filesystem/SMBWSDiscovery.h"
+
+#include <mutex>
+
+#include <libsmbclient.h>
+
+struct CachedDirEntry
+{
+ unsigned int type;
+ std::string name;
+};
+
+using namespace XFILE;
+
+CSMBDirectory::CSMBDirectory(void)
+{
+ smb.AddActiveConnection();
+}
+
+CSMBDirectory::~CSMBDirectory(void)
+{
+ smb.AddIdleConnection();
+}
+
+bool CSMBDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ // We accept smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
+
+ /* samba isn't thread safe with old interface, always lock */
+ std::unique_lock<CCriticalSection> lock(smb);
+
+ smb.Init();
+
+ //Separate roots for the authentication and the containing items to allow browsing to work correctly
+ std::string strRoot = url.Get();
+ std::string strAuth;
+
+ lock.unlock(); // OpenDir is locked
+
+ // if url provided does not having anything except smb protocol
+ // Do a WS-Discovery search to find possible smb servers to mimic smbv1 behaviour
+ if (strRoot == "smb://")
+ {
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return false;
+
+ auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (!settings)
+ return false;
+
+ // Check WS-Discovery daemon enabled, if not return as smb:// cant be handled further
+ if (settings->GetBool(CSettings::SETTING_SERVICES_WSDISCOVERY))
+ {
+ WSDiscovery::CWSDiscoveryPosix& WSInstance =
+ dynamic_cast<WSDiscovery::CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery());
+ return WSInstance.GetServerList(items);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ int fd = OpenDir(url, strAuth);
+ if (fd < 0)
+ return false;
+
+ URIUtils::AddSlashAtEnd(strRoot);
+ URIUtils::AddSlashAtEnd(strAuth);
+
+ std::string strFile;
+
+ // need to keep the samba lock for as short as possible.
+ // so we first cache all directory entries and then go over them again asking for stat
+ // "stat" is locked each time. that way the lock is freed between stat requests
+ std::vector<CachedDirEntry> vecEntries;
+ struct smbc_dirent* dirEnt;
+
+ lock.lock();
+ if (!smb.IsSmbValid())
+ return false;
+ while ((dirEnt = smbc_readdir(fd)))
+ {
+ CachedDirEntry aDir;
+ aDir.type = dirEnt->smbc_type;
+ aDir.name = dirEnt->name;
+ vecEntries.push_back(aDir);
+ }
+ smbc_closedir(fd);
+ lock.unlock();
+
+ for (size_t i=0; i<vecEntries.size(); i++)
+ {
+ CachedDirEntry aDir = vecEntries[i];
+
+ // We use UTF-8 internally, as does SMB
+ strFile = aDir.name;
+
+ if (!strFile.empty() && strFile != "." && strFile != ".."
+ && strFile != "lost+found"
+ && aDir.type != SMBC_PRINTER_SHARE && aDir.type != SMBC_IPC_SHARE)
+ {
+ int64_t iSize = 0;
+ bool bIsDir = true;
+ int64_t lTimeDate = 0;
+ bool hidden = false;
+
+ if(StringUtils::EndsWith(strFile, "$") && aDir.type == SMBC_FILE_SHARE )
+ continue;
+
+ if (StringUtils::StartsWith(strFile, "."))
+ hidden = true;
+
+ // only stat files that can give proper responses
+ if ( aDir.type == SMBC_FILE ||
+ aDir.type == SMBC_DIR )
+ {
+ // set this here to if the stat should fail
+ bIsDir = (aDir.type == SMBC_DIR);
+
+ struct stat info = {};
+ if ((m_flags & DIR_FLAG_NO_FILE_INFO)==0 && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambastatfiles)
+ {
+ // make sure we use the authenticated path which contains any default username
+ const std::string strFullName = strAuth + smb.URLEncode(strFile);
+
+ lock.lock();
+ if (!smb.IsSmbValid())
+ {
+ items.ClearItems();
+ return false;
+ }
+
+ if( smbc_stat(strFullName.c_str(), &info) == 0 )
+ {
+
+ char value[20];
+ // We poll for extended attributes which symbolizes bits but split up into a string. Where 0x02 is hidden and 0x12 is hidden directory.
+ // According to the libsmbclient.h it returns 0 on success and -1 on error.
+ // But before Samba 4.17.5 it seems to return the length of the returned value
+ // (which is 4), see https://bugzilla.samba.org/show_bug.cgi?id=14808.
+ // Checking for >= 0 should work both for the old erroneous and the correct behaviour.
+ if (smbc_getxattr(strFullName.c_str(), "system.dos_attr.mode", value, sizeof(value)) >= 0)
+ {
+ long longvalue = strtol(value, NULL, 16);
+ if (longvalue & SMBC_DOS_MODE_HIDDEN)
+ hidden = true;
+ }
+ else
+ CLog::Log(
+ LOGERROR,
+ "Getting extended attributes for the share: '{}'\nunix_err:'{:x}' error: '{}'",
+ CURL::GetRedacted(strFullName), errno, strerror(errno));
+
+ bIsDir = S_ISDIR(info.st_mode);
+ lTimeDate = info.st_mtime;
+ if(lTimeDate == 0) // if modification date is missing, use create date
+ lTimeDate = info.st_ctime;
+ iSize = info.st_size;
+ }
+ else
+ CLog::Log(LOGERROR, "{} - Failed to stat file {}", __FUNCTION__,
+ CURL::GetRedacted(strFullName));
+
+ lock.unlock();
+ }
+ }
+
+ KODI::TIME::FileTime fileTime, localTime;
+ KODI::TIME::TimeTToFileTime(lTimeDate, &fileTime);
+ KODI::TIME::FileTimeToLocalFileTime(&fileTime, &localTime);
+
+ if (bIsDir)
+ {
+ CFileItemPtr pItem(new CFileItem(strFile));
+ std::string path(strRoot);
+
+ // needed for network / workgroup browsing
+ // skip if root if we are given a server
+ if (aDir.type == SMBC_SERVER)
+ {
+ /* create url with same options, user, pass.. but no filename or host*/
+ CURL rooturl(strRoot);
+ rooturl.SetFileName("");
+ rooturl.SetHostName("");
+ path = smb.URLEncode(rooturl);
+ }
+ path = URIUtils::AddFileToFolder(path,aDir.name);
+ URIUtils::AddSlashAtEnd(path);
+ pItem->SetPath(path);
+ pItem->m_bIsFolder = true;
+ pItem->m_dateTime=localTime;
+ if (hidden)
+ pItem->SetProperty("file:hidden", true);
+ items.Add(pItem);
+ }
+ else
+ {
+ CFileItemPtr pItem(new CFileItem(strFile));
+ pItem->SetPath(strRoot + aDir.name);
+ pItem->m_bIsFolder = false;
+ pItem->m_dwSize = iSize;
+ pItem->m_dateTime=localTime;
+ if (hidden)
+ pItem->SetProperty("file:hidden", true);
+ items.Add(pItem);
+ }
+ }
+ }
+
+ return true;
+}
+
+int CSMBDirectory::Open(const CURL &url)
+{
+ smb.Init();
+ std::string strAuth;
+ return OpenDir(url, strAuth);
+}
+
+/// \brief Checks authentication against SAMBA share and prompts for username and password if needed
+/// \param strAuth The SMB style path
+/// \return SMB file descriptor
+int CSMBDirectory::OpenDir(const CURL& url, std::string& strAuth)
+{
+ int fd = -1;
+
+ /* make a writeable copy */
+ CURL urlIn = CSMB::GetResolvedUrl(url);
+
+ CPasswordManager::GetInstance().AuthenticateURL(urlIn);
+ strAuth = smb.URLEncode(urlIn);
+
+ // remove the / or \ at the end. the samba library does not strip them off
+ // don't do this for smb:// !!
+ std::string s = strAuth;
+ int len = s.length();
+ if (len > 1 && s.at(len - 2) != '/' &&
+ (s.at(len - 1) == '/' || s.at(len - 1) == '\\'))
+ {
+ s.erase(len - 1, 1);
+ }
+
+ CLog::LogFC(LOGDEBUG, LOGSAMBA, "Using authentication url {}", CURL::GetRedacted(s));
+
+ {
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return -1;
+ fd = smbc_opendir(s.c_str());
+ }
+
+ while (fd < 0) /* only to avoid goto in following code */
+ {
+ std::string cError;
+
+ if (errno == EACCES)
+ {
+ if (m_flags & DIR_FLAG_ALLOW_PROMPT)
+ RequireAuthentication(urlIn);
+ break;
+ }
+
+ if (errno == ENODEV || errno == ENOENT)
+ cError = StringUtils::Format(g_localizeStrings.Get(770), errno);
+ else
+ cError = strerror(errno);
+
+ if (m_flags & DIR_FLAG_ALLOW_PROMPT)
+ SetErrorDialog(257, cError.c_str());
+ break;
+ }
+
+ if (fd < 0)
+ {
+ // write error to logfile
+ CLog::Log(
+ LOGERROR,
+ "SMBDirectory->GetDirectory: Unable to open directory : '{}'\nunix_err:'{:x}' error : '{}'",
+ CURL::GetRedacted(strAuth), errno, strerror(errno));
+ }
+
+ return fd;
+}
+
+bool CSMBDirectory::Create(const CURL& url2)
+{
+ std::unique_lock<CCriticalSection> lock(smb);
+ smb.Init();
+
+ CURL url = CSMB::GetResolvedUrl(url2);
+ CPasswordManager::GetInstance().AuthenticateURL(url);
+ std::string strFileName = smb.URLEncode(url);
+
+ int result = smbc_mkdir(strFileName.c_str(), 0);
+ bool success = (result == 0 || EEXIST == errno);
+ if(!success)
+ CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno));
+
+ return success;
+}
+
+bool CSMBDirectory::Remove(const CURL& url2)
+{
+ std::unique_lock<CCriticalSection> lock(smb);
+ smb.Init();
+
+ CURL url = CSMB::GetResolvedUrl(url2);
+ CPasswordManager::GetInstance().AuthenticateURL(url);
+ std::string strFileName = smb.URLEncode(url);
+
+ int result = smbc_rmdir(strFileName.c_str());
+
+ if(result != 0 && errno != ENOENT)
+ {
+ CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+bool CSMBDirectory::Exists(const CURL& url2)
+{
+ std::unique_lock<CCriticalSection> lock(smb);
+ smb.Init();
+
+ CURL url = CSMB::GetResolvedUrl(url2);
+ CPasswordManager::GetInstance().AuthenticateURL(url);
+ std::string strFileName = smb.URLEncode(url);
+
+ struct stat info;
+ if (smbc_stat(strFileName.c_str(), &info) != 0)
+ return false;
+
+ return S_ISDIR(info.st_mode);
+}
+
diff --git a/xbmc/platform/posix/filesystem/SMBDirectory.h b/xbmc/platform/posix/filesystem/SMBDirectory.h
new file mode 100644
index 0000000..cef06d2
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBDirectory.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "MediaSource.h"
+#include "SMBFile.h"
+#include "filesystem/IDirectory.h"
+
+namespace XFILE
+{
+class CSMBDirectory : public IDirectory
+{
+public:
+ CSMBDirectory(void);
+ ~CSMBDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ONCE; }
+ bool Create(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+
+ int Open(const CURL &url);
+
+private:
+ int OpenDir(const CURL &url, std::string& strAuth);
+};
+}
diff --git a/xbmc/platform/posix/filesystem/SMBFile.cpp b/xbmc/platform/posix/filesystem/SMBFile.cpp
new file mode 100644
index 0000000..38a82c3
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBFile.cpp
@@ -0,0 +1,721 @@
+/*
+ * 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.
+ */
+
+// SMBFile.cpp: implementation of the CSMBFile class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "SMBFile.h"
+
+#include "PasswordManager.h"
+#include "SMBDirectory.h"
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "commons/Exception.h"
+#include "filesystem/SpecialProtocol.h"
+#include "network/DNSNameCache.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <inttypes.h>
+#include <mutex>
+
+#include <libsmbclient.h>
+
+using namespace XFILE;
+
+void xb_smbc_log(const char* msg)
+{
+ CLog::Log(LOGINFO, "{}{}", "smb: ", msg);
+}
+
+void xb_smbc_auth(const char *srv, const char *shr, char *wg, int wglen,
+ char *un, int unlen, char *pw, int pwlen)
+{
+}
+
+// WTF is this ?, we get the original server cache only
+// to set the server cache to this function which call the
+// original one anyway. Seems quite silly.
+smbc_get_cached_srv_fn orig_cache;
+SMBCSRV* xb_smbc_cache(SMBCCTX* c, const char* server, const char* share, const char* workgroup, const char* username)
+{
+ return orig_cache(c, server, share, workgroup, username);
+}
+
+bool CSMB::IsFirstInit = true;
+
+CSMB::CSMB()
+{
+ m_context = NULL;
+ m_OpenConnections = 0;
+ m_IdleTimeout = 0;
+}
+
+CSMB::~CSMB()
+{
+ Deinit();
+}
+
+void CSMB::Deinit()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ /* samba goes loco if deinited while it has some files opened */
+ if (m_context)
+ {
+ smbc_set_context(NULL);
+ smbc_free_context(m_context, 1);
+ m_context = NULL;
+ }
+}
+
+void CSMB::Init()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ if (!m_context)
+ {
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ // force libsmbclient to use our own smb.conf by overriding HOME
+ std::string truehome(getenv("HOME"));
+ setenv("HOME", CSpecialProtocol::TranslatePath("special://home").c_str(), 1);
+
+ // Create ~/.kodi/.smb/smb.conf. This file is used by libsmbclient.
+ // http://us1.samba.org/samba/docs/man/manpages-3/libsmbclient.7.html
+ // http://us1.samba.org/samba/docs/man/manpages-3/smb.conf.5.html
+ std::string smb_conf;
+ std::string home(getenv("HOME"));
+ URIUtils::RemoveSlashAtEnd(home);
+ smb_conf = home + "/.smb";
+ int result = mkdir(smb_conf.c_str(), 0755);
+ if (result == 0 || (errno == EEXIST && IsFirstInit))
+ {
+ smb_conf += "/smb.conf";
+ FILE* f = fopen(smb_conf.c_str(), "w");
+ if (f != NULL)
+ {
+ fprintf(f, "[global]\n");
+
+ fprintf(f, "\tlock directory = %s/.smb/\n", home.c_str());
+
+ // set minimum smbclient protocol version
+ if (settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL) > 0)
+ {
+ if (settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL) == 1)
+ fprintf(f, "\tclient min protocol = NT1\n");
+ else
+ fprintf(f, "\tclient min protocol = SMB%d\n", settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL));
+ }
+
+ // set maximum smbclient protocol version
+ if (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) > 0)
+ {
+ if (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) == 1)
+ fprintf(f, "\tclient max protocol = NT1\n");
+ else
+ fprintf(f, "\tclient max protocol = SMB%d\n", settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL));
+ }
+
+ // set legacy security options
+ if (settings->GetBool(CSettings::SETTING_SMB_LEGACYSECURITY) && (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) == 1))
+ {
+ fprintf(f, "\tclient NTLMv2 auth = no\n");
+ fprintf(f, "\tclient use spnego = no\n");
+ }
+
+ // set wins server if there's one. name resolve order defaults to 'lmhosts host wins bcast'.
+ // if no WINS server has been specified the wins method will be ignored.
+ if (settings->GetString(CSettings::SETTING_SMB_WINSSERVER).length() > 0 && !StringUtils::EqualsNoCase(settings->GetString(CSettings::SETTING_SMB_WINSSERVER), "0.0.0.0") )
+ {
+ fprintf(f, "\twins server = %s\n", settings->GetString(CSettings::SETTING_SMB_WINSSERVER).c_str());
+ fprintf(f, "\tname resolve order = bcast wins host\n");
+ }
+ else
+ fprintf(f, "\tname resolve order = bcast host\n");
+
+ // use user-configured charset. if no charset is specified,
+ // samba tries to use charset 850 but falls back to ASCII in case it is not available
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambadoscodepage.length() > 0)
+ fprintf(f, "\tdos charset = %s\n", CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambadoscodepage.c_str());
+
+ // include users configuration if available
+ fprintf(f, "\tinclude = %s/.smb/user.conf\n", home.c_str());
+
+ fclose(f);
+ }
+ }
+
+ // reads smb.conf so this MUST be after we create smb.conf
+ // multiple smbc_init calls are ignored by libsmbclient.
+ // note: this is important as it initializes the smb old
+ // interface compatibility. Samba 3.4.0 or higher has the new interface.
+ // note: we leak the following here once, not sure why yet.
+ // 48 bytes -> smb_xmalloc_array
+ // 32 bytes -> set_param_opt
+ // 16 bytes -> set_param_opt
+ smbc_init(xb_smbc_auth, 0);
+
+ // setup our context
+ m_context = smbc_new_context();
+
+ // restore HOME
+ setenv("HOME", truehome.c_str(), 1);
+
+#ifdef DEPRECATED_SMBC_INTERFACE
+ smbc_setDebug(m_context, CServiceBroker::GetLogging().CanLogComponent(LOGSAMBA) ? 10 : 0);
+ smbc_setFunctionAuthData(m_context, xb_smbc_auth);
+ orig_cache = smbc_getFunctionGetCachedServer(m_context);
+ smbc_setFunctionGetCachedServer(m_context, xb_smbc_cache);
+ smbc_setOptionOneSharePerServer(m_context, false);
+ smbc_setOptionBrowseMaxLmbCount(m_context, 0);
+ smbc_setTimeout(m_context, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambaclienttimeout * 1000);
+ // we do not need to strdup these, smbc_setXXX below will make their own copies
+ if (settings->GetString(CSettings::SETTING_SMB_WORKGROUP).length() > 0)
+ //! @bug libsmbclient < 4.9 isn't const correct
+ smbc_setWorkgroup(m_context, const_cast<char*>(settings->GetString(CSettings::SETTING_SMB_WORKGROUP).c_str()));
+ std::string guest = "guest";
+ //! @bug libsmbclient < 4.8 isn't const correct
+ smbc_setUser(m_context, const_cast<char*>(guest.c_str()));
+#else
+ m_context->debug = (CServiceBroker::GetLogging().CanLogComponent(LOGSAMBA) ? 10 : 0);
+ m_context->callbacks.auth_fn = xb_smbc_auth;
+ orig_cache = m_context->callbacks.get_cached_srv_fn;
+ m_context->callbacks.get_cached_srv_fn = xb_smbc_cache;
+ m_context->options.one_share_per_server = false;
+ m_context->options.browse_max_lmb_count = 0;
+ m_context->timeout = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambaclienttimeout * 1000;
+ // we need to strdup these, they will get free'd on smbc_free_context
+ if (settings->GetString(CSettings::SETTING_SMB_WORKGROUP).length() > 0)
+ m_context->workgroup = strdup(settings->GetString(CSettings::SETTING_SMB_WORKGROUP).c_str());
+ m_context->user = strdup("guest");
+#endif
+
+ // initialize samba and do some hacking into the settings
+ if (smbc_init_context(m_context))
+ {
+ // setup context using the smb old interface compatibility
+ SMBCCTX *old_context = smbc_set_context(m_context);
+ // free previous context or we leak it, this comes from smbc_init above.
+ // there is a bug in smbclient (old interface), if we init/set a context
+ // then set(null)/free it in DeInit above, the next smbc_set_context
+ // return the already freed previous context, free again and bang, crash.
+ // so we setup a stic bool to track the first init so we can free the
+ // context associated with the initial smbc_init.
+ if (old_context && IsFirstInit)
+ {
+ smbc_free_context(old_context, 1);
+ IsFirstInit = false;
+ }
+ }
+ else
+ {
+ smbc_free_context(m_context, 1);
+ m_context = NULL;
+ }
+ }
+ m_IdleTimeout = 180;
+}
+
+std::string CSMB::URLEncode(const CURL &url)
+{
+ /* due to smb wanting encoded urls we have to build it manually */
+
+ std::string flat = "smb://";
+
+ /* samba messes up of password is set but no username is set. don't know why yet */
+ /* probably the url parser that goes crazy */
+ if(url.GetUserName().length() > 0 /* || url.GetPassWord().length() > 0 */)
+ {
+ if(!url.GetDomain().empty())
+ {
+ flat += URLEncode(url.GetDomain());
+ flat += ";";
+ }
+ flat += URLEncode(url.GetUserName());
+ if(url.GetPassWord().length() > 0)
+ {
+ flat += ":";
+ flat += URLEncode(url.GetPassWord());
+ }
+ flat += "@";
+ }
+ flat += URLEncode(url.GetHostName());
+
+ if (url.HasPort())
+ {
+ flat += StringUtils::Format(":{}", url.GetPort());
+ }
+
+ /* okey sadly since a slash is an invalid name we have to tokenize */
+ std::vector<std::string> parts;
+ StringUtils::Tokenize(url.GetFileName(), parts, "/");
+ for (const std::string& it : parts)
+ {
+ flat += "/";
+ flat += URLEncode((it));
+ }
+
+ /* okey options should go here, thou current samba doesn't support any */
+
+ return flat;
+}
+
+std::string CSMB::URLEncode(const std::string &value)
+{
+ return CURL::Encode(value);
+}
+
+/* This is called from CApplication::ProcessSlow() and is used to tell if smbclient have been idle for too long */
+void CSMB::CheckIfIdle()
+{
+/* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
+ worst case scenario is that m_OpenConnections could read 0 and then changed to 1 if this happens it will enter the if which will lead to another check, which is locked. */
+ if (m_OpenConnections == 0)
+ { /* I've set the the maximum IDLE time to be 1 min and 30 sec. */
+ std::unique_lock<CCriticalSection> lock(*this);
+ if (m_OpenConnections == 0 /* check again - when locked */ && m_context != NULL)
+ {
+ if (m_IdleTimeout > 0)
+ {
+ m_IdleTimeout--;
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "Samba is idle. Closing the remaining connections");
+ smb.Deinit();
+ }
+ }
+ }
+}
+
+void CSMB::SetActivityTime()
+{
+ /* Since we get called every 500ms from ProcessSlow we limit the tick count to 180 */
+ /* That means we have 2 ticks per second which equals 180/2 == 90 seconds */
+ m_IdleTimeout = 180;
+}
+
+/* The following two function is used to keep track on how many Opened files/directories there are.
+ This makes the idle timer not count if a movie is paused for example */
+void CSMB::AddActiveConnection()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ m_OpenConnections++;
+}
+void CSMB::AddIdleConnection()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ m_OpenConnections--;
+ /* If we close a file we reset the idle timer so that we don't have any weird behaviours if a user
+ leaves the movie paused for a long while and then press stop */
+ m_IdleTimeout = 180;
+}
+
+CURL CSMB::GetResolvedUrl(const CURL& url)
+{
+ CURL tmpUrl(url);
+ std::string resolvedHostName;
+
+ if (CDNSNameCache::Lookup(tmpUrl.GetHostName(), resolvedHostName))
+ tmpUrl.SetHostName(resolvedHostName);
+
+ return tmpUrl;
+}
+
+CSMB smb;
+
+CSMBFile::CSMBFile()
+{
+ smb.Init();
+ m_fd = -1;
+ smb.AddActiveConnection();
+ m_allowRetry = true;
+}
+
+CSMBFile::~CSMBFile()
+{
+ Close();
+ smb.AddIdleConnection();
+}
+
+int64_t CSMBFile::GetPosition()
+{
+ if (m_fd == -1)
+ return -1;
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return -1;
+ return smbc_lseek(m_fd, 0, SEEK_CUR);
+}
+
+int64_t CSMBFile::GetLength()
+{
+ if (m_fd == -1)
+ return -1;
+ return m_fileSize;
+}
+
+bool CSMBFile::Open(const CURL& url)
+{
+ Close();
+
+ // we can't open files like smb://file.f or smb://server/file.f
+ // if a file matches the if below return false, it can't exist on a samba share.
+ if (!IsValidFile(url.GetFileName()))
+ {
+ CLog::Log(LOGINFO, "SMBFile->Open: Bad URL : '{}'", url.GetRedacted());
+ return false;
+ }
+ m_url = url;
+
+ // opening a file to another computer share will create a new session
+ // when opening smb://server xbms will try to find folder.jpg in all shares
+ // listed, which will create lot's of open sessions.
+
+ std::string strFileName;
+ m_fd = OpenFile(url, strFileName);
+
+ CLog::Log(LOGDEBUG, "CSMBFile::Open - opened {}, fd={}", url.GetRedacted(), m_fd);
+ if (m_fd == -1)
+ {
+ // write error to logfile
+ CLog::Log(LOGINFO, "SMBFile->Open: Unable to open file : '{}'\nunix_err:'{:x}' error : '{}'",
+ CURL::GetRedacted(strFileName), errno, strerror(errno));
+ return false;
+ }
+
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return false;
+ struct stat tmpBuffer;
+ if (smbc_stat(strFileName.c_str(), &tmpBuffer) < 0)
+ {
+ smbc_close(m_fd);
+ m_fd = -1;
+ return false;
+ }
+
+ m_fileSize = tmpBuffer.st_size;
+
+ int64_t ret = smbc_lseek(m_fd, 0, SEEK_SET);
+ if ( ret < 0 )
+ {
+ smbc_close(m_fd);
+ m_fd = -1;
+ return false;
+ }
+ // We've successfully opened the file!
+ return true;
+}
+
+
+/// \brief Checks authentication against SAMBA share. Reads password cache created in CSMBDirectory::OpenDir().
+/// \param strAuth The SMB style path
+/// \return SMB file descriptor
+/*
+int CSMBFile::OpenFile(std::string& strAuth)
+{
+ int fd = -1;
+
+ std::string strPath = g_passwordManager.GetSMBAuthFilename(strAuth);
+
+ fd = smbc_open(strPath.c_str(), O_RDONLY, 0);
+ //! @todo Run a loop here that prompts for our username/password as appropriate?
+ //! We have the ability to run a file (eg from a button action) without browsing to
+ //! the directory first. In the case of a password protected share that we do
+ //! not have the authentication information for, the above smbc_open() will have
+ //! returned negative, and the file will not be opened. While this is not a particular
+ //! likely scenario, we might want to implement prompting for the password in this case.
+ //! The code from SMBDirectory can be used for this.
+ if(fd >= 0)
+ strAuth = strPath;
+
+ return fd;
+}
+*/
+
+int CSMBFile::OpenFile(const CURL &url, std::string& strAuth)
+{
+ int fd = -1;
+ smb.Init();
+
+ strAuth = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
+ std::string strPath = strAuth;
+
+ {
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (smb.IsSmbValid())
+ fd = smbc_open(strPath.c_str(), O_RDONLY, 0);
+ }
+
+ if (fd >= 0)
+ strAuth = strPath;
+
+ return fd;
+}
+
+bool CSMBFile::Exists(const CURL& url)
+{
+ // we can't open files like smb://file.f or smb://server/file.f
+ // if a file matches the if below return false, it can't exist on a samba share.
+ if (!IsValidFile(url.GetFileName())) return false;
+
+ smb.Init();
+ std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
+
+ struct stat info;
+
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return false;
+ int iResult = smbc_stat(strFileName.c_str(), &info);
+
+ if (iResult < 0) return false;
+ return true;
+}
+
+int CSMBFile::Stat(struct __stat64* buffer)
+{
+ if (m_fd == -1)
+ return -1;
+
+ struct stat tmpBuffer = {};
+
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return -1;
+ int iResult = smbc_fstat(m_fd, &tmpBuffer);
+ CUtil::StatToStat64(buffer, &tmpBuffer);
+ return iResult;
+}
+
+int CSMBFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ smb.Init();
+ std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
+ std::unique_lock<CCriticalSection> lock(smb);
+
+ if (!smb.IsSmbValid())
+ return -1;
+ struct stat tmpBuffer = {};
+ int iResult = smbc_stat(strFileName.c_str(), &tmpBuffer);
+ CUtil::StatToStat64(buffer, &tmpBuffer);
+ return iResult;
+}
+
+int CSMBFile::Truncate(int64_t size)
+{
+ if (m_fd == -1) return 0;
+ /*
+ * This would force us to be dependant on SMBv3.2 which is GPLv3
+ * This is only used by the TagLib writers, which are not currently in use
+ * So log and warn until we implement TagLib writing & can re-implement this better.
+ std::unique_lock<CCriticalSection> lock(smb); // Init not called since it has to be "inited" by now
+
+#if defined(TARGET_ANDROID)
+ int iResult = 0;
+#else
+ int iResult = smbc_ftruncate(m_fd, size);
+#endif
+*/
+ CLog::Log(LOGWARNING, "{} - Warning(smbc_ftruncate called and not implemented)", __FUNCTION__);
+ return 0;
+}
+
+ssize_t CSMBFile::Read(void *lpBuf, size_t uiBufSize)
+{
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ if (m_fd == -1)
+ return -1;
+
+ // Some external libs (libass) use test read with zero size and
+ // null buffer pointer to check whether file is readable, but
+ // libsmbclient always return "-1" if called with null buffer
+ // regardless of buffer size.
+ // To overcome this, force return "0" in that case.
+ if (uiBufSize == 0 && lpBuf == NULL)
+ return 0;
+
+ std::unique_lock<CCriticalSection> lock(
+ smb); // Init not called since it has to be "inited" by now
+ if (!smb.IsSmbValid())
+ return -1;
+ smb.SetActivityTime();
+
+ ssize_t bytesRead = smbc_read(m_fd, lpBuf, (int)uiBufSize);
+
+ if (m_allowRetry && bytesRead < 0 && errno == EINVAL )
+ {
+ CLog::Log(LOGERROR, "{} - Error( {}, {}, {} ) - Retrying", __FUNCTION__, bytesRead, errno,
+ strerror(errno));
+ bytesRead = smbc_read(m_fd, lpBuf, (int)uiBufSize);
+ }
+
+ if ( bytesRead < 0 )
+ CLog::Log(LOGERROR, "{} - Error( {}, {}, {} )", __FUNCTION__, bytesRead, errno,
+ strerror(errno));
+
+ return bytesRead;
+}
+
+int64_t CSMBFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ if (m_fd == -1) return -1;
+
+ std::unique_lock<CCriticalSection> lock(
+ smb); // Init not called since it has to be "inited" by now
+ if (!smb.IsSmbValid())
+ return -1;
+ smb.SetActivityTime();
+ int64_t pos = smbc_lseek(m_fd, iFilePosition, iWhence);
+
+ if ( pos < 0 )
+ {
+ CLog::Log(LOGERROR, "{} - Error( {}, {}, {} )", __FUNCTION__, pos, errno, strerror(errno));
+ return -1;
+ }
+
+ return pos;
+}
+
+void CSMBFile::Close()
+{
+ if (m_fd != -1)
+ {
+ CLog::Log(LOGDEBUG, "CSMBFile::Close closing fd {}", m_fd);
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return;
+ smbc_close(m_fd);
+ }
+ m_fd = -1;
+}
+
+ssize_t CSMBFile::Write(const void* lpBuf, size_t uiBufSize)
+{
+ if (m_fd == -1) return -1;
+
+ // lpBuf can be safely casted to void* since xbmc_write will only read from it.
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return -1;
+
+ return smbc_write(m_fd, lpBuf, uiBufSize);
+}
+
+bool CSMBFile::Delete(const CURL& url)
+{
+ smb.Init();
+ std::string strFile = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
+
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return false;
+
+ int result = smbc_unlink(strFile.c_str());
+
+ if(result != 0)
+ CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno));
+
+ return (result == 0);
+}
+
+bool CSMBFile::Rename(const CURL& url, const CURL& urlnew)
+{
+ smb.Init();
+ std::string strFile = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
+ std::string strFileNew = GetAuthenticatedPath(CSMB::GetResolvedUrl(urlnew));
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return false;
+
+ int result = smbc_rename(strFile.c_str(), strFileNew.c_str());
+
+ if(result != 0)
+ CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno));
+
+ return (result == 0);
+}
+
+bool CSMBFile::OpenForWrite(const CURL& url, bool bOverWrite)
+{
+ m_fileSize = 0;
+
+ Close();
+
+ // we can't open files like smb://file.f or smb://server/file.f
+ // if a file matches the if below return false, it can't exist on a samba share.
+ if (!IsValidFile(url.GetFileName())) return false;
+
+ std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
+ std::unique_lock<CCriticalSection> lock(smb);
+ if (!smb.IsSmbValid())
+ return false;
+
+ if (bOverWrite)
+ {
+ CLog::Log(LOGWARNING, "SMBFile::OpenForWrite() called with overwriting enabled! - {}",
+ CURL::GetRedacted(strFileName));
+ m_fd = smbc_creat(strFileName.c_str(), 0);
+ }
+ else
+ {
+ m_fd = smbc_open(strFileName.c_str(), O_RDWR, 0);
+ }
+
+ if (m_fd == -1)
+ {
+ // write error to logfile
+ CLog::Log(LOGERROR, "SMBFile->Open: Unable to open file : '{}'\nunix_err:'{:x}' error : '{}'",
+ CURL::GetRedacted(strFileName), errno, strerror(errno));
+ return false;
+ }
+
+ // We've successfully opened the file!
+ return true;
+}
+
+bool CSMBFile::IsValidFile(const std::string& strFileName)
+{
+ if (strFileName.find('/') == std::string::npos || /* doesn't have sharename */
+ StringUtils::EndsWith(strFileName, "/.") || /* not current folder */
+ StringUtils::EndsWith(strFileName, "/..")) /* not parent folder */
+ return false;
+ return true;
+}
+
+std::string CSMBFile::GetAuthenticatedPath(const CURL &url)
+{
+ CURL authURL(CSMB::GetResolvedUrl(url));
+ CPasswordManager::GetInstance().AuthenticateURL(authURL);
+ return smb.URLEncode(authURL);
+}
+
+int CSMBFile::IoControl(EIoControl request, void* param)
+{
+ if (request == IOCTRL_SEEK_POSSIBLE)
+ return 1;
+
+ if (request == IOCTRL_SET_RETRY)
+ {
+ m_allowRetry = *(bool*) param;
+ return 0;
+ }
+
+ return -1;
+}
+
diff --git a/xbmc/platform/posix/filesystem/SMBFile.h b/xbmc/platform/posix/filesystem/SMBFile.h
new file mode 100644
index 0000000..8cc960e
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBFile.h
@@ -0,0 +1,93 @@
+/*
+ * 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
+
+// SMBFile.h: interface for the CSMBFile class.
+
+//
+
+//////////////////////////////////////////////////////////////////////
+
+
+#include "URL.h"
+#include "filesystem/IFile.h"
+#include "threads/CriticalSection.h"
+
+#define NT_STATUS_CONNECTION_REFUSED long(0xC0000000 | 0x0236)
+#define NT_STATUS_INVALID_HANDLE long(0xC0000000 | 0x0008)
+#define NT_STATUS_ACCESS_DENIED long(0xC0000000 | 0x0022)
+#define NT_STATUS_OBJECT_NAME_NOT_FOUND long(0xC0000000 | 0x0034)
+#define NT_STATUS_INVALID_COMPUTER_NAME long(0xC0000000 | 0x0122)
+
+struct _SMBCCTX;
+typedef _SMBCCTX SMBCCTX;
+
+class CSMB : public CCriticalSection
+{
+public:
+ CSMB();
+ ~CSMB();
+ void Init();
+ void Deinit();
+ /* Makes sense to be called after acquiring the lock */
+ bool IsSmbValid() const { return m_context != nullptr; }
+ void CheckIfIdle();
+ void SetActivityTime();
+ void AddActiveConnection();
+ void AddIdleConnection();
+ std::string URLEncode(const std::string &value);
+ std::string URLEncode(const CURL &url);
+
+ DWORD ConvertUnixToNT(int error);
+ static CURL GetResolvedUrl(const CURL& url);
+
+private:
+ SMBCCTX *m_context;
+ int m_OpenConnections;
+ unsigned int m_IdleTimeout;
+ static bool IsFirstInit;
+};
+
+extern CSMB smb;
+
+namespace XFILE
+{
+class CSMBFile : public IFile
+{
+public:
+ CSMBFile();
+ int OpenFile(const CURL &url, std::string& strAuth);
+ ~CSMBFile() override;
+ void Close() override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ int Stat(struct __stat64* buffer) override;
+ int Truncate(int64_t size) override;
+ int64_t GetLength() override;
+ int64_t GetPosition() override;
+ ssize_t Write(const void* lpBuf, size_t uiBufSize) override;
+
+ bool OpenForWrite(const CURL& url, bool bOverWrite = false) override;
+ bool Delete(const CURL& url) override;
+ bool Rename(const CURL& url, const CURL& urlnew) override;
+ int GetChunkSize() override { return 64*1024; }
+ int IoControl(EIoControl request, void* param) override;
+
+protected:
+ CURL m_url;
+ bool IsValidFile(const std::string& strFileName);
+ std::string GetAuthenticatedPath(const CURL &url);
+ int64_t m_fileSize;
+ int m_fd;
+ bool m_allowRetry;
+};
+}
diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp b/xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp
new file mode 100644
index 0000000..8bab0aa
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 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 "SMBWSDiscovery.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "network/IWSDiscovery.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include "platform/posix/filesystem/SMBWSDiscoveryListener.h"
+
+#include <algorithm>
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace std::chrono;
+using namespace WSDiscovery;
+
+namespace WSDiscovery
+{
+std::unique_ptr<IWSDiscovery> IWSDiscovery::GetInstance()
+{
+ return std::make_unique<WSDiscovery::CWSDiscoveryPosix>();
+}
+
+std::atomic<bool> CWSDiscoveryPosix::m_isInitialized{false};
+
+CWSDiscoveryPosix::CWSDiscoveryPosix()
+{
+ // Set our wsd_instance ID to seconds since epoch
+ auto epochduration = system_clock::now().time_since_epoch();
+ wsd_instance_id = epochduration.count() * system_clock::period::num / system_clock::period::den;
+
+ m_WSDListenerUDP = std::make_unique<CWSDiscoveryListenerUDP>();
+ m_isInitialized = true;
+}
+
+CWSDiscoveryPosix::~CWSDiscoveryPosix()
+{
+ StopServices();
+}
+
+bool CWSDiscoveryPosix::StopServices()
+{
+ m_WSDListenerUDP->Stop();
+
+ return true;
+}
+
+bool CWSDiscoveryPosix::StartServices()
+{
+ m_WSDListenerUDP->Start();
+
+ return true;
+}
+
+bool CWSDiscoveryPosix::IsRunning()
+{
+ return m_WSDListenerUDP->IsRunning();
+}
+
+bool CWSDiscoveryPosix::GetServerList(CFileItemList& items)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critWSD);
+
+ for (const auto& item : m_vecWSDInfo)
+ {
+ auto found = item.computer.find('/');
+ std::string host;
+ if (found != std::string::npos)
+ host = item.computer.substr(0, found);
+ else
+ {
+ if (item.xaddrs_host.empty())
+ continue;
+ host = item.xaddrs_host;
+ }
+
+ CFileItemPtr pItem = std::make_shared<CFileItem>(host);
+ pItem->SetPath("smb://" + host + '/');
+ pItem->m_bIsFolder = true;
+ items.Add(pItem);
+ }
+ }
+ return true;
+}
+
+bool CWSDiscoveryPosix::GetCached(const std::string& strHostName, std::string& strIpAddress)
+{
+ const std::string match = strHostName + "/";
+
+ std::unique_lock<CCriticalSection> lock(m_critWSD);
+ for (const auto& item : m_vecWSDInfo)
+ {
+ if (!item.computer.empty() && StringUtils::StartsWithNoCase(item.computer, match))
+ {
+ strIpAddress = item.xaddrs_host;
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryPosix::Lookup - {} -> {}", strHostName,
+ strIpAddress);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CWSDiscoveryPosix::SetItems(std::vector<wsd_req_info> entries)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critWSD);
+ m_vecWSDInfo = std::move(entries);
+ }
+}
+} // namespace WSDiscovery
diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscovery.h b/xbmc/platform/posix/filesystem/SMBWSDiscovery.h
new file mode 100644
index 0000000..139ff11
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBWSDiscovery.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 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 "network/IWSDiscovery.h"
+#include "threads/CriticalSection.h"
+#include "threads/SingleLock.h"
+
+#include "platform/posix/filesystem/SMBWSDiscoveryListener.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CFileItemList;
+
+namespace WSDiscovery
+{
+class CWSDiscoveryListenerUDP;
+}
+
+namespace WSDiscovery
+{
+struct wsd_req_info
+{
+ std::string action; // ToDo: Action probably isnt required to be stored
+ std::string msgid;
+ std::string types; // ToDo: Types may not be needed.
+ std::string address;
+ std::string xaddrs;
+ std::string xaddrs_host;
+ std::string computer;
+
+ bool operator==(const wsd_req_info& item) const
+ {
+ return ((item.xaddrs == xaddrs) && (item.address == address));
+ }
+};
+
+class CWSDiscoveryPosix : public WSDiscovery::IWSDiscovery
+{
+public:
+ CWSDiscoveryPosix();
+
+ // IWSDiscovery interface methods
+ ~CWSDiscoveryPosix() override;
+ bool StartServices() override;
+ bool StopServices() override;
+ bool IsRunning() override;
+
+ /*
+ * Get List of smb servers found by WSD
+ * out (CFileItemList&) List of fileitems populated with smb addresses
+ * return (bool) true if >0 WSD addresses found
+ */
+ bool GetServerList(CFileItemList& items);
+
+ long long GetInstanceID() const { return wsd_instance_id; }
+
+ /*
+ * Set List of WSD info request
+ * out (vector<wsd_req_info>) vector of WSD responses received
+ * return void
+ */
+ void SetItems(std::vector<WSDiscovery::wsd_req_info> entries);
+
+ /*
+ * Lookup host name in collected ws-discovery data
+ * in (const std::string&) Host name
+ * out (std::string&) IP address if found
+ * return (bool) true if found
+ */
+ bool GetCached(const std::string& strHostName, std::string& strIpAddress);
+
+ static bool IsInitialized() { return m_isInitialized; }
+
+private:
+ CCriticalSection m_critWSD;
+
+ /*
+ * As per docs - Pg32 - http://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf
+ * MUST be incremented by >= 1 each time the service has gone down, lost state,
+ * and came back up again. SHOULD NOT be incremented otherwise. Means to set
+ * this value include, but are not limited to:
+ * • A counter that is incremented on each 'cold' boot
+ * • The boot time of the service, expressed as seconds elapsed since midnight
+ * January 1, 1970
+ *
+ * Our implementation will only set this on creation of the class
+ * We arent providing services to clients, so this should be ok.
+ */
+ long long wsd_instance_id;
+
+ // WSD UDP daemon
+ std::unique_ptr<WSDiscovery::CWSDiscoveryListenerUDP> m_WSDListenerUDP;
+
+ std::vector<WSDiscovery::wsd_req_info> m_vecWSDInfo;
+
+ static std::atomic<bool> m_isInitialized;
+};
+} // namespace WSDiscovery
diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp
new file mode 100644
index 0000000..d42c19b
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 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 "SMBWSDiscoveryListener.h"
+
+#include "ServiceBroker.h"
+#include "filesystem/CurlFile.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include "platform/posix/filesystem/SMBWSDiscovery.h"
+
+#include <array>
+#include <chrono>
+#include <mutex>
+#include <stdio.h>
+#include <string>
+#include <utility>
+
+#include <arpa/inet.h>
+#include <fmt/format.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+using namespace WSDiscovery;
+
+namespace WSDiscovery
+{
+
+// Templates for SOAPXML messages for WS-Discovery messages
+static const std::string soap_msg_templ =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<soap:Envelope "
+ "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" "
+ "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" "
+ "xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" "
+ "xmlns:wsx=\"http://schemas.xmlsoap.org/ws/2004/09/mex\" "
+ "xmlns:wsdp=\"http://schemas.xmlsoap.org/ws/2006/02/devprof\" "
+ "xmlns:un0=\"http://schemas.microsoft.com/windows/pnpx/2005/10\" "
+ "xmlns:pub=\"http://schemas.microsoft.com/windows/pub/2005/07\">\n"
+ "<soap:Header>\n"
+ "<wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>\n"
+ "<wsa:Action>{}</wsa:Action>\n"
+ "<wsa:MessageID>urn:uuid:{}</wsa:MessageID>\n"
+ "<wsd:AppSequence InstanceId=\"{}\" MessageNumber=\"{}\" />\n"
+ "{}"
+ "</soap:Header>\n"
+ "{}"
+ "</soap:Envelope>\n";
+
+static const std::string hello_body = "<soap:Body>\n"
+ "<wsd:Hello>\n"
+ "<wsa:EndpointReference>\n"
+ "<wsa:Address>urn:uuid:{}</wsa:Address>\n"
+ "</wsa:EndpointReference>\n"
+ "<wsd:Types>wsdp:Device pub:Computer</wsd:Types>\n"
+ "<wsd:MetadataVersion>1</wsd:MetadataVersion>\n"
+ "</wsd:Hello>\n"
+ "</soap:Body>\n";
+
+static const std::string bye_body = "<soap:Body>\n"
+ "<wsd:Bye>\n"
+ "<wsa:EndpointReference>\n"
+ "<wsa:Address>urn:uuid:{}</wsa:Address>\n"
+ "</wsa:EndpointReference>\n"
+ "<wsd:Types>wsdp:Device pub:Computer</wsd:Types>\n"
+ "<wsd:MetadataVersion>2</wsd:MetadataVersion>\n"
+ "</wsd:Bye>\n"
+ "</soap:Body>\n";
+
+static const std::string probe_body = "<soap:Body>\n"
+ "<wsd:Probe>\n"
+ "<wsd:Types>wsdp:Device</wsd:Types>\n"
+ "</wsd:Probe>\n"
+ "</soap:Body>\n";
+
+static const std::string resolve_body = "<soap:Body>\n"
+ "<wsd:Resolve>\n"
+ "<wsa:EndpointReference>\n"
+ "<wsa:Address>"
+ "{}"
+ "</wsa:Address>\n"
+ "</wsa:EndpointReference>\n"
+ "</wsd:Resolve>\n"
+ "</soap:Body>\n";
+
+// These are the only actions we concern ourselves with for our WS-D implementation
+static const std::string WSD_ACT_HELLO = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Hello";
+static const std::string WSD_ACT_BYE = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Bye";
+static const std::string WSD_ACT_PROBEMATCH =
+ "http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches";
+static const std::string WSD_ACT_PROBE = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
+static const std::string WSD_ACT_RESOLVE =
+ "http://schemas.xmlsoap.org/ws/2005/04/discovery/Resolve";
+static const std::string WSD_ACT_RESOLVEMATCHES =
+ "http://schemas.xmlsoap.org/ws/2005/04/discovery/ResolveMatches";
+
+
+// These are xml start/finish tags we need info from
+static const std::array<std::pair<std::string, std::string>, 2> action_tag{
+ {{"<wsa:Action>", "</wsa:Action>"},
+ {"<wsa:Action SOAP-ENV:mustUnderstand=\"true\">", "</wsa:Action>"}}};
+
+static const std::array<std::pair<std::string, std::string>, 2> msgid_tag{
+ {{"<wsa:MessageID>", "</wsa:MessageID>"},
+ {"<wsa:MessageID SOAP-ENV:mustUnderstand=\"true\">", "</wsa:MessageID>"}}};
+
+static const std::array<std::pair<std::string, std::string>, 1> xaddrs_tag{
+ {{"<wsd:XAddrs>", "</wsd:XAddrs>"}}};
+
+static const std::array<std::pair<std::string, std::string>, 1> address_tag{
+ {{"<wsa:Address>", "</wsa:Address>"}}};
+
+static const std::array<std::pair<std::string, std::string>, 1> types_tag{
+ {{"<wsd:Types>", "</wsd:Types>"}}};
+
+static const std::string get_msg =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<soap:Envelope "
+ "xmlns:pnpx=\"http://schemas.microsoft.com/windows/pnpx/2005/10\" "
+ "xmlns:pub=\"http://schemas.microsoft.com/windows/pub/2005/07\" "
+ "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" "
+ "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" "
+ "xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" "
+ "xmlns:wsdp=\"http://schemas.xmlsoap.org/ws/2006/02/devprof\" "
+ "xmlns:wsx=\"http://schemas.xmlsoap.org/ws/2004/09/mex\"> "
+ "<soap:Header> "
+ "<wsa:To>{}</wsa:To> "
+ "<wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</wsa:Action> "
+ "<wsa:MessageID>urn:uuid:{}</wsa:MessageID> "
+ "<wsa:ReplyTo> "
+ "<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address> "
+ "</wsa:ReplyTo> "
+ "<wsa:From> "
+ "<wsa:Address>urn:uuid:{}</wsa:Address> "
+ "</wsa:From> "
+ "</soap:Header> "
+ "<soap:Body /> "
+ "</soap:Envelope>";
+
+static const std::array<std::pair<std::string, std::string>, 1> computer_tag{
+ {{"<pub:Computer>", "</pub:Computer>"}}};
+
+CWSDiscoveryListenerUDP::CWSDiscoveryListenerUDP()
+ : CThread("WSDiscoveryListenerUDP"), wsd_instance_address(StringUtils::CreateUUID())
+{
+}
+
+CWSDiscoveryListenerUDP::~CWSDiscoveryListenerUDP()
+{
+}
+
+void CWSDiscoveryListenerUDP::Stop()
+{
+ StopThread(true);
+ CLog::Log(LOGINFO, "CWSDiscoveryListenerUDP::Stop - Stopped");
+}
+
+void CWSDiscoveryListenerUDP::Start()
+{
+ if (IsRunning())
+ return;
+
+ // Clear existing data. We dont know how long service has been down, so we may not
+ // have correct services that may have sent BYE actions
+ m_vecWSDInfo.clear();
+ CWSDiscoveryPosix& WSInstance =
+ dynamic_cast<CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery());
+ WSInstance.SetItems(m_vecWSDInfo);
+
+ // Create thread and set low priority
+ Create();
+ SetPriority(ThreadPriority::LOWEST);
+ CLog::Log(LOGINFO, "CWSDiscoveryListenerUDP::Start - Started");
+}
+
+void CWSDiscoveryListenerUDP::Process()
+{
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::Process - Socket Creation failed");
+ return;
+ }
+
+ // set socket reuse
+ int enable = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable)) < 0)
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::Process - Reuse Option failed");
+ Cleanup(true);
+ return;
+ }
+
+ // set up destination address
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(wsdUDP);
+
+ // bind to receive address
+ if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::Process - Bind failed");
+ Cleanup(true);
+ return;
+ }
+
+ // use setsockopt() to request join a multicast group on all interfaces
+ // maybe use firstconnected?
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = inet_addr(WDSIPv4MultiGroup);
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)) < 0)
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::Process - Multicast Option failed");
+ Cleanup(true);
+ return;
+ }
+
+ // Disable receiving broadcast messages on loopback
+ // So we dont receive messages we send.
+ int disable = 0;
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&disable, sizeof(disable)) < 0)
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::Process - Loopback Disable Option failed");
+ Cleanup(true);
+ return;
+ }
+
+ std::string bufferoutput;
+ fd_set rset;
+ int nready;
+ struct timeval timeout;
+
+ FD_ZERO(&rset);
+
+ // Send HELLO to the world
+ AddCommand(WSD_ACT_HELLO);
+ DispatchCommand();
+
+ AddCommand(WSD_ACT_PROBE);
+ DispatchCommand();
+
+ while (!m_bStop)
+ {
+ FD_SET(fd, &rset);
+ timeout.tv_sec = 3;
+ timeout.tv_usec = 0;
+ nready = select((fd + 1), &rset, NULL, NULL, &timeout);
+
+ if (nready < 0)
+ break;
+
+ if (m_bStop)
+ break;
+
+ // if udp socket is readable receive the message.
+ if (FD_ISSET(fd, &rset))
+ {
+ bufferoutput = "";
+ char msgbuf[UDPBUFFSIZE];
+ socklen_t addrlen = sizeof(addr);
+ int nbytes = recvfrom(fd, msgbuf, UDPBUFFSIZE, 0, (struct sockaddr*)&addr, &addrlen);
+ msgbuf[nbytes] = '\0';
+ // turn msgbuf into std::string
+ bufferoutput.append(msgbuf, nbytes);
+
+ ParseBuffer(bufferoutput);
+ }
+ // Action any commands queued
+ while (DispatchCommand())
+ {
+ }
+ }
+
+ // Be a nice citizen and send BYE to the world
+ AddCommand(WSD_ACT_BYE);
+ DispatchCommand();
+ Cleanup(false);
+ return;
+}
+
+void CWSDiscoveryListenerUDP::Cleanup(bool aborted)
+{
+ close(fd);
+
+ if (aborted)
+ {
+ // Thread is stopping due to a failure state
+ // Update service setting to be disabled
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return;
+
+ auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (!settings)
+ return;
+
+ if (settings->GetBool(CSettings::SETTING_SERVICES_WSDISCOVERY))
+ {
+ settings->SetBool(CSettings::SETTING_SERVICES_WSDISCOVERY, false);
+ settings->Save();
+ }
+ }
+}
+
+bool CWSDiscoveryListenerUDP::DispatchCommand()
+{
+ Command sendCommand;
+ {
+ std::unique_lock<CCriticalSection> lock(crit_commandqueue);
+ if (m_commandbuffer.size() <= 0)
+ return false;
+
+ auto it = m_commandbuffer.begin();
+ sendCommand = *it;
+ m_commandbuffer.erase(it);
+ }
+
+ int ret;
+
+ // As its udp, send multiple messages due to unreliability
+ // Windows seems to send 4-6 times for reference
+ for (int i = 0; i < retries; i++)
+ {
+ do
+ {
+ ret = sendto(fd, sendCommand.commandMsg.c_str(), sendCommand.commandMsg.size(), 0,
+ (struct sockaddr*)&sendCommand.address, sizeof(sendCommand.address));
+ } while (ret == -1 && !m_bStop);
+ std::chrono::seconds sec(1);
+ CThread::Sleep(sec);
+ }
+
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::DispatchCommand - Command sent");
+
+ return true;
+}
+
+void CWSDiscoveryListenerUDP::AddCommand(const std::string& message,
+ const std::string& extraparameter /* = "" */)
+{
+
+ std::unique_lock<CCriticalSection> lock(crit_commandqueue);
+
+ std::string msg{};
+
+ if (!buildSoapMessage(message, msg, extraparameter))
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::AddCommand - Invalid Soap Message");
+ return;
+ }
+
+ // ToDo: IPv6
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(wsdUDP);
+
+ addr.sin_addr.s_addr = inet_addr(WDSIPv4MultiGroup);
+ memset(&addr.sin_zero, 0, sizeof(addr.sin_zero));
+
+ Command newCommand{{addr}, {msg}};
+
+ m_commandbuffer.push_back(newCommand);
+}
+
+void CWSDiscoveryListenerUDP::ParseBuffer(const std::string& buffer)
+{
+ wsd_req_info info;
+
+ // MUST have an action tag
+ std::string action = wsd_tag_find(buffer, action_tag);
+ if (action.empty())
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::ParseBuffer - No Action tag found");
+ return;
+ }
+
+ info.action = action;
+
+ // Only handle actions we need when received
+ if (!((action == WSD_ACT_HELLO) || (action == WSD_ACT_BYE) ||
+ (action == WSD_ACT_RESOLVEMATCHES) || (action == WSD_ACT_PROBEMATCH)))
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::ParseBuffer - Action not supported");
+ PrintWSDInfo(info);
+ return;
+ }
+
+ // MUST have a msgid tag
+ std::string msgid = wsd_tag_find(buffer, msgid_tag);
+ if (msgid.empty())
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::ParseBuffer - No msgid tag found");
+ return;
+ }
+
+ info.msgid = msgid;
+
+ std::string types = wsd_tag_find(buffer, types_tag);
+ info.types = types;
+ std::string address = wsd_tag_find(buffer, address_tag);
+ info.address = address;
+ std::string xaddrs = wsd_tag_find(buffer, xaddrs_tag);
+ info.xaddrs = xaddrs;
+
+ if (xaddrs.empty() && (action != WSD_ACT_BYE))
+ {
+ // Do a resolve against address to hopefully receive an xaddrs field=
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::ParseBuffer - Resolve Action dispatched");
+ AddCommand(WSD_ACT_RESOLVE, address);
+ // Discard this message
+ PrintWSDInfo(info);
+ return;
+ }
+
+ const std::string delim1 = "://";
+ const std::string delim2 = ":";
+ size_t found = info.xaddrs.find(delim1);
+ if (found != std::string::npos)
+ {
+ std::string tmpxaddrs = info.xaddrs.substr(found + delim1.size());
+ if (tmpxaddrs[0] != '[')
+ {
+ found = std::min(tmpxaddrs.find(delim2), tmpxaddrs.find('/'));
+ info.xaddrs_host = tmpxaddrs.substr(0, found);
+ }
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(crit_wsdqueue);
+ auto searchitem = std::find_if(m_vecWSDInfo.begin(), m_vecWSDInfo.end(),
+ [info](const wsd_req_info& item) { return item == info; });
+
+ if (searchitem == m_vecWSDInfo.end())
+ {
+ CWSDiscoveryPosix& WSInstance =
+ dynamic_cast<CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery());
+ if (info.action != WSD_ACT_BYE)
+ {
+ // Acceptable request received, add to our server list
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::ParseBuffer - Actionable message");
+ UnicastGet(info);
+ m_vecWSDInfo.emplace_back(info);
+ WSInstance.SetItems(m_vecWSDInfo);
+ PrintWSDInfo(info);
+ return;
+ }
+ else
+ {
+ // WSD_ACT_BYE does not include an xaddrs tag
+ // We only want to match the address when receiving a WSD_ACT_BYE message to
+ // remove from our maintained list
+ auto searchbye = std::find_if(
+ m_vecWSDInfo.begin(), m_vecWSDInfo.end(),
+ [info, this](const wsd_req_info& item) { return equalsAddress(item, info); });
+ if (searchbye != m_vecWSDInfo.end())
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::ParseBuffer - BYE action -"
+ " removing entry");
+ m_vecWSDInfo.erase(searchbye);
+ PrintWSDInfo(info);
+ WSInstance.SetItems(m_vecWSDInfo);
+ return;
+ }
+ }
+ }
+ }
+
+ // Only duplicate items get this far, silently drop
+ return;
+}
+
+bool CWSDiscoveryListenerUDP::buildSoapMessage(const std::string& action,
+ std::string& msg,
+ const std::string& extraparameter)
+{
+ auto msg_uuid = StringUtils::CreateUUID();
+ std::string body;
+ std::string relatesTo; // Not implemented, may not be needed for our limited usage
+ int messagenumber = 1;
+
+ CWSDiscoveryPosix& WSInstance =
+ dynamic_cast<CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery());
+ long long wsd_instance_id = WSInstance.GetInstanceID();
+
+ if (action == WSD_ACT_HELLO)
+ {
+ body = fmt::format(hello_body, wsd_instance_address);
+ }
+ else if (action == WSD_ACT_BYE)
+ {
+ body = fmt::format(bye_body, wsd_instance_address);
+ }
+ else if (action == WSD_ACT_PROBE)
+ {
+ body = probe_body;
+ }
+ else if (action == WSD_ACT_RESOLVE)
+ {
+ body = fmt::format(resolve_body, extraparameter);
+ }
+ if (body.empty())
+ {
+ // May lead to excessive logspam
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::buildSoapMessage unimplemented WSD_ACTION");
+ return false;
+ }
+
+ // Todo: how are fmt exception/failures handled with libfmt?
+ msg = fmt::format(soap_msg_templ, action, msg_uuid, wsd_instance_id, messagenumber, relatesTo,
+ body);
+
+ return true;
+}
+
+template<std::size_t SIZE>
+const std::string CWSDiscoveryListenerUDP::wsd_tag_find(
+ const std::string& xml, const std::array<std::pair<std::string, std::string>, SIZE>& tag)
+{
+ for (const auto& tagpair : tag)
+ {
+ std::size_t found1 = xml.find(tagpair.first);
+ if (found1 != std::string::npos)
+ {
+ std::size_t found2 = xml.find(tagpair.second);
+ if (found2 != std::string::npos)
+ {
+ return xml.substr((found1 + tagpair.first.size()),
+ (found2 - (found1 + tagpair.first.size())));
+ }
+ }
+ }
+ return "";
+}
+
+bool CWSDiscoveryListenerUDP::equalsAddress(const wsd_req_info& lhs, const wsd_req_info& rhs) const
+{
+ return lhs.address == rhs.address;
+}
+
+void CWSDiscoveryListenerUDP::PrintWSDInfo(const wsd_req_info& info)
+{
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryUtils::printstruct - message contents\n"
+ "\tAction: {}\n"
+ "\tMsgID: {}\n"
+ "\tAddress: {}\n"
+ "\tTypes: {}\n"
+ "\tXAddrs: {}\n"
+ "\tComputer: {}\n",
+ info.action, info.msgid, info.address, info.types, info.xaddrs, info.computer);
+}
+
+void CWSDiscoveryListenerUDP::UnicastGet(wsd_req_info& info)
+{
+ if (INADDR_NONE == inet_addr(info.xaddrs_host.c_str()))
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::UnicastGet - No IP address in {}",
+ info.xaddrs);
+ return;
+ }
+
+ std::string msg =
+ fmt::format(get_msg, info.address, StringUtils::CreateUUID(), wsd_instance_address);
+ XFILE::CCurlFile file;
+ file.SetAcceptEncoding("identity");
+ file.SetMimeType("application/soap+xml");
+ file.SetUserAgent("wsd");
+ file.SetRequestHeader("Connection", "Close");
+
+ std::string html;
+ if (!file.Post(info.xaddrs, msg, html))
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::UnicastGet - Could not fetch from {}", info.xaddrs);
+ return;
+ }
+ info.computer = wsd_tag_find(html, computer_tag);
+ if (info.computer.empty())
+ {
+ CLog::Log(LOGDEBUG, LOGWSDISCOVERY,
+ "CWSDiscoveryListenerUDP::UnicastGet - No computer tag found");
+ return;
+ }
+}
+
+} // namespace WSDiscovery
diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h
new file mode 100644
index 0000000..3459973
--- /dev/null
+++ b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 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 "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <string>
+#include <vector>
+
+#include <netinet/in.h>
+
+namespace WSDiscovery
+{
+struct wsd_req_info;
+}
+
+namespace WSDiscovery
+{
+class CWSDiscoveryListenerUDP : public CThread
+{
+public:
+ CWSDiscoveryListenerUDP();
+ ~CWSDiscoveryListenerUDP();
+
+ void Start();
+ void Stop();
+
+protected:
+ // CThread
+ void Process() override;
+
+private:
+ struct Command
+ {
+ struct sockaddr_in address;
+ std::string commandMsg;
+ };
+
+ /*
+ * Dispatch UDP command from command Queue
+ * return (bool) true if message dispatched
+ */
+ bool DispatchCommand();
+
+ /*
+ * Build SOAPXML command and add to command Queue
+ * in (string) action type
+ * in (string) extra data field (currently used solely for resolve addresses)
+ * return (void)
+ */
+ void AddCommand(const std::string& message, const std::string& extraparameter = "");
+
+ /*
+ * Process received broadcast messages and add accepted items to
+ * in (string) SOAPXML Message
+ * return (void)
+ */
+ void ParseBuffer(const std::string& buffer);
+
+ /*
+ * Generates an XML SOAP message given a particular action type
+ * in (string) action type
+ * in/out (string) created message
+ * in (string) extra data field (currently used for resolve addresses)
+ * return (bool) true if full message crafted
+ */
+ bool buildSoapMessage(const std::string& action,
+ std::string& msg,
+ const std::string& extraparameter);
+
+ // Closes socket and handles setting state for WS-Discovery
+ void Cleanup(bool aborted);
+
+ /*
+ * Use unicast Get to request computer name
+ * in/out (wsd_req_info&) host info to be updated
+ * return (void)
+ */
+ void UnicastGet(wsd_req_info& info);
+
+private:
+ template<std::size_t SIZE>
+ const std::string wsd_tag_find(const std::string& xml,
+ const std::array<std::pair<std::string, std::string>, SIZE>& tag);
+
+ // Debug print WSD packet data
+ void PrintWSDInfo(const WSDiscovery::wsd_req_info& info);
+
+ // compare WSD entry address
+ bool equalsAddress(const WSDiscovery::wsd_req_info& lhs,
+ const WSDiscovery::wsd_req_info& rhs) const;
+
+ // Socket FD for send/recv
+ int fd;
+
+ std::vector<Command> m_commandbuffer;
+
+ CCriticalSection crit_commandqueue;
+ CCriticalSection crit_wsdqueue;
+
+ std::vector<WSDiscovery::wsd_req_info> m_vecWSDInfo;
+
+ // GUID that should remain constant for an instance
+ const std::string wsd_instance_address;
+
+ // Number of sends for UDP messages
+ const int retries = 4;
+
+ // Max udp packet size (+ UDP header + IP header overhead = 65535)
+ const int UDPBUFFSIZE = 65507;
+
+ // Port for unicast/multicast WSD traffic
+ const int wsdUDP = 3702;
+
+ // ipv4 multicast group WSD - https://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf
+ const char* WDSIPv4MultiGroup = "239.255.255.250";
+
+ // ToDo: ipv6 broadcast address
+ // const char* WDSIPv6MultiGroup = "FF02::C"
+};
+} // namespace WSDiscovery
diff --git a/xbmc/platform/posix/main.cpp b/xbmc/platform/posix/main.cpp
new file mode 100644
index 0000000..5028aaa
--- /dev/null
+++ b/xbmc/platform/posix/main.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#if defined(TARGET_DARWIN_OSX)
+// SDL redefines main as SDL_main
+#ifdef HAS_SDL
+#include <SDL/SDL.h>
+#endif
+#endif
+
+#include "PlatformPosix.h"
+#include "application/AppEnvironment.h"
+#include "application/AppParamParser.h"
+#include "platform/xbmc.h"
+
+#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
+#include "platform/linux/AppParamParserLinux.h"
+#endif
+
+#include <cstdio>
+#include <cstring>
+#include <errno.h>
+#include <locale.h>
+#include <signal.h>
+
+#include <sys/resource.h>
+
+namespace
+{
+extern "C" void XBMC_POSIX_HandleSignal(int sig)
+{
+ // Setting an atomic flag is one of the only useful things that is permitted by POSIX
+ // in signal handlers
+ CPlatformPosix::RequestQuit();
+}
+} // namespace
+
+
+int main(int argc, char* argv[])
+{
+#if defined(_DEBUG)
+ struct rlimit rlim;
+ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
+ if (setrlimit(RLIMIT_CORE, &rlim) == -1)
+ fprintf(stderr, "Failed to set core size limit (%s).\n", strerror(errno));
+#endif
+
+ // Set up global SIGINT/SIGTERM handler
+ struct sigaction signalHandler;
+ std::memset(&signalHandler, 0, sizeof(signalHandler));
+ signalHandler.sa_handler = &XBMC_POSIX_HandleSignal;
+ signalHandler.sa_flags = SA_RESTART;
+ sigaction(SIGINT, &signalHandler, nullptr);
+ sigaction(SIGTERM, &signalHandler, nullptr);
+
+ setlocale(LC_NUMERIC, "C");
+
+#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
+ CAppParamParserLinux appParamParser;
+#else
+ CAppParamParser appParamParser;
+#endif
+ appParamParser.Parse(argv, argc);
+
+ CAppEnvironment::SetUp(appParamParser.GetAppParams());
+ int status = XBMC_Run(true);
+ CAppEnvironment::TearDown();
+
+ return status;
+}
diff --git a/xbmc/platform/posix/network/CMakeLists.txt b/xbmc/platform/posix/network/CMakeLists.txt
new file mode 100644
index 0000000..3f8f850
--- /dev/null
+++ b/xbmc/platform/posix/network/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES NetworkPosix.cpp)
+
+set(HEADERS NetworkPosix.h)
+
+core_add_library(platform_posix_network)
diff --git a/xbmc/platform/posix/network/NetworkPosix.cpp b/xbmc/platform/posix/network/NetworkPosix.cpp
new file mode 100644
index 0000000..003e5d7
--- /dev/null
+++ b/xbmc/platform/posix/network/NetworkPosix.cpp
@@ -0,0 +1,150 @@
+/*
+ * 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 "NetworkPosix.h"
+
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <utility>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+CNetworkInterfacePosix::CNetworkInterfacePosix(CNetworkPosix* network,
+ std::string interfaceName,
+ char interfaceMacAddrRaw[6])
+ : m_interfaceName(std::move(interfaceName)),
+ m_interfaceMacAdr(StringUtils::Format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
+ (uint8_t)interfaceMacAddrRaw[0],
+ (uint8_t)interfaceMacAddrRaw[1],
+ (uint8_t)interfaceMacAddrRaw[2],
+ (uint8_t)interfaceMacAddrRaw[3],
+ (uint8_t)interfaceMacAddrRaw[4],
+ (uint8_t)interfaceMacAddrRaw[5]))
+{
+ m_network = network;
+ memcpy(m_interfaceMacAddrRaw, interfaceMacAddrRaw, sizeof(m_interfaceMacAddrRaw));
+}
+
+bool CNetworkInterfacePosix::IsEnabled() const
+{
+ struct ifreq ifr;
+ strcpy(ifr.ifr_name, m_interfaceName.c_str());
+ if (ioctl(m_network->GetSocket(), SIOCGIFFLAGS, &ifr) < 0)
+ return false;
+
+ return ((ifr.ifr_flags & IFF_UP) == IFF_UP);
+}
+
+bool CNetworkInterfacePosix::IsConnected() const
+{
+ struct ifreq ifr;
+ int zero = 0;
+ memset(&ifr, 0, sizeof(struct ifreq));
+ strcpy(ifr.ifr_name, m_interfaceName.c_str());
+ if (ioctl(m_network->GetSocket(), SIOCGIFFLAGS, &ifr) < 0)
+ return false;
+
+ // ignore loopback
+ int iRunning = ((ifr.ifr_flags & IFF_RUNNING) && (!(ifr.ifr_flags & IFF_LOOPBACK)));
+
+ if (ioctl(m_network->GetSocket(), SIOCGIFADDR, &ifr) < 0)
+ return false;
+
+ // return only interfaces which has ip address
+ return iRunning && (0 != memcmp(ifr.ifr_addr.sa_data + sizeof(short), &zero, sizeof(int)));
+}
+
+std::string CNetworkInterfacePosix::GetCurrentIPAddress() const
+{
+ std::string result;
+
+ struct ifreq ifr;
+ strcpy(ifr.ifr_name, m_interfaceName.c_str());
+ ifr.ifr_addr.sa_family = AF_INET;
+ if (ioctl(m_network->GetSocket(), SIOCGIFADDR, &ifr) >= 0)
+ {
+ result = inet_ntoa((*((struct sockaddr_in*)&ifr.ifr_addr)).sin_addr);
+ }
+
+ return result;
+}
+
+std::string CNetworkInterfacePosix::GetCurrentNetmask() const
+{
+ std::string result;
+
+ struct ifreq ifr;
+ strcpy(ifr.ifr_name, m_interfaceName.c_str());
+ ifr.ifr_addr.sa_family = AF_INET;
+ if (ioctl(m_network->GetSocket(), SIOCGIFNETMASK, &ifr) >= 0)
+ {
+ result = inet_ntoa((*((struct sockaddr_in*)&ifr.ifr_addr)).sin_addr);
+ }
+
+ return result;
+}
+
+std::string CNetworkInterfacePosix::GetMacAddress() const
+{
+ return m_interfaceMacAdr;
+}
+
+void CNetworkInterfacePosix::GetMacAddressRaw(char rawMac[6]) const
+{
+ memcpy(rawMac, m_interfaceMacAddrRaw, 6);
+}
+
+CNetworkPosix::CNetworkPosix() : CNetworkBase()
+{
+ m_sock = socket(AF_INET, SOCK_DGRAM, 0);
+}
+
+CNetworkPosix::~CNetworkPosix()
+{
+ if (m_sock != -1)
+ close(CNetworkPosix::m_sock);
+
+ std::vector<CNetworkInterface*>::iterator it = m_interfaces.begin();
+ while (it != m_interfaces.end())
+ {
+ CNetworkInterface* nInt = *it;
+ delete nInt;
+ it = m_interfaces.erase(it);
+ }
+}
+
+std::vector<CNetworkInterface*>& CNetworkPosix::GetInterfaceList()
+{
+ return m_interfaces;
+}
+
+//! @bug
+//! Overwrite the GetFirstConnectedInterface and requery
+//! the interface list if no connected device is found
+//! this fixes a bug when no network is available after first start of xbmc
+//! and the interface comes up during runtime
+CNetworkInterface* CNetworkPosix::GetFirstConnectedInterface()
+{
+ CNetworkInterface* pNetIf = CNetworkBase::GetFirstConnectedInterface();
+
+ // no connected Interfaces found? - requeryInterfaceList
+ if (!pNetIf)
+ {
+ CLog::Log(LOGDEBUG, "{} no connected interface found - requery list", __FUNCTION__);
+ queryInterfaceList();
+ //retry finding a connected if
+ pNetIf = CNetworkBase::GetFirstConnectedInterface();
+ }
+
+ return pNetIf;
+}
diff --git a/xbmc/platform/posix/network/NetworkPosix.h b/xbmc/platform/posix/network/NetworkPosix.h
new file mode 100644
index 0000000..68d885e
--- /dev/null
+++ b/xbmc/platform/posix/network/NetworkPosix.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 "network/Network.h"
+
+#include <string>
+#include <vector>
+
+class CNetworkPosix;
+
+class CNetworkInterfacePosix : public CNetworkInterface
+{
+public:
+ CNetworkInterfacePosix(CNetworkPosix* network,
+ std::string interfaceName,
+ char interfaceMacAddrRaw[6]);
+ virtual ~CNetworkInterfacePosix() override = default;
+
+ bool IsEnabled() const override;
+ bool IsConnected() const override;
+ std::string GetCurrentIPAddress() const override;
+ std::string GetCurrentNetmask() const override;
+
+ std::string GetMacAddress() const override;
+ void GetMacAddressRaw(char rawMac[6]) const override;
+
+protected:
+ std::string m_interfaceName;
+ CNetworkPosix* m_network;
+
+private:
+ std::string m_interfaceMacAdr;
+ char m_interfaceMacAddrRaw[6];
+};
+
+class CNetworkPosix : public CNetworkBase
+{
+public:
+ virtual ~CNetworkPosix() override;
+
+ std::vector<CNetworkInterface*>& GetInterfaceList() override;
+ CNetworkInterface* GetFirstConnectedInterface() override;
+
+ int GetSocket() { return m_sock; }
+
+protected:
+ CNetworkPosix();
+ std::vector<CNetworkInterface*> m_interfaces;
+
+private:
+ virtual void GetMacAddress(const std::string& interfaceName, char macAddrRaw[6]) = 0;
+ virtual void queryInterfaceList() = 0;
+ int m_sock;
+};
diff --git a/xbmc/platform/posix/storage/discs/CMakeLists.txt b/xbmc/platform/posix/storage/discs/CMakeLists.txt
new file mode 100644
index 0000000..7587043
--- /dev/null
+++ b/xbmc/platform/posix/storage/discs/CMakeLists.txt
@@ -0,0 +1,7 @@
+if(ENABLE_OPTICAL)
+ set(SOURCES DiscDriveHandlerPosix.cpp)
+
+ set(HEADERS DiscDriveHandlerPosix.h)
+
+ core_add_library(platform_posix_storage_discs)
+endif()
diff --git a/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp
new file mode 100644
index 0000000..1ef288a
--- /dev/null
+++ b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 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 "DiscDriveHandlerPosix.h"
+
+#include "storage/cdioSupport.h"
+#include "utils/log.h"
+
+#include <memory>
+
+namespace
+{
+constexpr int MAX_OPEN_RETRIES = 3;
+}
+using namespace MEDIA_DETECT;
+
+std::shared_ptr<IDiscDriveHandler> IDiscDriveHandler::CreateInstance()
+{
+ return std::make_shared<CDiscDriveHandlerPosix>();
+}
+
+DriveState CDiscDriveHandlerPosix::GetDriveState(const std::string& devicePath)
+{
+ DriveState driveStatus = DriveState::NOT_READY;
+ const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance();
+ if (!libCdio)
+ {
+ CLog::LogF(LOGERROR, "Failed to obtain libcdio handler");
+ return driveStatus;
+ }
+
+ CdIo_t* cdio = libCdio->cdio_open(devicePath.c_str(), DRIVER_UNKNOWN);
+ if (!cdio)
+ {
+ return driveStatus;
+ }
+
+ CdioTrayStatus status = libCdio->mmc_get_tray_status(cdio);
+
+ switch (status)
+ {
+ case CdioTrayStatus::CLOSED:
+ case CdioTrayStatus::UNKNOWN:
+ driveStatus = DriveState::CLOSED_MEDIA_UNDEFINED;
+ break;
+
+ case CdioTrayStatus::OPEN:
+ driveStatus = DriveState::OPEN;
+ break;
+
+ case CdioTrayStatus::DRIVER_ERROR:
+ driveStatus = DriveState::NOT_READY;
+ break;
+
+ default:
+ CLog::LogF(LOGWARNING, "Unknown/unhandled drive state interpreted as DRIVE_NOT_READY");
+ break;
+ }
+ libCdio->cdio_destroy(cdio);
+
+ return driveStatus;
+}
+
+TrayState CDiscDriveHandlerPosix::GetTrayState(const std::string& devicePath)
+{
+ TrayState trayStatus = TrayState::UNDEFINED;
+ const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance();
+ if (!libCdio)
+ {
+ CLog::LogF(LOGERROR, "Failed to obtain libcdio handler");
+ return trayStatus;
+ }
+
+ discmode_t discmode = CDIO_DISC_MODE_NO_INFO;
+ CdIo_t* cdio = libCdio->cdio_open(devicePath.c_str(), DRIVER_UNKNOWN);
+ if (!cdio)
+ {
+ return trayStatus;
+ }
+
+ discmode = libCdio->cdio_get_discmode(cdio);
+
+ if (discmode == CDIO_DISC_MODE_NO_INFO)
+ {
+ trayStatus = TrayState::CLOSED_NO_MEDIA;
+ }
+ else if (discmode == CDIO_DISC_MODE_ERROR)
+ {
+ trayStatus = TrayState::UNDEFINED;
+ }
+ else
+ {
+ trayStatus = TrayState::CLOSED_MEDIA_PRESENT;
+ }
+ libCdio->cdio_destroy(cdio);
+
+ return trayStatus;
+}
+
+void CDiscDriveHandlerPosix::EjectDriveTray(const std::string& devicePath)
+{
+ const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance();
+ if (!libCdio)
+ {
+ CLog::LogF(LOGERROR, "Failed to obtain libcdio handler");
+ return;
+ }
+
+ int retries = MAX_OPEN_RETRIES;
+ CdIo_t* cdio = libCdio->cdio_open(devicePath.c_str(), DRIVER_UNKNOWN);
+ while (cdio && retries-- > 0)
+ {
+ const driver_return_code_t ret = libCdio->cdio_eject_media(&cdio);
+ if (ret == DRIVER_OP_SUCCESS)
+ break;
+ }
+ libCdio->cdio_destroy(cdio);
+}
+
+void CDiscDriveHandlerPosix::CloseDriveTray(const std::string& devicePath)
+{
+ const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance();
+ if (!libCdio)
+ {
+ CLog::LogF(LOGERROR, "Failed to obtain libcdio handler");
+ return;
+ }
+
+ const driver_return_code_t ret = libCdio->cdio_close_tray(devicePath.c_str(), nullptr);
+ if (ret != DRIVER_OP_SUCCESS)
+ {
+ CLog::LogF(LOGERROR, "Closing tray failed for device {}: {}", devicePath,
+ libCdio->cdio_driver_errmsg(ret));
+ }
+}
+
+void CDiscDriveHandlerPosix::ToggleDriveTray(const std::string& devicePath)
+{
+ if (GetDriveState(devicePath) == DriveState::OPEN)
+ {
+ CloseDriveTray(devicePath);
+ }
+ else
+ {
+ EjectDriveTray(devicePath);
+ }
+}
diff --git a/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h
new file mode 100644
index 0000000..11c4d9a
--- /dev/null
+++ b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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/discs/IDiscDriveHandler.h"
+
+#include <string>
+
+class CDiscDriveHandlerPosix : public IDiscDriveHandler
+{
+public:
+ /*! \brief Posix DiscDriveHandler constructor
+ */
+ CDiscDriveHandlerPosix() = default;
+
+ /*! \brief Posix DiscDriveHandler default destructor
+ */
+ ~CDiscDriveHandlerPosix() override = default;
+
+ /*! \brief Get the optical drive state provided its device path
+ * \param devicePath the path for the device drive (e.g. /dev/sr0)
+ * \return The drive state
+ */
+ DriveState GetDriveState(const std::string& devicePath) override;
+
+ /*! \brief Get the optical drive tray state provided the drive device path
+ * \param devicePath the path for the device drive (e.g. /dev/sr0)
+ * \return The drive state
+ */
+ TrayState GetTrayState(const std::string& devicePath) override;
+
+ /*! \brief Eject the provided drive device
+ * \param devicePath the path for the device drive (e.g. /dev/sr0)
+ */
+ void EjectDriveTray(const std::string& devicePath) override;
+
+ /*! \brief Close the provided drive device
+ * \note Some drives support closing appart from opening/eject
+ * \param devicePath the path for the device drive (e.g. /dev/sr0)
+ */
+ void CloseDriveTray(const std::string& devicePath) override;
+
+ /*! \brief Toggle the state of a given drive device
+ *
+ * Will internally call EjectDriveTray or CloseDriveTray depending on
+ * the internal state of the drive (i.e. if open -> CloseDriveTray /
+ * if closed -> EjectDriveTray)
+ *
+ * \param devicePath the path for the device drive (e.g. /dev/sr0)
+ */
+ void ToggleDriveTray(const std::string& devicePath) override;
+};
diff --git a/xbmc/platform/posix/threads/CMakeLists.txt b/xbmc/platform/posix/threads/CMakeLists.txt
new file mode 100644
index 0000000..251a7e1
--- /dev/null
+++ b/xbmc/platform/posix/threads/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES RecursiveMutex.cpp)
+set(HEADERS RecursiveMutex.h)
+
+if(NOT CORE_SYSTEM_NAME STREQUAL linux AND NOT CORE_SYSTEM_NAME STREQUAL android)
+ list(APPEND SOURCES ThreadImplPosix.cpp)
+ list(APPEND HEADERS ThreadImplPosix.h)
+endif()
+
+core_add_library(platform_posix_threads)
diff --git a/xbmc/platform/posix/threads/RecursiveMutex.cpp b/xbmc/platform/posix/threads/RecursiveMutex.cpp
new file mode 100644
index 0000000..652bc37
--- /dev/null
+++ b/xbmc/platform/posix/threads/RecursiveMutex.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2022 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 "RecursiveMutex.h"
+
+namespace XbmcThreads
+{
+
+static pthread_mutexattr_t recursiveAttr;
+
+static bool SetRecursiveAttr()
+{
+ static bool alreadyCalled = false;
+
+ if (!alreadyCalled)
+ {
+ pthread_mutexattr_init(&recursiveAttr);
+ pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE);
+#if !defined(TARGET_ANDROID)
+ pthread_mutexattr_setprotocol(&recursiveAttr, PTHREAD_PRIO_INHERIT);
+#endif
+ alreadyCalled = true;
+ }
+
+ return true; // note, we never call destroy.
+}
+
+static bool recursiveAttrSet = SetRecursiveAttr();
+
+pthread_mutexattr_t& CRecursiveMutex::getRecursiveAttr()
+{
+ if (!recursiveAttrSet) // this is only possible in the single threaded startup code
+ recursiveAttrSet = SetRecursiveAttr();
+
+ return recursiveAttr;
+}
+
+} // namespace XbmcThreads
diff --git a/xbmc/platform/posix/threads/RecursiveMutex.h b/xbmc/platform/posix/threads/RecursiveMutex.h
new file mode 100644
index 0000000..eed0fb2
--- /dev/null
+++ b/xbmc/platform/posix/threads/RecursiveMutex.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 <mutex>
+
+#include <pthread.h>
+namespace XbmcThreads
+{
+
+/*!
+ * \brief This class exists purely for the ability to
+ * set mutex attribute PTHREAD_PRIO_INHERIT.
+ * Currently there is no way to set this using
+ * std::recursive_mutex.
+ *
+ */
+class CRecursiveMutex
+{
+private:
+ pthread_mutex_t m_mutex;
+
+ static pthread_mutexattr_t& getRecursiveAttr();
+
+public:
+ CRecursiveMutex(const CRecursiveMutex&) = delete;
+ CRecursiveMutex& operator=(const CRecursiveMutex&) = delete;
+
+ inline CRecursiveMutex() { pthread_mutex_init(&m_mutex, &getRecursiveAttr()); }
+
+ inline ~CRecursiveMutex() { pthread_mutex_destroy(&m_mutex); }
+
+ inline void lock() { pthread_mutex_lock(&m_mutex); }
+
+ inline void unlock() { pthread_mutex_unlock(&m_mutex); }
+
+ inline bool try_lock() { return (pthread_mutex_trylock(&m_mutex) == 0); }
+
+ inline std::recursive_mutex::native_handle_type native_handle() { return &m_mutex; }
+};
+
+} // namespace XbmcThreads
diff --git a/xbmc/platform/posix/threads/ThreadImplPosix.cpp b/xbmc/platform/posix/threads/ThreadImplPosix.cpp
new file mode 100644
index 0000000..ae5b849
--- /dev/null
+++ b/xbmc/platform/posix/threads/ThreadImplPosix.cpp
@@ -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 "ThreadImplPosix.h"
+
+#include "utils/log.h"
+
+#include <pthread.h>
+
+std::unique_ptr<IThreadImpl> IThreadImpl::CreateThreadImpl(std::thread::native_handle_type handle)
+{
+ return std::make_unique<CThreadImplPosix>(handle);
+}
+
+CThreadImplPosix::CThreadImplPosix(std::thread::native_handle_type handle) : IThreadImpl(handle)
+{
+}
+
+void CThreadImplPosix::SetThreadInfo(const std::string& name)
+{
+#if defined(TARGET_DARWIN)
+ pthread_setname_np(name.c_str());
+#endif
+}
+
+bool CThreadImplPosix::SetPriority(const ThreadPriority& priority)
+{
+ CLog::Log(LOGDEBUG, "[threads] setting priority is not supported on this platform");
+ return false;
+}
diff --git a/xbmc/platform/posix/threads/ThreadImplPosix.h b/xbmc/platform/posix/threads/ThreadImplPosix.h
new file mode 100644
index 0000000..7bba5e1
--- /dev/null
+++ b/xbmc/platform/posix/threads/ThreadImplPosix.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 "threads/IThreadImpl.h"
+
+class CThreadImplPosix : public IThreadImpl
+{
+public:
+ CThreadImplPosix(std::thread::native_handle_type handle);
+
+ ~CThreadImplPosix() override = default;
+
+ void SetThreadInfo(const std::string& name) override;
+
+ bool SetPriority(const ThreadPriority& priority) override;
+};
diff --git a/xbmc/platform/posix/utils/CMakeLists.txt b/xbmc/platform/posix/utils/CMakeLists.txt
new file mode 100644
index 0000000..8fb2abc
--- /dev/null
+++ b/xbmc/platform/posix/utils/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES Mmap.cpp
+ PosixInterfaceForCLog.cpp
+ SharedMemory.cpp)
+
+set(HEADERS FileHandle.h
+ Mmap.h
+ PosixInterfaceForCLog.h
+ SharedMemory.h)
+
+core_add_library(platform_posix_utils)
diff --git a/xbmc/platform/posix/utils/FileHandle.h b/xbmc/platform/posix/utils/FileHandle.h
new file mode 100644
index 0000000..e50a357
--- /dev/null
+++ b/xbmc/platform/posix/utils/FileHandle.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "utils/ScopeGuard.h"
+
+#include <unistd.h>
+
+namespace KODI
+{
+namespace UTILS
+{
+namespace POSIX
+{
+
+class CFileHandle : public CScopeGuard<int, -1, decltype(close)>
+{
+public:
+ CFileHandle() noexcept : CScopeGuard(close, -1) {}
+ explicit CFileHandle(int fd) : CScopeGuard(close, fd) {}
+};
+
+}
+}
+}
diff --git a/xbmc/platform/posix/utils/Mmap.cpp b/xbmc/platform/posix/utils/Mmap.cpp
new file mode 100644
index 0000000..3d40d3c
--- /dev/null
+++ b/xbmc/platform/posix/utils/Mmap.cpp
@@ -0,0 +1,27 @@
+/*
+ * 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 "Mmap.h"
+
+#include <system_error>
+
+using namespace KODI::UTILS::POSIX;
+
+CMmap::CMmap(void* addr, std::size_t length, int prot, int flags, int fildes, off_t offset)
+: m_size{length}, m_memory{mmap(addr, length, prot, flags, fildes, offset)}
+{
+ if (m_memory == MAP_FAILED)
+ {
+ throw std::system_error(errno, std::generic_category(), "mmap failed");
+ }
+}
+
+CMmap::~CMmap()
+{
+ munmap(m_memory, m_size);
+} \ No newline at end of file
diff --git a/xbmc/platform/posix/utils/Mmap.h b/xbmc/platform/posix/utils/Mmap.h
new file mode 100644
index 0000000..2470d60
--- /dev/null
+++ b/xbmc/platform/posix/utils/Mmap.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+#include <sys/mman.h>
+
+namespace KODI
+{
+namespace UTILS
+{
+namespace POSIX
+{
+
+/**
+ * Wrapper for mapped memory that automatically calls munmap on destruction
+ */
+class CMmap
+{
+public:
+ /**
+ * See mmap(3p) for parameter description
+ */
+ CMmap(void* addr, std::size_t length, int prot, int flags, int fildes, off_t offset);
+ ~CMmap();
+
+ void* Data() const
+ {
+ return m_memory;
+ }
+ std::size_t Size() const
+ {
+ return m_size;
+ }
+
+private:
+ CMmap(CMmap const& other) = delete;
+ CMmap& operator=(CMmap const& other) = delete;
+
+ std::size_t m_size;
+ void* m_memory;
+};
+
+}
+}
+}
diff --git a/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp b/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp
new file mode 100644
index 0000000..6b8bf48
--- /dev/null
+++ b/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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 "PosixInterfaceForCLog.h"
+
+#include "ServiceBroker.h"
+#include "application/AppParams.h"
+
+#include <spdlog/sinks/dist_sink.h>
+#include <spdlog/sinks/stdout_color_sinks.h>
+
+#if !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN)
+std::unique_ptr<IPlatformLog> IPlatformLog::CreatePlatformLog()
+{
+ return std::make_unique<CPosixInterfaceForCLog>();
+}
+#endif
+
+void CPosixInterfaceForCLog::AddSinks(
+ std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const
+{
+ if (CServiceBroker::GetAppParams()->GetLogTarget() == "console")
+ distributionSink->add_sink(std::make_shared<spdlog::sinks::stdout_color_sink_st>());
+}
diff --git a/xbmc/platform/posix/utils/PosixInterfaceForCLog.h b/xbmc/platform/posix/utils/PosixInterfaceForCLog.h
new file mode 100644
index 0000000..0336b16
--- /dev/null
+++ b/xbmc/platform/posix/utils/PosixInterfaceForCLog.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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 "utils/IPlatformLog.h"
+
+class CPosixInterfaceForCLog : public IPlatformLog
+{
+public:
+ CPosixInterfaceForCLog() = default;
+ virtual ~CPosixInterfaceForCLog() override = default;
+
+ spdlog_filename_t GetLogFilename(const std::string& filename) const override { return filename; }
+ void AddSinks(
+ std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const override;
+};
diff --git a/xbmc/platform/posix/utils/SharedMemory.cpp b/xbmc/platform/posix/utils/SharedMemory.cpp
new file mode 100644
index 0000000..6c1318e
--- /dev/null
+++ b/xbmc/platform/posix/utils/SharedMemory.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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 "SharedMemory.h"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#if defined(HAVE_LINUX_MEMFD)
+#include <linux/memfd.h>
+#include <sys/syscall.h>
+#endif
+
+#include <cerrno>
+#include <cstdlib>
+#include <system_error>
+
+#include "utils/log.h"
+
+using namespace KODI::UTILS::POSIX;
+
+CSharedMemory::CSharedMemory(std::size_t size)
+: m_size{size}, m_fd{Open()}, m_mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0)
+{
+}
+
+CFileHandle CSharedMemory::Open()
+{
+ CFileHandle fd;
+ try
+ {
+ fd = OpenMemfd();
+ }
+ catch (std::system_error const& error)
+ {
+ if (error.code() == std::errc::function_not_supported)
+ {
+ CLog::Log(LOGDEBUG, "Kernel does not support memfd, falling back to plain shm");
+ fd = OpenShm();
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ if (ftruncate(fd, m_size) < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "ftruncate");
+ }
+
+ return fd;
+}
+
+CFileHandle CSharedMemory::OpenMemfd()
+{
+#if defined(SYS_memfd_create) && defined(HAVE_LINUX_MEMFD)
+ // This is specific to Linux >= 3.17, but preferred over shm_create if available
+ // because it is race-free
+ int fd = syscall(SYS_memfd_create, "kodi", MFD_CLOEXEC);
+ if (fd < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "memfd_create");
+ }
+
+ return CFileHandle(fd);
+#else
+ throw std::system_error(std::make_error_code(std::errc::function_not_supported), "memfd_create");
+#endif
+}
+
+CFileHandle CSharedMemory::OpenShm()
+{
+ char* xdgRuntimeDir = std::getenv("XDG_RUNTIME_DIR");
+ if (!xdgRuntimeDir)
+ {
+ throw std::runtime_error("XDG_RUNTIME_DIR environment variable must be set");
+ }
+
+ std::string tmpFilename(xdgRuntimeDir);
+ tmpFilename.append("/kodi-shared-XXXXXX");
+
+ int rawFd;
+#if defined(HAVE_MKOSTEMP)
+ // Opening the file with O_CLOEXEC is preferred since it avoids races where
+ // other threads might exec() before setting the CLOEXEC flag
+ rawFd = mkostemp(&tmpFilename[0], O_CLOEXEC);
+#else
+ rawFd = mkstemp(&tmpFilename[0]);
+#endif
+
+ if (rawFd < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "mkstemp");
+ }
+ CFileHandle fd(rawFd);
+
+ int flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "fcntl F_GETFD");
+ }
+ // Set FD_CLOEXEC if unset
+ if (!(flags & FD_CLOEXEC) && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ {
+ throw std::system_error(errno, std::generic_category(), "fcntl F_SETFD FD_CLOEXEC");
+ }
+
+ unlink(tmpFilename.c_str());
+
+ return fd;
+} \ No newline at end of file
diff --git a/xbmc/platform/posix/utils/SharedMemory.h b/xbmc/platform/posix/utils/SharedMemory.h
new file mode 100644
index 0000000..b556de6
--- /dev/null
+++ b/xbmc/platform/posix/utils/SharedMemory.h
@@ -0,0 +1,62 @@
+/*
+ * 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 "FileHandle.h"
+#include "Mmap.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace UTILS
+{
+namespace POSIX
+{
+
+/**
+ * Get a chunk of shared memory of specified size
+ *
+ * The shared memory is automatically allocated, truncated to the correct size
+ * and memory-mapped.
+ */
+class CSharedMemory
+{
+public:
+ explicit CSharedMemory(std::size_t size);
+
+ std::size_t Size() const
+ {
+ return m_size;
+ }
+ void* Data() const
+ {
+ return m_mmap.Data();
+ }
+ int Fd() const
+ {
+ return m_fd;
+ }
+
+private:
+ CSharedMemory(CSharedMemory const& other) = delete;
+ CSharedMemory& operator=(CSharedMemory const& other) = delete;
+
+ CFileHandle Open();
+ CFileHandle OpenMemfd();
+ CFileHandle OpenShm();
+
+ std::size_t m_size;
+ CFileHandle m_fd;
+ CMmap m_mmap;
+};
+
+}
+}
+}
diff --git a/xbmc/platform/xbmc.cpp b/xbmc/platform/xbmc.cpp
new file mode 100644
index 0000000..a6de8fb
--- /dev/null
+++ b/xbmc/platform/xbmc.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 "application/Application.h"
+#include "platform/MessagePrinter.h"
+
+#ifdef TARGET_WINDOWS_DESKTOP
+#include "platform/win32/IMMNotificationClient.h"
+#include <mmdeviceapi.h>
+#include <wrl/client.h>
+#endif
+
+#if defined(TARGET_ANDROID)
+#include "platform/android/activity/XBMCApp.h"
+#endif
+
+extern "C" int XBMC_Run(bool renderGUI)
+{
+ int status = -1;
+
+ if (!g_application.Create())
+ {
+ CMessagePrinter::DisplayError("ERROR: Unable to create application. Exiting");
+ return status;
+ }
+
+#if defined(TARGET_ANDROID)
+ CXBMCApp::Get().Initialize();
+#endif
+
+ if (renderGUI && !g_application.CreateGUI())
+ {
+ CMessagePrinter::DisplayError("ERROR: Unable to create GUI. Exiting");
+ if (g_application.Stop(EXITCODE_QUIT))
+ g_application.Cleanup();
+ return status;
+ }
+ if (!g_application.Initialize())
+ {
+ CMessagePrinter::DisplayError("ERROR: Unable to Initialize. Exiting");
+ return status;
+ }
+
+#ifdef TARGET_WINDOWS_DESKTOP
+ Microsoft::WRL::ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
+ CMMNotificationClient cMMNC;
+ HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator,
+ reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
+ if (SUCCEEDED(hr))
+ {
+ pEnumerator->RegisterEndpointNotificationCallback(&cMMNC);
+ pEnumerator = nullptr;
+ }
+#endif
+
+ status = g_application.Run();
+
+#ifdef TARGET_WINDOWS_DESKTOP
+ // the end
+ hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator,
+ reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
+ if (SUCCEEDED(hr))
+ {
+ pEnumerator->UnregisterEndpointNotificationCallback(&cMMNC);
+ pEnumerator = nullptr;
+ }
+#endif
+
+#if defined(TARGET_ANDROID)
+ CXBMCApp::Get().Deinitialize();
+#endif
+
+ return status;
+}
diff --git a/xbmc/platform/xbmc.h b/xbmc/platform/xbmc.h
new file mode 100644
index 0000000..206acdb
--- /dev/null
+++ b/xbmc/platform/xbmc.h
@@ -0,0 +1,15 @@
+/*
+ * 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 <memory>
+
+class CAppParams;
+
+extern "C" int XBMC_Run(bool renderGUI);