summaryrefslogtreecommitdiffstats
path: root/xbmc/application
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/application')
-rw-r--r--xbmc/application/AppEnvironment.cpp32
-rw-r--r--xbmc/application/AppEnvironment.h20
-rw-r--r--xbmc/application/AppInboundProtocol.cpp31
-rw-r--r--xbmc/application/AppInboundProtocol.h24
-rw-r--r--xbmc/application/AppParamParser.cpp118
-rw-r--r--xbmc/application/AppParamParser.h34
-rw-r--r--xbmc/application/AppParams.cpp20
-rw-r--r--xbmc/application/AppParams.h89
-rw-r--r--xbmc/application/Application.cpp3693
-rw-r--r--xbmc/application/Application.h257
-rw-r--r--xbmc/application/ApplicationActionListeners.cpp48
-rw-r--r--xbmc/application/ApplicationActionListeners.h53
-rw-r--r--xbmc/application/ApplicationComponents.h15
-rw-r--r--xbmc/application/ApplicationEnums.h25
-rw-r--r--xbmc/application/ApplicationPlayer.cpp1061
-rw-r--r--xbmc/application/ApplicationPlayer.h205
-rw-r--r--xbmc/application/ApplicationPlayerCallback.cpp346
-rw-r--r--xbmc/application/ApplicationPlayerCallback.h43
-rw-r--r--xbmc/application/ApplicationPowerHandling.cpp598
-rw-r--r--xbmc/application/ApplicationPowerHandling.h116
-rw-r--r--xbmc/application/ApplicationSettingsHandling.cpp214
-rw-r--r--xbmc/application/ApplicationSettingsHandling.h34
-rw-r--r--xbmc/application/ApplicationSkinHandling.cpp514
-rw-r--r--xbmc/application/ApplicationSkinHandling.h53
-rw-r--r--xbmc/application/ApplicationStackHelper.cpp332
-rw-r--r--xbmc/application/ApplicationStackHelper.h213
-rw-r--r--xbmc/application/ApplicationVolumeHandling.cpp201
-rw-r--r--xbmc/application/ApplicationVolumeHandling.h71
-rw-r--r--xbmc/application/CMakeLists.txt29
-rw-r--r--xbmc/application/IApplicationComponent.h16
30 files changed, 8505 insertions, 0 deletions
diff --git a/xbmc/application/AppEnvironment.cpp b/xbmc/application/AppEnvironment.cpp
new file mode 100644
index 0000000..32d4c4f
--- /dev/null
+++ b/xbmc/application/AppEnvironment.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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 "AppEnvironment.h"
+
+#include "ServiceBroker.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+void CAppEnvironment::SetUp(const std::shared_ptr<CAppParams>& appParams)
+{
+ CServiceBroker::RegisterAppParams(appParams);
+ CServiceBroker::CreateLogging();
+ const auto settingsComponent = std::make_shared<CSettingsComponent>();
+ settingsComponent->Initialize();
+ CServiceBroker::RegisterSettingsComponent(settingsComponent);
+}
+
+void CAppEnvironment::TearDown()
+{
+ CServiceBroker::GetLogging().UnregisterFromSettings();
+ CServiceBroker::GetSettingsComponent()->Deinitialize();
+ CServiceBroker::UnregisterSettingsComponent();
+ CServiceBroker::GetLogging().Deinitialize();
+ CServiceBroker::DestroyLogging();
+ CServiceBroker::UnregisterAppParams();
+}
diff --git a/xbmc/application/AppEnvironment.h b/xbmc/application/AppEnvironment.h
new file mode 100644
index 0000000..d301866
--- /dev/null
+++ b/xbmc/application/AppEnvironment.h
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+
+class CAppParams;
+
+class CAppEnvironment
+{
+public:
+ static void SetUp(const std::shared_ptr<CAppParams>& appParams);
+ static void TearDown();
+};
diff --git a/xbmc/application/AppInboundProtocol.cpp b/xbmc/application/AppInboundProtocol.cpp
new file mode 100644
index 0000000..7a1c80d
--- /dev/null
+++ b/xbmc/application/AppInboundProtocol.cpp
@@ -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.
+ */
+
+#include "AppInboundProtocol.h"
+
+#include "ServiceBroker.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPowerHandling.h"
+
+CAppInboundProtocol::CAppInboundProtocol(CApplication &app) : m_pApp(app)
+{
+
+}
+
+bool CAppInboundProtocol::OnEvent(XBMC_Event &event)
+{
+ return m_pApp.OnEvent(event);
+}
+
+void CAppInboundProtocol::SetRenderGUI(bool renderGUI)
+{
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ appPower->SetRenderGUI(renderGUI);
+}
diff --git a/xbmc/application/AppInboundProtocol.h b/xbmc/application/AppInboundProtocol.h
new file mode 100644
index 0000000..b0489e5
--- /dev/null
+++ b/xbmc/application/AppInboundProtocol.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 "windowing/XBMC_events.h"
+
+class CApplication;
+
+class CAppInboundProtocol
+{
+public:
+ CAppInboundProtocol(CApplication &app);
+ bool OnEvent(XBMC_Event &event);
+ void SetRenderGUI(bool renderGUI);
+
+protected:
+ CApplication &m_pApp;
+};
diff --git a/xbmc/application/AppParamParser.cpp b/xbmc/application/AppParamParser.cpp
new file mode 100644
index 0000000..ca9cd2a
--- /dev/null
+++ b/xbmc/application/AppParamParser.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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 "AppParamParser.h"
+
+#include "CompileInfo.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "application/AppParams.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/log.h"
+
+#include <iostream>
+#include <stdlib.h>
+#include <string>
+
+namespace
+{
+
+constexpr const char* versionText =
+ R"""({0} Media Center {1}
+Copyright (C) {2} Team {0} - http://kodi.tv
+)""";
+
+constexpr const char* helpText =
+ R"""(Usage: {0} [OPTION]... [FILE]...
+
+Arguments:
+ -fs Runs {1} in full screen
+ --standalone {1} runs in a stand alone environment without a window
+ manager and supporting applications. For example, that
+ enables network settings.
+ -p or --portable {1} will look for configurations in install folder instead of ~/.{0}
+ --debug Enable debug logging
+ --version Print version information
+ --test Enable test mode. [FILE] required.
+ --settings=<filename> Loads specified file after advancedsettings.xml replacing any settings specified
+ specified file must exist in special://xbmc/system/
+)""";
+
+} // namespace
+
+CAppParamParser::CAppParamParser() : m_params(std::make_shared<CAppParams>())
+{
+}
+
+void CAppParamParser::Parse(const char* const* argv, int nArgs)
+{
+ std::vector<std::string> args;
+ args.reserve(nArgs);
+
+ for (int i = 0; i < nArgs; i++)
+ {
+ args.emplace_back(argv[i]);
+ if (i > 0)
+ ParseArg(argv[i]);
+ }
+
+ if (nArgs > 1)
+ {
+ // testmode is only valid if at least one item to play was given
+ if (m_params->GetPlaylist().IsEmpty())
+ m_params->SetTestMode(false);
+ }
+
+ // Record raw paramerters
+ m_params->SetRawArgs(std::move(args));
+}
+
+void CAppParamParser::DisplayVersion()
+{
+ std::cout << StringUtils::Format(versionText, CSysInfo::GetAppName(), CSysInfo::GetVersion(),
+ CCompileInfo::GetCopyrightYears());
+ exit(0);
+}
+
+void CAppParamParser::DisplayHelp()
+{
+ std::string lcAppName = CSysInfo::GetAppName();
+ StringUtils::ToLower(lcAppName);
+
+ std::cout << StringUtils::Format(helpText, lcAppName, CSysInfo::GetAppName());
+}
+
+void CAppParamParser::ParseArg(const std::string &arg)
+{
+ if (arg == "-fs" || arg == "--fullscreen")
+ m_params->SetStartFullScreen(true);
+ else if (arg == "-h" || arg == "--help")
+ {
+ DisplayHelp();
+ exit(0);
+ }
+ else if (arg == "-v" || arg == "--version")
+ DisplayVersion();
+ else if (arg == "--standalone")
+ m_params->SetStandAlone(true);
+ else if (arg == "-p" || arg == "--portable")
+ m_params->SetPlatformDirectories(false);
+ else if (arg == "--debug")
+ m_params->SetLogLevel(LOG_LEVEL_DEBUG);
+ else if (arg == "--test")
+ m_params->SetTestMode(true);
+ else if (arg.substr(0, 11) == "--settings=")
+ m_params->SetSettingsFile(arg.substr(11));
+ else if (arg.length() != 0 && arg[0] != '-')
+ {
+ const CFileItemPtr item = std::make_shared<CFileItem>(arg);
+ item->SetPath(arg);
+ m_params->GetPlaylist().Add(item);
+ }
+}
diff --git a/xbmc/application/AppParamParser.h b/xbmc/application/AppParamParser.h
new file mode 100644
index 0000000..44907f8
--- /dev/null
+++ b/xbmc/application/AppParamParser.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 <memory>
+#include <string>
+
+class CAppParams;
+
+class CAppParamParser
+{
+public:
+ CAppParamParser();
+ virtual ~CAppParamParser() = default;
+
+ void Parse(const char* const* argv, int nArgs);
+
+ std::shared_ptr<CAppParams> GetAppParams() const { return m_params; }
+
+protected:
+ virtual void ParseArg(const std::string& arg);
+ virtual void DisplayHelp();
+
+private:
+ void DisplayVersion();
+
+ std::shared_ptr<CAppParams> m_params;
+};
diff --git a/xbmc/application/AppParams.cpp b/xbmc/application/AppParams.cpp
new file mode 100644
index 0000000..97c09e3
--- /dev/null
+++ b/xbmc/application/AppParams.cpp
@@ -0,0 +1,20 @@
+/*
+ * 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 "AppParams.h"
+
+#include "FileItem.h"
+
+CAppParams::CAppParams() : m_playlist(std::make_unique<CFileItemList>())
+{
+}
+
+void CAppParams::SetRawArgs(std::vector<std::string> args)
+{
+ m_rawArgs = std::move(args);
+}
diff --git a/xbmc/application/AppParams.h b/xbmc/application/AppParams.h
new file mode 100644
index 0000000..4452cd7
--- /dev/null
+++ b/xbmc/application/AppParams.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-202 Team Kodi
+ * 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 "commons/ilog.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CFileItemList;
+
+class CAppParams
+{
+public:
+ CAppParams();
+ virtual ~CAppParams() = default;
+
+ int GetLogLevel() const { return m_logLevel; }
+ void SetLogLevel(int logLevel) { m_logLevel = logLevel; }
+
+ bool IsStartFullScreen() const { return m_startFullScreen; }
+ void SetStartFullScreen(bool startFullScreen) { m_startFullScreen = startFullScreen; }
+
+ bool IsStandAlone() const { return m_standAlone; }
+ void SetStandAlone(bool standAlone) { m_standAlone = standAlone; }
+
+ bool HasPlatformDirectories() const { return m_platformDirectories; }
+ void SetPlatformDirectories(bool platformDirectories)
+ {
+ m_platformDirectories = platformDirectories;
+ }
+
+ bool IsTestMode() const { return m_testmode; }
+ void SetTestMode(bool testMode) { m_testmode = testMode; }
+
+ const std::string& GetSettingsFile() const { return m_settingsFile; }
+ void SetSettingsFile(const std::string& settingsFile) { m_settingsFile = settingsFile; }
+
+ const std::string& GetWindowing() const { return m_windowing; }
+ void SetWindowing(const std::string& windowing) { m_windowing = windowing; }
+
+ const std::string& GetLogTarget() const { return m_logTarget; }
+ void SetLogTarget(const std::string& logTarget) { m_logTarget = logTarget; }
+
+ CFileItemList& GetPlaylist() const { return *m_playlist; }
+
+ /*!
+ * \brief Get the raw command-line arguments
+ *
+ * Note: Raw arguments are currently not used by Kodi, but they will be
+ * useful if/when ROS 2 support is ever merged.
+ *
+ * \return The arguments. Note that the leading argument is the executable
+ * path name.
+ */
+ const std::vector<std::string>& GetRawArgs() const { return m_rawArgs; }
+
+ /*!
+ * \brief Set the raw command-line arguments
+ *
+ * \args The arguments. Note that the leading argument is the executable path
+ * name.
+ */
+ void SetRawArgs(std::vector<std::string> args);
+
+private:
+ int m_logLevel{LOG_LEVEL_NORMAL};
+
+ bool m_startFullScreen{false};
+ bool m_standAlone{false};
+ bool m_platformDirectories{true};
+ bool m_testmode{false};
+
+ std::string m_settingsFile;
+ std::string m_windowing;
+ std::string m_logTarget;
+
+ std::unique_ptr<CFileItemList> m_playlist;
+
+ // The raw command-line arguments
+ std::vector<std::string> m_rawArgs;
+};
diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp
new file mode 100644
index 0000000..0765cf3
--- /dev/null
+++ b/xbmc/application/Application.cpp
@@ -0,0 +1,3693 @@
+/*
+ * 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.h"
+
+#include "Autorun.h"
+#include "CompileInfo.h"
+#include "GUIInfoManager.h"
+#include "HDRStatus.h"
+#include "LangInfo.h"
+#include "PlayListPlayer.h"
+#include "ServiceManager.h"
+#include "URL.h"
+#include "Util.h"
+#include "addons/AddonManager.h"
+#include "addons/RepositoryUpdater.h"
+#include "addons/Service.h"
+#include "addons/Skin.h"
+#include "addons/VFSEntry.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "application/AppInboundProtocol.h"
+#include "application/AppParams.h"
+#include "application/ApplicationActionListeners.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationPowerHandling.h"
+#include "application/ApplicationSkinHandling.h"
+#include "application/ApplicationStackHelper.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
+#include "cores/IPlayer.h"
+#include "cores/playercorefactory/PlayerCoreFactory.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogCache.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "events/EventLog.h"
+#include "events/NotificationEvent.h"
+#include "filesystem/File.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIControlProfiler.h"
+#include "guilib/GUIFontManager.h"
+#include "guilib/StereoscopicsManager.h"
+#include "guilib/TextureManager.h"
+#include "interfaces/builtins/Builtins.h"
+#include "interfaces/generic/ScriptInvocationManager.h"
+#include "music/MusicLibraryQueue.h"
+#include "music/tags/MusicInfoTag.h"
+#include "network/EventServer.h"
+#include "network/Network.h"
+#include "platform/Environment.h"
+#include "playlists/PlayListFactory.h"
+#include "threads/SystemClock.h"
+#include "utils/ContentUtils.h"
+#include "utils/JobManager.h"
+#include "utils/LangCodeExpander.h"
+#include "utils/Screenshot.h"
+#include "utils/Variant.h"
+#include "video/Bookmark.h"
+#include "video/VideoLibraryQueue.h"
+
+#ifdef HAS_PYTHON
+#include "interfaces/python/XBPython.h"
+#endif
+#include "GUILargeTextureManager.h"
+#include "GUIPassword.h"
+#include "GUIUserMessages.h"
+#include "SectionLoader.h"
+#include "SeekHandler.h"
+#include "ServiceBroker.h"
+#include "TextureCache.h"
+#include "cores/DllLoader/DllLoaderContainer.h"
+#include "filesystem/Directory.h"
+#include "filesystem/DirectoryCache.h"
+#include "filesystem/DllLibCurl.h"
+#include "filesystem/PluginDirectory.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/GUIAudioManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/InertialScrollingHandler.h"
+#include "input/KeyboardLayoutManager.h"
+#include "input/actions/ActionTranslator.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/ThreadMessage.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "playlists/PlayList.h"
+#include "playlists/SmartPlayList.h"
+#include "powermanagement/PowerManager.h"
+#include "profiles/ProfileManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/MediaSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "speech/ISpeechRecognition.h"
+#include "threads/SingleLock.h"
+#include "utils/CPUInfo.h"
+#include "utils/FileExtensionProvider.h"
+#include "utils/RegExp.h"
+#include "utils/SystemInfo.h"
+#include "utils/TimeUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include <cmath>
+
+#ifdef HAS_UPNP
+#include "network/upnp/UPnP.h"
+#include "filesystem/UPnPDirectory.h"
+#endif
+#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
+#include "platform/posix/filesystem/SMBFile.h"
+#endif
+#ifdef HAS_FILESYSTEM_NFS
+#include "filesystem/NFSFile.h"
+#endif
+#include "PartyModeManager.h"
+#include "network/ZeroconfBrowser.h"
+#ifndef TARGET_POSIX
+#include "platform/win32/threads/Win32Exception.h"
+#endif
+#include "interfaces/json-rpc/JSONRPC.h"
+#include "interfaces/AnnouncementManager.h"
+#include "peripherals/Peripherals.h"
+#include "music/infoscanner/MusicInfoScanner.h"
+#include "music/MusicUtils.h"
+#include "music/MusicThumbLoader.h"
+
+// Windows includes
+#include "guilib/GUIWindowManager.h"
+#include "video/PlayerController.h"
+
+// Dialog includes
+#include "addons/gui/GUIDialogAddonSettings.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogSimpleMenu.h"
+#include "video/dialogs/GUIDialogVideoBookmarks.h"
+
+// PVR related include Files
+#include "pvr/PVRManager.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "pvr/guilib/PVRGUIActionsPowerManagement.h"
+
+#ifdef TARGET_WINDOWS
+#include "win32util.h"
+#endif
+
+#ifdef TARGET_DARWIN_OSX
+#include "platform/darwin/osx/CocoaInterface.h"
+#include "platform/darwin/osx/XBMCHelper.h"
+#endif
+#ifdef TARGET_DARWIN
+#include "platform/darwin/DarwinUtils.h"
+#endif
+
+#ifdef HAS_DVD_DRIVE
+#include <cdio/logging.h>
+#endif
+
+#include "DatabaseManager.h"
+#include "input/InputManager.h"
+#include "storage/MediaManager.h"
+#include "utils/AlarmClock.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#ifdef TARGET_POSIX
+#include "platform/posix/XHandle.h"
+#include "platform/posix/PlatformPosix.h"
+#endif
+
+#if defined(TARGET_ANDROID)
+#include "platform/android/activity/XBMCApp.h"
+#endif
+
+#ifdef TARGET_WINDOWS
+#include "platform/Environment.h"
+#endif
+
+//TODO: XInitThreads
+#ifdef HAVE_X11
+#include <X11/Xlib.h>
+#endif
+
+#include "FileItem.h"
+#include "addons/AddonSystemSettings.h"
+#include "cores/FFmpeg.h"
+#include "pictures/GUIWindowSlideShow.h"
+#include "utils/CharsetConverter.h"
+
+#include <mutex>
+
+using namespace ADDON;
+using namespace XFILE;
+#ifdef HAS_DVD_DRIVE
+using namespace MEDIA_DETECT;
+#endif
+using namespace VIDEO;
+using namespace MUSIC_INFO;
+using namespace EVENTSERVER;
+using namespace JSONRPC;
+using namespace PVR;
+using namespace PERIPHERALS;
+using namespace KODI;
+using namespace KODI::MESSAGING;
+using namespace ActiveAE;
+
+using namespace XbmcThreads;
+using namespace std::chrono_literals;
+
+using KODI::MESSAGING::HELPERS::DialogResponse;
+
+using namespace std::chrono_literals;
+
+#define MAX_FFWD_SPEED 5
+
+CApplication::CApplication(void)
+ :
+#ifdef HAS_DVD_DRIVE
+ m_Autorun(new CAutorun()),
+#endif
+ m_pInertialScrollingHandler(new CInertialScrollingHandler()),
+ m_WaitingExternalCalls(0)
+{
+ TiXmlBase::SetCondenseWhiteSpace(false);
+
+#ifdef HAVE_X11
+ XInitThreads();
+#endif
+
+ // register application components
+ RegisterComponent(std::make_shared<CApplicationActionListeners>(m_critSection));
+ RegisterComponent(std::make_shared<CApplicationPlayer>());
+ RegisterComponent(std::make_shared<CApplicationPowerHandling>());
+ RegisterComponent(std::make_shared<CApplicationSkinHandling>(this, this, m_bInitializing));
+ RegisterComponent(std::make_shared<CApplicationVolumeHandling>());
+ RegisterComponent(std::make_shared<CApplicationStackHelper>());
+}
+
+CApplication::~CApplication(void)
+{
+ DeregisterComponent(typeid(CApplicationStackHelper));
+ DeregisterComponent(typeid(CApplicationVolumeHandling));
+ DeregisterComponent(typeid(CApplicationSkinHandling));
+ DeregisterComponent(typeid(CApplicationPowerHandling));
+ DeregisterComponent(typeid(CApplicationPlayer));
+ DeregisterComponent(typeid(CApplicationActionListeners));
+}
+
+bool CApplication::OnEvent(XBMC_Event& newEvent)
+{
+ std::unique_lock<CCriticalSection> lock(m_portSection);
+ m_portEvents.push_back(newEvent);
+ return true;
+}
+
+void CApplication::HandlePortEvents()
+{
+ std::unique_lock<CCriticalSection> lock(m_portSection);
+ while (!m_portEvents.empty())
+ {
+ auto newEvent = m_portEvents.front();
+ m_portEvents.pop_front();
+ CSingleExit lock(m_portSection);
+ switch(newEvent.type)
+ {
+ case XBMC_QUIT:
+ if (!m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ break;
+ case XBMC_VIDEORESIZE:
+ if (CServiceBroker::GetGUI()->GetWindowManager().Initialized())
+ {
+ if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().ApplyWindowResize(newEvent.resize.w, newEvent.resize.h);
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ settings->SetInt(CSettings::SETTING_WINDOW_WIDTH, newEvent.resize.w);
+ settings->SetInt(CSettings::SETTING_WINDOW_HEIGHT, newEvent.resize.h);
+ settings->Save();
+ }
+#ifdef TARGET_WINDOWS
+ else
+ {
+ // this may occurs when OS tries to resize application window
+ //CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true);
+ //auto& gfxContext = CServiceBroker::GetWinSystem()->GetGfxContext();
+ //gfxContext.SetVideoResolution(gfxContext.GetVideoResolution(), true);
+ // try to resize window back to it's full screen size
+ //! TODO: DX windowing should emit XBMC_FULLSCREEN_UPDATE instead with the proper dimensions
+ //! and position to avoid the ifdef in common code
+ auto& res_info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
+ CServiceBroker::GetWinSystem()->ResizeWindow(res_info.iScreenWidth, res_info.iScreenHeight, 0, 0);
+ }
+#endif
+ }
+ break;
+ case XBMC_FULLSCREEN_UPDATE:
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
+ {
+ CServiceBroker::GetWinSystem()->ResizeWindow(newEvent.resize.w, newEvent.resize.h,
+ newEvent.move.x, newEvent.move.y);
+ }
+ break;
+ }
+ case XBMC_VIDEOMOVE:
+ {
+ CServiceBroker::GetWinSystem()->OnMove(newEvent.move.x, newEvent.move.y);
+ }
+ break;
+ case XBMC_MODECHANGE:
+ CServiceBroker::GetWinSystem()->GetGfxContext().ApplyModeChange(newEvent.mode.res);
+ break;
+ case XBMC_USEREVENT:
+ CServiceBroker::GetAppMessenger()->PostMsg(static_cast<uint32_t>(newEvent.user.code));
+ break;
+ case XBMC_SETFOCUS:
+ {
+ // Reset the screensaver
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+ appPower->ResetScreenSaver();
+ appPower->WakeUpScreenSaverAndDPMS();
+ // Send a mouse motion event with no dx,dy for getting the current guiitem selected
+ OnAction(CAction(ACTION_MOUSE_MOVE, 0, static_cast<float>(newEvent.focus.x), static_cast<float>(newEvent.focus.y), 0, 0));
+ break;
+ }
+ default:
+ CServiceBroker::GetInputManager().OnEvent(newEvent);
+ }
+ }
+}
+
+extern "C" void __stdcall init_emu_environ();
+extern "C" void __stdcall update_emu_environ();
+extern "C" void __stdcall cleanup_emu_environ();
+
+bool CApplication::Create()
+{
+ m_bStop = false;
+
+ RegisterSettings();
+
+ CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo());
+
+ // Register JobManager service
+ CServiceBroker::RegisterJobManager(std::make_shared<CJobManager>());
+
+ // Announcement service
+ m_pAnnouncementManager = std::make_shared<ANNOUNCEMENT::CAnnouncementManager>();
+ m_pAnnouncementManager->Start();
+ CServiceBroker::RegisterAnnouncementManager(m_pAnnouncementManager);
+
+ const auto appMessenger = std::make_shared<CApplicationMessenger>();
+ CServiceBroker::RegisterAppMessenger(appMessenger);
+
+ const auto keyboardLayoutManager = std::make_shared<CKeyboardLayoutManager>();
+ CServiceBroker::RegisterKeyboardLayoutManager(keyboardLayoutManager);
+
+ m_ServiceManager.reset(new CServiceManager());
+
+ if (!m_ServiceManager->InitStageOne())
+ {
+ return false;
+ }
+
+ // here we register all global classes for the CApplicationMessenger,
+ // after that we can send messages to the corresponding modules
+ appMessenger->RegisterReceiver(this);
+ appMessenger->RegisterReceiver(&CServiceBroker::GetPlaylistPlayer());
+ appMessenger->SetGUIThread(CThread::GetCurrentThreadId());
+ appMessenger->SetProcessThread(CThread::GetCurrentThreadId());
+
+ // copy required files
+ CUtil::CopyUserDataIfNeeded("special://masterprofile/", "RssFeeds.xml");
+ CUtil::CopyUserDataIfNeeded("special://masterprofile/", "favourites.xml");
+ CUtil::CopyUserDataIfNeeded("special://masterprofile/", "Lircmap.xml");
+
+ CServiceBroker::GetLogging().Initialize(CSpecialProtocol::TranslatePath("special://logpath"));
+
+#ifdef TARGET_POSIX //! @todo Win32 has no special://home/ mapping by default, so we
+ //! must create these here. Ideally this should be using special://home/ and
+ //! be platform agnostic (i.e. unify the InitDirectories*() functions)
+ if (!CServiceBroker::GetAppParams()->HasPlatformDirectories())
+#endif
+ {
+ CDirectory::Create("special://xbmc/addons");
+ }
+
+ // Init our DllLoaders emu env
+ init_emu_environ();
+
+ PrintStartupLog();
+
+ // initialize network protocols
+ avformat_network_init();
+ // set avutil callback
+ av_log_set_callback(ff_avutil_log);
+
+ CLog::Log(LOGINFO, "loading settings");
+ const auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent->Load())
+ return false;
+
+ CLog::Log(LOGINFO, "creating subdirectories");
+ const std::shared_ptr<CProfileManager> profileManager = settingsComponent->GetProfileManager();
+ const std::shared_ptr<CSettings> settings = settingsComponent->GetSettings();
+ CLog::Log(LOGINFO, "userdata folder: {}",
+ CURL::GetRedacted(profileManager->GetProfileUserDataFolder()));
+ CLog::Log(LOGINFO, "recording folder: {}",
+ CURL::GetRedacted(settings->GetString(CSettings::SETTING_AUDIOCDS_RECORDINGPATH)));
+ CLog::Log(LOGINFO, "screenshots folder: {}",
+ CURL::GetRedacted(settings->GetString(CSettings::SETTING_DEBUG_SCREENSHOTPATH)));
+ CDirectory::Create(profileManager->GetUserDataFolder());
+ CDirectory::Create(profileManager->GetProfileUserDataFolder());
+ profileManager->CreateProfileFolders();
+
+ update_emu_environ();//apply the GUI settings
+
+ // application inbound service
+ m_pAppPort = std::make_shared<CAppInboundProtocol>(*this);
+ CServiceBroker::RegisterAppPort(m_pAppPort);
+
+ if (!m_ServiceManager->InitStageTwo(
+ settingsComponent->GetProfileManager()->GetProfileUserDataFolder()))
+ {
+ return false;
+ }
+
+ m_pActiveAE.reset(new ActiveAE::CActiveAE());
+ CServiceBroker::RegisterAE(m_pActiveAE.get());
+
+ // initialize m_replayGainSettings
+ GetComponent<CApplicationVolumeHandling>()->CacheReplayGainSettings(*settings);
+
+ // load the keyboard layouts
+ if (!keyboardLayoutManager->Load())
+ {
+ CLog::Log(LOGFATAL, "CApplication::Create: Unable to load keyboard layouts");
+ return false;
+ }
+
+ // set user defined CA trust bundle
+ std::string caCert =
+ CSpecialProtocol::TranslatePath(settingsComponent->GetAdvancedSettings()->m_caTrustFile);
+ if (!caCert.empty())
+ {
+ if (XFILE::CFile::Exists(caCert))
+ {
+ CEnvironment::setenv("SSL_CERT_FILE", caCert, 1);
+ CLog::Log(LOGDEBUG, "CApplication::Create - SSL_CERT_FILE: {}", caCert);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "CApplication::Create - Error reading SSL_CERT_FILE: {} -> ignored",
+ caCert);
+ }
+ }
+
+ CUtil::InitRandomSeed();
+
+ m_lastRenderTime = std::chrono::steady_clock::now();
+ return true;
+}
+
+bool CApplication::CreateGUI()
+{
+ m_frameMoveGuard.lock();
+
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+ appPower->SetRenderGUI(true);
+
+ auto windowSystems = KODI::WINDOWING::CWindowSystemFactory::GetWindowSystems();
+
+ const std::string& windowing = CServiceBroker::GetAppParams()->GetWindowing();
+
+ if (!windowing.empty())
+ windowSystems = {windowing};
+
+ for (auto& windowSystem : windowSystems)
+ {
+ CLog::Log(LOGDEBUG, "CApplication::{} - trying to init {} windowing system", __FUNCTION__,
+ windowSystem);
+ m_pWinSystem = KODI::WINDOWING::CWindowSystemFactory::CreateWindowSystem(windowSystem);
+
+ if (!m_pWinSystem)
+ continue;
+
+ if (!windowing.empty() && windowing != windowSystem)
+ continue;
+
+ CServiceBroker::RegisterWinSystem(m_pWinSystem.get());
+
+ if (!m_pWinSystem->InitWindowSystem())
+ {
+ CLog::Log(LOGDEBUG, "CApplication::{} - unable to init {} windowing system", __FUNCTION__,
+ windowSystem);
+ m_pWinSystem->DestroyWindowSystem();
+ m_pWinSystem.reset();
+ CServiceBroker::UnregisterWinSystem();
+ continue;
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "CApplication::{} - using the {} windowing system", __FUNCTION__,
+ windowSystem);
+ break;
+ }
+ }
+
+ if (!m_pWinSystem)
+ {
+ CLog::Log(LOGFATAL, "CApplication::{} - unable to init windowing system", __FUNCTION__);
+ CServiceBroker::UnregisterWinSystem();
+ return false;
+ }
+
+ // Retrieve the matching resolution based on GUI settings
+ bool sav_res = false;
+ CDisplaySettings::GetInstance().SetCurrentResolution(CDisplaySettings::GetInstance().GetDisplayResolution());
+ CLog::Log(LOGINFO, "Checking resolution {}",
+ CDisplaySettings::GetInstance().GetCurrentResolution());
+ if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsValidResolution(CDisplaySettings::GetInstance().GetCurrentResolution()))
+ {
+ CLog::Log(LOGINFO, "Setting safe mode {}", RES_DESKTOP);
+ // defer saving resolution after window was created
+ CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP);
+ sav_res = true;
+ }
+
+ // update the window resolution
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ CServiceBroker::GetWinSystem()->SetWindowResolution(settings->GetInt(CSettings::SETTING_WINDOW_WIDTH), settings->GetInt(CSettings::SETTING_WINDOW_HEIGHT));
+
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_startFullScreen && CDisplaySettings::GetInstance().GetCurrentResolution() == RES_WINDOW)
+ {
+ // defer saving resolution after window was created
+ CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP);
+ sav_res = true;
+ }
+
+ if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsValidResolution(CDisplaySettings::GetInstance().GetCurrentResolution()))
+ {
+ // Oh uh - doesn't look good for starting in their wanted screenmode
+ CLog::Log(LOGERROR, "The screen resolution requested is not valid, resetting to a valid mode");
+ CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP);
+ sav_res = true;
+ }
+ if (!InitWindow())
+ {
+ return false;
+ }
+
+ // Set default screen saver mode
+ auto screensaverModeSetting = std::static_pointer_cast<CSettingString>(settings->GetSetting(CSettings::SETTING_SCREENSAVER_MODE));
+ // Can only set this after windowing has been initialized since it depends on it
+ if (CServiceBroker::GetWinSystem()->GetOSScreenSaver())
+ {
+ // If OS has a screen saver, use it by default
+ screensaverModeSetting->SetDefault("");
+ }
+ else
+ {
+ // If OS has no screen saver, use Kodi one by default
+ screensaverModeSetting->SetDefault("screensaver.xbmc.builtin.dim");
+ }
+
+ if (sav_res)
+ CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true);
+
+ m_pGUI.reset(new CGUIComponent());
+ m_pGUI->Init();
+
+ // Splash requires gui component!!
+ CServiceBroker::GetRenderSystem()->ShowSplash("");
+
+ // The key mappings may already have been loaded by a peripheral
+ CLog::Log(LOGINFO, "load keymapping");
+ if (!CServiceBroker::GetInputManager().LoadKeymaps())
+ return false;
+
+ RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+ CLog::Log(LOGINFO, "GUI format {}x{}, Display {}", info.iWidth, info.iHeight, info.strMode);
+
+ return true;
+}
+
+bool CApplication::InitWindow(RESOLUTION res)
+{
+ if (res == RES_INVALID)
+ res = CDisplaySettings::GetInstance().GetCurrentResolution();
+
+ bool bFullScreen = res != RES_WINDOW;
+ if (!CServiceBroker::GetWinSystem()->CreateNewWindow(CSysInfo::GetAppName(),
+ bFullScreen, CDisplaySettings::GetInstance().GetResolutionInfo(res)))
+ {
+ CLog::Log(LOGFATAL, "CApplication::Create: Unable to create window");
+ return false;
+ }
+
+ if (!CServiceBroker::GetRenderSystem()->InitRenderSystem())
+ {
+ CLog::Log(LOGFATAL, "CApplication::Create: Unable to init rendering system");
+ return false;
+ }
+ // set GUI res and force the clear of the screen
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(res, false);
+ return true;
+}
+
+bool CApplication::Initialize()
+{
+ m_pActiveAE->Start();
+ // restore AE's previous volume state
+
+ const auto appVolume = GetComponent<CApplicationVolumeHandling>();
+ const auto level = appVolume->GetVolumeRatio();
+ const auto muted = appVolume->IsMuted();
+ appVolume->SetHardwareVolume(level);
+ CServiceBroker::GetActiveAE()->SetMute(muted);
+
+#if defined(HAS_DVD_DRIVE) && !defined(TARGET_WINDOWS) // somehow this throws an "unresolved external symbol" on win32
+ // turn off cdio logging
+ cdio_loglevel_default = CDIO_LOG_ERROR;
+#endif
+
+ // load the language and its translated strings
+ if (!LoadLanguage(false))
+ return false;
+
+ // load media manager sources (e.g. root addon type sources depend on language strings to be available)
+ CServiceBroker::GetMediaManager().LoadSources();
+
+ const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
+
+ profileManager->GetEventLog().Add(EventPtr(new CNotificationEvent(
+ StringUtils::Format(g_localizeStrings.Get(177), g_sysinfo.GetAppName()),
+ StringUtils::Format(g_localizeStrings.Get(178), g_sysinfo.GetAppName()),
+ "special://xbmc/media/icon256x256.png", EventLevel::Basic)));
+
+ m_ServiceManager->GetNetwork().WaitForNet();
+
+ // initialize (and update as needed) our databases
+ CDatabaseManager &databaseManager = m_ServiceManager->GetDatabaseManager();
+
+ CEvent event(true);
+ CServiceBroker::GetJobManager()->Submit([&databaseManager, &event]() {
+ databaseManager.Initialize();
+ event.Set();
+ });
+
+ std::string localizedStr = g_localizeStrings.Get(24150);
+ int iDots = 1;
+ while (!event.Wait(1000ms))
+ {
+ if (databaseManager.IsUpgrading())
+ CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr + std::string(iDots, '.'));
+
+ if (iDots == 3)
+ iDots = 1;
+ else
+ ++iDots;
+ }
+ CServiceBroker::GetRenderSystem()->ShowSplash("");
+
+ // Initialize GUI font manager to build/update fonts cache
+ //! @todo Move GUIFontManager into service broker and drop the global reference
+ event.Reset();
+ GUIFontManager& guiFontManager = g_fontManager;
+ CServiceBroker::GetJobManager()->Submit([&guiFontManager, &event]() {
+ guiFontManager.Initialize();
+ event.Set();
+ });
+ localizedStr = g_localizeStrings.Get(39175);
+ iDots = 1;
+ while (!event.Wait(1000ms))
+ {
+ if (g_fontManager.IsUpdating())
+ CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr +
+ std::string(iDots, '.'));
+
+ if (iDots == 3)
+ iDots = 1;
+ else
+ ++iDots;
+ }
+ CServiceBroker::GetRenderSystem()->ShowSplash("");
+
+ // GUI depends on seek handler
+ GetComponent<CApplicationPlayer>()->GetSeekHandler().Configure();
+
+ const auto skinHandling = GetComponent<CApplicationSkinHandling>();
+
+ bool uiInitializationFinished = false;
+
+ if (CServiceBroker::GetGUI()->GetWindowManager().Initialized())
+ {
+ const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ CServiceBroker::GetGUI()->GetWindowManager().CreateWindows();
+
+ skinHandling->m_confirmSkinChange = false;
+
+ std::vector<AddonInfoPtr> incompatibleAddons;
+ event.Reset();
+
+ // Addon migration
+ if (CServiceBroker::GetAddonMgr().GetIncompatibleEnabledAddonInfos(incompatibleAddons))
+ {
+ if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON)
+ {
+ CServiceBroker::GetJobManager()->Submit(
+ [&event, &incompatibleAddons]() {
+ if (CServiceBroker::GetRepositoryUpdater().CheckForUpdates())
+ CServiceBroker::GetRepositoryUpdater().Await();
+
+ incompatibleAddons = CServiceBroker::GetAddonMgr().MigrateAddons();
+ event.Set();
+ },
+ CJob::PRIORITY_DEDICATED);
+ localizedStr = g_localizeStrings.Get(24151);
+ iDots = 1;
+ while (!event.Wait(1000ms))
+ {
+ CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr +
+ std::string(iDots, '.'));
+ if (iDots == 3)
+ iDots = 1;
+ else
+ ++iDots;
+ }
+ m_incompatibleAddons = incompatibleAddons;
+ }
+ else
+ {
+ // If no update is active disable all incompatible addons during start
+ m_incompatibleAddons =
+ CServiceBroker::GetAddonMgr().DisableIncompatibleAddons(incompatibleAddons);
+ }
+ }
+
+ // Start splashscreen and load skin
+ CServiceBroker::GetRenderSystem()->ShowSplash("");
+ skinHandling->m_confirmSkinChange = true;
+
+ auto setting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN);
+ if (!setting)
+ {
+ CLog::Log(LOGFATAL, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN);
+ return false;
+ }
+
+ CServiceBroker::RegisterTextureCache(std::make_shared<CTextureCache>());
+
+ std::string skinId = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN);
+ if (!skinHandling->LoadSkin(skinId))
+ {
+ CLog::Log(LOGERROR, "Failed to load skin '{}'", skinId);
+ std::string defaultSkin =
+ std::static_pointer_cast<const CSettingString>(setting)->GetDefault();
+ if (!skinHandling->LoadSkin(defaultSkin))
+ {
+ CLog::Log(LOGFATAL, "Default skin '{}' could not be loaded! Terminating..", defaultSkin);
+ return false;
+ }
+ }
+
+ // initialize splash window after splash screen disappears
+ // because we need a real window in the background which gets
+ // rendered while we load the main window or enter the master lock key
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SPLASH);
+
+ if (settings->GetBool(CSettings::SETTING_MASTERLOCK_STARTUPLOCK) &&
+ profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE &&
+ !profileManager->GetMasterProfile().getLockCode().empty())
+ {
+ g_passwordManager.CheckStartUpLock();
+ }
+
+ // check if we should use the login screen
+ if (profileManager->UsingLoginScreen())
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_LOGIN_SCREEN);
+ }
+ else
+ {
+ // activate the configured start window
+ int firstWindow = g_SkinInfo->GetFirstWindow();
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(firstWindow);
+
+ if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_STARTUP_ANIM))
+ {
+ CLog::Log(LOGWARNING, "CApplication::Initialize - startup.xml taints init process");
+ }
+
+ // the startup window is considered part of the initialization as it most likely switches to the final window
+ uiInitializationFinished = firstWindow != WINDOW_STARTUP_ANIM;
+ }
+ }
+ else //No GUI Created
+ {
+ uiInitializationFinished = true;
+ }
+
+ CJSONRPC::Initialize();
+
+ CServiceBroker::RegisterSpeechRecognition(speech::ISpeechRecognition::CreateInstance());
+
+ if (!m_ServiceManager->InitStageThree(profileManager))
+ {
+ CLog::Log(LOGERROR, "Application - Init3 failed");
+ }
+
+ g_sysinfo.Refresh();
+
+ CLog::Log(LOGINFO, "removing tempfiles");
+ CUtil::RemoveTempFiles();
+
+ if (!profileManager->UsingLoginScreen())
+ {
+ UpdateLibraries();
+ SetLoggingIn(false);
+ }
+
+ m_slowTimer.StartZero();
+
+ // register action listeners
+ const auto appListener = GetComponent<CApplicationActionListeners>();
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ appListener->RegisterActionListener(&appPlayer->GetSeekHandler());
+ appListener->RegisterActionListener(&CPlayerController::GetInstance());
+
+ CServiceBroker::GetRepositoryUpdater().Start();
+ if (!profileManager->UsingLoginScreen())
+ CServiceBroker::GetServiceAddons().Start();
+
+ CLog::Log(LOGINFO, "initialize done");
+
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+ appPower->CheckOSScreenSaverInhibitionSetting();
+ // reset our screensaver (starts timers etc.)
+ appPower->ResetScreenSaver();
+
+ // if the user interfaces has been fully initialized let everyone know
+ if (uiInitializationFinished)
+ {
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UI_READY);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ }
+
+ return true;
+}
+
+bool CApplication::OnSettingsSaving() const
+{
+ // don't save settings when we're busy stopping the application
+ // a lot of screens try to save settings on deinit and deinit is
+ // called for every screen when the application is stopping
+ return !m_bStop;
+}
+
+void CApplication::Render()
+{
+ // do not render if we are stopped or in background
+ if (m_bStop)
+ return;
+
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+
+ bool hasRendered = false;
+
+ // Whether externalplayer is playing and we're unfocused
+ bool extPlayerActive = appPlayer->IsExternalPlaying() && !m_AppFocused;
+
+ if (!extPlayerActive && CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() &&
+ !appPlayer->IsPausedPlayback())
+ {
+ appPower->ResetScreenSaver();
+ }
+
+ if (!CServiceBroker::GetRenderSystem()->BeginRender())
+ return;
+
+ // render gui layer
+ if (appPower->GetRenderGUI() && !m_skipGuiRender)
+ {
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode())
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_LEFT);
+ hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_MONO)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_RIGHT);
+ hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_OFF);
+ }
+ else
+ {
+ hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();
+ }
+ // execute post rendering actions (finalize window closing)
+ CServiceBroker::GetGUI()->GetWindowManager().AfterRender();
+
+ m_lastRenderTime = std::chrono::steady_clock::now();
+ }
+
+ // render video layer
+ CServiceBroker::GetGUI()->GetWindowManager().RenderEx();
+
+ CServiceBroker::GetRenderSystem()->EndRender();
+
+ // reset our info cache - we do this at the end of Render so that it is
+ // fresh for the next process(), or after a windowclose animation (where process()
+ // isn't called)
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+ infoMgr.ResetCache();
+ infoMgr.GetInfoProviders().GetGUIControlsInfoProvider().ResetContainerMovingCache();
+
+ if (hasRendered)
+ {
+ infoMgr.GetInfoProviders().GetSystemInfoProvider().UpdateFPS();
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().Flip(hasRendered,
+ appPlayer->IsRenderingVideoLayer());
+
+ CTimeUtils::UpdateFrameTime(hasRendered);
+}
+
+bool CApplication::OnAction(const CAction &action)
+{
+ // special case for switching between GUI & fullscreen mode.
+ if (action.GetID() == ACTION_SHOW_GUI)
+ { // Switch to fullscreen mode if we can
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ {
+ if (gui->GetWindowManager().SwitchToFullScreen())
+ {
+ GetComponent<CApplicationPowerHandling>()->m_navigationTimer.StartZero();
+ return true;
+ }
+ }
+ }
+
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+
+ if (action.GetID() == ACTION_TOGGLE_FULLSCREEN)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().ToggleFullScreen();
+ appPlayer->TriggerUpdateResolution();
+ return true;
+ }
+
+ if (action.IsMouse())
+ CServiceBroker::GetInputManager().SetMouseActive(true);
+
+ if (action.GetID() == ACTION_CREATE_EPISODE_BOOKMARK)
+ {
+ CGUIDialogVideoBookmarks::OnAddEpisodeBookmark();
+ }
+ if (action.GetID() == ACTION_CREATE_BOOKMARK)
+ {
+ CGUIDialogVideoBookmarks::OnAddBookmark();
+ }
+
+ // The action PLAYPAUSE behaves as ACTION_PAUSE if we are currently
+ // playing or ACTION_PLAYER_PLAY if we are seeking (FF/RW) or not playing.
+ if (action.GetID() == ACTION_PLAYER_PLAYPAUSE)
+ {
+ CGUIWindowSlideShow* pSlideShow = CServiceBroker::GetGUI()->
+ GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if ((appPlayer->IsPlaying() && appPlayer->GetPlaySpeed() == 1) ||
+ (pSlideShow && pSlideShow->InSlideShow() && !pSlideShow->IsPaused()))
+ return OnAction(CAction(ACTION_PAUSE));
+ else
+ return OnAction(CAction(ACTION_PLAYER_PLAY));
+ }
+
+ //if the action would start or stop inertial scrolling
+ //by gesture - bypass the normal OnAction handler of current window
+ if( !m_pInertialScrollingHandler->CheckForInertialScrolling(&action) )
+ {
+ // in normal case
+ // just pass the action to the current window and let it handle it
+ if (CServiceBroker::GetGUI()->GetWindowManager().OnAction(action))
+ {
+ GetComponent<CApplicationPowerHandling>()->ResetNavigationTimer();
+ return true;
+ }
+ }
+
+ // handle extra global presses
+
+ // notify action listeners
+ if (GetComponent<CApplicationActionListeners>()->NotifyActionListeners(action))
+ return true;
+
+ // screenshot : take a screenshot :)
+ if (action.GetID() == ACTION_TAKE_SCREENSHOT)
+ {
+ CScreenShot::TakeScreenshot();
+ return true;
+ }
+ // Display HDR : toggle HDR on/off
+ if (action.GetID() == ACTION_HDR_TOGGLE)
+ {
+ // Only enables manual HDR toggle if no video is playing or auto HDR switch is disabled
+ if (appPlayer->IsPlayingVideo() &&
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CServiceBroker::GetWinSystem()->SETTING_WINSYSTEM_IS_HDR_DISPLAY))
+ return true;
+
+ HDR_STATUS hdrStatus = CServiceBroker::GetWinSystem()->ToggleHDR();
+
+ if (hdrStatus == HDR_STATUS::HDR_OFF)
+ {
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34220),
+ g_localizeStrings.Get(34221));
+ }
+ else if (hdrStatus == HDR_STATUS::HDR_ON)
+ {
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34220),
+ g_localizeStrings.Get(34222));
+ }
+ return true;
+ }
+ // Tone Mapping : switch to next tone map method
+ if (action.GetID() == ACTION_CYCLE_TONEMAP_METHOD)
+ {
+ // Only enables tone mapping switch if display is not HDR capable or HDR is not enabled
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CServiceBroker::GetWinSystem()->SETTING_WINSYSTEM_IS_HDR_DISPLAY) &&
+ CServiceBroker::GetWinSystem()->IsHDRDisplay())
+ return true;
+
+ if (appPlayer->IsPlayingVideo())
+ {
+ CVideoSettings vs = appPlayer->GetVideoSettings();
+ vs.m_ToneMapMethod = static_cast<ETONEMAPMETHOD>(static_cast<int>(vs.m_ToneMapMethod) + 1);
+ if (vs.m_ToneMapMethod >= VS_TONEMAPMETHOD_MAX)
+ vs.m_ToneMapMethod =
+ static_cast<ETONEMAPMETHOD>(static_cast<int>(VS_TONEMAPMETHOD_OFF) + 1);
+
+ appPlayer->SetVideoSettings(vs);
+
+ int code = 0;
+ switch (vs.m_ToneMapMethod)
+ {
+ case VS_TONEMAPMETHOD_REINHARD:
+ code = 36555;
+ break;
+ case VS_TONEMAPMETHOD_ACES:
+ code = 36557;
+ break;
+ case VS_TONEMAPMETHOD_HABLE:
+ code = 36558;
+ break;
+ default:
+ throw std::logic_error("Tonemapping method not found. Did you forget to add a mapping?");
+ }
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34224),
+ g_localizeStrings.Get(code), 1000, false, 500);
+ }
+ return true;
+ }
+ // built in functions : execute the built-in
+ if (action.GetID() == ACTION_BUILT_IN_FUNCTION)
+ {
+ if (!CBuiltins::GetInstance().IsSystemPowerdownCommand(action.GetName()) ||
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown())
+ {
+ CBuiltins::GetInstance().Execute(action.GetName());
+ GetComponent<CApplicationPowerHandling>()->ResetNavigationTimer();
+ }
+ return true;
+ }
+
+ // reload keymaps
+ if (action.GetID() == ACTION_RELOAD_KEYMAPS)
+ CServiceBroker::GetInputManager().ReloadKeymaps();
+
+ // show info : Shows the current video or song information
+ if (action.GetID() == ACTION_SHOW_INFO)
+ {
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().ToggleShowInfo();
+ return true;
+ }
+
+ if (action.GetID() == ACTION_SET_RATING && appPlayer->IsPlayingAudio())
+ {
+ int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_itemCurrentFile->GetMusicInfoTag()->GetUserrating());
+ if (userrating < 0) // Nothing selected, so user rating unchanged
+ return true;
+ userrating = std::min(userrating, 10);
+ if (userrating != m_itemCurrentFile->GetMusicInfoTag()->GetUserrating())
+ {
+ m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating);
+ // Mirror changes to GUI item
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);
+
+ // Asynchronously update song userrating in music library
+ MUSIC_UTILS::UpdateSongRatingJob(m_itemCurrentFile, userrating);
+
+ // Tell all windows (e.g. playlistplayer, media windows) to update the fileitem
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ }
+ return true;
+ }
+
+ else if ((action.GetID() == ACTION_INCREASE_RATING || action.GetID() == ACTION_DECREASE_RATING) &&
+ appPlayer->IsPlayingAudio())
+ {
+ int userrating = m_itemCurrentFile->GetMusicInfoTag()->GetUserrating();
+ bool needsUpdate(false);
+ if (userrating > 0 && action.GetID() == ACTION_DECREASE_RATING)
+ {
+ m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating - 1);
+ needsUpdate = true;
+ }
+ else if (userrating < 10 && action.GetID() == ACTION_INCREASE_RATING)
+ {
+ m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating + 1);
+ needsUpdate = true;
+ }
+ if (needsUpdate)
+ {
+ // Mirror changes to current GUI item
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);
+
+ // Asynchronously update song userrating in music library
+ MUSIC_UTILS::UpdateSongRatingJob(m_itemCurrentFile, m_itemCurrentFile->GetMusicInfoTag()->GetUserrating());
+
+ // send a message to all windows to tell them to update the fileitem (eg playlistplayer, media windows)
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ }
+
+ return true;
+ }
+ else if ((action.GetID() == ACTION_INCREASE_RATING || action.GetID() == ACTION_DECREASE_RATING) &&
+ appPlayer->IsPlayingVideo())
+ {
+ int rating = m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating;
+ bool needsUpdate(false);
+ if (rating > 1 && action.GetID() == ACTION_DECREASE_RATING)
+ {
+ m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating = rating - 1;
+ needsUpdate = true;
+ }
+ else if (rating < 10 && action.GetID() == ACTION_INCREASE_RATING)
+ {
+ m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating = rating + 1;
+ needsUpdate = true;
+ }
+ if (needsUpdate)
+ {
+ // Mirror changes to GUI item
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);
+
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ db.SetVideoUserRating(m_itemCurrentFile->GetVideoInfoTag()->m_iDbId,
+ m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating,
+ m_itemCurrentFile->GetVideoInfoTag()->m_type);
+ db.Close();
+ }
+ // send a message to all windows to tell them to update the fileitem (eg playlistplayer, media windows)
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ }
+ return true;
+ }
+
+ // Now check with the playlist player if action can be handled.
+ // In case of ACTION_PREV_ITEM, we only allow the playlist player to take it if we're less than ACTION_PREV_ITEM_THRESHOLD seconds into playback.
+ if (!(action.GetID() == ACTION_PREV_ITEM && appPlayer->CanSeek() &&
+ GetTime() > ACTION_PREV_ITEM_THRESHOLD))
+ {
+ if (CServiceBroker::GetPlaylistPlayer().OnAction(action))
+ return true;
+ }
+
+ // Now check with the player if action can be handled.
+ bool bIsPlayingPVRChannel = (CServiceBroker::GetPVRManager().IsStarted() &&
+ CurrentFileItem().IsPVRChannel());
+
+ bool bNotifyPlayer = false;
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
+ bNotifyPlayer = true;
+ else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME)
+ bNotifyPlayer = true;
+ else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION && bIsPlayingPVRChannel)
+ bNotifyPlayer = true;
+ else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_DIALOG_VIDEO_OSD ||
+ (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_DIALOG_MUSIC_OSD && bIsPlayingPVRChannel))
+ {
+ switch (action.GetID())
+ {
+ case ACTION_NEXT_ITEM:
+ case ACTION_PREV_ITEM:
+ case ACTION_CHANNEL_UP:
+ case ACTION_CHANNEL_DOWN:
+ bNotifyPlayer = true;
+ break;
+ default:
+ break;
+ }
+ }
+ else if (action.GetID() == ACTION_STOP)
+ bNotifyPlayer = true;
+
+ if (bNotifyPlayer)
+ {
+ if (appPlayer->OnAction(action))
+ return true;
+ }
+
+ // stop : stops playing current audio song
+ if (action.GetID() == ACTION_STOP)
+ {
+ StopPlaying();
+ return true;
+ }
+
+ // In case the playlist player nor the player didn't handle PREV_ITEM, because we are past the ACTION_PREV_ITEM_THRESHOLD secs limit.
+ // If so, we just jump to the start of the track.
+ if (action.GetID() == ACTION_PREV_ITEM && appPlayer->CanSeek())
+ {
+ SeekTime(0);
+ appPlayer->SetPlaySpeed(1);
+ return true;
+ }
+
+ // forward action to graphic context and see if it can handle it
+ if (CServiceBroker::GetGUI()->GetStereoscopicsManager().OnAction(action))
+ return true;
+
+ if (appPlayer->IsPlaying())
+ {
+ // forward channel switches to the player - he knows what to do
+ if (action.GetID() == ACTION_CHANNEL_UP || action.GetID() == ACTION_CHANNEL_DOWN)
+ {
+ appPlayer->OnAction(action);
+ return true;
+ }
+
+ // pause : toggle pause action
+ if (action.GetID() == ACTION_PAUSE)
+ {
+ appPlayer->Pause();
+ // go back to normal play speed on unpause
+ if (!appPlayer->IsPaused() && appPlayer->GetPlaySpeed() != 1)
+ appPlayer->SetPlaySpeed(1);
+
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetAudioManager().Enable(appPlayer->IsPaused());
+ return true;
+ }
+ // play: unpause or set playspeed back to normal
+ if (action.GetID() == ACTION_PLAYER_PLAY)
+ {
+ // if currently paused - unpause
+ if (appPlayer->IsPaused())
+ return OnAction(CAction(ACTION_PAUSE));
+ // if we do a FF/RW then go back to normal speed
+ if (appPlayer->GetPlaySpeed() != 1)
+ appPlayer->SetPlaySpeed(1);
+ return true;
+ }
+ if (!appPlayer->IsPaused())
+ {
+ if (action.GetID() == ACTION_PLAYER_FORWARD || action.GetID() == ACTION_PLAYER_REWIND)
+ {
+ float playSpeed = appPlayer->GetPlaySpeed();
+
+ if (action.GetID() == ACTION_PLAYER_REWIND && (playSpeed == 1)) // Enables Rewinding
+ playSpeed *= -2;
+ else if (action.GetID() == ACTION_PLAYER_REWIND && playSpeed > 1) //goes down a notch if you're FFing
+ playSpeed /= 2;
+ else if (action.GetID() == ACTION_PLAYER_FORWARD && playSpeed < 1) //goes up a notch if you're RWing
+ playSpeed /= 2;
+ else
+ playSpeed *= 2;
+
+ if (action.GetID() == ACTION_PLAYER_FORWARD && playSpeed == -1) //sets iSpeed back to 1 if -1 (didn't plan for a -1)
+ playSpeed = 1;
+ if (playSpeed > 32 || playSpeed < -32)
+ playSpeed = 1;
+
+ appPlayer->SetPlaySpeed(playSpeed);
+ return true;
+ }
+ else if ((action.GetAmount() || appPlayer->GetPlaySpeed() != 1) &&
+ (action.GetID() == ACTION_ANALOG_REWIND || action.GetID() == ACTION_ANALOG_FORWARD))
+ {
+ // calculate the speed based on the amount the button is held down
+ int iPower = (int)(action.GetAmount() * MAX_FFWD_SPEED + 0.5f);
+ // amount can be negative, for example rewind and forward share the same axis
+ iPower = std::abs(iPower);
+ // returns 0 -> MAX_FFWD_SPEED
+ int iSpeed = 1 << iPower;
+ if (iSpeed != 1 && action.GetID() == ACTION_ANALOG_REWIND)
+ iSpeed = -iSpeed;
+ appPlayer->SetPlaySpeed(static_cast<float>(iSpeed));
+ if (iSpeed == 1)
+ CLog::Log(LOGDEBUG,"Resetting playspeed");
+ return true;
+ }
+ }
+ // allow play to unpause
+ else
+ {
+ if (action.GetID() == ACTION_PLAYER_PLAY)
+ {
+ // unpause, and set the playspeed back to normal
+ appPlayer->Pause();
+
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetAudioManager().Enable(appPlayer->IsPaused());
+
+ appPlayer->SetPlaySpeed(1);
+ return true;
+ }
+ }
+ }
+
+
+ if (action.GetID() == ACTION_SWITCH_PLAYER)
+ {
+ const CPlayerCoreFactory &playerCoreFactory = m_ServiceManager->GetPlayerCoreFactory();
+
+ if (appPlayer->IsPlaying())
+ {
+ std::vector<std::string> players;
+ CFileItem item(*m_itemCurrentFile.get());
+ playerCoreFactory.GetPlayers(item, players);
+ std::string player = playerCoreFactory.SelectPlayerDialog(players);
+ if (!player.empty())
+ {
+ item.SetStartOffset(CUtil::ConvertSecsToMilliSecs(GetTime()));
+ PlayFile(item, player, true);
+ }
+ }
+ else
+ {
+ std::vector<std::string> players;
+ playerCoreFactory.GetRemotePlayers(players);
+ std::string player = playerCoreFactory.SelectPlayerDialog(players);
+ if (!player.empty())
+ {
+ PlayFile(CFileItem(), player, false);
+ }
+ }
+ }
+
+ if (CServiceBroker::GetPeripherals().OnAction(action))
+ return true;
+
+ if (action.GetID() == ACTION_MUTE)
+ {
+ const auto appVolume = GetComponent<CApplicationVolumeHandling>();
+ appVolume->ToggleMute();
+ appVolume->ShowVolumeBar(&action);
+ return true;
+ }
+
+ if (action.GetID() == ACTION_TOGGLE_DIGITAL_ANALOG)
+ {
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ bool passthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH);
+ settings->SetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH, !passthrough);
+
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SETTINGS_SYSTEM)
+ {
+ CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0,0,WINDOW_INVALID,CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ }
+ return true;
+ }
+
+ // Check for global volume control
+ if ((action.GetAmount() && (action.GetID() == ACTION_VOLUME_UP || action.GetID() == ACTION_VOLUME_DOWN)) || action.GetID() == ACTION_VOLUME_SET)
+ {
+ const auto appVolume = GetComponent<CApplicationVolumeHandling>();
+ if (!appPlayer->IsPassthrough())
+ {
+ if (appVolume->IsMuted())
+ appVolume->UnMute();
+ float volume = appVolume->GetVolumeRatio();
+ int volumesteps = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOOUTPUT_VOLUMESTEPS);
+ // sanity check
+ if (volumesteps == 0)
+ volumesteps = 90;
+
+// Android has steps based on the max available volume level
+#if defined(TARGET_ANDROID)
+ float step = (CApplicationVolumeHandling::VOLUME_MAXIMUM -
+ CApplicationVolumeHandling::VOLUME_MINIMUM) /
+ CXBMCApp::GetMaxSystemVolume();
+#else
+ float step = (CApplicationVolumeHandling::VOLUME_MAXIMUM -
+ CApplicationVolumeHandling::VOLUME_MINIMUM) /
+ volumesteps;
+
+ if (action.GetRepeat())
+ step *= action.GetRepeat() * 50; // 50 fps
+#endif
+ if (action.GetID() == ACTION_VOLUME_UP)
+ volume += action.GetAmount() * action.GetAmount() * step;
+ else if (action.GetID() == ACTION_VOLUME_DOWN)
+ volume -= action.GetAmount() * action.GetAmount() * step;
+ else
+ volume = action.GetAmount() * step;
+ if (volume != appVolume->GetVolumeRatio())
+ appVolume->SetVolume(volume, false);
+ }
+ // show visual feedback of volume or passthrough indicator
+ appVolume->ShowVolumeBar(&action);
+ return true;
+ }
+
+ if (action.GetID() == ACTION_GUIPROFILE_BEGIN)
+ {
+ CGUIControlProfiler::Instance().SetOutputFile(CSpecialProtocol::TranslatePath("special://home/guiprofiler.xml"));
+ CGUIControlProfiler::Instance().Start();
+ return true;
+ }
+ if (action.GetID() == ACTION_SHOW_PLAYLIST)
+ {
+ const PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ if (playlistId == PLAYLIST::TYPE_VIDEO &&
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_VIDEO_PLAYLIST)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST);
+ }
+ else if (playlistId == PLAYLIST::TYPE_MUSIC &&
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() !=
+ WINDOW_MUSIC_PLAYLIST)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST);
+ }
+ return true;
+ }
+ return false;
+}
+
+int CApplication::GetMessageMask()
+{
+ return TMSG_MASK_APPLICATION;
+}
+
+void CApplication::OnApplicationMessage(ThreadMessage* pMsg)
+{
+ uint32_t msg = pMsg->dwMessage;
+ if (msg == TMSG_SYSTEM_POWERDOWN)
+ {
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown())
+ msg = pMsg->param1; // perform requested shutdown action
+ else
+ return; // no shutdown
+ }
+
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+
+ switch (msg)
+ {
+ case TMSG_POWERDOWN:
+ if (Stop(EXITCODE_POWERDOWN))
+ CServiceBroker::GetPowerManager().Powerdown();
+ break;
+
+ case TMSG_QUIT:
+ Stop(EXITCODE_QUIT);
+ break;
+
+ case TMSG_SHUTDOWN:
+ GetComponent<CApplicationPowerHandling>()->HandleShutdownMessage();
+ break;
+
+ case TMSG_RENDERER_FLUSH:
+ appPlayer->FlushRenderer();
+ break;
+
+ case TMSG_HIBERNATE:
+ CServiceBroker::GetPowerManager().Hibernate();
+ break;
+
+ case TMSG_SUSPEND:
+ CServiceBroker::GetPowerManager().Suspend();
+ break;
+
+ case TMSG_RESTART:
+ case TMSG_RESET:
+ if (Stop(EXITCODE_REBOOT))
+ CServiceBroker::GetPowerManager().Reboot();
+ break;
+
+ case TMSG_RESTARTAPP:
+#if defined(TARGET_WINDOWS) || defined(TARGET_LINUX)
+ Stop(EXITCODE_RESTARTAPP);
+#endif
+ break;
+
+ case TMSG_INHIBITIDLESHUTDOWN:
+ GetComponent<CApplicationPowerHandling>()->InhibitIdleShutdown(pMsg->param1 != 0);
+ break;
+
+ case TMSG_INHIBITSCREENSAVER:
+ GetComponent<CApplicationPowerHandling>()->InhibitScreenSaver(pMsg->param1 != 0);
+ break;
+
+ case TMSG_ACTIVATESCREENSAVER:
+ GetComponent<CApplicationPowerHandling>()->ActivateScreenSaver();
+ break;
+
+ case TMSG_RESETSCREENSAVER:
+ GetComponent<CApplicationPowerHandling>()->m_bResetScreenSaver = true;
+ break;
+
+ case TMSG_VOLUME_SHOW:
+ {
+ CAction action(pMsg->param1);
+ GetComponent<CApplicationVolumeHandling>()->ShowVolumeBar(&action);
+ }
+ break;
+
+#ifdef TARGET_ANDROID
+ case TMSG_DISPLAY_SETUP:
+ // We might come from a refresh rate switch destroying the native window; use the context resolution
+ *static_cast<bool*>(pMsg->lpVoid) = InitWindow(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution());
+ GetComponent<CApplicationPowerHandling>()->SetRenderGUI(true);
+ break;
+
+ case TMSG_DISPLAY_DESTROY:
+ *static_cast<bool*>(pMsg->lpVoid) = CServiceBroker::GetWinSystem()->DestroyWindow();
+ GetComponent<CApplicationPowerHandling>()->SetRenderGUI(false);
+ break;
+#endif
+
+ case TMSG_START_ANDROID_ACTIVITY:
+ {
+#if defined(TARGET_ANDROID)
+ if (pMsg->params.size())
+ {
+ CXBMCApp::StartActivity(pMsg->params[0], pMsg->params.size() > 1 ? pMsg->params[1] : "",
+ pMsg->params.size() > 2 ? pMsg->params[2] : "",
+ pMsg->params.size() > 3 ? pMsg->params[3] : "",
+ pMsg->params.size() > 4 ? pMsg->params[4] : "",
+ pMsg->params.size() > 5 ? pMsg->params[5] : "",
+ pMsg->params.size() > 6 ? pMsg->params[6] : "",
+ pMsg->params.size() > 7 ? pMsg->params[7] : "",
+ pMsg->params.size() > 8 ? pMsg->params[8] : "");
+ }
+#endif
+ }
+ break;
+
+ case TMSG_NETWORKMESSAGE:
+ m_ServiceManager->GetNetwork().NetworkMessage(static_cast<CNetworkBase::EMESSAGE>(pMsg->param1),
+ pMsg->param2);
+ break;
+
+ case TMSG_SETLANGUAGE:
+ SetLanguage(pMsg->strParam);
+ break;
+
+
+ case TMSG_SWITCHTOFULLSCREEN:
+ {
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetWindowManager().SwitchToFullScreen(true);
+ break;
+ }
+ case TMSG_VIDEORESIZE:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEORESIZE;
+ newEvent.resize.w = pMsg->param1;
+ newEvent.resize.h = pMsg->param2;
+ OnEvent(newEvent);
+ CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
+ }
+ break;
+
+ case TMSG_SETVIDEORESOLUTION:
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(static_cast<RESOLUTION>(pMsg->param1), pMsg->param2 == 1);
+ break;
+
+ case TMSG_TOGGLEFULLSCREEN:
+ CServiceBroker::GetWinSystem()->GetGfxContext().ToggleFullScreen();
+ appPlayer->TriggerUpdateResolution();
+ break;
+
+ case TMSG_MINIMIZE:
+ CServiceBroker::GetWinSystem()->Minimize();
+ break;
+
+ case TMSG_EXECUTE_OS:
+ // Suspend AE temporarily so exclusive or hog-mode sinks
+ // don't block external player's access to audio device
+ IAE *audioengine;
+ audioengine = CServiceBroker::GetActiveAE();
+ if (audioengine)
+ {
+ if (!audioengine->Suspend())
+ {
+ CLog::Log(LOGINFO, "{}: Failed to suspend AudioEngine before launching external program",
+ __FUNCTION__);
+ }
+ }
+#if defined(TARGET_DARWIN)
+ CLog::Log(LOGINFO, "ExecWait is not implemented on this platform");
+#elif defined(TARGET_POSIX)
+ CUtil::RunCommandLine(pMsg->strParam, (pMsg->param1 == 1));
+#elif defined(TARGET_WINDOWS)
+ CWIN32Util::XBMCShellExecute(pMsg->strParam.c_str(), (pMsg->param1 == 1));
+#endif
+ // Resume AE processing of XBMC native audio
+ if (audioengine)
+ {
+ if (!audioengine->Resume())
+ {
+ CLog::Log(LOGFATAL, "{}: Failed to restart AudioEngine after return from external player",
+ __FUNCTION__);
+ }
+ }
+ break;
+
+ case TMSG_EXECUTE_SCRIPT:
+ CScriptInvocationManager::GetInstance().ExecuteAsync(pMsg->strParam);
+ break;
+
+ case TMSG_EXECUTE_BUILT_IN:
+ CBuiltins::GetInstance().Execute(pMsg->strParam);
+ break;
+
+ case TMSG_PICTURE_SHOW:
+ {
+ CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (!pSlideShow) return;
+
+ // stop playing file
+ if (appPlayer->IsPlayingVideo())
+ StopPlaying();
+
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
+ CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
+
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+ appPower->ResetScreenSaver();
+ appPower->WakeUpScreenSaverAndDPMS();
+
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_SLIDESHOW)
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
+ if (URIUtils::IsZIP(pMsg->strParam) || URIUtils::IsRAR(pMsg->strParam)) // actually a cbz/cbr
+ {
+ CFileItemList items;
+ CURL pathToUrl;
+ if (URIUtils::IsZIP(pMsg->strParam))
+ pathToUrl = URIUtils::CreateArchivePath("zip", CURL(pMsg->strParam), "");
+ else
+ pathToUrl = URIUtils::CreateArchivePath("rar", CURL(pMsg->strParam), "");
+
+ CUtil::GetRecursiveListing(pathToUrl.Get(), items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), XFILE::DIR_FLAG_NO_FILE_DIRS);
+ if (items.Size() > 0)
+ {
+ pSlideShow->Reset();
+ for (int i = 0; i<items.Size(); ++i)
+ {
+ pSlideShow->Add(items[i].get());
+ }
+ pSlideShow->Select(items[0]->GetPath());
+ }
+ }
+ else
+ {
+ CFileItem item(pMsg->strParam, false);
+ pSlideShow->Reset();
+ pSlideShow->Add(&item);
+ pSlideShow->Select(pMsg->strParam);
+ }
+ }
+ break;
+
+ case TMSG_PICTURE_SLIDESHOW:
+ {
+ CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (!pSlideShow) return;
+
+ if (appPlayer->IsPlayingVideo())
+ StopPlaying();
+
+ pSlideShow->Reset();
+
+ CFileItemList items;
+ std::string strPath = pMsg->strParam;
+ std::string extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
+ if (pMsg->param1)
+ extensions += "|.tbn";
+ CUtil::GetRecursiveListing(strPath, items, extensions);
+
+ if (items.Size() > 0)
+ {
+ for (int i = 0; i<items.Size(); ++i)
+ pSlideShow->Add(items[i].get());
+ pSlideShow->StartSlideShow(); //Start the slideshow!
+ }
+
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_SLIDESHOW)
+ {
+ if (items.Size() == 0)
+ {
+ CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_SCREENSAVER_MODE, "screensaver.xbmc.builtin.dim");
+ GetComponent<CApplicationPowerHandling>()->ActivateScreenSaver();
+ }
+ else
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
+ }
+
+ }
+ break;
+
+ case TMSG_LOADPROFILE:
+ {
+ const int profile = pMsg->param1;
+ if (profile >= 0)
+ CServiceBroker::GetSettingsComponent()->GetProfileManager()->LoadProfile(static_cast<unsigned int>(profile));
+ }
+
+ break;
+
+ case TMSG_EVENT:
+ {
+ if (pMsg->lpVoid)
+ {
+ XBMC_Event* event = static_cast<XBMC_Event*>(pMsg->lpVoid);
+ OnEvent(*event);
+ delete event;
+ }
+ }
+ break;
+
+ case TMSG_UPDATE_PLAYER_ITEM:
+ {
+ std::unique_ptr<CFileItem> item{static_cast<CFileItem*>(pMsg->lpVoid)};
+ if (item)
+ {
+ m_itemCurrentFile->UpdateInfo(*item);
+ CServiceBroker::GetGUI()->GetInfoManager().UpdateCurrentItem(*m_itemCurrentFile);
+ }
+ }
+ break;
+
+ default:
+ CLog::Log(LOGERROR, "{}: Unhandled threadmessage sent, {}", __FUNCTION__, msg);
+ break;
+ }
+}
+
+void CApplication::LockFrameMoveGuard()
+{
+ ++m_WaitingExternalCalls;
+ m_frameMoveGuard.lock();
+ ++m_ProcessedExternalCalls;
+ CServiceBroker::GetWinSystem()->GetGfxContext().lock();
+};
+
+void CApplication::UnlockFrameMoveGuard()
+{
+ --m_WaitingExternalCalls;
+ CServiceBroker::GetWinSystem()->GetGfxContext().unlock();
+ m_frameMoveGuard.unlock();
+};
+
+void CApplication::FrameMove(bool processEvents, bool processGUI)
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ bool renderGUI = GetComponent<CApplicationPowerHandling>()->GetRenderGUI();
+ if (processEvents)
+ {
+ // currently we calculate the repeat time (ie time from last similar keypress) just global as fps
+ float frameTime = m_frameTime.GetElapsedSeconds();
+ m_frameTime.StartZero();
+ // never set a frametime less than 2 fps to avoid problems when debugging and on breaks
+ if (frameTime > 0.5f)
+ frameTime = 0.5f;
+
+ if (processGUI && renderGUI)
+ {
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ // check if there are notifications to display
+ CGUIDialogKaiToast *toast = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKaiToast>(WINDOW_DIALOG_KAI_TOAST);
+ if (toast && toast->DoWork())
+ {
+ if (!toast->IsDialogRunning())
+ {
+ toast->Open();
+ }
+ }
+ }
+
+ HandlePortEvents();
+ CServiceBroker::GetInputManager().Process(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), frameTime);
+
+ if (processGUI && renderGUI)
+ {
+ m_pInertialScrollingHandler->ProcessInertialScroll(frameTime);
+ appPlayer->GetSeekHandler().FrameMove();
+ }
+
+ // Open the door for external calls e.g python exactly here.
+ // Window size can be between 2 and 10ms and depends on number of continuous requests
+ if (m_WaitingExternalCalls)
+ {
+ CSingleExit ex(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_frameMoveGuard.unlock();
+
+ // Calculate a window size between 2 and 10ms, 4 continuous requests let the window grow by 1ms
+ // When not playing video we allow it to increase to 80ms
+ unsigned int max_sleep = 10;
+ if (!appPlayer->IsPlayingVideo() || appPlayer->IsPausedPlayback())
+ max_sleep = 80;
+ unsigned int sleepTime = std::max(static_cast<unsigned int>(2), std::min(m_ProcessedExternalCalls >> 2, max_sleep));
+ KODI::TIME::Sleep(std::chrono::milliseconds(sleepTime));
+ m_frameMoveGuard.lock();
+ m_ProcessedExternalDecay = 5;
+ }
+ if (m_ProcessedExternalDecay && --m_ProcessedExternalDecay == 0)
+ m_ProcessedExternalCalls = 0;
+ }
+
+ if (processGUI && renderGUI)
+ {
+ m_skipGuiRender = false;
+
+ /*! @todo look into the possibility to use this for GBM
+ int fps = 0;
+
+ // This code reduces rendering fps of the GUI layer when playing videos in fullscreen mode
+ // it makes only sense on architectures with multiple layers
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() && !m_appPlayer.IsPausedPlayback() && m_appPlayer.IsRenderingVideoLayer())
+ fps = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_LIMITGUIUPDATE);
+
+ auto now = std::chrono::steady_clock::now();
+
+ auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastRenderTime).count();
+ if (fps > 0 && frameTime * fps < 1000)
+ m_skipGuiRender = true;
+ */
+
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiSmartRedraw && m_guiRefreshTimer.IsTimePast())
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_TIMER, 0, 0);
+ m_guiRefreshTimer.Set(500ms);
+ }
+
+ if (!m_bStop)
+ {
+ if (!m_skipGuiRender)
+ CServiceBroker::GetGUI()->GetWindowManager().Process(CTimeUtils::GetFrameTime());
+ }
+ CServiceBroker::GetGUI()->GetWindowManager().FrameMove();
+ }
+
+ appPlayer->FrameMove();
+
+ // this will go away when render systems gets its own thread
+ CServiceBroker::GetWinSystem()->DriveRenderLoop();
+}
+
+
+void CApplication::ResetCurrentItem()
+{
+ m_itemCurrentFile->Reset();
+ if (m_pGUI)
+ m_pGUI->GetInfoManager().ResetCurrentItem();
+}
+
+int CApplication::Run()
+{
+ CLog::Log(LOGINFO, "Running the application...");
+
+ std::chrono::time_point<std::chrono::steady_clock> lastFrameTime;
+ std::chrono::milliseconds frameTime;
+ const unsigned int noRenderFrameTime = 15; // Simulates ~66fps
+
+ CFileItemList& playlist = CServiceBroker::GetAppParams()->GetPlaylist();
+ if (playlist.Size() > 0)
+ {
+ CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST::TYPE_MUSIC, playlist);
+ CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC);
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_PLAY, -1);
+ }
+
+ // Run the app
+ while (!m_bStop)
+ {
+ // Animate and render a frame
+
+ lastFrameTime = std::chrono::steady_clock::now();
+ Process();
+
+ bool renderGUI = GetComponent<CApplicationPowerHandling>()->GetRenderGUI();
+ if (!m_bStop)
+ {
+ FrameMove(true, renderGUI);
+ }
+
+ if (renderGUI && !m_bStop)
+ {
+ Render();
+ }
+ else if (!renderGUI)
+ {
+ auto now = std::chrono::steady_clock::now();
+ frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastFrameTime);
+ if (frameTime.count() < noRenderFrameTime)
+ KODI::TIME::Sleep(std::chrono::milliseconds(noRenderFrameTime - frameTime.count()));
+ }
+ }
+
+ Cleanup();
+
+ CLog::Log(LOGINFO, "Exiting the application...");
+ return m_ExitCode;
+}
+
+bool CApplication::Cleanup()
+{
+ try
+ {
+ ResetCurrentItem();
+ StopPlaying();
+
+ if (m_ServiceManager)
+ m_ServiceManager->DeinitStageThree();
+
+ CServiceBroker::UnregisterSpeechRecognition();
+
+ CLog::Log(LOGINFO, "unload skin");
+ GetComponent<CApplicationSkinHandling>()->UnloadSkin();
+
+ CServiceBroker::UnregisterTextureCache();
+
+ // stop all remaining scripts; must be done after skin has been unloaded,
+ // not before some windows still need it when deinitializing during skin
+ // unloading
+ CScriptInvocationManager::GetInstance().Uninitialize();
+
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+ appPower->m_globalScreensaverInhibitor.Release();
+ appPower->m_screensaverInhibitor.Release();
+
+ CRenderSystemBase *renderSystem = CServiceBroker::GetRenderSystem();
+ if (renderSystem)
+ renderSystem->DestroyRenderSystem();
+
+ CWinSystemBase *winSystem = CServiceBroker::GetWinSystem();
+ if (winSystem)
+ winSystem->DestroyWindow();
+
+ if (m_pGUI)
+ m_pGUI->GetWindowManager().DestroyWindows();
+
+ CLog::Log(LOGINFO, "unload sections");
+
+ // Shutdown as much as possible of the
+ // application, to reduce the leaks dumped
+ // to the vc output window before calling
+ // _CrtDumpMemoryLeaks(). Most of the leaks
+ // shown are no real leaks, as parts of the app
+ // are still allocated.
+
+ g_localizeStrings.Clear();
+ g_LangCodeExpander.Clear();
+ g_charsetConverter.clear();
+ g_directoryCache.Clear();
+ //CServiceBroker::GetInputManager().ClearKeymaps(); //! @todo
+ CEventServer::RemoveInstance();
+ DllLoaderContainer::Clear();
+ CServiceBroker::GetPlaylistPlayer().Clear();
+
+ if (m_ServiceManager)
+ m_ServiceManager->DeinitStageTwo();
+
+#ifdef TARGET_POSIX
+ CXHandle::DumpObjectTracker();
+
+#ifdef HAS_DVD_DRIVE
+ CLibcdio::ReleaseInstance();
+#endif
+#endif
+#ifdef _CRTDBG_MAP_ALLOC
+ _CrtDumpMemoryLeaks();
+ while(1); // execution ends
+#endif
+
+ if (m_pGUI)
+ {
+ m_pGUI->Deinit();
+ m_pGUI.reset();
+ }
+
+ if (winSystem)
+ {
+ winSystem->DestroyWindowSystem();
+ CServiceBroker::UnregisterWinSystem();
+ winSystem = nullptr;
+ m_pWinSystem.reset();
+ }
+
+ // Cleanup was called more than once on exit during my tests
+ if (m_ServiceManager)
+ {
+ m_ServiceManager->DeinitStageOne();
+ m_ServiceManager.reset();
+ }
+
+ CServiceBroker::UnregisterKeyboardLayoutManager();
+
+ CServiceBroker::UnregisterAppMessenger();
+
+ CServiceBroker::UnregisterAnnouncementManager();
+ m_pAnnouncementManager->Deinitialize();
+ m_pAnnouncementManager.reset();
+
+ CServiceBroker::UnregisterJobManager();
+ CServiceBroker::UnregisterCPUInfo();
+
+ UnregisterSettings();
+
+ m_bInitializing = true;
+
+ return true;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "Exception in CApplication::Cleanup()");
+ return false;
+ }
+}
+
+bool CApplication::Stop(int exitCode)
+{
+#if defined(TARGET_ANDROID)
+ // Note: On Android, the app must be stopped asynchronously, once Android has
+ // signalled that the app shall be destroyed. See android_main() implementation.
+ if (!CXBMCApp::Get().Stop(exitCode))
+ return false;
+#endif
+
+ CLog::Log(LOGINFO, "Stopping the application...");
+
+ bool success = true;
+
+ CLog::Log(LOGINFO, "Stopping player");
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ appPlayer->ClosePlayer();
+
+ {
+ // close inbound port
+ CServiceBroker::UnregisterAppPort();
+ XbmcThreads::EndTime<> timer(1000ms);
+ while (m_pAppPort.use_count() > 1)
+ {
+ KODI::TIME::Sleep(100ms);
+ if (timer.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "CApplication::Stop - CAppPort still in use, app may crash");
+ break;
+ }
+ }
+ m_pAppPort.reset();
+ }
+
+ try
+ {
+ m_frameMoveGuard.unlock();
+
+ CVariant vExitCode(CVariant::VariantTypeObject);
+ vExitCode["exitcode"] = exitCode;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::System, "OnQuit", vExitCode);
+
+ // Abort any active screensaver
+ GetComponent<CApplicationPowerHandling>()->WakeUpScreenSaverAndDPMS();
+
+ g_alarmClock.StopThread();
+
+ CLog::Log(LOGINFO, "Storing total System Uptime");
+ g_sysinfo.SetTotalUptime(g_sysinfo.GetTotalUptime() + (int)(CTimeUtils::GetFrameTime() / 60000));
+
+ // Update the settings information (volume, uptime etc. need saving)
+ if (CFile::Exists(CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetSettingsFile()))
+ {
+ CLog::Log(LOGINFO, "Saving settings");
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
+ }
+ else
+ CLog::Log(LOGINFO, "Not saving settings (settings.xml is not present)");
+
+ // kodi may crash or deadlock during exit (shutdown / reboot) due to
+ // either a bug in core or misbehaving addons. so try saving
+ // skin settings early
+ CLog::Log(LOGINFO, "Saving skin settings");
+ if (g_SkinInfo != nullptr)
+ g_SkinInfo->SaveSettings();
+
+ m_bStop = true;
+ // Add this here to keep the same ordering behaviour for now
+ // Needs cleaning up
+ CServiceBroker::GetAppMessenger()->Stop();
+ m_AppFocused = false;
+ m_ExitCode = exitCode;
+ CLog::Log(LOGINFO, "Stopping all");
+
+ // cancel any jobs from the jobmanager
+ CServiceBroker::GetJobManager()->CancelJobs();
+
+ // stop scanning before we kill the network and so on
+ if (CMusicLibraryQueue::GetInstance().IsRunning())
+ CMusicLibraryQueue::GetInstance().CancelAllJobs();
+
+ if (CVideoLibraryQueue::GetInstance().IsRunning())
+ CVideoLibraryQueue::GetInstance().CancelAllJobs();
+
+ CServiceBroker::GetAppMessenger()->Cleanup();
+
+ m_ServiceManager->GetNetwork().NetworkMessage(CNetworkBase::SERVICES_DOWN, 0);
+
+#ifdef HAS_ZEROCONF
+ if(CZeroconfBrowser::IsInstantiated())
+ {
+ CLog::Log(LOGINFO, "Stopping zeroconf browser");
+ CZeroconfBrowser::GetInstance()->Stop();
+ CZeroconfBrowser::ReleaseInstance();
+ }
+#endif
+
+ for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
+ vfsAddon->DisconnectAll();
+
+#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
+ smb.Deinit();
+#endif
+
+#if defined(TARGET_DARWIN_OSX)
+ if (XBMCHelper::GetInstance().IsAlwaysOn() == false)
+ XBMCHelper::GetInstance().Stop();
+#endif
+
+ // Stop services before unloading Python
+ CServiceBroker::GetServiceAddons().Stop();
+
+ // Stop any other python scripts that may be looping waiting for monitor.abortRequested()
+ CScriptInvocationManager::GetInstance().StopRunningScripts();
+
+ // unregister action listeners
+ const auto appListener = GetComponent<CApplicationActionListeners>();
+ appListener->UnregisterActionListener(&GetComponent<CApplicationPlayer>()->GetSeekHandler());
+ appListener->UnregisterActionListener(&CPlayerController::GetInstance());
+
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetAudioManager().DeInitialize();
+
+ // shutdown the AudioEngine
+ CServiceBroker::UnregisterAE();
+ m_pActiveAE->Shutdown();
+ m_pActiveAE.reset();
+
+ CLog::Log(LOGINFO, "Application stopped");
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "Exception in CApplication::Stop()");
+ success = false;
+ }
+
+ cleanup_emu_environ();
+
+ KODI::TIME::Sleep(200ms);
+
+ return success;
+}
+
+namespace
+{
+class CCreateAndLoadPlayList : public IRunnable
+{
+public:
+ CCreateAndLoadPlayList(CFileItem& item, std::unique_ptr<PLAYLIST::CPlayList>& playlist)
+ : m_item(item), m_playlist(playlist)
+ {
+ }
+
+ void Run() override
+ {
+ const std::unique_ptr<PLAYLIST::CPlayList> playlist(PLAYLIST::CPlayListFactory::Create(m_item));
+ if (playlist)
+ {
+ if (playlist->Load(m_item.GetPath()))
+ *m_playlist = *playlist;
+ }
+ }
+
+private:
+ CFileItem& m_item;
+ std::unique_ptr<PLAYLIST::CPlayList>& m_playlist;
+};
+} // namespace
+
+bool CApplication::PlayMedia(CFileItem& item, const std::string& player, PLAYLIST::Id playlistId)
+{
+ // if the item is a plugin we need to resolve the plugin paths
+ if (URIUtils::HasPluginPath(item) && !XFILE::CPluginDirectory::GetResolvedPluginResult(item))
+ return false;
+
+ if (item.IsSmartPlayList())
+ {
+ CFileItemList items;
+ CUtil::GetRecursiveListing(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS);
+ if (items.Size())
+ {
+ CSmartPlaylist smartpl;
+ //get name and type of smartplaylist, this will always succeed as GetDirectory also did this.
+ smartpl.OpenAndReadName(item.GetURL());
+ PLAYLIST::CPlayList playlist;
+ playlist.Add(items);
+ PLAYLIST::Id smartplPlaylistId = PLAYLIST::TYPE_VIDEO;
+
+ if (smartpl.GetType() == "songs" || smartpl.GetType() == "albums" ||
+ smartpl.GetType() == "artists")
+ smartplPlaylistId = PLAYLIST::TYPE_MUSIC;
+
+ return ProcessAndStartPlaylist(smartpl.GetName(), playlist, smartplPlaylistId);
+ }
+ }
+ else if (item.IsPlayList() || item.IsInternetStream())
+ {
+ // Not owner. Dialog auto-deletes itself.
+ CGUIDialogCache* dlgCache =
+ new CGUIDialogCache(5s, g_localizeStrings.Get(10214), item.GetLabel());
+
+ //is or could be a playlist
+ std::unique_ptr<PLAYLIST::CPlayList> playlist;
+ CCreateAndLoadPlayList getPlaylist(item, playlist);
+ bool cancelled = !CGUIDialogBusy::Wait(&getPlaylist, 100, true);
+
+ if (dlgCache)
+ {
+ dlgCache->Close();
+ if (dlgCache->IsCanceled())
+ cancelled = true;
+ }
+
+ if (cancelled)
+ return true;
+
+ if (playlist)
+ {
+
+ if (playlistId != PLAYLIST::TYPE_NONE)
+ {
+ int track=0;
+ if (item.HasProperty("playlist_starting_track"))
+ track = (int)item.GetProperty("playlist_starting_track").asInteger();
+ return ProcessAndStartPlaylist(item.GetPath(), *playlist, playlistId, track);
+ }
+ else
+ {
+ CLog::Log(LOGWARNING,
+ "CApplication::PlayMedia called to play a playlist {} but no idea which playlist "
+ "to use, playing first item",
+ item.GetPath());
+ if (playlist->size())
+ return PlayFile(*(*playlist)[0], "", false);
+ }
+ }
+ }
+ else if (item.IsPVR())
+ {
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayMedia(item);
+ }
+
+ CURL path(item.GetPath());
+ if (path.GetProtocol() == "game")
+ {
+ AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(path.GetHostName(), addon, AddonType::GAMEDLL,
+ OnlyEnabled::CHOICE_YES))
+ {
+ CFileItem addonItem(addon);
+ return PlayFile(addonItem, player, false);
+ }
+ }
+
+ //nothing special just play
+ return PlayFile(item, player, false);
+}
+
+// PlayStack()
+// For playing a multi-file video. Particularly inefficient
+// on startup, as we are required to calculate the length
+// of each video, so we open + close each one in turn.
+// A faster calculation of video time would improve this
+// substantially.
+// return value: same with PlayFile()
+bool CApplication::PlayStack(CFileItem& item, bool bRestart)
+{
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+ if (!stackHelper->InitializeStack(item))
+ return false;
+
+ int startoffset = stackHelper->InitializeStackStartPartAndOffset(item);
+
+ CFileItem selectedStackPart = stackHelper->GetCurrentStackPartFileItem();
+ selectedStackPart.SetStartOffset(startoffset);
+
+ if (item.HasProperty("savedplayerstate"))
+ {
+ selectedStackPart.SetProperty("savedplayerstate", item.GetProperty("savedplayerstate")); // pass on to part
+ item.ClearProperty("savedplayerstate");
+ }
+
+ return PlayFile(selectedStackPart, "", true);
+}
+
+bool CApplication::PlayFile(CFileItem item, const std::string& player, bool bRestart)
+{
+ // Ensure the MIME type has been retrieved for http:// and shout:// streams
+ if (item.GetMimeType().empty())
+ item.FillInMimeType();
+
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (!bRestart)
+ {
+ // bRestart will be true when called from PlayStack(), skipping this block
+ appPlayer->SetPlaySpeed(1);
+
+ m_nextPlaylistItem = -1;
+ stackHelper->Clear();
+
+ if (item.IsVideo())
+ CUtil::ClearSubtitles();
+ }
+
+ if (item.IsDiscStub())
+ {
+ return CServiceBroker::GetMediaManager().playStubFile(item);
+ }
+
+ if (item.IsPlayList())
+ return false;
+
+ // if the item is a plugin we need to resolve the plugin paths
+ if (URIUtils::HasPluginPath(item) && !XFILE::CPluginDirectory::GetResolvedPluginResult(item))
+ return false;
+
+#ifdef HAS_UPNP
+ if (URIUtils::IsUPnP(item.GetPath()))
+ {
+ if (!XFILE::CUPnPDirectory::GetResource(item.GetURL(), item))
+ return false;
+ }
+#endif
+
+ // if we have a stacked set of files, we need to setup our stack routines for
+ // "seamless" seeking and total time of the movie etc.
+ // will recall with restart set to true
+ if (item.IsStack())
+ return PlayStack(item, bRestart);
+
+ CPlayerOptions options;
+
+ if (item.HasProperty("StartPercent"))
+ {
+ options.startpercent = item.GetProperty("StartPercent").asDouble();
+ item.SetStartOffset(0);
+ }
+
+ options.starttime = CUtil::ConvertMilliSecsToSecs(item.GetStartOffset());
+
+ if (bRestart)
+ {
+ // have to be set here due to playstack using this for starting the file
+ if (item.HasVideoInfoTag())
+ options.state = item.GetVideoInfoTag()->GetResumePoint().playerState;
+ }
+ if (!bRestart || stackHelper->IsPlayingISOStack())
+ {
+ // the following code block is only applicable when bRestart is false OR to ISO stacks
+
+ if (item.IsVideo())
+ {
+ // open the d/b and retrieve the bookmarks for the current movie
+ CVideoDatabase dbs;
+ dbs.Open();
+
+ std::string path = item.GetPath();
+ std::string videoInfoTagPath(item.GetVideoInfoTag()->m_strFileNameAndPath);
+ if (videoInfoTagPath.find("removable://") == 0 || item.IsVideoDb())
+ path = videoInfoTagPath;
+ dbs.LoadVideoInfo(path, *item.GetVideoInfoTag());
+
+ if (item.HasProperty("savedplayerstate"))
+ {
+ options.starttime = CUtil::ConvertMilliSecsToSecs(item.GetStartOffset());
+ options.state = item.GetProperty("savedplayerstate").asString();
+ item.ClearProperty("savedplayerstate");
+ }
+ else if (item.GetStartOffset() == STARTOFFSET_RESUME)
+ {
+ options.starttime = 0.0;
+ if (item.IsResumePointSet())
+ {
+ options.starttime = item.GetCurrentResumeTime();
+ if (item.HasVideoInfoTag())
+ options.state = item.GetVideoInfoTag()->GetResumePoint().playerState;
+ }
+ else
+ {
+ CBookmark bookmark;
+ std::string path = item.GetPath();
+ if (item.HasVideoInfoTag() && StringUtils::StartsWith(item.GetVideoInfoTag()->m_strFileNameAndPath, "removable://"))
+ path = item.GetVideoInfoTag()->m_strFileNameAndPath;
+ else if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
+ path = item.GetProperty("original_listitem_url").asString();
+ if (dbs.GetResumeBookMark(path, bookmark))
+ {
+ options.starttime = bookmark.timeInSeconds;
+ options.state = bookmark.playerState;
+ }
+ }
+
+ if (options.starttime == 0.0 && item.HasVideoInfoTag())
+ {
+ // No resume point is set, but check if this item is part of a multi-episode file
+ const CVideoInfoTag *tag = item.GetVideoInfoTag();
+
+ if (tag->m_iBookmarkId > 0)
+ {
+ CBookmark bookmark;
+ dbs.GetBookMarkForEpisode(*tag, bookmark);
+ options.starttime = bookmark.timeInSeconds;
+ options.state = bookmark.playerState;
+ }
+ }
+ }
+ else if (item.HasVideoInfoTag())
+ {
+ const CVideoInfoTag *tag = item.GetVideoInfoTag();
+
+ if (tag->m_iBookmarkId > 0)
+ {
+ CBookmark bookmark;
+ dbs.GetBookMarkForEpisode(*tag, bookmark);
+ options.starttime = bookmark.timeInSeconds;
+ options.state = bookmark.playerState;
+ }
+ }
+
+ dbs.Close();
+ }
+ }
+
+ // a disc image might be Blu-Ray disc
+ if (!(options.startpercent > 0.0 || options.starttime > 0.0) &&
+ (item.IsBDFile() || item.IsDiscImage()))
+ {
+ //check if we must show the simplified bd menu
+ if (!CGUIDialogSimpleMenu::ShowPlaySelection(item))
+ return true;
+ }
+
+ // this really aught to be inside !bRestart, but since PlayStack
+ // uses that to init playback, we have to keep it outside
+ const PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ if (item.IsAudio() && playlistId == PLAYLIST::TYPE_MUSIC)
+ { // playing from a playlist by the looks
+ // don't switch to fullscreen if we are not playing the first item...
+ options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() &&
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_MUSICFILES_SELECTACTION) &&
+ !CMediaSettings::GetInstance().DoesMediaStartWindowed();
+ }
+ else if (item.IsVideo() && playlistId == PLAYLIST::TYPE_VIDEO &&
+ CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size() > 1)
+ { // playing from a playlist by the looks
+ // don't switch to fullscreen if we are not playing the first item...
+ options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() &&
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreenOnMovieStart &&
+ !CMediaSettings::GetInstance().DoesMediaStartWindowed();
+ }
+ else if (stackHelper->IsPlayingRegularStack())
+ {
+ //! @todo - this will fail if user seeks back to first file in stack
+ if (stackHelper->GetCurrentPartNumber() == 0 ||
+ stackHelper->GetRegisteredStack(item)->GetStartOffset() != 0)
+ options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->
+ m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed();
+ else
+ options.fullscreen = false;
+ }
+ else
+ options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->
+ m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed();
+
+ // stereo streams may have lower quality, i.e. 32bit vs 16 bit
+ options.preferStereo = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoPreferStereoStream &&
+ CServiceBroker::GetActiveAE()->HasStereoAudioChannelCount();
+
+ // reset VideoStartWindowed as it's a temp setting
+ CMediaSettings::GetInstance().SetMediaStartWindowed(false);
+
+ {
+ // for playing a new item, previous playing item's callback may already
+ // pushed some delay message into the threadmessage list, they are not
+ // expected be processed after or during the new item playback starting.
+ // so we clean up previous playing item's playback callback delay messages here.
+ int previousMsgsIgnoredByNewPlaying[] = {
+ GUI_MSG_PLAYBACK_STARTED,
+ GUI_MSG_PLAYBACK_ENDED,
+ GUI_MSG_PLAYBACK_STOPPED,
+ GUI_MSG_PLAYLIST_CHANGED,
+ GUI_MSG_PLAYLISTPLAYER_STOPPED,
+ GUI_MSG_PLAYLISTPLAYER_STARTED,
+ GUI_MSG_PLAYLISTPLAYER_CHANGED,
+ GUI_MSG_QUEUE_NEXT_ITEM,
+ 0
+ };
+ int dMsgCount = CServiceBroker::GetGUI()->GetWindowManager().RemoveThreadMessageByMessageIds(&previousMsgsIgnoredByNewPlaying[0]);
+ if (dMsgCount > 0)
+ CLog::LogF(LOGDEBUG, "Ignored {} playback thread messages", dMsgCount);
+ }
+
+ const auto appVolume = GetComponent<CApplicationVolumeHandling>();
+ appPlayer->OpenFile(item, options, m_ServiceManager->GetPlayerCoreFactory(), player, *this);
+ appPlayer->SetVolume(appVolume->GetVolumeRatio());
+ appPlayer->SetMute(appVolume->IsMuted());
+
+#if !defined(TARGET_POSIX)
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetAudioManager().Enable(false);
+#endif
+
+ if (item.HasPVRChannelInfoTag())
+ CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE);
+
+ return true;
+}
+
+void CApplication::PlaybackCleanup()
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (!appPlayer->IsPlaying())
+ {
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui)
+ CServiceBroker::GetGUI()->GetAudioManager().Enable(true);
+ appPlayer->OpenNext(m_ServiceManager->GetPlayerCoreFactory());
+ }
+
+ if (!appPlayer->IsPlayingVideo())
+ {
+ if(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO ||
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
+ }
+ else
+ {
+ // resets to res_desktop or look&feel resolution (including refreshrate)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFullScreenVideo(false);
+ }
+#ifdef TARGET_DARWIN_EMBEDDED
+ CDarwinUtils::SetScheduling(false);
+#endif
+ }
+
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+
+ if (!appPlayer->IsPlayingAudio() &&
+ CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_NONE &&
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION)
+ {
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); // save vis settings
+ appPower->WakeUpScreenSaverAndDPMS();
+ CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
+ }
+
+ // DVD ejected while playing in vis ?
+ if (!appPlayer->IsPlayingAudio() &&
+ (m_itemCurrentFile->IsCDDA() || m_itemCurrentFile->IsOnDVD()) &&
+ !CServiceBroker::GetMediaManager().IsDiscInDrive() &&
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION)
+ {
+ // yes, disable vis
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); // save vis settings
+ appPower->WakeUpScreenSaverAndDPMS();
+ CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
+ }
+
+ if (!appPlayer->IsPlaying())
+ {
+ stackHelper->Clear();
+ appPlayer->ResetPlayer();
+ }
+
+ if (CServiceBroker::GetAppParams()->IsTestMode())
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+}
+
+bool CApplication::IsPlayingFullScreenVideo() const
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ return appPlayer->IsPlayingVideo() &&
+ CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo();
+}
+
+bool CApplication::IsFullScreen()
+{
+ return IsPlayingFullScreenVideo() ||
+ (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION) ||
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW;
+}
+
+void CApplication::StopPlaying()
+{
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+
+ if (gui)
+ {
+ int iWin = gui->GetWindowManager().GetActiveWindow();
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlaying())
+ {
+ appPlayer->ClosePlayer();
+
+ // turn off visualisation window when stopping
+ if ((iWin == WINDOW_VISUALISATION ||
+ iWin == WINDOW_FULLSCREEN_VIDEO ||
+ iWin == WINDOW_FULLSCREEN_GAME) &&
+ !m_bStop)
+ gui->GetWindowManager().PreviousWindow();
+
+ g_partyModeManager.Disable();
+ }
+ }
+}
+
+bool CApplication::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_NOTIFY_ALL:
+ {
+ if (message.GetParam1()==GUI_MSG_REMOVED_MEDIA)
+ {
+ // Update general playlist: Remove DVD playlist items
+ int nRemoved = CServiceBroker::GetPlaylistPlayer().RemoveDVDItems();
+ if ( nRemoved > 0 )
+ {
+ CGUIMessage msg( GUI_MSG_PLAYLIST_CHANGED, 0, 0 );
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage( msg );
+ }
+ // stop the file if it's on dvd (will set the resume point etc)
+ if (m_itemCurrentFile->IsOnDVD())
+ StopPlaying();
+ }
+ else if (message.GetParam1() == GUI_MSG_UI_READY)
+ {
+ // remove splash window
+ CServiceBroker::GetGUI()->GetWindowManager().Delete(WINDOW_SPLASH);
+
+ // show the volumebar if the volume is muted
+ const auto appVolume = GetComponent<CApplicationVolumeHandling>();
+ if (appVolume->IsMuted() ||
+ appVolume->GetVolumeRatio() <= CApplicationVolumeHandling::VOLUME_MINIMUM)
+ appVolume->ShowVolumeBar();
+
+ if (!m_incompatibleAddons.empty())
+ {
+ // filter addons that are not dependencies
+ std::vector<std::string> disabledAddonNames;
+ for (const auto& addoninfo : m_incompatibleAddons)
+ {
+ if (!CAddonType::IsDependencyType(addoninfo->MainType()))
+ disabledAddonNames.emplace_back(addoninfo->Name());
+ }
+
+ // migration (incompatible addons) dialog
+ auto addonList = StringUtils::Join(disabledAddonNames, ", ");
+ auto msg = StringUtils::Format(g_localizeStrings.Get(24149), addonList);
+ HELPERS::ShowOKDialogText(CVariant{24148}, CVariant{std::move(msg)});
+ m_incompatibleAddons.clear();
+ }
+
+ // show info dialog about moved configuration files if needed
+ ShowAppMigrationMessage();
+
+ // offer enabling addons at kodi startup that are disabled due to
+ // e.g. os package manager installation on linux
+ ConfigureAndEnableAddons();
+
+ m_bInitializing = false;
+
+ if (message.GetSenderId() == WINDOW_SETTINGS_PROFILES)
+ GetComponent<CApplicationSkinHandling>()->ReloadSkin(false);
+ }
+ else if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem())
+ {
+ CFileItemPtr item = std::static_pointer_cast<CFileItem>(message.GetItem());
+ if (m_itemCurrentFile->IsSamePath(item.get()))
+ {
+ m_itemCurrentFile->UpdateInfo(*item);
+ CServiceBroker::GetGUI()->GetInfoManager().UpdateCurrentItem(*item);
+ }
+ }
+ }
+ break;
+
+ case GUI_MSG_PLAYBACK_STARTED:
+ {
+#ifdef TARGET_DARWIN_EMBEDDED
+ // @TODO move this away to platform code
+ CDarwinUtils::SetScheduling(GetComponent<CApplicationPlayer>()->IsPlayingVideo());
+#endif
+ PLAYLIST::CPlayList playList = CServiceBroker::GetPlaylistPlayer().GetPlaylist(
+ CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist());
+
+ // Update our infoManager with the new details etc.
+ if (m_nextPlaylistItem >= 0)
+ {
+ // playing an item which is not in the list - player might be stopped already
+ // so do nothing
+ if (playList.size() <= m_nextPlaylistItem)
+ return true;
+
+ // we've started a previously queued item
+ CFileItemPtr item = playList[m_nextPlaylistItem];
+ // update the playlist manager
+ int currentSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong();
+ int param = ((currentSong & 0xffff) << 16) | (m_nextPlaylistItem & 0xffff);
+ CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_CHANGED, 0, 0, CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(), param, item);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ CServiceBroker::GetPlaylistPlayer().SetCurrentSong(m_nextPlaylistItem);
+ m_itemCurrentFile.reset(new CFileItem(*item));
+ }
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);
+ g_partyModeManager.OnSongChange(true);
+
+#ifdef HAS_PYTHON
+ // informs python script currently running playback has started
+ // (does nothing if python is not loaded)
+ CServiceBroker::GetXBPython().OnPlayBackStarted(*m_itemCurrentFile);
+#endif
+
+ CVariant param;
+ param["player"]["speed"] = 1;
+ param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay",
+ m_itemCurrentFile, param);
+
+ // we don't want a busy dialog when switching channels
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ if (!m_itemCurrentFile->IsLiveTV() ||
+ (!appPlayer->IsPlayingVideo() && !appPlayer->IsPlayingAudio()))
+ {
+ CGUIDialogBusy* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusy>(
+ WINDOW_DIALOG_BUSY);
+ if (dialog && !dialog->IsDialogRunning())
+ dialog->WaitOnEvent(m_playerEvent);
+ }
+
+ return true;
+ }
+ break;
+
+ case GUI_MSG_QUEUE_NEXT_ITEM:
+ {
+ // Check to see if our playlist player has a new item for us,
+ // and if so, we check whether our current player wants the file
+ int iNext = CServiceBroker::GetPlaylistPlayer().GetNextSong();
+ PLAYLIST::CPlayList& playlist = CServiceBroker::GetPlaylistPlayer().GetPlaylist(
+ CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist());
+ if (iNext < 0 || iNext >= playlist.size())
+ {
+ GetComponent<CApplicationPlayer>()->OnNothingToQueueNotify();
+ return true; // nothing to do
+ }
+
+ // ok, grab the next song
+ CFileItem file(*playlist[iNext]);
+ // handle plugin://
+ CURL url(file.GetDynPath());
+ if (url.IsProtocol("plugin"))
+ XFILE::CPluginDirectory::GetPluginResult(url.Get(), file, false);
+
+ // Don't queue if next media type is different from current one
+ bool bNothingToQueue = false;
+
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ if (!file.IsVideo() && appPlayer->IsPlayingVideo())
+ bNothingToQueue = true;
+ else if ((!file.IsAudio() || file.IsVideo()) && appPlayer->IsPlayingAudio())
+ bNothingToQueue = true;
+
+ if (bNothingToQueue)
+ {
+ appPlayer->OnNothingToQueueNotify();
+ return true;
+ }
+
+#ifdef HAS_UPNP
+ if (URIUtils::IsUPnP(file.GetDynPath()))
+ {
+ if (!XFILE::CUPnPDirectory::GetResource(file.GetDynURL(), file))
+ return true;
+ }
+#endif
+
+ // ok - send the file to the player, if it accepts it
+ if (appPlayer->QueueNextFile(file))
+ {
+ // player accepted the next file
+ m_nextPlaylistItem = iNext;
+ }
+ else
+ {
+ /* Player didn't accept next file: *ALWAYS* advance playlist in this case so the player can
+ queue the next (if it wants to) and it doesn't keep looping on this song */
+ CServiceBroker::GetPlaylistPlayer().SetCurrentSong(iNext);
+ }
+
+ return true;
+ }
+ break;
+
+ case GUI_MSG_PLAY_TRAILER:
+ {
+ const CFileItem* item = dynamic_cast<CFileItem*>(message.GetItem().get());
+ if (item == nullptr)
+ {
+ CLog::LogF(LOGERROR, "Supplied item is not a CFileItem! Trailer cannot be played.");
+ return false;
+ }
+
+ std::unique_ptr<CFileItem> trailerItem =
+ ContentUtils::GeneratePlayableTrailerItem(*item, g_localizeStrings.Get(20410));
+
+ if (item->IsPlayList())
+ {
+ std::unique_ptr<CFileItemList> fileitemList = std::make_unique<CFileItemList>();
+ fileitemList->Add(std::move(trailerItem));
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, -1, -1,
+ static_cast<void*>(fileitemList.release()));
+ }
+ else
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 1, 0,
+ static_cast<void*>(trailerItem.release()));
+ }
+ break;
+ }
+
+ case GUI_MSG_PLAYBACK_STOPPED:
+ m_playerEvent.Set();
+ ResetCurrentItem();
+ PlaybackCleanup();
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnPlayBackStopped();
+#endif
+ return true;
+
+ case GUI_MSG_PLAYBACK_ENDED:
+ {
+ m_playerEvent.Set();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+ if (stackHelper->IsPlayingRegularStack() && stackHelper->HasNextStackPartFileItem())
+ { // just play the next item in the stack
+ PlayFile(stackHelper->SetNextStackPartCurrentFileItem(), "", true);
+ return true;
+ }
+ ResetCurrentItem();
+ if (!CServiceBroker::GetPlaylistPlayer().PlayNext(1, true))
+ GetComponent<CApplicationPlayer>()->ClosePlayer();
+
+ PlaybackCleanup();
+
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnPlayBackEnded();
+#endif
+ return true;
+ }
+
+ case GUI_MSG_PLAYLISTPLAYER_STOPPED:
+ ResetCurrentItem();
+ if (GetComponent<CApplicationPlayer>()->IsPlaying())
+ StopPlaying();
+ PlaybackCleanup();
+ return true;
+
+ case GUI_MSG_PLAYBACK_AVSTARTED:
+ m_playerEvent.Set();
+#ifdef HAS_PYTHON
+ // informs python script currently running playback has started
+ // (does nothing if python is not loaded)
+ CServiceBroker::GetXBPython().OnAVStarted(*m_itemCurrentFile);
+#endif
+ return true;
+
+ case GUI_MSG_PLAYBACK_AVCHANGE:
+#ifdef HAS_PYTHON
+ // informs python script currently running playback has started
+ // (does nothing if python is not loaded)
+ CServiceBroker::GetXBPython().OnAVChange();
+#endif
+ return true;
+
+ case GUI_MSG_PLAYBACK_ERROR:
+ HELPERS::ShowOKDialogText(CVariant{16026}, CVariant{16027});
+ return true;
+
+ case GUI_MSG_PLAYLISTPLAYER_STARTED:
+ case GUI_MSG_PLAYLISTPLAYER_CHANGED:
+ {
+ return true;
+ }
+ break;
+ case GUI_MSG_FULLSCREEN:
+ { // Switch to fullscreen, if we can
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetWindowManager().SwitchToFullScreen();
+
+ return true;
+ }
+ break;
+ case GUI_MSG_EXECUTE:
+ if (message.GetNumStringParams())
+ return ExecuteXBMCAction(message.GetStringParam(), message.GetItem());
+ break;
+ }
+ return false;
+}
+
+bool CApplication::ExecuteXBMCAction(std::string actionStr, const CGUIListItemPtr &item /* = NULL */)
+{
+ // see if it is a user set string
+
+ //We don't know if there is unsecure information in this yet, so we
+ //postpone any logging
+ const std::string in_actionStr(actionStr);
+ if (item)
+ actionStr = GUILIB::GUIINFO::CGUIInfoLabel::GetItemLabel(actionStr, item.get());
+ else
+ actionStr = GUILIB::GUIINFO::CGUIInfoLabel::GetLabel(actionStr, INFO::DEFAULT_CONTEXT);
+
+ // user has asked for something to be executed
+ if (CBuiltins::GetInstance().HasCommand(actionStr))
+ {
+ if (!CBuiltins::GetInstance().IsSystemPowerdownCommand(actionStr) ||
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown())
+ CBuiltins::GetInstance().Execute(actionStr);
+ }
+ else
+ {
+ // try translating the action from our ButtonTranslator
+ unsigned int actionID;
+ if (CActionTranslator::TranslateString(actionStr, actionID))
+ {
+ OnAction(CAction(actionID));
+ return true;
+ }
+ CFileItem item(actionStr, false);
+#ifdef HAS_PYTHON
+ if (item.IsPythonScript())
+ { // a python script
+ CScriptInvocationManager::GetInstance().ExecuteAsync(item.GetPath());
+ }
+ else
+#endif
+ if (item.IsAudio() || item.IsVideo() || item.IsGame())
+ { // an audio or video file
+ PlayFile(item, "");
+ }
+ else
+ {
+ //At this point we have given up to translate, so even though
+ //there may be insecure information, we log it.
+ CLog::LogF(LOGDEBUG, "Tried translating, but failed to understand {}", in_actionStr);
+ return false;
+ }
+ }
+ return true;
+}
+
+// inform the user that the configuration data has moved from old XBMC location
+// to new Kodi location - if applicable
+void CApplication::ShowAppMigrationMessage()
+{
+ // .kodi_migration_complete will be created from the installer/packaging
+ // once an old XBMC configuration was moved to the new Kodi location
+ // if this is the case show the migration info to the user once which
+ // tells him to have a look into the wiki where the move of configuration
+ // is further explained.
+ if (CFile::Exists("special://home/.kodi_data_was_migrated") &&
+ !CFile::Exists("special://home/.kodi_migration_info_shown"))
+ {
+ HELPERS::ShowOKDialogText(CVariant{24128}, CVariant{24129});
+ CFile tmpFile;
+ // create the file which will prevent this dialog from appearing in the future
+ tmpFile.OpenForWrite("special://home/.kodi_migration_info_shown");
+ tmpFile.Close();
+ }
+}
+
+void CApplication::ConfigureAndEnableAddons()
+{
+ std::vector<std::shared_ptr<IAddon>>
+ disabledAddons; /*!< Installed addons, but not auto-enabled via manifest */
+
+ auto& addonMgr = CServiceBroker::GetAddonMgr();
+
+ if (addonMgr.GetDisabledAddons(disabledAddons) && !disabledAddons.empty())
+ {
+ // this applies to certain platforms only:
+ // look at disabled addons with disabledReason == NONE, usually those are installed via package managers or manually.
+ // also try to enable add-ons with disabledReason == INCOMPATIBLE at startup for all platforms.
+
+ bool isConfigureAddonsAtStartupEnabled =
+ m_ServiceManager->GetPlatform().IsConfigureAddonsAtStartupEnabled();
+
+ for (const auto& addon : disabledAddons)
+ {
+ if (addonMgr.IsAddonDisabledWithReason(addon->ID(), ADDON::AddonDisabledReason::INCOMPATIBLE))
+ {
+ auto addonInfo = addonMgr.GetAddonInfo(addon->ID(), AddonType::UNKNOWN);
+ if (addonInfo && addonMgr.IsCompatible(addonInfo))
+ {
+ CLog::Log(LOGDEBUG, "CApplication::{}: enabling the compatible version of [{}].",
+ __FUNCTION__, addon->ID());
+ addonMgr.EnableAddon(addon->ID());
+ }
+ continue;
+ }
+
+ if (addonMgr.IsAddonDisabledExcept(addon->ID(), ADDON::AddonDisabledReason::NONE) ||
+ CAddonType::IsDependencyType(addon->MainType()))
+ {
+ continue;
+ }
+
+ if (isConfigureAddonsAtStartupEnabled)
+ {
+ if (HELPERS::ShowYesNoDialogLines(CVariant{24039}, // Disabled add-ons
+ CVariant{24059}, // Would you like to enable this add-on?
+ CVariant{addon->Name()}) == DialogResponse::CHOICE_YES)
+ {
+ if (addon->CanHaveAddonOrInstanceSettings())
+ {
+ if (CGUIDialogAddonSettings::ShowForAddon(addon))
+ {
+ // only enable if settings dialog hasn't been cancelled
+ addonMgr.EnableAddon(addon->ID());
+ }
+ }
+ else
+ {
+ addonMgr.EnableAddon(addon->ID());
+ }
+ }
+ else
+ {
+ // user chose not to configure/enable so we're not asking anymore
+ addonMgr.UpdateDisabledReason(addon->ID(), ADDON::AddonDisabledReason::USER);
+ }
+ }
+ }
+ }
+}
+
+void CApplication::Process()
+{
+ // dispatch the messages generated by python or other threads to the current window
+ CServiceBroker::GetGUI()->GetWindowManager().DispatchThreadMessages();
+
+ // process messages which have to be send to the gui
+ // (this can only be done after CServiceBroker::GetGUI()->GetWindowManager().Render())
+ CServiceBroker::GetAppMessenger()->ProcessWindowMessages();
+
+ // handle any active scripts
+
+ {
+ // Allow processing of script threads to let them shut down properly.
+ CSingleExit ex(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_frameMoveGuard.unlock();
+ CScriptInvocationManager::GetInstance().Process();
+ m_frameMoveGuard.lock();
+ }
+
+ // process messages, even if a movie is playing
+ CServiceBroker::GetAppMessenger()->ProcessMessages();
+ if (m_bStop) return; //we're done, everything has been unloaded
+
+ // update sound
+ GetComponent<CApplicationPlayer>()->DoAudioWork();
+
+ // do any processing that isn't needed on each run
+ if( m_slowTimer.GetElapsedMilliseconds() > 500 )
+ {
+ m_slowTimer.Reset();
+ ProcessSlow();
+ }
+}
+
+// We get called every 500ms
+void CApplication::ProcessSlow()
+{
+ // process skin resources (skin timers)
+ GetComponent<CApplicationSkinHandling>()->ProcessSkin();
+
+ CServiceBroker::GetPowerManager().ProcessEvents();
+
+#if defined(TARGET_DARWIN_OSX) && defined(SDL_FOUND)
+ // There is an issue on OS X that several system services ask the cursor to become visible
+ // during their startup routines. Given that we can't control this, we hack it in by
+ // forcing the
+ if (CServiceBroker::GetWinSystem()->IsFullScreen())
+ { // SDL thinks it's hidden
+ Cocoa_HideMouse();
+ }
+#endif
+
+ // Temporarily pause pausable jobs when viewing video/picture
+ int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
+ if (CurrentFileItem().IsVideo() ||
+ CurrentFileItem().IsPicture() ||
+ currentWindow == WINDOW_FULLSCREEN_VIDEO ||
+ currentWindow == WINDOW_FULLSCREEN_GAME ||
+ currentWindow == WINDOW_SLIDESHOW)
+ {
+ CServiceBroker::GetJobManager()->PauseJobs();
+ }
+ else
+ {
+ CServiceBroker::GetJobManager()->UnPauseJobs();
+ }
+
+ // Check if we need to activate the screensaver / DPMS.
+ const auto appPower = GetComponent<CApplicationPowerHandling>();
+ appPower->CheckScreenSaverAndDPMS();
+
+ // Check if we need to shutdown (if enabled).
+#if defined(TARGET_DARWIN)
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME) &&
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
+#else
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME))
+#endif
+ {
+ appPower->CheckShutdown();
+ }
+
+#if defined(TARGET_POSIX)
+ if (CPlatformPosix::TestQuitFlag())
+ {
+ CLog::Log(LOGINFO, "Quitting due to POSIX signal");
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ }
+#endif
+
+ // check if we should restart the player
+ CheckDelayedPlayerRestart();
+
+ // check if we can unload any unreferenced dlls or sections
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ if (!appPlayer->IsPlayingVideo())
+ CSectionLoader::UnloadDelayed();
+
+#ifdef TARGET_ANDROID
+ // Pass the slow loop to droid
+ CXBMCApp::Get().ProcessSlow();
+#endif
+
+ // check for any idle curl connections
+ g_curlInterface.CheckIdle();
+
+ CServiceBroker::GetGUI()->GetLargeTextureManager().CleanupUnusedImages();
+
+ CServiceBroker::GetGUI()->GetTextureManager().FreeUnusedTextures(5000);
+
+#ifdef HAS_DVD_DRIVE
+ // checks whats in the DVD drive and tries to autostart the content (xbox games, dvd, cdda, avi files...)
+ if (!appPlayer->IsPlayingVideo())
+ m_Autorun->HandleAutorun();
+#endif
+
+ // update upnp server/renderer states
+#ifdef HAS_UPNP
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP) && UPNP::CUPnP::IsInstantiated())
+ UPNP::CUPnP::GetInstance()->UpdateState();
+#endif
+
+#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
+ smb.CheckIfIdle();
+#endif
+
+#ifdef HAS_FILESYSTEM_NFS
+ gNfsConnection.CheckIfIdle();
+#endif
+
+ for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
+ vfsAddon->ClearOutIdle();
+
+ CServiceBroker::GetMediaManager().ProcessEvents();
+
+ // if we don't render the gui there's no reason to start the screensaver.
+ // that way the screensaver won't kick in if we maximize the XBMC window
+ // after the screensaver start time.
+ if (!appPower->GetRenderGUI())
+ appPower->ResetScreenSaverTimer();
+}
+
+void CApplication::DelayedPlayerRestart()
+{
+ m_restartPlayerTimer.StartZero();
+}
+
+void CApplication::CheckDelayedPlayerRestart()
+{
+ if (m_restartPlayerTimer.GetElapsedSeconds() > 3)
+ {
+ m_restartPlayerTimer.Stop();
+ m_restartPlayerTimer.Reset();
+ Restart(true);
+ }
+}
+
+void CApplication::Restart(bool bSamePosition)
+{
+ // this function gets called when the user changes a setting (like noninterleaved)
+ // and which means we gotta close & reopen the current playing file
+
+ // first check if we're playing a file
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ if (!appPlayer->IsPlayingVideo() && !appPlayer->IsPlayingAudio())
+ return ;
+
+ if (!appPlayer->HasPlayer())
+ return ;
+
+ // do we want to return to the current position in the file
+ if (!bSamePosition)
+ {
+ // no, then just reopen the file and start at the beginning
+ PlayFile(*m_itemCurrentFile, "", true);
+ return ;
+ }
+
+ // else get current position
+ double time = GetTime();
+
+ // get player state, needed for dvd's
+ std::string state = appPlayer->GetPlayerState();
+
+ // set the requested starttime
+ m_itemCurrentFile->SetStartOffset(CUtil::ConvertSecsToMilliSecs(time));
+
+ // reopen the file
+ if (PlayFile(*m_itemCurrentFile, "", true))
+ appPlayer->SetPlayerState(state);
+}
+
+const std::string& CApplication::CurrentFile()
+{
+ return m_itemCurrentFile->GetPath();
+}
+
+std::shared_ptr<CFileItem> CApplication::CurrentFileItemPtr()
+{
+ return m_itemCurrentFile;
+}
+
+CFileItem& CApplication::CurrentFileItem()
+{
+ return *m_itemCurrentFile;
+}
+
+const CFileItem& CApplication::CurrentUnstackedItem()
+{
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (stackHelper->IsPlayingISOStack() || stackHelper->IsPlayingRegularStack())
+ return stackHelper->GetCurrentStackPartFileItem();
+ else
+ return *m_itemCurrentFile;
+}
+
+// Returns the total time in seconds of the current media. Fractional
+// portions of a second are possible - but not necessarily supported by the
+// player class. This returns a double to be consistent with GetTime() and
+// SeekTime().
+double CApplication::GetTotalTime() const
+{
+ double rc = 0.0;
+
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (appPlayer->IsPlaying())
+ {
+ if (stackHelper->IsPlayingRegularStack())
+ rc = stackHelper->GetStackTotalTimeMs() * 0.001;
+ else
+ rc = appPlayer->GetTotalTime() * 0.001;
+ }
+
+ return rc;
+}
+
+// Returns the current time in seconds of the currently playing media.
+// Fractional portions of a second are possible. This returns a double to
+// be consistent with GetTotalTime() and SeekTime().
+double CApplication::GetTime() const
+{
+ double rc = 0.0;
+
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (appPlayer->IsPlaying())
+ {
+ if (stackHelper->IsPlayingRegularStack())
+ {
+ uint64_t startOfCurrentFile = stackHelper->GetCurrentStackPartStartTimeMs();
+ rc = (startOfCurrentFile + appPlayer->GetTime()) * 0.001;
+ }
+ else
+ rc = appPlayer->GetTime() * 0.001;
+ }
+
+ return rc;
+}
+
+// Sets the current position of the currently playing media to the specified
+// time in seconds. Fractional portions of a second are valid. The passed
+// time is the time offset from the beginning of the file as opposed to a
+// delta from the current position. This method accepts a double to be
+// consistent with GetTime() and GetTotalTime().
+void CApplication::SeekTime( double dTime )
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (appPlayer->IsPlaying() && (dTime >= 0.0))
+ {
+ if (!appPlayer->CanSeek())
+ return;
+
+ if (stackHelper->IsPlayingRegularStack())
+ {
+ // find the item in the stack we are seeking to, and load the new
+ // file if necessary, and calculate the correct seek within the new
+ // file. Otherwise, just fall through to the usual routine if the
+ // time is higher than our total time.
+ int partNumberToPlay =
+ stackHelper->GetStackPartNumberAtTimeMs(static_cast<uint64_t>(dTime * 1000.0));
+ uint64_t startOfNewFile = stackHelper->GetStackPartStartTimeMs(partNumberToPlay);
+ if (partNumberToPlay == stackHelper->GetCurrentPartNumber())
+ appPlayer->SeekTime(static_cast<uint64_t>(dTime * 1000.0) - startOfNewFile);
+ else
+ { // seeking to a new file
+ stackHelper->SetStackPartCurrentFileItem(partNumberToPlay);
+ CFileItem* item = new CFileItem(stackHelper->GetCurrentStackPartFileItem());
+ item->SetStartOffset(static_cast<uint64_t>(dTime * 1000.0) - startOfNewFile);
+ // don't just call "PlayFile" here, as we are quite likely called from the
+ // player thread, so we won't be able to delete ourselves.
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 1, 0, static_cast<void*>(item));
+ }
+ return;
+ }
+ // convert to milliseconds and perform seek
+ appPlayer->SeekTime(static_cast<int64_t>(dTime * 1000.0));
+ }
+}
+
+float CApplication::GetPercentage() const
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (appPlayer->IsPlaying())
+ {
+ if (appPlayer->GetTotalTime() == 0 && appPlayer->IsPlayingAudio() &&
+ m_itemCurrentFile->HasMusicInfoTag())
+ {
+ const CMusicInfoTag& tag = *m_itemCurrentFile->GetMusicInfoTag();
+ if (tag.GetDuration() > 0)
+ return (float)(GetTime() / tag.GetDuration() * 100);
+ }
+
+ if (stackHelper->IsPlayingRegularStack())
+ {
+ double totalTime = GetTotalTime();
+ if (totalTime > 0.0)
+ return (float)(GetTime() / totalTime * 100);
+ }
+ else
+ return appPlayer->GetPercentage();
+ }
+ return 0.0f;
+}
+
+float CApplication::GetCachePercentage() const
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (appPlayer->IsPlaying())
+ {
+ // Note that the player returns a relative cache percentage and we want an absolute percentage
+ if (stackHelper->IsPlayingRegularStack())
+ {
+ float stackedTotalTime = (float) GetTotalTime();
+ // We need to take into account the stack's total time vs. currently playing file's total time
+ if (stackedTotalTime > 0.0f)
+ return std::min(100.0f,
+ GetPercentage() + (appPlayer->GetCachePercentage() *
+ appPlayer->GetTotalTime() * 0.001f / stackedTotalTime));
+ }
+ else
+ return std::min(100.0f, appPlayer->GetPercentage() + appPlayer->GetCachePercentage());
+ }
+ return 0.0f;
+}
+
+void CApplication::SeekPercentage(float percent)
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ const auto stackHelper = GetComponent<CApplicationStackHelper>();
+
+ if (appPlayer->IsPlaying() && (percent >= 0.0f))
+ {
+ if (!appPlayer->CanSeek())
+ return;
+ if (stackHelper->IsPlayingRegularStack())
+ SeekTime(static_cast<double>(percent) * 0.01 * GetTotalTime());
+ else
+ appPlayer->SeekPercentage(percent);
+ }
+}
+
+std::string CApplication::GetCurrentPlayer()
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ return appPlayer->GetCurrentPlayer();
+}
+
+void CApplication::UpdateLibraries()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_UPDATEONSTARTUP))
+ {
+ CLog::LogF(LOGINFO, "Starting video library startup scan");
+ CVideoLibraryQueue::GetInstance().ScanLibrary(
+ "", false, !settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_BACKGROUNDUPDATE));
+ }
+
+ if (settings->GetBool(CSettings::SETTING_MUSICLIBRARY_UPDATEONSTARTUP))
+ {
+ CLog::LogF(LOGINFO, "Starting music library startup scan");
+ CMusicLibraryQueue::GetInstance().ScanLibrary(
+ "", MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL,
+ !settings->GetBool(CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE));
+ }
+}
+
+void CApplication::UpdateCurrentPlayArt()
+{
+ const auto appPlayer = GetComponent<CApplicationPlayer>();
+ if (!appPlayer->IsPlayingAudio())
+ return;
+ //Clear and reload the art for the currently playing item to show updated art on OSD
+ m_itemCurrentFile->ClearArt();
+ CMusicThumbLoader loader;
+ loader.LoadItem(m_itemCurrentFile.get());
+ // Mirror changes to GUI item
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);
+}
+
+bool CApplication::ProcessAndStartPlaylist(const std::string& strPlayList,
+ PLAYLIST::CPlayList& playlist,
+ PLAYLIST::Id playlistId,
+ int track)
+{
+ CLog::Log(LOGDEBUG, "CApplication::ProcessAndStartPlaylist({}, {})", strPlayList, playlistId);
+
+ // initial exit conditions
+ // no songs in playlist just return
+ if (playlist.size() == 0)
+ return false;
+
+ // illegal playlist
+ if (playlistId == PLAYLIST::TYPE_NONE || playlistId == PLAYLIST::TYPE_PICTURE)
+ return false;
+
+ // setup correct playlist
+ CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId);
+
+ // if the playlist contains an internet stream, this file will be used
+ // to generate a thumbnail for musicplayer.cover
+ m_strPlayListFile = strPlayList;
+
+ // add the items to the playlist player
+ CServiceBroker::GetPlaylistPlayer().Add(playlistId, playlist);
+
+ // if we have a playlist
+ if (CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size())
+ {
+ // start playing it
+ CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId);
+ CServiceBroker::GetPlaylistPlayer().Reset();
+ CServiceBroker::GetPlaylistPlayer().Play(track, "");
+ return true;
+ }
+ return false;
+}
+
+bool CApplication::GetRenderGUI() const
+{
+ return GetComponent<CApplicationPowerHandling>()->GetRenderGUI();
+}
+
+bool CApplication::SetLanguage(const std::string &strLanguage)
+{
+ // nothing to be done if the language hasn't changed
+ if (strLanguage == CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_LANGUAGE))
+ return true;
+
+ return CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_LOCALE_LANGUAGE, strLanguage);
+}
+
+bool CApplication::LoadLanguage(bool reload)
+{
+ // load the configured language
+ if (!g_langInfo.SetLanguage("", reload))
+ return false;
+
+ // set the proper audio and subtitle languages
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ g_langInfo.SetAudioLanguage(settings->GetString(CSettings::SETTING_LOCALE_AUDIOLANGUAGE));
+ g_langInfo.SetSubtitleLanguage(settings->GetString(CSettings::SETTING_LOCALE_SUBTITLELANGUAGE));
+
+ return true;
+}
+
+void CApplication::SetLoggingIn(bool switchingProfiles)
+{
+ // don't save skin settings on unloading when logging into another profile
+ // because in that case we have already loaded the new profile and
+ // would therefore write the previous skin's settings into the new profile
+ // instead of into the previous one
+ GetComponent<CApplicationSkinHandling>()->m_saveSkinOnUnloading = !switchingProfiles;
+}
+
+void CApplication::PrintStartupLog()
+{
+ CLog::Log(LOGINFO, "-----------------------------------------------------------------------");
+ CLog::Log(LOGINFO, "Starting {} ({}). Platform: {} {} {}-bit", CSysInfo::GetAppName(),
+ CSysInfo::GetVersion(), g_sysinfo.GetBuildTargetPlatformName(),
+ g_sysinfo.GetBuildTargetCpuFamily(), g_sysinfo.GetXbmcBitness());
+
+ std::string buildType;
+#if defined(_DEBUG)
+ buildType = "Debug";
+#elif defined(NDEBUG)
+ buildType = "Release";
+#else
+ buildType = "Unknown";
+#endif
+
+ CLog::Log(LOGINFO, "Using {} {} x{}", buildType, CSysInfo::GetAppName(),
+ g_sysinfo.GetXbmcBitness());
+ CLog::Log(LOGINFO, "{} compiled {} by {} for {} {} {}-bit {} ({})", CSysInfo::GetAppName(),
+ CSysInfo::GetBuildDate(), g_sysinfo.GetUsedCompilerNameAndVer(),
+ g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetBuildTargetCpuFamily(),
+ g_sysinfo.GetXbmcBitness(), g_sysinfo.GetBuildTargetPlatformVersionDecoded(),
+ g_sysinfo.GetBuildTargetPlatformVersion());
+
+ std::string deviceModel(g_sysinfo.GetModelName());
+ if (!g_sysinfo.GetManufacturerName().empty())
+ deviceModel = g_sysinfo.GetManufacturerName() + " " +
+ (deviceModel.empty() ? std::string("device") : deviceModel);
+ if (!deviceModel.empty())
+ CLog::Log(LOGINFO, "Running on {} with {}, kernel: {} {} {}-bit version {}", deviceModel,
+ g_sysinfo.GetOsPrettyNameWithVersion(), g_sysinfo.GetKernelName(),
+ g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetKernelBitness(),
+ g_sysinfo.GetKernelVersionFull());
+ else
+ CLog::Log(LOGINFO, "Running on {}, kernel: {} {} {}-bit version {}",
+ g_sysinfo.GetOsPrettyNameWithVersion(), g_sysinfo.GetKernelName(),
+ g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetKernelBitness(),
+ g_sysinfo.GetKernelVersionFull());
+
+ CLog::Log(LOGINFO, "FFmpeg version/source: {}", av_version_info());
+
+ std::string cpuModel(CServiceBroker::GetCPUInfo()->GetCPUModel());
+ if (!cpuModel.empty())
+ {
+ CLog::Log(LOGINFO, "Host CPU: {}, {} core{} available", cpuModel,
+ CServiceBroker::GetCPUInfo()->GetCPUCount(),
+ (CServiceBroker::GetCPUInfo()->GetCPUCount() == 1) ? "" : "s");
+ }
+ else
+ CLog::Log(LOGINFO, "{} CPU core{} available", CServiceBroker::GetCPUInfo()->GetCPUCount(),
+ (CServiceBroker::GetCPUInfo()->GetCPUCount() == 1) ? "" : "s");
+
+ // Any system info logging that is unique to a platform
+ m_ServiceManager->GetPlatform().PlatformSyslog();
+
+#if defined(__arm__) || defined(__aarch64__)
+ CLog::Log(LOGINFO, "ARM Features: Neon {}",
+ (CServiceBroker::GetCPUInfo()->GetCPUFeatures() & CPU_FEATURE_NEON) ? "enabled"
+ : "disabled");
+#endif
+ CSpecialProtocol::LogPaths();
+
+#ifdef HAS_WEB_SERVER
+ CLog::Log(LOGINFO, "Webserver extra whitelist paths: {}",
+ StringUtils::Join(CCompileInfo::GetWebserverExtraWhitelist(), ", "));
+#endif
+
+ // Check, whether libkodi.so was reused (happens on Android, where the system does not unload
+ // the lib on activity end, but keeps it loaded (as long as there is enough memory) and reuses
+ // it on next activity start.
+ static bool firstRun = true;
+
+ CLog::Log(LOGINFO, "The executable running is: {}{}", CUtil::ResolveExecutablePath(),
+ firstRun ? "" : " [reused]");
+
+ firstRun = false;
+
+ std::string hostname("[unknown]");
+ m_ServiceManager->GetNetwork().GetHostName(hostname);
+ CLog::Log(LOGINFO, "Local hostname: {}", hostname);
+ std::string lowerAppName = CCompileInfo::GetAppName();
+ StringUtils::ToLower(lowerAppName);
+ CLog::Log(LOGINFO, "Log File is located: {}.log",
+ CSpecialProtocol::TranslatePath("special://logpath/" + lowerAppName));
+ CRegExp::LogCheckUtf8Support();
+ CLog::Log(LOGINFO, "-----------------------------------------------------------------------");
+}
+
+void CApplication::CloseNetworkShares()
+{
+ CLog::Log(LOGDEBUG,"CApplication::CloseNetworkShares: Closing all network shares");
+
+#if defined(HAS_FILESYSTEM_SMB) && !defined(TARGET_WINDOWS)
+ smb.Deinit();
+#endif
+
+#ifdef HAS_FILESYSTEM_NFS
+ gNfsConnection.Deinit();
+#endif
+
+ for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
+ vfsAddon->DisconnectAll();
+}
diff --git a/xbmc/application/Application.h b/xbmc/application/Application.h
new file mode 100644
index 0000000..a37dcf7
--- /dev/null
+++ b/xbmc/application/Application.h
@@ -0,0 +1,257 @@
+/*
+ * 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 "application/ApplicationComponents.h"
+#include "application/ApplicationEnums.h"
+#include "application/ApplicationPlayerCallback.h"
+#include "application/ApplicationSettingsHandling.h"
+#include "guilib/IMsgTargetCallback.h"
+#include "guilib/IWindowManagerCallback.h"
+#include "messaging/IMessageTarget.h"
+#include "playlists/PlayListTypes.h"
+#include "threads/SystemClock.h"
+#include "utils/GlobalsHandling.h"
+#include "utils/Stopwatch.h"
+#include "windowing/Resolution.h"
+#include "windowing/XBMC_events.h"
+
+#include <atomic>
+#include <chrono>
+#include <deque>
+#include <memory>
+#include <string>
+#include <vector>
+
+class CAction;
+class CAppInboundProtocol;
+class CBookmark;
+class CFileItem;
+class CFileItemList;
+class CGUIComponent;
+class CInertialScrollingHandler;
+class CKey;
+class CSeekHandler;
+class CServiceManager;
+class CSettingsComponent;
+class CSplash;
+class CWinSystemBase;
+
+namespace ADDON
+{
+ class CSkinInfo;
+ class IAddon;
+ typedef std::shared_ptr<IAddon> AddonPtr;
+ class CAddonInfo;
+}
+
+namespace ANNOUNCEMENT
+{
+ class CAnnouncementManager;
+}
+
+namespace MEDIA_DETECT
+{
+ class CAutorun;
+}
+
+namespace PLAYLIST
+{
+ class CPlayList;
+}
+
+namespace ActiveAE
+{
+ class CActiveAE;
+}
+
+namespace VIDEO
+{
+ class CVideoInfoScanner;
+}
+
+namespace MUSIC_INFO
+{
+ class CMusicInfoScanner;
+}
+
+class CApplication : public IWindowManagerCallback,
+ public IMsgTargetCallback,
+ public KODI::MESSAGING::IMessageTarget,
+ public CApplicationComponents,
+ public CApplicationPlayerCallback,
+ public CApplicationSettingsHandling
+{
+friend class CAppInboundProtocol;
+
+public:
+
+ // If playback time of current item is greater than this value, ACTION_PREV_ITEM will seek to start
+ // of currently playing item, otherwise it will seek to start of the previous item in playlist
+ static const unsigned int ACTION_PREV_ITEM_THRESHOLD = 3; // seconds;
+
+ CApplication(void);
+ ~CApplication(void) override;
+
+ bool Create();
+ bool Initialize();
+ int Run();
+ bool Cleanup();
+
+ void FrameMove(bool processEvents, bool processGUI = true) override;
+ void Render() override;
+
+ bool IsInitialized() const { return !m_bInitializing; }
+ bool IsStopping() const { return m_bStop; }
+
+ bool CreateGUI();
+ bool InitWindow(RESOLUTION res = RES_INVALID);
+
+ bool Stop(int exitCode);
+ const std::string& CurrentFile();
+ CFileItem& CurrentFileItem();
+ std::shared_ptr<CFileItem> CurrentFileItemPtr();
+ const CFileItem& CurrentUnstackedItem();
+ bool OnMessage(CGUIMessage& message) override;
+ std::string GetCurrentPlayer();
+
+ int GetMessageMask() override;
+ void OnApplicationMessage(KODI::MESSAGING::ThreadMessage* pMsg) override;
+
+ bool PlayMedia(CFileItem& item, const std::string& player, PLAYLIST::Id playlistId);
+ bool ProcessAndStartPlaylist(const std::string& strPlayList,
+ PLAYLIST::CPlayList& playlist,
+ PLAYLIST::Id playlistId,
+ int track = 0);
+ bool PlayFile(CFileItem item, const std::string& player, bool bRestart = false);
+ void StopPlaying();
+ void Restart(bool bSamePosition = true);
+ void DelayedPlayerRestart();
+ void CheckDelayedPlayerRestart();
+ bool IsPlayingFullScreenVideo() const;
+ bool IsFullScreen();
+ bool OnAction(const CAction &action);
+ void CloseNetworkShares();
+
+ void ConfigureAndEnableAddons();
+ void ShowAppMigrationMessage();
+ void Process() override;
+ void ProcessSlow();
+ /*!
+ \brief Returns the total time in fractional seconds of the currently playing media
+
+ Beware that this method returns fractional seconds whereas IPlayer::GetTotalTime() returns milliseconds.
+ */
+ double GetTotalTime() const;
+ /*!
+ \brief Returns the current time in fractional seconds of the currently playing media
+
+ Beware that this method returns fractional seconds whereas IPlayer::GetTime() returns milliseconds.
+ */
+ double GetTime() const;
+ float GetPercentage() const;
+
+ // Get the percentage of data currently cached/buffered (aq/vq + FileCache) from the input stream if applicable.
+ float GetCachePercentage() const;
+
+ void SeekPercentage(float percent);
+ void SeekTime( double dTime = 0.0 );
+
+ void UpdateLibraries();
+
+ void UpdateCurrentPlayArt();
+
+ bool ExecuteXBMCAction(std::string action, const CGUIListItemPtr &item = NULL);
+
+#ifdef HAS_DVD_DRIVE
+ std::unique_ptr<MEDIA_DETECT::CAutorun> m_Autorun;
+#endif
+
+ std::string m_strPlayListFile;
+
+ bool IsAppFocused() const { return m_AppFocused; }
+
+ bool GetRenderGUI() const override;
+
+ bool SetLanguage(const std::string &strLanguage);
+ bool LoadLanguage(bool reload);
+
+ void SetLoggingIn(bool switchingProfiles);
+
+ std::unique_ptr<CServiceManager> m_ServiceManager;
+
+ /*!
+ \brief Locks calls from outside kodi (e.g. python) until framemove is processed.
+ */
+ void LockFrameMoveGuard();
+
+ /*!
+ \brief Unlocks calls from outside kodi (e.g. python).
+ */
+ void UnlockFrameMoveGuard();
+
+protected:
+ bool OnSettingsSaving() const override;
+ void PlaybackCleanup();
+
+ // inbound protocol
+ bool OnEvent(XBMC_Event& newEvent);
+
+ std::shared_ptr<ANNOUNCEMENT::CAnnouncementManager> m_pAnnouncementManager;
+ std::unique_ptr<CGUIComponent> m_pGUI;
+ std::unique_ptr<CWinSystemBase> m_pWinSystem;
+ std::unique_ptr<ActiveAE::CActiveAE> m_pActiveAE;
+ std::shared_ptr<CAppInboundProtocol> m_pAppPort;
+ std::deque<XBMC_Event> m_portEvents;
+ CCriticalSection m_portSection;
+
+ // timer information
+ CStopWatch m_restartPlayerTimer;
+ CStopWatch m_frameTime;
+ CStopWatch m_slowTimer;
+ XbmcThreads::EndTime<> m_guiRefreshTimer;
+
+ std::string m_prevMedia;
+ bool m_bInitializing = true;
+
+ int m_nextPlaylistItem = -1;
+
+ std::chrono::time_point<std::chrono::steady_clock> m_lastRenderTime;
+ bool m_skipGuiRender = false;
+
+ std::unique_ptr<MUSIC_INFO::CMusicInfoScanner> m_musicInfoScanner;
+
+ bool PlayStack(CFileItem& item, bool bRestart);
+
+ void HandlePortEvents();
+
+ std::unique_ptr<CInertialScrollingHandler> m_pInertialScrollingHandler;
+
+ std::vector<std::shared_ptr<ADDON::CAddonInfo>>
+ m_incompatibleAddons; /*!< Result of addon migration (incompatible addon infos) */
+
+public:
+ bool m_bStop{false};
+ bool m_AppFocused{true};
+
+private:
+ void PrintStartupLog();
+ void ResetCurrentItem();
+
+ mutable CCriticalSection m_critSection; /*!< critical section for all changes to this class, except for changes to triggers */
+
+ CCriticalSection m_frameMoveGuard; /*!< critical section for synchronizing GUI actions from inside and outside (python) */
+ std::atomic_uint m_WaitingExternalCalls; /*!< counts threads which are waiting to be processed in FrameMove */
+ unsigned int m_ProcessedExternalCalls = 0; /*!< counts calls which are processed during one "door open" cycle in FrameMove */
+ unsigned int m_ProcessedExternalDecay = 0; /*!< counts to close door after a few frames of no python activity */
+ int m_ExitCode{EXITCODE_QUIT};
+};
+
+XBMC_GLOBAL_REF(CApplication,g_application);
+#define g_application XBMC_GLOBAL_USE(CApplication)
diff --git a/xbmc/application/ApplicationActionListeners.cpp b/xbmc/application/ApplicationActionListeners.cpp
new file mode 100644
index 0000000..83c798e
--- /dev/null
+++ b/xbmc/application/ApplicationActionListeners.cpp
@@ -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.
+ */
+
+#include "ApplicationActionListeners.h"
+
+#include "interfaces/IActionListener.h"
+#include "threads/CriticalSection.h"
+
+#include <algorithm>
+#include <mutex>
+
+CApplicationActionListeners::CApplicationActionListeners(CCriticalSection& section)
+ : m_critSection(section)
+{
+}
+
+void CApplicationActionListeners::RegisterActionListener(IActionListener* listener)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ const auto it = std::find(m_actionListeners.begin(), m_actionListeners.end(), listener);
+ if (it == m_actionListeners.end())
+ m_actionListeners.push_back(listener);
+}
+
+void CApplicationActionListeners::UnregisterActionListener(IActionListener* listener)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto it = std::find(m_actionListeners.begin(), m_actionListeners.end(), listener);
+ if (it != m_actionListeners.end())
+ m_actionListeners.erase(it);
+}
+
+bool CApplicationActionListeners::NotifyActionListeners(const CAction& action) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& listener : m_actionListeners)
+ {
+ if (listener->OnAction(action))
+ return true;
+ }
+
+ return false;
+}
diff --git a/xbmc/application/ApplicationActionListeners.h b/xbmc/application/ApplicationActionListeners.h
new file mode 100644
index 0000000..8f72dd4
--- /dev/null
+++ b/xbmc/application/ApplicationActionListeners.h
@@ -0,0 +1,53 @@
+/*
+ * 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 "application/IApplicationComponent.h"
+
+#include <vector>
+
+class CAction;
+class CApplication;
+class CCriticalSection;
+class IActionListener;
+
+/*!
+ * \brief Class handling application support for action listeners.
+ */
+
+class CApplicationActionListeners : public IApplicationComponent
+{
+ friend class CApplication;
+
+public:
+ CApplicationActionListeners(CCriticalSection& sect);
+
+ /*!
+ \brief Register an action listener.
+ \param listener The listener to register
+ */
+ void RegisterActionListener(IActionListener* listener);
+ /*!
+ \brief Unregister an action listener.
+ \param listener The listener to unregister
+ */
+ void UnregisterActionListener(IActionListener* listener);
+
+protected:
+ /*!
+ \brief Delegates the action to all registered action handlers.
+ \param action The action
+ \return true, if the action was taken by one of the action listener.
+ */
+ bool NotifyActionListeners(const CAction& action) const;
+
+ std::vector<IActionListener*> m_actionListeners;
+
+ CCriticalSection& m_critSection;
+};
diff --git a/xbmc/application/ApplicationComponents.h b/xbmc/application/ApplicationComponents.h
new file mode 100644
index 0000000..33ae0d3
--- /dev/null
+++ b/xbmc/application/ApplicationComponents.h
@@ -0,0 +1,15 @@
+/*
+ * 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 "application/IApplicationComponent.h"
+#include "utils/ComponentContainer.h"
+
+//! \brief Convenience alias for application components.
+using CApplicationComponents = CComponentContainer<IApplicationComponent>;
diff --git a/xbmc/application/ApplicationEnums.h b/xbmc/application/ApplicationEnums.h
new file mode 100644
index 0000000..3afa41e
--- /dev/null
+++ b/xbmc/application/ApplicationEnums.h
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+enum StartupAction
+{
+ STARTUP_ACTION_NONE = 0,
+ STARTUP_ACTION_PLAY_TV,
+ STARTUP_ACTION_PLAY_RADIO
+};
+
+// Do not change the numbers; external scripts depend on them
+enum
+{
+ EXITCODE_QUIT = 0,
+ EXITCODE_POWERDOWN = 64,
+ EXITCODE_RESTARTAPP = 65,
+ EXITCODE_REBOOT = 66,
+};
diff --git a/xbmc/application/ApplicationPlayer.cpp b/xbmc/application/ApplicationPlayer.cpp
new file mode 100644
index 0000000..ef571a6
--- /dev/null
+++ b/xbmc/application/ApplicationPlayer.cpp
@@ -0,0 +1,1061 @@
+/*
+ * 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 "ApplicationPlayer.h"
+
+#include "ServiceBroker.h"
+#include "cores/DataCacheCore.h"
+#include "cores/IPlayer.h"
+#include "cores/VideoPlayer/VideoPlayer.h"
+#include "cores/playercorefactory/PlayerCoreFactory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+std::shared_ptr<const IPlayer> CApplicationPlayer::GetInternal() const
+{
+ std::unique_lock<CCriticalSection> lock(m_playerLock);
+ return m_pPlayer;
+}
+
+std::shared_ptr<IPlayer> CApplicationPlayer::GetInternal()
+{
+ std::unique_lock<CCriticalSection> lock(m_playerLock);
+ return m_pPlayer;
+}
+
+void CApplicationPlayer::ClosePlayer()
+{
+ m_nextItem.pItem.reset();
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ CloseFile();
+ ResetPlayer();
+ }
+}
+
+void CApplicationPlayer::ResetPlayer()
+{
+ // we need to do this directly on the member
+ std::unique_lock<CCriticalSection> lock(m_playerLock);
+ m_pPlayer.reset();
+}
+
+void CApplicationPlayer::CloseFile(bool reopen)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ player->CloseFile(reopen);
+ }
+}
+
+void CApplicationPlayer::CreatePlayer(const CPlayerCoreFactory &factory, const std::string &player, IPlayerCallback& callback)
+{
+ std::unique_lock<CCriticalSection> lock(m_playerLock);
+ if (!m_pPlayer)
+ {
+ CDataCacheCore::GetInstance().Reset();
+ m_pPlayer = factory.CreatePlayer(player, callback);
+ }
+}
+
+std::string CApplicationPlayer::GetCurrentPlayer() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ return player->m_name;
+ }
+ return "";
+}
+
+bool CApplicationPlayer::OpenFile(const CFileItem& item, const CPlayerOptions& options,
+ const CPlayerCoreFactory &factory,
+ const std::string &playerName, IPlayerCallback& callback)
+{
+ // get player type
+ std::string newPlayer;
+ if (!playerName.empty())
+ newPlayer = playerName;
+ else
+ newPlayer = factory.GetDefaultPlayer(item);
+
+ // check if we need to close current player
+ // VideoPlayer can open a new file while playing
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player && player->IsPlaying())
+ {
+ bool needToClose = false;
+
+ if (item.IsDiscImage() || item.IsDVDFile())
+ needToClose = true;
+
+ if (player->m_name != newPlayer)
+ needToClose = true;
+
+ if (player->m_type != "video")
+ needToClose = true;
+
+ if (needToClose)
+ {
+ m_nextItem.pItem = std::make_shared<CFileItem>(item);
+ m_nextItem.options = options;
+ m_nextItem.playerName = newPlayer;
+ m_nextItem.callback = &callback;
+
+ CloseFile();
+ if (player->m_name != newPlayer)
+ {
+ std::unique_lock<CCriticalSection> lock(m_playerLock);
+ m_pPlayer.reset();
+ }
+ return true;
+ }
+ }
+ else if (player && player->m_name != newPlayer)
+ {
+ CloseFile();
+ {
+ std::unique_lock<CCriticalSection> lock(m_playerLock);
+ m_pPlayer.reset();
+ player.reset();
+ }
+ }
+
+ if (!player)
+ {
+ CreatePlayer(factory, newPlayer, callback);
+ player = GetInternal();
+ if (!player)
+ return false;
+ }
+
+ bool ret = player->OpenFile(item, options);
+
+ m_nextItem.pItem.reset();
+
+ // reset caching timers
+ m_audioStreamUpdate.SetExpired();
+ m_videoStreamUpdate.SetExpired();
+ m_subtitleStreamUpdate.SetExpired();
+
+ return ret;
+}
+
+void CApplicationPlayer::OpenNext(const CPlayerCoreFactory &factory)
+{
+ if (m_nextItem.pItem)
+ {
+ OpenFile(*m_nextItem.pItem, m_nextItem.options,
+ factory,
+ m_nextItem.playerName, *m_nextItem.callback);
+ m_nextItem.pItem.reset();
+ }
+}
+
+bool CApplicationPlayer::HasPlayer() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return player != nullptr;
+}
+
+int CApplicationPlayer::GetChapter() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetChapter();
+ else
+ return -1;
+}
+
+int CApplicationPlayer::GetChapterCount() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetChapterCount();
+ else
+ return 0;
+}
+
+void CApplicationPlayer::GetChapterName(std::string& strChapterName, int chapterIdx) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ player->GetChapterName(strChapterName, chapterIdx);
+}
+
+int64_t CApplicationPlayer::GetChapterPos(int chapterIdx) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetChapterPos(chapterIdx);
+
+ return -1;
+}
+
+bool CApplicationPlayer::HasAudio() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->HasAudio());
+}
+
+bool CApplicationPlayer::HasVideo() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->HasVideo());
+}
+
+bool CApplicationPlayer::HasGame() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->HasGame());
+}
+
+PLAYLIST::Id CApplicationPlayer::GetPreferredPlaylist() const
+{
+ if (IsPlayingVideo())
+ return PLAYLIST::TYPE_VIDEO;
+
+ if (IsPlayingAudio())
+ return PLAYLIST::TYPE_MUSIC;
+
+ return PLAYLIST::TYPE_NONE;
+}
+
+bool CApplicationPlayer::HasRDS() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->HasRDS());
+}
+
+bool CApplicationPlayer::IsPaused() const
+{
+ return (GetPlaySpeed() == 0);
+}
+
+bool CApplicationPlayer::IsPlaying() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->IsPlaying());
+}
+
+bool CApplicationPlayer::IsPausedPlayback() const
+{
+ return (IsPlaying() && (GetPlaySpeed() == 0));
+}
+
+bool CApplicationPlayer::IsPlayingAudio() const
+{
+ return (IsPlaying() && !HasVideo() && HasAudio());
+}
+
+bool CApplicationPlayer::IsPlayingVideo() const
+{
+ return (IsPlaying() && HasVideo());
+}
+
+bool CApplicationPlayer::IsPlayingGame() const
+{
+ return (IsPlaying() && HasGame());
+}
+
+bool CApplicationPlayer::IsPlayingRDS() const
+{
+ return (IsPlaying() && HasRDS());
+}
+
+void CApplicationPlayer::Pause()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ player->Pause();
+ }
+}
+
+void CApplicationPlayer::SetMute(bool bOnOff)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetMute(bOnOff);
+}
+
+void CApplicationPlayer::SetVolume(float volume)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetVolume(volume);
+}
+
+void CApplicationPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->Seek(bPlus, bLargeStep, bChapterOverride);
+}
+
+void CApplicationPlayer::SeekPercentage(float fPercent)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SeekPercentage(fPercent);
+}
+
+bool CApplicationPlayer::IsPassthrough() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->IsPassthrough());
+}
+
+bool CApplicationPlayer::CanSeek() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->CanSeek());
+}
+
+bool CApplicationPlayer::SeekScene(bool bPlus)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ return (player && player->SeekScene(bPlus));
+}
+
+void CApplicationPlayer::SeekTime(int64_t iTime)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SeekTime(iTime);
+}
+
+void CApplicationPlayer::SeekTimeRelative(int64_t iTime)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ // use relative seeking if implemented by player
+ if (!player->SeekTimeRelative(iTime))
+ {
+ int64_t abstime = GetTime() + iTime;
+ player->SeekTime(abstime);
+ }
+ }
+}
+
+int64_t CApplicationPlayer::GetTime() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return CDataCacheCore::GetInstance().GetPlayTime();
+ else
+ return 0;
+}
+
+int64_t CApplicationPlayer::GetMinTime() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return CDataCacheCore::GetInstance().GetMinTime();
+ else
+ return 0;
+}
+
+int64_t CApplicationPlayer::GetMaxTime() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return CDataCacheCore::GetInstance().GetMaxTime();
+ else
+ return 0;
+}
+
+time_t CApplicationPlayer::GetStartTime() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return CDataCacheCore::GetInstance().GetStartTime();
+ else
+ return 0;
+}
+
+int64_t CApplicationPlayer::GetTotalTime() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ int64_t total = CDataCacheCore::GetInstance().GetMaxTime() - CDataCacheCore::GetInstance().GetMinTime();
+ return total;
+ }
+ else
+ return 0;
+}
+
+bool CApplicationPlayer::IsCaching() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->IsCaching());
+}
+
+bool CApplicationPlayer::IsInMenu() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->IsInMenu());
+}
+
+MenuType CApplicationPlayer::GetSupportedMenuType() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (!player)
+ {
+ return MenuType::NONE;
+ }
+ return player->GetSupportedMenuType();
+}
+
+int CApplicationPlayer::GetCacheLevel() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetCacheLevel();
+ else
+ return 0;
+}
+
+int CApplicationPlayer::GetSubtitleCount() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetSubtitleCount();
+ else
+ return 0;
+}
+
+int CApplicationPlayer::GetAudioStream()
+{
+ if (!m_audioStreamUpdate.IsTimePast())
+ return m_iAudioStream;
+
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ m_iAudioStream = player->GetAudioStream();
+ m_audioStreamUpdate.Set(1000ms);
+ return m_iAudioStream;
+ }
+ else
+ return 0;
+}
+
+int CApplicationPlayer::GetSubtitle()
+{
+ if (!m_subtitleStreamUpdate.IsTimePast())
+ return m_iSubtitleStream;
+
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ m_iSubtitleStream = player->GetSubtitle();
+ m_subtitleStreamUpdate.Set(1000ms);
+ return m_iSubtitleStream;
+ }
+ else
+ return 0;
+}
+
+bool CApplicationPlayer::GetSubtitleVisible() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ return player && player->GetSubtitleVisible();
+}
+
+bool CApplicationPlayer::CanPause() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ return (player && player->CanPause());
+}
+
+bool CApplicationPlayer::HasTeletextCache() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->HasTeletextCache();
+ else
+ return false;
+}
+
+std::shared_ptr<TextCacheStruct_t> CApplicationPlayer::GetTeletextCache()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ return player->GetTeletextCache();
+ else
+ return {};
+}
+
+float CApplicationPlayer::GetPercentage() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ float fPercent = CDataCacheCore::GetInstance().GetPlayPercentage();
+ return std::max(0.0f, std::min(fPercent, 100.0f));
+ }
+ else
+ return 0.0;
+}
+
+float CApplicationPlayer::GetCachePercentage() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetCachePercentage();
+ else
+ return 0.0;
+}
+
+void CApplicationPlayer::SetSpeed(float speed)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetSpeed(speed);
+}
+
+void CApplicationPlayer::SetTempo(float tempo)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetTempo(tempo);
+}
+
+void CApplicationPlayer::FrameAdvance(int frames)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->FrameAdvance(frames);
+}
+
+void CApplicationPlayer::DoAudioWork()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->DoAudioWork();
+}
+
+std::string CApplicationPlayer::GetPlayerState()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ return player->GetPlayerState();
+ else
+ return "";
+}
+
+bool CApplicationPlayer::QueueNextFile(const CFileItem &file)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ return (player && player->QueueNextFile(file));
+}
+
+bool CApplicationPlayer::SetPlayerState(const std::string& state)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ return (player && player->SetPlayerState(state));
+}
+
+void CApplicationPlayer::OnNothingToQueueNotify()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->OnNothingToQueueNotify();
+}
+
+void CApplicationPlayer::GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ player->GetVideoStreamInfo(streamId, info);
+}
+
+void CApplicationPlayer::GetAudioStreamInfo(int index, AudioStreamInfo& info) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ player->GetAudioStreamInfo(index, info);
+}
+
+int CApplicationPlayer::GetPrograms(std::vector<ProgramInfo> &programs)
+{
+ int ret = 0;
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ ret = player->GetPrograms(programs);
+ return ret;
+}
+
+void CApplicationPlayer::SetProgram(int progId)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetProgram(progId);
+}
+
+int CApplicationPlayer::GetProgramsCount() const
+{
+ int ret = 0;
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ ret = player->GetProgramsCount();
+ return ret;
+}
+
+bool CApplicationPlayer::OnAction(const CAction &action)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ return (player && player->OnAction(action));
+}
+
+int CApplicationPlayer::GetAudioStreamCount() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetAudioStreamCount();
+ else
+ return 0;
+}
+
+int CApplicationPlayer::GetVideoStream()
+{
+ if (!m_videoStreamUpdate.IsTimePast())
+ return m_iVideoStream;
+
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ m_iVideoStream = player->GetVideoStream();
+ m_videoStreamUpdate.Set(1000ms);
+ return m_iVideoStream;
+ }
+ else
+ return 0;
+}
+
+int CApplicationPlayer::GetVideoStreamCount() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetVideoStreamCount();
+ else
+ return 0;
+}
+
+void CApplicationPlayer::SetAudioStream(int iStream)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ player->SetAudioStream(iStream);
+ m_iAudioStream = iStream;
+ m_audioStreamUpdate.Set(1000ms);
+ }
+}
+
+void CApplicationPlayer::GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ player->GetSubtitleStreamInfo(index, info);
+}
+
+void CApplicationPlayer::SetSubtitle(int iStream)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ player->SetSubtitle(iStream);
+ m_iSubtitleStream = iStream;
+ m_subtitleStreamUpdate.Set(1000ms);
+ }
+}
+
+void CApplicationPlayer::SetSubtitleVisible(bool bVisible)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ player->SetSubtitleVisible(bVisible);
+ }
+}
+
+void CApplicationPlayer::SetSubtitleVerticalPosition(int value, bool save)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ player->SetSubtitleVerticalPosition(value, save);
+ }
+}
+
+void CApplicationPlayer::SetTime(int64_t time)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ return player->SetTime(time);
+}
+
+void CApplicationPlayer::SetTotalTime(int64_t time)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetTotalTime(time);
+}
+
+void CApplicationPlayer::SetVideoStream(int iStream)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ player->SetVideoStream(iStream);
+ m_iVideoStream = iStream;
+ m_videoStreamUpdate.Set(1000ms);
+ }
+}
+
+void CApplicationPlayer::AddSubtitle(const std::string& strSubPath)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->AddSubtitle(strSubPath);
+}
+
+void CApplicationPlayer::SetSubTitleDelay(float fValue)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetSubTitleDelay(fValue);
+}
+
+void CApplicationPlayer::SetAVDelay(float fValue)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetAVDelay(fValue);
+}
+
+void CApplicationPlayer::SetDynamicRangeCompression(long drc)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetDynamicRangeCompression(drc);
+}
+
+void CApplicationPlayer::LoadPage(int p, int sp, unsigned char* buffer)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->LoadPage(p, sp, buffer);
+}
+
+void CApplicationPlayer::GetAudioCapabilities(std::vector<int>& audioCaps) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ player->GetAudioCapabilities(audioCaps);
+}
+
+void CApplicationPlayer::GetSubtitleCapabilities(std::vector<int>& subCaps) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ player->GetSubtitleCapabilities(subCaps);
+}
+
+int CApplicationPlayer::SeekChapter(int iChapter)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ return player->SeekChapter(iChapter);
+ else
+ return 0;
+}
+
+void CApplicationPlayer::SetPlaySpeed(float speed)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (!player)
+ return;
+
+ if (!IsPlayingAudio() && !IsPlayingVideo())
+ return ;
+
+ SetSpeed(speed);
+}
+
+float CApplicationPlayer::GetPlaySpeed() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ return CDataCacheCore::GetInstance().GetSpeed();
+ }
+ else
+ return 0;
+}
+
+float CApplicationPlayer::GetPlayTempo() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ return CDataCacheCore::GetInstance().GetTempo();
+ }
+ else
+ return 0;
+}
+
+bool CApplicationPlayer::SupportsTempo() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->SupportsTempo();
+ else
+ return false;
+}
+
+void CApplicationPlayer::FrameMove()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ if (CDataCacheCore::GetInstance().IsPlayerStateChanged())
+ // CApplicationMessenger would be overhead because we are already in gui thread
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_STATE_CHANGED);
+ }
+}
+
+void CApplicationPlayer::Render(bool clear, uint32_t alpha, bool gui)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->Render(clear, alpha, gui);
+}
+
+void CApplicationPlayer::FlushRenderer()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->FlushRenderer();
+}
+
+void CApplicationPlayer::SetRenderViewMode(int mode, float zoom, float par, float shift, bool stretch)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->SetRenderViewMode(mode, zoom, par, shift, stretch);
+}
+
+float CApplicationPlayer::GetRenderAspectRatio() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetRenderAspectRatio();
+ else
+ return 1.0;
+}
+
+void CApplicationPlayer::TriggerUpdateResolution()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->TriggerUpdateResolution();
+}
+
+bool CApplicationPlayer::IsRenderingVideo() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->IsRenderingVideo();
+ else
+ return false;
+}
+
+bool CApplicationPlayer::IsRenderingGuiLayer() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return CServiceBroker::GetDataCacheCore().GetGuiRender();
+ else
+ return false;
+}
+
+bool CApplicationPlayer::IsRenderingVideoLayer() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return CServiceBroker::GetDataCacheCore().GetVideoRender();
+ else
+ return false;
+}
+
+bool CApplicationPlayer::Supports(EINTERLACEMETHOD method) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->Supports(method);
+ else
+ return false;
+}
+
+EINTERLACEMETHOD CApplicationPlayer::GetDeinterlacingMethodDefault() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->GetDeinterlacingMethodDefault();
+ else
+ return EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE;
+}
+
+bool CApplicationPlayer::Supports(ESCALINGMETHOD method) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->Supports(method);
+ else
+ return false;
+}
+
+bool CApplicationPlayer::Supports(ERENDERFEATURE feature) const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->Supports(feature);
+ else
+ return false;
+}
+
+unsigned int CApplicationPlayer::RenderCaptureAlloc()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ return player->RenderCaptureAlloc();
+ else
+ return 0;
+}
+
+void CApplicationPlayer::RenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->RenderCapture(captureId, width, height, flags);
+}
+
+void CApplicationPlayer::RenderCaptureRelease(unsigned int captureId)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ player->RenderCaptureRelease(captureId);
+}
+
+bool CApplicationPlayer::RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ return player->RenderCaptureGetPixels(captureId, millis, buffer, size);
+ else
+ return false;
+}
+
+bool CApplicationPlayer::IsExternalPlaying() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ if (player->IsPlaying() && player->m_type == "external")
+ return true;
+ }
+ return false;
+}
+
+bool CApplicationPlayer::IsRemotePlaying() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ if (player->IsPlaying() && player->m_type == "remote")
+ return true;
+ }
+ return false;
+}
+
+CVideoSettings CApplicationPlayer::GetVideoSettings() const
+{
+ std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ {
+ return player->GetVideoSettings();
+ }
+ return CVideoSettings();
+}
+
+void CApplicationPlayer::SetVideoSettings(CVideoSettings& settings)
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ if (player)
+ {
+ return player->SetVideoSettings(settings);
+ }
+}
+
+CSeekHandler& CApplicationPlayer::GetSeekHandler()
+{
+ return m_seekHandler;
+}
+
+const CSeekHandler& CApplicationPlayer::GetSeekHandler() const
+{
+ return m_seekHandler;
+}
+
+void CApplicationPlayer::SetUpdateStreamDetails()
+{
+ std::shared_ptr<IPlayer> player = GetInternal();
+ CVideoPlayer* vp = dynamic_cast<CVideoPlayer*>(player.get());
+ if (vp)
+ vp->SetUpdateStreamDetails();
+}
+
+bool CApplicationPlayer::HasGameAgent() const
+{
+ const std::shared_ptr<const IPlayer> player = GetInternal();
+ if (player)
+ return player->HasGameAgent();
+
+ return false;
+}
+
+int CApplicationPlayer::GetSubtitleDelay() const
+{
+ // converts subtitle delay to a percentage
+ const auto& advSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ const auto delay = this->GetVideoSettings().m_SubtitleDelay;
+ const auto range = advSettings->m_videoSubsDelayRange;
+ return static_cast<int>(0.5f + (delay + range) / (2.f * range) * 100.0f);
+}
+
+int CApplicationPlayer::GetAudioDelay() const
+{
+ // converts audio delay to a percentage
+ const auto& advSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ const auto delay = this->GetVideoSettings().m_AudioDelay;
+ const auto range = advSettings->m_videoAudioDelayRange;
+ return static_cast<int>(0.5f + (delay + range) / (2.f * range) * 100.0f);
+}
diff --git a/xbmc/application/ApplicationPlayer.h b/xbmc/application/ApplicationPlayer.h
new file mode 100644
index 0000000..f760936
--- /dev/null
+++ b/xbmc/application/ApplicationPlayer.h
@@ -0,0 +1,205 @@
+/*
+ * 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 "SeekHandler.h"
+#include "application/IApplicationComponent.h"
+#include "cores/IPlayer.h"
+#include "cores/MenuType.h"
+#include "playlists/PlayListTypes.h"
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CAction;
+class CPlayerCoreFactory;
+class CPlayerOptions;
+class CStreamDetails;
+
+struct AudioStreamInfo;
+struct VideoStreamInfo;
+struct SubtitleStreamInfo;
+struct TextCacheStruct_t;
+
+class CApplicationPlayer : public IApplicationComponent
+{
+public:
+ CApplicationPlayer() = default;
+
+ // player management
+ void ClosePlayer();
+ void ResetPlayer();
+ std::string GetCurrentPlayer() const;
+ float GetPlaySpeed() const;
+ float GetPlayTempo() const;
+ bool HasPlayer() const;
+ bool OpenFile(const CFileItem& item, const CPlayerOptions& options,
+ const CPlayerCoreFactory &factory,
+ const std::string &playerName, IPlayerCallback& callback);
+ void OpenNext(const CPlayerCoreFactory &factory);
+ void SetPlaySpeed(float speed);
+ void SetTempo(float tempo);
+ void FrameAdvance(int frames);
+
+ void FrameMove();
+ void Render(bool clear, uint32_t alpha = 255, bool gui = true);
+ void FlushRenderer();
+ void SetRenderViewMode(int mode, float zoom, float par, float shift, bool stretch);
+ float GetRenderAspectRatio() const;
+ void TriggerUpdateResolution();
+ bool IsRenderingVideo() const;
+ bool IsRenderingGuiLayer() const;
+ bool IsRenderingVideoLayer() const;
+ bool Supports(EINTERLACEMETHOD method) const;
+ EINTERLACEMETHOD GetDeinterlacingMethodDefault() const;
+ bool Supports(ESCALINGMETHOD method) const;
+ bool Supports(ERENDERFEATURE feature) const;
+ unsigned int RenderCaptureAlloc();
+ void RenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags = 0);
+ void RenderCaptureRelease(unsigned int captureId);
+ bool RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size);
+ bool IsExternalPlaying() const;
+ bool IsRemotePlaying() const;
+
+ // proxy calls
+ void AddSubtitle(const std::string& strSubPath);
+ bool CanPause() const;
+ bool CanSeek() const;
+ void DoAudioWork();
+ int GetAudioDelay() const;
+ void GetAudioCapabilities(std::vector<int>& audioCaps) const;
+ int GetAudioStream();
+ int GetAudioStreamCount() const;
+ void GetAudioStreamInfo(int index, AudioStreamInfo& info) const;
+ int GetCacheLevel() const;
+ float GetCachePercentage() const;
+ int GetChapterCount() const;
+ int GetChapter() const;
+ void GetChapterName(std::string& strChapterName, int chapterIdx = -1) const;
+ int64_t GetChapterPos(int chapterIdx = -1) const;
+ float GetPercentage() const;
+ std::string GetPlayerState();
+ PLAYLIST::Id GetPreferredPlaylist() const;
+ int GetSubtitleDelay() const;
+ int GetSubtitle();
+ void GetSubtitleCapabilities(std::vector<int>& subCaps) const;
+ int GetSubtitleCount() const;
+ void GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const;
+ bool GetSubtitleVisible() const;
+ bool HasTeletextCache() const;
+ std::shared_ptr<TextCacheStruct_t> GetTeletextCache();
+ int64_t GetTime() const;
+ int64_t GetMinTime() const;
+ int64_t GetMaxTime() const;
+ time_t GetStartTime() const;
+ int64_t GetTotalTime() const;
+ int GetVideoStream();
+ int GetVideoStreamCount() const;
+ void GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const;
+ int GetPrograms(std::vector<ProgramInfo>& programs);
+ void SetProgram(int progId);
+ int GetProgramsCount() const;
+ bool HasAudio() const;
+
+ /*!
+ * \brief Get the supported menu type
+ * \return The supported menu type
+ */
+ MenuType GetSupportedMenuType() const;
+
+ bool HasVideo() const;
+ bool HasGame() const;
+ bool HasRDS() const;
+ bool IsCaching() const;
+ bool IsInMenu() const;
+ bool IsPaused() const;
+ bool IsPausedPlayback() const;
+ bool IsPassthrough() const;
+ bool IsPlaying() const;
+ bool IsPlayingAudio() const;
+ bool IsPlayingVideo() const;
+ bool IsPlayingGame() const;
+ bool IsPlayingRDS() const;
+ void LoadPage(int p, int sp, unsigned char* buffer);
+ bool OnAction(const CAction &action);
+ void OnNothingToQueueNotify();
+ void Pause();
+ bool QueueNextFile(const CFileItem &file);
+ void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false);
+ int SeekChapter(int iChapter);
+ void SeekPercentage(float fPercent = 0);
+ bool SeekScene(bool bPlus = true);
+ void SeekTime(int64_t iTime = 0);
+ void SeekTimeRelative(int64_t iTime = 0);
+ void SetAudioStream(int iStream);
+ void SetAVDelay(float fValue = 0.0f);
+ void SetDynamicRangeCompression(long drc);
+ void SetMute(bool bOnOff);
+ bool SetPlayerState(const std::string& state);
+ void SetSubtitle(int iStream);
+ void SetSubTitleDelay(float fValue = 0.0f);
+ void SetSubtitleVisible(bool bVisible);
+
+ /*!
+ * \brief Set the subtitle vertical position,
+ * it depends on current screen resolution
+ * \param value The subtitle position in pixels
+ * \param save If true, the value will be saved to resolution info
+ */
+ void SetSubtitleVerticalPosition(const int value, bool save);
+
+ void SetTime(int64_t time);
+ void SetTotalTime(int64_t time);
+ void SetVideoStream(int iStream);
+ void SetVolume(float volume);
+ void SetSpeed(float speed);
+ bool SupportsTempo() const;
+
+ CVideoSettings GetVideoSettings() const;
+ void SetVideoSettings(CVideoSettings& settings);
+
+ CSeekHandler& GetSeekHandler();
+ const CSeekHandler& GetSeekHandler() const;
+
+ void SetUpdateStreamDetails();
+
+ /*!
+ * \copydoc IPlayer::HasGameAgent
+ */
+ bool HasGameAgent() const;
+
+private:
+ std::shared_ptr<const IPlayer> GetInternal() const;
+ std::shared_ptr<IPlayer> GetInternal();
+ void CreatePlayer(const CPlayerCoreFactory &factory, const std::string &player, IPlayerCallback& callback);
+ void CloseFile(bool reopen = false);
+
+ std::shared_ptr<IPlayer> m_pPlayer;
+ mutable CCriticalSection m_playerLock;
+ CSeekHandler m_seekHandler;
+
+ // cache player state
+ XbmcThreads::EndTime<> m_audioStreamUpdate;
+ int m_iAudioStream;
+ XbmcThreads::EndTime<> m_videoStreamUpdate;
+ int m_iVideoStream;
+ XbmcThreads::EndTime<> m_subtitleStreamUpdate;
+ int m_iSubtitleStream;
+
+ struct SNextItem
+ {
+ std::shared_ptr<CFileItem> pItem;
+ CPlayerOptions options = {};
+ std::string playerName;
+ IPlayerCallback *callback = nullptr;
+ } m_nextItem;
+};
diff --git a/xbmc/application/ApplicationPlayerCallback.cpp b/xbmc/application/ApplicationPlayerCallback.cpp
new file mode 100644
index 0000000..0dbb4b9
--- /dev/null
+++ b/xbmc/application/ApplicationPlayerCallback.cpp
@@ -0,0 +1,346 @@
+/*
+ * 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 "ApplicationPlayerCallback.h"
+
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationStackHelper.h"
+#include "cores/DataCacheCore.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/StereoscopicsManager.h"
+#include "interfaces/AnnouncementManager.h"
+#include "interfaces/json-rpc/JSONUtils.h"
+#include "interfaces/python/XBPython.h"
+#include "profiles/ProfileManager.h"
+#include "pvr/PVRManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/MediaSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/SaveFileStateJob.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoInfoTag.h"
+
+CApplicationPlayerCallback::CApplicationPlayerCallback()
+ : m_itemCurrentFile(new CFileItem), m_playerEvent(true, true)
+{
+}
+
+void CApplicationPlayerCallback::OnPlayBackEnded()
+{
+ CLog::LogF(LOGDEBUG, "CApplicationPlayerCallback::OnPlayBackEnded");
+
+ CServiceBroker::GetPVRManager().OnPlaybackEnded(*m_itemCurrentFile);
+
+ CVariant data(CVariant::VariantTypeObject);
+ data["end"] = true;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnStop",
+ m_itemCurrentFile, data);
+
+ CGUIMessage msg(GUI_MSG_PLAYBACK_ENDED, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+}
+
+void CApplicationPlayerCallback::OnPlayBackStarted(const CFileItem& file)
+{
+ CLog::LogF(LOGDEBUG, "CApplication::OnPlayBackStarted");
+
+ // check if VideoPlayer should set file item stream details from its current streams
+ const bool isBlu_dvd_image_or_stream = (URIUtils::IsBluray(file.GetPath()) || file.IsDVDFile() ||
+ file.IsDiscImage() || file.IsInternetStream());
+
+ const bool hasNoStreamDetails =
+ (!file.HasVideoInfoTag() || !file.GetVideoInfoTag()->HasStreamDetails());
+
+ if (file.GetProperty("get_stream_details_from_player").asBoolean() ||
+ (hasNoStreamDetails && isBlu_dvd_image_or_stream))
+ {
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ appPlayer->SetUpdateStreamDetails();
+ }
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto stackHelper = components.GetComponent<CApplicationStackHelper>();
+ if (stackHelper->IsPlayingISOStack() || stackHelper->IsPlayingRegularStack())
+ m_itemCurrentFile.reset(new CFileItem(*stackHelper->GetRegisteredStack(file)));
+ else
+ m_itemCurrentFile.reset(new CFileItem(file));
+
+ /* When playing video pause any low priority jobs, they will be unpaused when playback stops.
+ * This should speed up player startup for files on internet filesystems (eg. webdav) and
+ * increase performance on low powered systems (Atom/ARM).
+ */
+ if (file.IsVideo() || file.IsGame())
+ {
+ CServiceBroker::GetJobManager()->PauseJobs();
+ }
+
+ CServiceBroker::GetPVRManager().OnPlaybackStarted(*m_itemCurrentFile);
+ stackHelper->OnPlayBackStarted(file);
+
+ m_playerEvent.Reset();
+
+ CGUIMessage msg(GUI_MSG_PLAYBACK_STARTED, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+}
+
+void CApplicationPlayerCallback::OnPlayerCloseFile(const CFileItem& file,
+ const CBookmark& bookmarkParam)
+{
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto stackHelper = components.GetComponent<CApplicationStackHelper>();
+
+ std::unique_lock<CCriticalSection> lock(stackHelper->m_critSection);
+
+ CFileItem fileItem(file);
+ CBookmark bookmark = bookmarkParam;
+ CBookmark resumeBookmark;
+ bool playCountUpdate = false;
+ float percent = 0.0f;
+
+ // Make sure we don't reset existing bookmark etc. on eg. player start failure
+ if (bookmark.timeInSeconds == 0.0)
+ return;
+
+ if (stackHelper->GetRegisteredStack(fileItem) != nullptr &&
+ stackHelper->GetRegisteredStackTotalTimeMs(fileItem) > 0)
+ {
+ // regular stack case: we have to save the bookmark on the stack
+ fileItem = *stackHelper->GetRegisteredStack(file);
+ // the bookmark coming from the player is only relative to the current part, thus needs to be corrected with these attributes (start time will be 0 for non-stackparts)
+ bookmark.timeInSeconds += stackHelper->GetRegisteredStackPartStartTimeMs(file) / 1000.0;
+ if (stackHelper->GetRegisteredStackTotalTimeMs(file) > 0)
+ bookmark.totalTimeInSeconds = stackHelper->GetRegisteredStackTotalTimeMs(file) / 1000.0;
+ bookmark.partNumber = stackHelper->GetRegisteredStackPartNumber(file);
+ }
+
+ percent = bookmark.timeInSeconds / bookmark.totalTimeInSeconds * 100;
+
+ const std::shared_ptr<CAdvancedSettings> advancedSettings =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ if ((fileItem.IsAudio() && advancedSettings->m_audioPlayCountMinimumPercent > 0 &&
+ percent >= advancedSettings->m_audioPlayCountMinimumPercent) ||
+ (fileItem.IsVideo() && advancedSettings->m_videoPlayCountMinimumPercent > 0 &&
+ percent >= advancedSettings->m_videoPlayCountMinimumPercent))
+ {
+ playCountUpdate = true;
+ }
+
+ if (advancedSettings->m_videoIgnorePercentAtEnd > 0 &&
+ bookmark.totalTimeInSeconds - bookmark.timeInSeconds <
+ 0.01 * static_cast<double>(advancedSettings->m_videoIgnorePercentAtEnd) *
+ bookmark.totalTimeInSeconds)
+ {
+ resumeBookmark.timeInSeconds = -1.0;
+ }
+ else if (bookmark.timeInSeconds > advancedSettings->m_videoIgnoreSecondsAtStart)
+ {
+ resumeBookmark = bookmark;
+ if (stackHelper->GetRegisteredStack(file) != nullptr)
+ {
+ // also update video info tag with total time
+ fileItem.GetVideoInfoTag()->m_streamDetails.SetVideoDuration(
+ 0, resumeBookmark.totalTimeInSeconds);
+ }
+ }
+ else
+ {
+ resumeBookmark.timeInSeconds = 0.0;
+ }
+
+ if (CServiceBroker::GetSettingsComponent()
+ ->GetProfileManager()
+ ->GetCurrentProfile()
+ .canWriteDatabases())
+ {
+ CSaveFileState::DoWork(fileItem, resumeBookmark, playCountUpdate);
+ }
+}
+
+void CApplicationPlayerCallback::OnPlayBackPaused()
+{
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnPlayBackPaused();
+#endif
+
+ CVariant param;
+ param["player"]["speed"] = 0;
+ param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPause",
+ m_itemCurrentFile, param);
+}
+
+void CApplicationPlayerCallback::OnPlayBackResumed()
+{
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnPlayBackResumed();
+#endif
+
+ CVariant param;
+ param["player"]["speed"] = 1;
+ param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnResume",
+ m_itemCurrentFile, param);
+}
+
+void CApplicationPlayerCallback::OnPlayBackStopped()
+{
+ CLog::LogF(LOGDEBUG, "CApplication::OnPlayBackStopped");
+
+ CServiceBroker::GetPVRManager().OnPlaybackStopped(*m_itemCurrentFile);
+
+ CVariant data(CVariant::VariantTypeObject);
+ data["end"] = false;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnStop",
+ m_itemCurrentFile, data);
+
+ CGUIMessage msg(GUI_MSG_PLAYBACK_STOPPED, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+}
+
+void CApplicationPlayerCallback::OnPlayBackError()
+{
+ //@todo Playlists can be continued by calling OnPlaybackEnded instead
+ // open error dialog
+ CGUIMessage msg(GUI_MSG_PLAYBACK_ERROR, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ OnPlayBackStopped();
+}
+
+void CApplicationPlayerCallback::OnQueueNextItem()
+{
+ CLog::LogF(LOGDEBUG, "CApplication::OnQueueNextItem");
+
+ // informs python script currently running that we are requesting the next track
+ // (does nothing if python is not loaded)
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnQueueNextItem(); // currently unimplemented
+#endif
+
+ CGUIMessage msg(GUI_MSG_QUEUE_NEXT_ITEM, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+}
+
+void CApplicationPlayerCallback::OnPlayBackSeek(int64_t iTime, int64_t seekOffset)
+{
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnPlayBackSeek(static_cast<int>(iTime),
+ static_cast<int>(seekOffset));
+#endif
+
+ CVariant param;
+ JSONRPC::CJSONUtils::MillisecondsToTimeObject(iTime, param["player"]["time"]);
+ JSONRPC::CJSONUtils::MillisecondsToTimeObject(seekOffset, param["player"]["seekoffset"]);
+ param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ param["player"]["speed"] = static_cast<int>(appPlayer->GetPlaySpeed());
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnSeek",
+ m_itemCurrentFile, param);
+
+ CDataCacheCore::GetInstance().SeekFinished(static_cast<int>(seekOffset));
+}
+
+void CApplicationPlayerCallback::OnPlayBackSeekChapter(int iChapter)
+{
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnPlayBackSeekChapter(iChapter);
+#endif
+}
+
+void CApplicationPlayerCallback::OnPlayBackSpeedChanged(int iSpeed)
+{
+#ifdef HAS_PYTHON
+ CServiceBroker::GetXBPython().OnPlayBackSpeedChanged(iSpeed);
+#endif
+
+ CVariant param;
+ param["player"]["speed"] = iSpeed;
+ param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnSpeedChanged",
+ m_itemCurrentFile, param);
+}
+
+void CApplicationPlayerCallback::OnAVChange()
+{
+ CLog::LogF(LOGDEBUG, "CApplication::OnAVChange");
+
+ CServiceBroker::GetGUI()->GetStereoscopicsManager().OnStreamChange();
+
+ CGUIMessage msg(GUI_MSG_PLAYBACK_AVCHANGE, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+
+ CVariant param;
+ param["player"]["speed"] = 1;
+ param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnAVChange",
+ m_itemCurrentFile, param);
+}
+
+void CApplicationPlayerCallback::OnAVStarted(const CFileItem& file)
+{
+ CLog::LogF(LOGDEBUG, "CApplication::OnAVStarted");
+
+ CGUIMessage msg(GUI_MSG_PLAYBACK_AVSTARTED, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+
+ CVariant param;
+ param["player"]["speed"] = 1;
+ param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnAVStart",
+ m_itemCurrentFile, param);
+}
+
+void CApplicationPlayerCallback::RequestVideoSettings(const CFileItem& fileItem)
+{
+ CVideoDatabase dbs;
+ if (dbs.Open())
+ {
+ CLog::Log(LOGDEBUG, "Loading settings for {}", CURL::GetRedacted(fileItem.GetPath()));
+
+ // Load stored settings if they exist, otherwise use default
+ CVideoSettings vs;
+ if (!dbs.GetVideoSettings(fileItem, vs))
+ vs = CMediaSettings::GetInstance().GetDefaultVideoSettings();
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ appPlayer->SetVideoSettings(vs);
+
+ dbs.Close();
+ }
+}
+
+void CApplicationPlayerCallback::StoreVideoSettings(const CFileItem& fileItem,
+ const CVideoSettings& vs)
+{
+ CVideoDatabase dbs;
+ if (dbs.Open())
+ {
+ if (vs != CMediaSettings::GetInstance().GetDefaultVideoSettings())
+ {
+ dbs.SetVideoSettings(fileItem, vs);
+ }
+ else
+ {
+ dbs.EraseVideoSettings(fileItem);
+ }
+ dbs.Close();
+ }
+}
diff --git a/xbmc/application/ApplicationPlayerCallback.h b/xbmc/application/ApplicationPlayerCallback.h
new file mode 100644
index 0000000..8cfd40b
--- /dev/null
+++ b/xbmc/application/ApplicationPlayerCallback.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 "cores/IPlayerCallback.h"
+#include "threads/Event.h"
+
+#include <memory>
+
+class CApplicationStackHelper;
+class CFileItem;
+
+class CApplicationPlayerCallback : public IPlayerCallback
+{
+public:
+ CApplicationPlayerCallback();
+
+ void OnPlayBackEnded() override;
+ void OnPlayBackStarted(const CFileItem& file) override;
+ void OnPlayerCloseFile(const CFileItem& file, const CBookmark& bookmark) override;
+ void OnPlayBackPaused() override;
+ void OnPlayBackResumed() override;
+ void OnPlayBackStopped() override;
+ void OnPlayBackError() override;
+ void OnQueueNextItem() override;
+ void OnPlayBackSeek(int64_t iTime, int64_t seekOffset) override;
+ void OnPlayBackSeekChapter(int iChapter) override;
+ void OnPlayBackSpeedChanged(int iSpeed) override;
+ void OnAVChange() override;
+ void OnAVStarted(const CFileItem& file) override;
+ void RequestVideoSettings(const CFileItem& fileItem) override;
+ void StoreVideoSettings(const CFileItem& fileItem, const CVideoSettings& vs) override;
+
+protected:
+ std::shared_ptr<CFileItem> m_itemCurrentFile; //!< Currently playing file
+ CEvent m_playerEvent;
+};
diff --git a/xbmc/application/ApplicationPowerHandling.cpp b/xbmc/application/ApplicationPowerHandling.cpp
new file mode 100644
index 0000000..ea182d5
--- /dev/null
+++ b/xbmc/application/ApplicationPowerHandling.cpp
@@ -0,0 +1,598 @@
+/*
+ * 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 "ApplicationPowerHandling.h"
+
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "addons/Addon.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "addons/gui/GUIDialogAddonSettings.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "interfaces/AnnouncementManager.h"
+#include "interfaces/generic/ScriptInvocationManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "music/MusicLibraryQueue.h"
+#include "powermanagement/DPMSSupport.h"
+#include "powermanagement/PowerTypes.h"
+#include "profiles/ProfileManager.h"
+#include "pvr/PVRManager.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsPowerManagement.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/AlarmClock.h"
+#include "utils/log.h"
+#include "video/VideoLibraryQueue.h"
+#include "windowing/WinSystem.h"
+
+void CApplicationPowerHandling::ResetScreenSaver()
+{
+ // reset our timers
+ m_shutdownTimer.StartZero();
+
+ // screen saver timer is reset only if we're not already in screensaver or
+ // DPMS mode
+ if ((!m_screensaverActive && m_iScreenSaveLock == 0) && !m_dpmsIsActive)
+ ResetScreenSaverTimer();
+}
+
+void CApplicationPowerHandling::ResetScreenSaverTimer()
+{
+ m_screenSaverTimer.StartZero();
+}
+
+void CApplicationPowerHandling::ResetSystemIdleTimer()
+{
+ // reset system idle timer
+ m_idleTimer.StartZero();
+}
+
+void CApplicationPowerHandling::ResetNavigationTimer()
+{
+ m_navigationTimer.StartZero();
+}
+
+void CApplicationPowerHandling::SetRenderGUI(bool renderGUI)
+{
+ if (renderGUI && !m_renderGUI)
+ {
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
+ }
+ m_renderGUI = renderGUI;
+}
+
+void CApplicationPowerHandling::StopScreenSaverTimer()
+{
+ m_screenSaverTimer.Stop();
+}
+
+bool CApplicationPowerHandling::ToggleDPMS(bool manual)
+{
+ auto winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return false;
+
+ std::shared_ptr<CDPMSSupport> dpms = winSystem->GetDPMSManager();
+ if (!dpms)
+ return false;
+
+ if (manual || (m_dpmsIsManual == manual))
+ {
+ if (m_dpmsIsActive)
+ {
+ m_dpmsIsActive = false;
+ m_dpmsIsManual = false;
+ SetRenderGUI(true);
+ CheckOSScreenSaverInhibitionSetting();
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "OnDPMSDeactivated");
+ return dpms->DisablePowerSaving();
+ }
+ else
+ {
+ if (dpms->EnablePowerSaving(dpms->GetSupportedModes()[0]))
+ {
+ m_dpmsIsActive = true;
+ m_dpmsIsManual = manual;
+ SetRenderGUI(false);
+ CheckOSScreenSaverInhibitionSetting();
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "OnDPMSActivated");
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool CApplicationPowerHandling::WakeUpScreenSaverAndDPMS(bool bPowerOffKeyPressed /* = false */)
+{
+ bool result = false;
+
+ // First reset DPMS, if active
+ if (m_dpmsIsActive)
+ {
+ if (m_dpmsIsManual)
+ return false;
+ //! @todo if screensaver lock is specified but screensaver is not active
+ //! (DPMS came first), activate screensaver now.
+ ToggleDPMS(false);
+ ResetScreenSaverTimer();
+ result = !m_screensaverActive || WakeUpScreenSaver(bPowerOffKeyPressed);
+ }
+ else if (m_screensaverActive)
+ result = WakeUpScreenSaver(bPowerOffKeyPressed);
+
+ if (result)
+ {
+ // allow listeners to ignore the deactivation if it precedes a powerdown/suspend etc
+ CVariant data(CVariant::VariantTypeObject);
+ data["shuttingdown"] = bPowerOffKeyPressed;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI,
+ "OnScreensaverDeactivated", data);
+ }
+
+ return result;
+}
+
+bool CApplicationPowerHandling::WakeUpScreenSaver(bool bPowerOffKeyPressed /* = false */)
+{
+ if (m_iScreenSaveLock == 2)
+ return false;
+
+ // if Screen saver is active
+ if (m_screensaverActive && !m_screensaverIdInUse.empty())
+ {
+ if (m_iScreenSaveLock == 0)
+ {
+ const std::shared_ptr<CProfileManager> profileManager =
+ CServiceBroker::GetSettingsComponent()->GetProfileManager();
+ if (profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE &&
+ (profileManager->UsingLoginScreen() ||
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_MASTERLOCK_STARTUPLOCK)) &&
+ profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE &&
+ m_screensaverIdInUse != "screensaver.xbmc.builtin.dim" &&
+ m_screensaverIdInUse != "screensaver.xbmc.builtin.black" &&
+ m_screensaverIdInUse != "visualization")
+ {
+ m_iScreenSaveLock = 2;
+ CGUIMessage msg(GUI_MSG_CHECK_LOCK, 0, 0);
+
+ CGUIWindow* pWindow =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_SCREENSAVER);
+ if (pWindow)
+ pWindow->OnMessage(msg);
+ }
+ }
+ if (m_iScreenSaveLock == -1)
+ {
+ m_iScreenSaveLock = 0;
+ return true;
+ }
+
+ // disable screensaver
+ m_screensaverActive = false;
+ m_iScreenSaveLock = 0;
+ ResetScreenSaverTimer();
+
+ if (m_screensaverIdInUse == "visualization")
+ {
+ // we can just continue as usual from vis mode
+ return false;
+ }
+ else if (m_screensaverIdInUse == "screensaver.xbmc.builtin.dim" ||
+ m_screensaverIdInUse == "screensaver.xbmc.builtin.black" ||
+ m_screensaverIdInUse.empty())
+ {
+ return true;
+ }
+ else
+ { // we're in screensaver window
+ if (m_pythonScreenSaver)
+ {
+// What sound does a python screensaver make?
+#define SCRIPT_ALARM "sssssscreensaver"
+#define SCRIPT_TIMEOUT 15 // seconds
+
+ /* FIXME: This is a hack but a proper fix is non-trivial. Basically this code
+ * makes sure the addon gets terminated after we've moved out of the screensaver window.
+ * If we don't do this, we may simply lockup.
+ */
+ g_alarmClock.Start(SCRIPT_ALARM, SCRIPT_TIMEOUT,
+ "StopScript(" + m_pythonScreenSaver->LibPath() + ")", true, false);
+ m_pythonScreenSaver.reset();
+ }
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SCREENSAVER)
+ CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); // show the previous window
+ else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW)
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_SLIDESHOW, -1,
+ static_cast<void*>(new CAction(ACTION_STOP)));
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+void CApplicationPowerHandling::CheckOSScreenSaverInhibitionSetting()
+{
+ // Kodi screen saver overrides OS one: always inhibit OS screen saver then
+ // except when DPMS is active (inhibiting the screen saver then might also
+ // disable DPMS again)
+ if (!m_dpmsIsActive &&
+ !CServiceBroker::GetSettingsComponent()
+ ->GetSettings()
+ ->GetString(CSettings::SETTING_SCREENSAVER_MODE)
+ .empty() &&
+ CServiceBroker::GetWinSystem()->GetOSScreenSaver())
+ {
+ if (!m_globalScreensaverInhibitor)
+ {
+ m_globalScreensaverInhibitor =
+ CServiceBroker::GetWinSystem()->GetOSScreenSaver()->CreateInhibitor();
+ }
+ }
+ else if (m_globalScreensaverInhibitor)
+ {
+ m_globalScreensaverInhibitor.Release();
+ }
+}
+
+void CApplicationPowerHandling::CheckScreenSaverAndDPMS()
+{
+ bool maybeScreensaver = true;
+ if (m_dpmsIsActive)
+ maybeScreensaver = false;
+ else if (m_screensaverActive)
+ maybeScreensaver = false;
+ else if (CServiceBroker::GetSettingsComponent()
+ ->GetSettings()
+ ->GetString(CSettings::SETTING_SCREENSAVER_MODE)
+ .empty())
+ maybeScreensaver = false;
+
+ auto winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return;
+
+ std::shared_ptr<CDPMSSupport> dpms = winSystem->GetDPMSManager();
+
+ bool maybeDPMS = true;
+ if (m_dpmsIsActive)
+ maybeDPMS = false;
+ else if (!dpms || !dpms->IsSupported())
+ maybeDPMS = false;
+ else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_POWERMANAGEMENT_DISPLAYSOFF) <= 0)
+ maybeDPMS = false;
+
+ // whether the current state of the application should be regarded as active even when there is no
+ // explicit user activity such as input
+ bool haveIdleActivity = false;
+
+ if (m_bResetScreenSaver)
+ {
+ m_bResetScreenSaver = false;
+ haveIdleActivity = true;
+ }
+
+ // When inhibit screensaver is enabled prevent screensaver from kicking in
+ if (m_bInhibitScreenSaver)
+ haveIdleActivity = true;
+
+ // Are we playing a video and it is not paused?
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer && appPlayer->IsPlayingVideo() && !appPlayer->IsPaused())
+ haveIdleActivity = true;
+
+ // Are we playing some music in fullscreen vis?
+ else if (appPlayer && appPlayer->IsPlayingAudio() &&
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION &&
+ !CServiceBroker::GetSettingsComponent()
+ ->GetSettings()
+ ->GetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION)
+ .empty())
+ {
+ haveIdleActivity = true;
+ }
+
+ // Handle OS screen saver state
+ if (haveIdleActivity && CServiceBroker::GetWinSystem()->GetOSScreenSaver())
+ {
+ // Always inhibit OS screen saver during these kinds of activities
+ if (!m_screensaverInhibitor)
+ {
+ m_screensaverInhibitor =
+ CServiceBroker::GetWinSystem()->GetOSScreenSaver()->CreateInhibitor();
+ }
+ }
+ else if (m_screensaverInhibitor)
+ {
+ m_screensaverInhibitor.Release();
+ }
+
+ // Has the screen saver window become active?
+ if (maybeScreensaver &&
+ CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_SCREENSAVER))
+ {
+ m_screensaverActive = true;
+ maybeScreensaver = false;
+ }
+
+ if (m_screensaverActive && haveIdleActivity)
+ {
+ WakeUpScreenSaverAndDPMS();
+ return;
+ }
+
+ if (!maybeScreensaver && !maybeDPMS)
+ return; // Nothing to do.
+
+ // See if we need to reset timer.
+ if (haveIdleActivity)
+ {
+ ResetScreenSaverTimer();
+ return;
+ }
+
+ float elapsed = m_screenSaverTimer.IsRunning() ? m_screenSaverTimer.GetElapsedSeconds() : 0.f;
+
+ // DPMS has priority (it makes the screensaver not needed)
+ if (maybeDPMS && elapsed > CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_POWERMANAGEMENT_DISPLAYSOFF) *
+ 60)
+ {
+ ToggleDPMS(false);
+ WakeUpScreenSaver();
+ }
+ else if (maybeScreensaver &&
+ elapsed > CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_SCREENSAVER_TIME) *
+ 60)
+ {
+ ActivateScreenSaver();
+ }
+}
+
+// activate the screensaver.
+// if forceType is true, we ignore the various conditions that can alter
+// the type of screensaver displayed
+void CApplicationPowerHandling::ActivateScreenSaver(bool forceType /*= false */)
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer && appPlayer->IsPlayingAudio() &&
+ settings->GetBool(CSettings::SETTING_SCREENSAVER_USEMUSICVISINSTEAD) &&
+ !settings->GetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION).empty())
+ { // just activate the visualisation if user toggled the usemusicvisinstead option
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VISUALISATION);
+ return;
+ }
+
+ m_screensaverActive = true;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "OnScreensaverActivated");
+
+ // disable screensaver lock from the login screen
+ m_iScreenSaveLock =
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_LOGIN_SCREEN ? 1 : 0;
+
+ m_screensaverIdInUse = settings->GetString(CSettings::SETTING_SCREENSAVER_MODE);
+
+ if (!forceType)
+ {
+ if (m_screensaverIdInUse == "screensaver.xbmc.builtin.dim" ||
+ m_screensaverIdInUse == "screensaver.xbmc.builtin.black" || m_screensaverIdInUse.empty())
+ {
+ return;
+ }
+
+ // Enforce Dim for special cases.
+ bool bUseDim = false;
+ if (CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true))
+ bUseDim = true;
+ else if (appPlayer && appPlayer->IsPlayingVideo() &&
+ settings->GetBool(CSettings::SETTING_SCREENSAVER_USEDIMONPAUSE))
+ bUseDim = true;
+ else if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().IsRunningChannelScan())
+ bUseDim = true;
+
+ if (bUseDim)
+ m_screensaverIdInUse = "screensaver.xbmc.builtin.dim";
+ }
+
+ if (m_screensaverIdInUse == "screensaver.xbmc.builtin.dim" ||
+ m_screensaverIdInUse == "screensaver.xbmc.builtin.black" || m_screensaverIdInUse.empty())
+ {
+ return;
+ }
+ else if (CServiceBroker::GetAddonMgr().GetAddon(m_screensaverIdInUse, m_pythonScreenSaver,
+ ADDON::AddonType::SCREENSAVER,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ std::string libPath = m_pythonScreenSaver->LibPath();
+ if (CScriptInvocationManager::GetInstance().HasLanguageInvoker(libPath))
+ {
+ CLog::Log(LOGDEBUG, "using python screensaver add-on {}", m_screensaverIdInUse);
+
+ // Don't allow a previously-scheduled alarm to kill our new screensaver
+ g_alarmClock.Stop(SCRIPT_ALARM, true);
+
+ if (!CScriptInvocationManager::GetInstance().Stop(libPath))
+ CScriptInvocationManager::GetInstance().ExecuteAsync(
+ libPath,
+ ADDON::AddonPtr(new ADDON::CAddon(dynamic_cast<ADDON::CAddon&>(*m_pythonScreenSaver))));
+ return;
+ }
+ m_pythonScreenSaver.reset();
+ }
+
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SCREENSAVER);
+}
+
+void CApplicationPowerHandling::InhibitScreenSaver(bool inhibit)
+{
+ m_bInhibitScreenSaver = inhibit;
+}
+
+bool CApplicationPowerHandling::IsScreenSaverInhibited() const
+{
+ return m_bInhibitScreenSaver;
+}
+
+// Global Idle Time in Seconds
+// idle time will be reset if on any OnKey()
+// int return: system Idle time in seconds! 0 is no idle!
+int CApplicationPowerHandling::GlobalIdleTime()
+{
+ if (!m_idleTimer.IsRunning())
+ m_idleTimer.StartZero();
+ return (int)m_idleTimer.GetElapsedSeconds();
+}
+
+float CApplicationPowerHandling::NavigationIdleTime()
+{
+ if (!m_navigationTimer.IsRunning())
+ m_navigationTimer.StartZero();
+ return m_navigationTimer.GetElapsedSeconds();
+}
+
+void CApplicationPowerHandling::StopShutdownTimer()
+{
+ m_shutdownTimer.Stop();
+}
+
+void CApplicationPowerHandling::ResetShutdownTimers()
+{
+ // reset system shutdown timer
+ m_shutdownTimer.StartZero();
+
+ // delete custom shutdown timer
+ if (g_alarmClock.HasAlarm("shutdowntimer"))
+ g_alarmClock.Stop("shutdowntimer", true);
+}
+
+void CApplicationPowerHandling::HandleShutdownMessage()
+{
+ switch (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE))
+ {
+ case POWERSTATE_SHUTDOWN:
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_POWERDOWN);
+ break;
+
+ case POWERSTATE_SUSPEND:
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SUSPEND);
+ break;
+
+ case POWERSTATE_HIBERNATE:
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_HIBERNATE);
+ break;
+
+ case POWERSTATE_QUIT:
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ break;
+
+ case POWERSTATE_MINIMIZE:
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE);
+ break;
+
+ default:
+ CLog::Log(LOGERROR, "{}: No valid shutdownstate matched", __FUNCTION__);
+ break;
+ }
+}
+
+void CApplicationPowerHandling::CheckShutdown()
+{
+ // first check if we should reset the timer
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer)
+ return;
+
+ if (m_bInhibitIdleShutdown || appPlayer->IsPlaying() ||
+ appPlayer->IsPausedPlayback() // is something playing?
+ || CMusicLibraryQueue::GetInstance().IsRunning() ||
+ CVideoLibraryQueue::GetInstance().IsRunning() ||
+ CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(
+ WINDOW_DIALOG_PROGRESS) // progress dialog is onscreen
+ ||
+ !CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown(false))
+ {
+ m_shutdownTimer.StartZero();
+ return;
+ }
+
+ float elapsed = m_shutdownTimer.IsRunning() ? m_shutdownTimer.GetElapsedSeconds() : 0.f;
+ if (elapsed > CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME) *
+ 60)
+ {
+ // Since it is a sleep instead of a shutdown, let's set everything to reset when we wake up.
+ m_shutdownTimer.Stop();
+
+ // Sleep the box
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SHUTDOWN);
+ }
+}
+
+void CApplicationPowerHandling::InhibitIdleShutdown(bool inhibit)
+{
+ m_bInhibitIdleShutdown = inhibit;
+}
+
+bool CApplicationPowerHandling::IsIdleShutdownInhibited() const
+{
+ return m_bInhibitIdleShutdown;
+}
+
+bool CApplicationPowerHandling::OnSettingChanged(const CSetting& setting)
+{
+ const std::string& settingId = setting.GetId();
+
+ if (settingId == CSettings::SETTING_SCREENSAVER_MODE)
+ {
+ CheckOSScreenSaverInhibitionSetting();
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool CApplicationPowerHandling::OnSettingAction(const CSetting& setting)
+{
+ const std::string& settingId = setting.GetId();
+
+ if (settingId == CSettings::SETTING_SCREENSAVER_PREVIEW)
+ ActivateScreenSaver(true);
+ else if (settingId == CSettings::SETTING_SCREENSAVER_SETTINGS)
+ {
+ ADDON::AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_SCREENSAVER_MODE),
+ addon, ADDON::AddonType::SCREENSAVER, ADDON::OnlyEnabled::CHOICE_YES))
+ CGUIDialogAddonSettings::ShowForAddon(addon);
+ }
+ else
+ return false;
+
+ return true;
+}
diff --git a/xbmc/application/ApplicationPowerHandling.h b/xbmc/application/ApplicationPowerHandling.h
new file mode 100644
index 0000000..9d78c6e
--- /dev/null
+++ b/xbmc/application/ApplicationPowerHandling.h
@@ -0,0 +1,116 @@
+/*
+ * 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 "application/IApplicationComponent.h"
+
+#ifdef TARGET_WINDOWS
+#include "powermanagement/WinIdleTimer.h"
+#endif
+#include "utils/Stopwatch.h"
+#include "windowing/OSScreenSaver.h"
+
+#include <string>
+
+namespace ADDON
+{
+class IAddon;
+using AddonPtr = std::shared_ptr<IAddon>;
+} // namespace ADDON
+
+class CApplication;
+class CSetting;
+
+/*!
+ * \brief Class handling application support for screensavers, dpms and shutdown timers.
+ */
+
+class CApplicationPowerHandling : public IApplicationComponent
+{
+ friend class CApplication;
+
+public:
+ bool IsInScreenSaver() const { return m_screensaverActive; }
+ bool IsScreenSaverInhibited() const;
+ void ResetScreenSaver();
+ void SetScreenSaverLockFailed() { m_iScreenSaveLock = -1; }
+ void SetScreenSaverUnlocked() { m_iScreenSaveLock = 1; }
+ void StopScreenSaverTimer();
+ std::string ScreensaverIdInUse() const { return m_screensaverIdInUse; }
+
+ bool GetRenderGUI() const { return m_renderGUI; }
+ void SetRenderGUI(bool renderGUI);
+
+ int GlobalIdleTime();
+ void ResetSystemIdleTimer();
+ bool IsIdleShutdownInhibited() const;
+
+ void ResetShutdownTimers();
+ void StopShutdownTimer();
+
+ void ResetNavigationTimer();
+
+ bool IsDPMSActive() const { return m_dpmsIsActive; }
+ bool ToggleDPMS(bool manual);
+
+ // Wakes up from the screensaver and / or DPMS. Returns true if woken up.
+ bool WakeUpScreenSaverAndDPMS(bool bPowerOffKeyPressed = false);
+
+ bool OnSettingChanged(const CSetting& setting);
+ bool OnSettingAction(const CSetting& setting);
+
+protected:
+ void ActivateScreenSaver(bool forceType = false);
+ void CheckOSScreenSaverInhibitionSetting();
+ // Checks whether the screensaver and / or DPMS should become active.
+ void CheckScreenSaverAndDPMS();
+ void InhibitScreenSaver(bool inhibit);
+ void ResetScreenSaverTimer();
+ bool WakeUpScreenSaver(bool bPowerOffKeyPressed = false);
+
+ void InhibitIdleShutdown(bool inhibit);
+
+ /*! \brief Helper method to determine how to handle TMSG_SHUTDOWN
+ */
+ void HandleShutdownMessage();
+ void CheckShutdown();
+
+ float NavigationIdleTime();
+
+ bool m_renderGUI{false};
+
+ bool m_bInhibitScreenSaver = false;
+ bool m_bResetScreenSaver = false;
+ ADDON::AddonPtr
+ m_pythonScreenSaver; // @warning: Fallback for Python interface, for binaries not needed!
+ bool m_screensaverActive = false;
+ // -1 = failed, 0 = locked, 1 = unlocked, 2 = check in progress
+ int m_iScreenSaveLock = 0;
+ std::string m_screensaverIdInUse;
+
+ bool m_dpmsIsActive = false;
+ bool m_dpmsIsManual = false;
+
+ bool m_bInhibitIdleShutdown = false;
+ CStopWatch m_navigationTimer;
+ CStopWatch m_shutdownTimer;
+
+#ifdef TARGET_WINDOWS
+ CWinIdleTimer m_idleTimer;
+ CWinIdleTimer m_screenSaverTimer;
+#else
+ CStopWatch m_idleTimer;
+ CStopWatch m_screenSaverTimer;
+#endif
+
+ // OS screen saver inhibitor that is always active if user selected a Kodi screen saver
+ KODI::WINDOWING::COSScreenSaverInhibitor m_globalScreensaverInhibitor;
+ // Inhibitor that is active e.g. during video playback
+ KODI::WINDOWING::COSScreenSaverInhibitor m_screensaverInhibitor;
+};
diff --git a/xbmc/application/ApplicationSettingsHandling.cpp b/xbmc/application/ApplicationSettingsHandling.cpp
new file mode 100644
index 0000000..0a2c1b1
--- /dev/null
+++ b/xbmc/application/ApplicationSettingsHandling.cpp
@@ -0,0 +1,214 @@
+/*
+ * 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 "ApplicationSettingsHandling.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "addons/gui/GUIDialogAddonSettings.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationPowerHandling.h"
+#include "application/ApplicationSkinHandling.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingsManager.h"
+#if defined(TARGET_DARWIN_OSX)
+#include "utils/StringUtils.h"
+#endif
+
+namespace
+{
+bool IsPlaying(const std::string& condition,
+ const std::string& value,
+ const SettingConstPtr& setting,
+ void* data)
+{
+ return data ? static_cast<CApplicationPlayer*>(data)->IsPlaying() : false;
+}
+} // namespace
+
+void CApplicationSettingsHandling::RegisterSettings()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ CSettingsManager* settingsMgr = settings->GetSettingsManager();
+
+ settingsMgr->RegisterSettingsHandler(this);
+
+ settingsMgr->RegisterCallback(this, {CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH,
+ CSettings::SETTING_LOOKANDFEEL_SKIN,
+ CSettings::SETTING_LOOKANDFEEL_SKINSETTINGS,
+ CSettings::SETTING_LOOKANDFEEL_FONT,
+ CSettings::SETTING_LOOKANDFEEL_SKINTHEME,
+ CSettings::SETTING_LOOKANDFEEL_SKINCOLORS,
+ CSettings::SETTING_LOOKANDFEEL_SKINZOOM,
+ CSettings::SETTING_MUSICPLAYER_REPLAYGAINPREAMP,
+ CSettings::SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP,
+ CSettings::SETTING_MUSICPLAYER_REPLAYGAINTYPE,
+ CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING,
+ CSettings::SETTING_SCRAPERS_MUSICVIDEOSDEFAULT,
+ CSettings::SETTING_SCREENSAVER_MODE,
+ CSettings::SETTING_SCREENSAVER_PREVIEW,
+ CSettings::SETTING_SCREENSAVER_SETTINGS,
+ CSettings::SETTING_AUDIOCDS_SETTINGS,
+ CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION,
+ CSettings::SETTING_VIDEOSCREEN_TESTPATTERN,
+ CSettings::SETTING_VIDEOPLAYER_USEMEDIACODEC,
+ CSettings::SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE,
+ CSettings::SETTING_AUDIOOUTPUT_VOLUMESTEPS,
+ CSettings::SETTING_SOURCE_VIDEOS,
+ CSettings::SETTING_SOURCE_MUSIC,
+ CSettings::SETTING_SOURCE_PICTURES,
+ CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN});
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer)
+ return;
+
+ settingsMgr->RegisterCallback(
+ &appPlayer->GetSeekHandler(),
+ {CSettings::SETTING_VIDEOPLAYER_SEEKDELAY, CSettings::SETTING_VIDEOPLAYER_SEEKSTEPS,
+ CSettings::SETTING_MUSICPLAYER_SEEKDELAY, CSettings::SETTING_MUSICPLAYER_SEEKSTEPS});
+
+ settingsMgr->AddDynamicCondition("isplaying", IsPlaying, appPlayer.get());
+
+ settings->RegisterSubSettings(this);
+}
+
+void CApplicationSettingsHandling::UnregisterSettings()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ CSettingsManager* settingsMgr = settings->GetSettingsManager();
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer)
+ return;
+
+ settings->UnregisterSubSettings(this);
+ settingsMgr->RemoveDynamicCondition("isplaying");
+ settingsMgr->UnregisterCallback(&appPlayer->GetSeekHandler());
+ settingsMgr->UnregisterCallback(this);
+ settingsMgr->UnregisterSettingsHandler(this);
+}
+
+void CApplicationSettingsHandling::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (!setting)
+ return;
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appSkin = components.GetComponent<CApplicationSkinHandling>();
+ if (appSkin->OnSettingChanged(*setting))
+ return;
+
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ if (appVolume->OnSettingChanged(*setting))
+ return;
+
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->OnSettingChanged(*setting))
+ return;
+
+ const std::string& settingId = setting->GetId();
+
+ if (settingId == CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN)
+ {
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot())
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(), true);
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH)
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_RESTART);
+ }
+}
+
+void CApplicationSettingsHandling::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
+{
+ if (!setting)
+ return;
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->OnSettingAction(*setting))
+ return;
+
+ const std::string& settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINSETTINGS)
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SKIN_SETTINGS);
+ else if (settingId == CSettings::SETTING_AUDIOCDS_SETTINGS)
+ {
+ ADDON::AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_AUDIOCDS_ENCODER),
+ addon, ADDON::AddonType::AUDIOENCODER, ADDON::OnlyEnabled::CHOICE_YES))
+ CGUIDialogAddonSettings::ShowForAddon(addon);
+ }
+ else if (settingId == CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION)
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SCREEN_CALIBRATION);
+ else if (settingId == CSettings::SETTING_SOURCE_VIDEOS)
+ {
+ std::vector<std::string> params{"library://video/files.xml", "return"};
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV, params);
+ }
+ else if (settingId == CSettings::SETTING_SOURCE_MUSIC)
+ {
+ std::vector<std::string> params{"library://music/files.xml", "return"};
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_NAV, params);
+ }
+ else if (settingId == CSettings::SETTING_SOURCE_PICTURES)
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_PICTURES);
+}
+
+bool CApplicationSettingsHandling::OnSettingUpdate(const std::shared_ptr<CSetting>& setting,
+ const char* oldSettingId,
+ const TiXmlNode* oldSettingNode)
+{
+ if (!setting)
+ return false;
+
+#if defined(TARGET_DARWIN_OSX)
+ if (setting->GetId() == CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE)
+ {
+ std::shared_ptr<CSettingString> audioDevice = std::static_pointer_cast<CSettingString>(setting);
+ // Gotham and older didn't enumerate audio devices per stream on osx
+ // add stream0 per default which should be ok for all old settings.
+ if (!StringUtils::EqualsNoCase(audioDevice->GetValue(), "DARWINOSX:default") &&
+ StringUtils::FindWords(audioDevice->GetValue().c_str(), ":stream") == std::string::npos)
+ {
+ std::string newSetting = audioDevice->GetValue();
+ newSetting += ":stream0";
+ return audioDevice->SetValue(newSetting);
+ }
+ }
+#endif
+
+ return false;
+}
+
+bool CApplicationSettingsHandling::Load(const TiXmlNode* settings)
+{
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ return appVolume->Load(settings);
+}
+
+bool CApplicationSettingsHandling::Save(TiXmlNode* settings) const
+{
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ return appVolume->Save(settings);
+}
diff --git a/xbmc/application/ApplicationSettingsHandling.h b/xbmc/application/ApplicationSettingsHandling.h
new file mode 100644
index 0000000..5dedd8f
--- /dev/null
+++ b/xbmc/application/ApplicationSettingsHandling.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 "settings/ISubSettings.h"
+#include "settings/lib/ISettingCallback.h"
+#include "settings/lib/ISettingsHandler.h"
+
+/*!
+ * \brief Class handling application support for settings.
+ */
+
+class CApplicationSettingsHandling : public ISettingCallback,
+ public ISettingsHandler,
+ public ISubSettings
+{
+protected:
+ void RegisterSettings();
+ void UnregisterSettings();
+
+ bool Load(const TiXmlNode* settings) override;
+ bool Save(TiXmlNode* settings) const override;
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+ void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override;
+ bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting,
+ const char* oldSettingId,
+ const TiXmlNode* oldSettingNode) override;
+};
diff --git a/xbmc/application/ApplicationSkinHandling.cpp b/xbmc/application/ApplicationSkinHandling.cpp
new file mode 100644
index 0000000..edca76b
--- /dev/null
+++ b/xbmc/application/ApplicationSkinHandling.cpp
@@ -0,0 +1,514 @@
+/*
+ * 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 "ApplicationSkinHandling.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "GUILargeTextureManager.h"
+#include "GUIUserMessages.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "TextureCache.h"
+#include "addons/AddonManager.h"
+#include "addons/AddonVersion.h"
+#include "addons/Skin.h"
+#include "addons/addoninfo/AddonType.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "dialogs/GUIDialogButtonMenu.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogSubMenu.h"
+#include "filesystem/Directory.h"
+#include "filesystem/DirectoryCache.h"
+#include "guilib/GUIAudioManager.h"
+#include "guilib/GUIColorManager.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIFontManager.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/StereoscopicsManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/SkinSettings.h"
+#include "settings/lib/Setting.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+#include "video/dialogs/GUIDialogFullScreenInfo.h"
+
+using namespace KODI::MESSAGING;
+
+CApplicationSkinHandling::CApplicationSkinHandling(IMsgTargetCallback* msgCb,
+ IWindowManagerCallback* wCb,
+ bool& bInitializing)
+ : m_msgCb(msgCb), m_wCb(wCb), m_bInitializing(bInitializing)
+{
+}
+
+bool CApplicationSkinHandling::LoadSkin(const std::string& skinID)
+{
+ std::shared_ptr<ADDON::CSkinInfo> skin;
+ {
+ ADDON::AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(skinID, addon, ADDON::AddonType::SKIN,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ return false;
+ skin = std::static_pointer_cast<ADDON::CSkinInfo>(addon);
+ }
+
+ // store player and rendering state
+ bool bPreviousPlayingState = false;
+
+ enum class RENDERING_STATE
+ {
+ NONE,
+ VIDEO,
+ GAME,
+ } previousRenderingState = RENDERING_STATE::NONE;
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer && appPlayer->IsPlayingVideo())
+ {
+ bPreviousPlayingState = !appPlayer->IsPausedPlayback();
+ if (bPreviousPlayingState)
+ appPlayer->Pause();
+ appPlayer->FlushRenderer();
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
+ previousRenderingState = RENDERING_STATE::VIDEO;
+ }
+ else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() ==
+ WINDOW_FULLSCREEN_GAME)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
+ previousRenderingState = RENDERING_STATE::GAME;
+ }
+ }
+
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ // store current active window with its focused control
+ int currentWindowID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
+ int currentFocusedControlID = -1;
+ if (currentWindowID != WINDOW_INVALID)
+ {
+ CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID);
+ if (pWindow)
+ currentFocusedControlID = pWindow->GetFocusedControlID();
+ }
+
+ UnloadSkin();
+
+ skin->Start();
+
+ // migrate any skin-specific settings that are still stored in guisettings.xml
+ CSkinSettings::GetInstance().MigrateSettings(skin);
+
+ // check if the skin has been properly loaded and if it has a Home.xml
+ if (!skin->HasSkinFile("Home.xml"))
+ {
+ CLog::Log(LOGERROR, "failed to load requested skin '{}'", skin->ID());
+ return false;
+ }
+
+ CLog::Log(LOGINFO, " load skin from: {} (version: {})", skin->Path(),
+ skin->Version().asString());
+ g_SkinInfo = skin;
+
+ CLog::Log(LOGINFO, " load fonts for skin...");
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetMediaDir(skin->Path());
+ g_directoryCache.ClearSubPaths(skin->Path());
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ CServiceBroker::GetGUI()->GetColorManager().Load(
+ settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS));
+
+ g_SkinInfo->LoadIncludes();
+
+ g_fontManager.LoadFonts(settings->GetString(CSettings::SETTING_LOOKANDFEEL_FONT));
+
+ // load in the skin strings
+ std::string langPath = URIUtils::AddFileToFolder(skin->Path(), "language");
+ URIUtils::AddSlashAtEnd(langPath);
+
+ g_localizeStrings.LoadSkinStrings(langPath,
+ settings->GetString(CSettings::SETTING_LOCALE_LANGUAGE));
+ g_SkinInfo->LoadTimers();
+
+ const auto start = std::chrono::steady_clock::now();
+
+ CLog::Log(LOGINFO, " load new skin...");
+
+ // Load custom windows
+ LoadCustomWindows();
+
+ const auto end = std::chrono::steady_clock::now();
+ std::chrono::duration<double, std::milli> duration = end - start;
+
+ CLog::Log(LOGDEBUG, "Load Skin XML: {:.2f} ms", duration.count());
+
+ CLog::Log(LOGINFO, " initialize new skin...");
+ CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(m_msgCb);
+ CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&CServiceBroker::GetPlaylistPlayer());
+ CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&g_fontManager);
+ CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(
+ &CServiceBroker::GetGUI()->GetStereoscopicsManager());
+ CServiceBroker::GetGUI()->GetWindowManager().SetCallback(*m_wCb);
+
+ //@todo should be done by GUIComponents
+ CServiceBroker::GetGUI()->GetWindowManager().Initialize();
+ CServiceBroker::GetGUI()->GetAudioManager().Enable(true);
+ CServiceBroker::GetGUI()->GetAudioManager().Load();
+ CServiceBroker::GetTextureCache()->Initialize();
+
+ if (g_SkinInfo->HasSkinFile("DialogFullScreenInfo.xml"))
+ CServiceBroker::GetGUI()->GetWindowManager().Add(new CGUIDialogFullScreenInfo);
+
+ CLog::Log(LOGINFO, " skin loaded...");
+
+ // leave the graphics lock
+ lock.unlock();
+
+ // restore active window
+ if (currentWindowID != WINDOW_INVALID)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(currentWindowID);
+ if (currentFocusedControlID != -1)
+ {
+ CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID);
+ if (pWindow && pWindow->HasSaveLastControl())
+ {
+ CGUIMessage msg(GUI_MSG_SETFOCUS, currentWindowID, currentFocusedControlID, 0);
+ pWindow->OnMessage(msg);
+ }
+ }
+ }
+
+ // restore player and rendering state
+ if (appPlayer && appPlayer->IsPlayingVideo())
+ {
+ if (bPreviousPlayingState)
+ appPlayer->Pause();
+
+ switch (previousRenderingState)
+ {
+ case RENDERING_STATE::VIDEO:
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_VIDEO);
+ break;
+ case RENDERING_STATE::GAME:
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_GAME);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+void CApplicationSkinHandling::UnloadSkin()
+{
+ if (g_SkinInfo != nullptr && m_saveSkinOnUnloading)
+ g_SkinInfo->SaveSettings();
+ else if (!m_saveSkinOnUnloading)
+ m_saveSkinOnUnloading = true;
+
+ if (g_SkinInfo)
+ g_SkinInfo->Unload();
+
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ {
+ gui->GetAudioManager().Enable(false);
+
+ gui->GetWindowManager().DeInitialize();
+ CServiceBroker::GetTextureCache()->Deinitialize();
+
+ // remove the skin-dependent window
+ gui->GetWindowManager().Delete(WINDOW_DIALOG_FULLSCREEN_INFO);
+
+ gui->GetTextureManager().Cleanup();
+ gui->GetLargeTextureManager().CleanupUnusedImages(true);
+
+ g_fontManager.Clear();
+
+ gui->GetColorManager().Clear();
+
+ gui->GetInfoManager().Clear();
+ }
+
+ // The g_SkinInfo shared_ptr ought to be reset here
+ // but there are too many places it's used without checking for nullptr
+ // and as a result a race condition on exit can cause a crash.
+ CLog::Log(LOGINFO, "Unloaded skin");
+}
+
+bool CApplicationSkinHandling::LoadCustomWindows()
+{
+ // Start from wherever home.xml is
+ std::vector<std::string> vecSkinPath;
+ g_SkinInfo->GetSkinPaths(vecSkinPath);
+
+ for (const auto& skinPath : vecSkinPath)
+ {
+ CLog::Log(LOGINFO, "Loading custom window XMLs from skin path {}", skinPath);
+
+ CFileItemList items;
+ if (XFILE::CDirectory::GetDirectory(skinPath, items, ".xml", XFILE::DIR_FLAG_NO_FILE_DIRS))
+ {
+ for (const auto& item : items)
+ {
+ if (item->m_bIsFolder)
+ continue;
+
+ std::string skinFile = URIUtils::GetFileName(item->GetPath());
+ if (StringUtils::StartsWithNoCase(skinFile, "custom"))
+ {
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(item->GetPath()))
+ {
+ CLog::Log(LOGERROR, "Unable to load custom window XML {}. Line {}\n{}", item->GetPath(),
+ xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
+ continue;
+ }
+
+ // Root element should be <window>
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ std::string strValue = pRootElement->Value();
+ if (!StringUtils::EqualsNoCase(strValue, "window"))
+ {
+ CLog::Log(LOGERROR, "No <window> root element found for custom window in {}", skinFile);
+ continue;
+ }
+
+ int id = WINDOW_INVALID;
+
+ // Read the type attribute or element to get the window type to create
+ // If no type is specified, create a CGUIWindow as default
+ std::string strType;
+ if (pRootElement->Attribute("type"))
+ strType = pRootElement->Attribute("type");
+ else
+ {
+ const TiXmlNode* pType = pRootElement->FirstChild("type");
+ if (pType && pType->FirstChild())
+ strType = pType->FirstChild()->Value();
+ }
+
+ // Read the id attribute or element to get the window id
+ if (!pRootElement->Attribute("id", &id))
+ {
+ const TiXmlNode* pType = pRootElement->FirstChild("id");
+ if (pType && pType->FirstChild())
+ id = atol(pType->FirstChild()->Value());
+ }
+
+ int windowId = id + WINDOW_HOME;
+ if (id == WINDOW_INVALID ||
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(windowId))
+ {
+ // No id specified or id already in use
+ CLog::Log(LOGERROR, "No id specified or id already in use for custom window in {}",
+ skinFile);
+ continue;
+ }
+
+ CGUIWindow* pWindow = nullptr;
+ bool hasVisibleCondition = false;
+
+ if (StringUtils::EqualsNoCase(strType, "dialog"))
+ {
+ DialogModalityType modality = DialogModalityType::MODAL;
+ hasVisibleCondition = pRootElement->FirstChildElement("visible") != nullptr;
+ // By default dialogs that have visible conditions are considered modeless unless explicitly
+ // set to "modal" by the skinner using the "modality" attribute in the root XML element of the window
+ if (hasVisibleCondition &&
+ (!pRootElement->Attribute("modality") ||
+ !StringUtils::EqualsNoCase(pRootElement->Attribute("modality"), "modal")))
+ modality = DialogModalityType::MODELESS;
+
+ pWindow = new CGUIDialog(windowId, skinFile, modality);
+ }
+ else if (StringUtils::EqualsNoCase(strType, "submenu"))
+ {
+ pWindow = new CGUIDialogSubMenu(windowId, skinFile);
+ }
+ else if (StringUtils::EqualsNoCase(strType, "buttonmenu"))
+ {
+ pWindow = new CGUIDialogButtonMenu(windowId, skinFile);
+ }
+ else
+ {
+ pWindow = new CGUIWindow(windowId, skinFile);
+ }
+
+ if (!pWindow)
+ {
+ CLog::Log(LOGERROR, "Failed to create custom window from {}", skinFile);
+ continue;
+ }
+
+ pWindow->SetCustom(true);
+
+ // Determining whether our custom dialog is modeless (visible condition is present)
+ // will be done on load. Therefore we need to initialize the custom dialog on gui init.
+ pWindow->SetLoadType(hasVisibleCondition ? CGUIWindow::LOAD_ON_GUI_INIT
+ : CGUIWindow::KEEP_IN_MEMORY);
+
+ CServiceBroker::GetGUI()->GetWindowManager().AddCustomWindow(pWindow);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void CApplicationSkinHandling::ReloadSkin(bool confirm)
+{
+ if (!g_SkinInfo || m_bInitializing)
+ return; // Don't allow reload before skin is loaded by system
+
+ std::string oldSkin = g_SkinInfo->ID();
+
+ CGUIMessage msg(GUI_MSG_LOAD_SKIN, -1,
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ std::string newSkin = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN);
+ if (LoadSkin(newSkin))
+ {
+ /* The Reset() or SetString() below will cause recursion, so the m_confirmSkinChange boolean is set so as to not prompt the
+ user as to whether they want to keep the current skin. */
+ if (confirm && m_confirmSkinChange)
+ {
+ if (HELPERS::ShowYesNoDialogText(CVariant{13123}, CVariant{13111}, CVariant{""}, CVariant{""},
+ 10000) != HELPERS::DialogResponse::CHOICE_YES)
+ {
+ m_confirmSkinChange = false;
+ settings->SetString(CSettings::SETTING_LOOKANDFEEL_SKIN, oldSkin);
+ }
+ else
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_STARTUP_ANIM);
+ }
+ }
+ else
+ {
+ // skin failed to load - we revert to the default only if we didn't fail loading the default
+ auto setting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN);
+ if (!setting)
+ {
+ CLog::Log(LOGFATAL, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN);
+ return;
+ }
+
+ std::string defaultSkin = std::static_pointer_cast<CSettingString>(setting)->GetDefault();
+ if (newSkin != defaultSkin)
+ {
+ m_confirmSkinChange = false;
+ setting->Reset();
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(24102),
+ g_localizeStrings.Get(24103));
+ }
+ }
+ m_confirmSkinChange = true;
+}
+
+bool CApplicationSkinHandling::OnSettingChanged(const CSetting& setting)
+{
+ const std::string& settingId = setting.GetId();
+
+ if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN ||
+ settingId == CSettings::SETTING_LOOKANDFEEL_FONT ||
+ settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME ||
+ settingId == CSettings::SETTING_LOOKANDFEEL_SKINCOLORS)
+ {
+ // check if we should ignore this change event due to changing skins in which case we have to
+ // change several settings and each one of them could lead to a complete skin reload which would
+ // result in multiple skin reloads. Therefore we manually specify to ignore specific settings
+ // which are going to be changed.
+ if (m_ignoreSkinSettingChanges)
+ return true;
+
+ // if the skin changes and the current color/theme/font is not the default one, reset
+ // the it to the default value
+ if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN)
+ {
+ const std::shared_ptr<CSettings> settings =
+ CServiceBroker::GetSettingsComponent()->GetSettings();
+ SettingPtr skinRelatedSetting =
+ settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS);
+ if (!skinRelatedSetting->IsDefault())
+ {
+ m_ignoreSkinSettingChanges = true;
+ skinRelatedSetting->Reset();
+ }
+
+ skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINTHEME);
+ if (!skinRelatedSetting->IsDefault())
+ {
+ m_ignoreSkinSettingChanges = true;
+ skinRelatedSetting->Reset();
+ }
+
+ skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_FONT);
+ if (!skinRelatedSetting->IsDefault())
+ {
+ m_ignoreSkinSettingChanges = true;
+ skinRelatedSetting->Reset();
+ }
+ }
+ else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME)
+ {
+ std::shared_ptr<CSettingString> skinColorsSetting = std::static_pointer_cast<CSettingString>(
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
+ CSettings::SETTING_LOOKANDFEEL_SKINCOLORS));
+ m_ignoreSkinSettingChanges = true;
+
+ // we also need to adjust the skin color setting
+ std::string colorTheme = static_cast<const CSettingString&>(setting).GetValue();
+ URIUtils::RemoveExtension(colorTheme);
+ if (setting.IsDefault() || StringUtils::EqualsNoCase(colorTheme, "Textures"))
+ skinColorsSetting->Reset();
+ else
+ skinColorsSetting->SetValue(colorTheme);
+ }
+
+ m_ignoreSkinSettingChanges = false;
+
+ if (g_SkinInfo)
+ {
+ // now we can finally reload skins
+ std::string builtin("ReloadSkin");
+ if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN && m_confirmSkinChange)
+ builtin += "(confirm)";
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, builtin);
+ }
+ }
+ else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINZOOM)
+ {
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESIZE);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ }
+ else
+ return false;
+
+ return true;
+}
+
+void CApplicationSkinHandling::ProcessSkin() const
+{
+ if (g_SkinInfo != nullptr)
+ g_SkinInfo->ProcessTimers();
+}
diff --git a/xbmc/application/ApplicationSkinHandling.h b/xbmc/application/ApplicationSkinHandling.h
new file mode 100644
index 0000000..fc5c3d0
--- /dev/null
+++ b/xbmc/application/ApplicationSkinHandling.h
@@ -0,0 +1,53 @@
+/*
+ * 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 "application/IApplicationComponent.h"
+
+#include <string>
+
+class CApplication;
+class CSetting;
+class IMsgTargetCallback;
+class IWindowManagerCallback;
+
+/*!
+ * \brief Class handling application support for skin management.
+ */
+class CApplicationSkinHandling : public IApplicationComponent
+{
+ friend class CApplication;
+
+public:
+ CApplicationSkinHandling(IMsgTargetCallback* msgCb,
+ IWindowManagerCallback* wCb,
+ bool& bInitializing);
+
+ void UnloadSkin();
+
+ bool OnSettingChanged(const CSetting& setting);
+ void ReloadSkin(bool confirm = false);
+
+protected:
+ bool LoadSkin(const std::string& skinID);
+ bool LoadCustomWindows();
+
+ /*!
+ * \brief Called by the application main/render thread for processing
+ * operations belonging to the skin.
+ */
+ void ProcessSkin() const;
+
+ bool m_saveSkinOnUnloading = true;
+ bool m_confirmSkinChange = true;
+ bool m_ignoreSkinSettingChanges = false;
+ IMsgTargetCallback* m_msgCb;
+ IWindowManagerCallback* m_wCb;
+ bool& m_bInitializing;
+};
diff --git a/xbmc/application/ApplicationStackHelper.cpp b/xbmc/application/ApplicationStackHelper.cpp
new file mode 100644
index 0000000..a1e8a0e
--- /dev/null
+++ b/xbmc/application/ApplicationStackHelper.cpp
@@ -0,0 +1,332 @@
+/*
+ * 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 "ApplicationStackHelper.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "Util.h"
+#include "cores/VideoPlayer/DVDFileInfo.h"
+#include "filesystem/StackDirectory.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "video/VideoDatabase.h"
+
+#include <utility>
+
+using namespace XFILE;
+
+CApplicationStackHelper::CApplicationStackHelper(void)
+ : m_currentStack(new CFileItemList)
+{
+}
+
+void CApplicationStackHelper::Clear()
+{
+ m_currentStackPosition = 0;
+ m_currentStack->Clear();
+}
+
+void CApplicationStackHelper::OnPlayBackStarted(const CFileItem& item)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // time to clean up stack map
+ if (!HasRegisteredStack(item))
+ m_stackmap.clear();
+ else
+ {
+ auto stack = GetRegisteredStack(item);
+ Stackmap::iterator itr = m_stackmap.begin();
+ while (itr != m_stackmap.end())
+ {
+ if (itr->second->m_pStack != stack)
+ {
+ itr = m_stackmap.erase(itr);
+ }
+ else
+ {
+ ++itr;
+ }
+ }
+ }
+}
+
+bool CApplicationStackHelper::InitializeStack(const CFileItem & item)
+{
+ if (!item.IsStack())
+ return false;
+
+ auto stack = std::make_shared<CFileItem>(item);
+
+ Clear();
+ // read and determine kind of stack
+ CStackDirectory dir;
+ if (!dir.GetDirectory(item.GetURL(), *m_currentStack) || m_currentStack->IsEmpty())
+ return false;
+ for (int i = 0; i < m_currentStack->Size(); i++)
+ {
+ // keep cross-references between stack parts and the stack
+ SetRegisteredStack(GetStackPartFileItem(i), stack);
+ SetRegisteredStackPartNumber(GetStackPartFileItem(i), i);
+ }
+ m_currentStackIsDiscImageStack = CFileItem(CStackDirectory::GetFirstStackedFile(item.GetPath()), false).IsDiscImage();
+
+ return true;
+}
+
+int CApplicationStackHelper::InitializeStackStartPartAndOffset(const CFileItem& item)
+{
+ CVideoDatabase dbs;
+ int64_t startoffset = 0;
+
+ // case 1: stacked ISOs
+ if (m_currentStackIsDiscImageStack)
+ {
+ // first assume values passed to the stack
+ int selectedFile = item.m_lStartPartNumber;
+ startoffset = item.GetStartOffset();
+
+ // check if we instructed the stack to resume from default
+ if (startoffset == STARTOFFSET_RESUME) // selected file is not specified, pick the 'last' resume point
+ {
+ if (dbs.Open())
+ {
+ CBookmark bookmark;
+ std::string path = item.GetPath();
+ if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
+ path = item.GetProperty("original_listitem_url").asString();
+ if (dbs.GetResumeBookMark(path, bookmark))
+ {
+ startoffset = CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds);
+ selectedFile = bookmark.partNumber;
+ }
+ dbs.Close();
+ }
+ else
+ CLog::LogF(LOGERROR, "Cannot open VideoDatabase");
+ }
+
+ // make sure that the selected part is within the boundaries
+ if (selectedFile <= 0)
+ {
+ CLog::LogF(LOGWARNING, "Selected part {} out of range, playing part 1", selectedFile);
+ selectedFile = 1;
+ }
+ else if (selectedFile > m_currentStack->Size())
+ {
+ CLog::LogF(LOGWARNING, "Selected part {} out of range, playing part {}", selectedFile,
+ m_currentStack->Size());
+ selectedFile = m_currentStack->Size();
+ }
+
+ // set startoffset in selected item, track stack item for updating purposes, and finally play disc part
+ m_currentStackPosition = selectedFile - 1;
+ startoffset = startoffset > 0 ? STARTOFFSET_RESUME : 0;
+ }
+ // case 2: all other stacks
+ else
+ {
+ // see if we have the info in the database
+ //! @todo If user changes the time speed (FPS via framerate conversion stuff)
+ //! then these times will be wrong.
+ //! Also, this is really just a hack for the slow load up times we have
+ //! A much better solution is a fast reader of FPS and fileLength
+ //! that we can use on a file to get it's time.
+ std::vector<uint64_t> times;
+ bool haveTimes(false);
+
+ if (dbs.Open())
+ {
+ haveTimes = dbs.GetStackTimes(item.GetPath(), times);
+ dbs.Close();
+ }
+
+ // calculate the total time of the stack
+ uint64_t totalTimeMs = 0;
+ for (int i = 0; i < m_currentStack->Size(); i++)
+ {
+ if (haveTimes)
+ {
+ // set end time in every part
+ GetStackPartFileItem(i).SetEndOffset(times[i]);
+ }
+ else
+ {
+ int duration;
+ if (!CDVDFileInfo::GetFileDuration(GetStackPartFileItem(i).GetPath(), duration))
+ {
+ m_currentStack->Clear();
+ return false;
+ }
+ totalTimeMs += duration;
+ // set end time in every part
+ GetStackPartFileItem(i).SetEndOffset(totalTimeMs);
+ times.push_back(totalTimeMs);
+ }
+ // set start time in every part
+ SetRegisteredStackPartStartTimeMs(GetStackPartFileItem(i), GetStackPartStartTimeMs(i));
+ }
+ // set total time in every part
+ totalTimeMs = GetStackTotalTimeMs();
+ for (int i = 0; i < m_currentStack->Size(); i++)
+ SetRegisteredStackTotalTimeMs(GetStackPartFileItem(i), totalTimeMs);
+
+ uint64_t msecs = item.GetStartOffset();
+
+ if (!haveTimes || item.GetStartOffset() == STARTOFFSET_RESUME)
+ {
+ if (dbs.Open())
+ {
+ // have our times now, so update the dB
+ if (!haveTimes && !times.empty())
+ dbs.SetStackTimes(item.GetPath(), times);
+
+ if (item.GetStartOffset() == STARTOFFSET_RESUME)
+ {
+ // can only resume seek here, not dvdstate
+ CBookmark bookmark;
+ std::string path = item.GetPath();
+ if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
+ path = item.GetProperty("original_listitem_url").asString();
+ if (dbs.GetResumeBookMark(path, bookmark))
+ msecs = static_cast<uint64_t>(bookmark.timeInSeconds * 1000);
+ else
+ msecs = 0;
+ }
+ dbs.Close();
+ }
+ }
+
+ m_currentStackPosition = GetStackPartNumberAtTimeMs(msecs);
+ startoffset = msecs - GetStackPartStartTimeMs(m_currentStackPosition);
+ }
+ return startoffset;
+}
+
+bool CApplicationStackHelper::IsPlayingISOStack() const
+{
+ return m_currentStack->Size() > 0 && m_currentStackIsDiscImageStack;
+}
+
+bool CApplicationStackHelper::IsPlayingRegularStack() const
+{
+ return m_currentStack->Size() > 0 && !m_currentStackIsDiscImageStack;
+}
+
+bool CApplicationStackHelper::HasNextStackPartFileItem() const
+{
+ return m_currentStackPosition < m_currentStack->Size() - 1;
+}
+
+uint64_t CApplicationStackHelper::GetStackPartEndTimeMs(int partNumber) const
+{
+ return GetStackPartFileItem(partNumber).GetEndOffset();
+}
+
+uint64_t CApplicationStackHelper::GetStackTotalTimeMs() const
+{
+ return GetStackPartEndTimeMs(m_currentStack->Size() - 1);
+}
+
+int CApplicationStackHelper::GetStackPartNumberAtTimeMs(uint64_t msecs)
+{
+ if (msecs > 0)
+ {
+ // work out where to seek to
+ for (int partNumber = 0; partNumber < m_currentStack->Size(); partNumber++)
+ {
+ if (msecs < GetStackPartEndTimeMs(partNumber))
+ return partNumber;
+ }
+ }
+ return 0;
+}
+
+void CApplicationStackHelper::ClearAllRegisteredStackInformation()
+{
+ m_stackmap.clear();
+}
+
+std::shared_ptr<const CFileItem> CApplicationStackHelper::GetRegisteredStack(
+ const CFileItem& item) const
+{
+ return GetStackPartInformation(item.GetPath())->m_pStack;
+}
+
+bool CApplicationStackHelper::HasRegisteredStack(const CFileItem& item) const
+{
+ const auto it = m_stackmap.find(item.GetPath());
+ return it != m_stackmap.end() && it->second != nullptr;
+}
+
+void CApplicationStackHelper::SetRegisteredStack(const CFileItem& item,
+ std::shared_ptr<CFileItem> stackItem)
+{
+ GetStackPartInformation(item.GetPath())->m_pStack = std::move(stackItem);
+}
+
+CFileItem& CApplicationStackHelper::GetStackPartFileItem(int partNumber)
+{
+ return *(*m_currentStack)[partNumber];
+}
+
+const CFileItem& CApplicationStackHelper::GetStackPartFileItem(int partNumber) const
+{
+ return *(*m_currentStack)[partNumber];
+}
+
+int CApplicationStackHelper::GetRegisteredStackPartNumber(const CFileItem& item)
+{
+ return GetStackPartInformation(item.GetPath())->m_lStackPartNumber;
+}
+
+void CApplicationStackHelper::SetRegisteredStackPartNumber(const CFileItem& item, int partNumber)
+{
+ GetStackPartInformation(item.GetPath())->m_lStackPartNumber = partNumber;
+}
+
+uint64_t CApplicationStackHelper::GetRegisteredStackPartStartTimeMs(const CFileItem& item) const
+{
+ return GetStackPartInformation(item.GetPath())->m_lStackPartStartTimeMs;
+}
+
+void CApplicationStackHelper::SetRegisteredStackPartStartTimeMs(const CFileItem& item, uint64_t startTime)
+{
+ GetStackPartInformation(item.GetPath())->m_lStackPartStartTimeMs = startTime;
+}
+
+uint64_t CApplicationStackHelper::GetRegisteredStackTotalTimeMs(const CFileItem& item) const
+{
+ return GetStackPartInformation(item.GetPath())->m_lStackTotalTimeMs;
+}
+
+void CApplicationStackHelper::SetRegisteredStackTotalTimeMs(const CFileItem& item, uint64_t totalTime)
+{
+ GetStackPartInformation(item.GetPath())->m_lStackTotalTimeMs = totalTime;
+}
+
+CApplicationStackHelper::StackPartInformationPtr CApplicationStackHelper::GetStackPartInformation(
+ const std::string& key)
+{
+ if (m_stackmap.count(key) == 0)
+ {
+ StackPartInformationPtr value(new StackPartInformation());
+ m_stackmap[key] = value;
+ }
+ return m_stackmap[key];
+}
+
+CApplicationStackHelper::StackPartInformationPtr CApplicationStackHelper::GetStackPartInformation(
+ const std::string& key) const
+{
+ const auto it = m_stackmap.find(key);
+ if (it == m_stackmap.end())
+ return std::make_shared<StackPartInformation>();
+ return it->second;
+}
diff --git a/xbmc/application/ApplicationStackHelper.h b/xbmc/application/ApplicationStackHelper.h
new file mode 100644
index 0000000..6af0044
--- /dev/null
+++ b/xbmc/application/ApplicationStackHelper.h
@@ -0,0 +1,213 @@
+/*
+ * 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 "application/IApplicationComponent.h"
+#include "threads/CriticalSection.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+class CFileItem;
+class CFileItemList;
+
+class CApplicationStackHelper : public IApplicationComponent
+{
+public:
+ CApplicationStackHelper(void);
+ ~CApplicationStackHelper() = default;
+
+ void Clear();
+ void OnPlayBackStarted(const CFileItem& item);
+
+ /*!
+ \brief Initialize stack
+ \param item the FileItem object that is the stack
+ */
+ bool InitializeStack(const CFileItem& item);
+
+ /*!
+ \brief Initialize stack times for each part, start & end, total time, and current part number if resume offset is specified.
+ \param item the FileItem object that is the stack
+ */
+ int InitializeStackStartPartAndOffset(const CFileItem& item);
+
+ /*!
+ \brief returns the current part number
+ */
+ int GetCurrentPartNumber() const { return m_currentStackPosition; }
+
+ /*!
+ \brief Returns true if Application is currently playing an ISO stack
+ */
+ bool IsPlayingISOStack() const;
+
+ /*!
+ \brief Returns true if Application is currently playing a Regular (non-ISO) stack
+ */
+ bool IsPlayingRegularStack() const;
+
+ /*!
+ \brief returns true if there is a next part available
+ */
+ bool HasNextStackPartFileItem() const;
+
+ /*!
+ \brief sets the next stack part as the current and returns a reference to it
+ */
+ const CFileItem& SetNextStackPartCurrentFileItem()
+ {
+ return GetStackPartFileItem(++m_currentStackPosition);
+ }
+
+ /*!
+ \brief sets a given stack part as the current and returns a reference to it
+ \param partNumber the number of the part that needs to become the current one
+ */
+ const CFileItem& SetStackPartCurrentFileItem(int partNumber)
+ {
+ return GetStackPartFileItem(m_currentStackPosition = partNumber);
+ }
+
+ /*!
+ \brief Returns the FileItem currently playing back as part of a (non-ISO) stack playback
+ */
+ const CFileItem& GetCurrentStackPartFileItem() const
+ {
+ return GetStackPartFileItem(m_currentStackPosition);
+ }
+
+ /*!
+ \brief Returns the end time of a FileItem part of a (non-ISO) stack playback
+ \param partNumber the requested part number in the stack
+ */
+ uint64_t GetStackPartEndTimeMs(int partNumber) const;
+
+ /*!
+ \brief Returns the start time of a FileItem part of a (non-ISO) stack playback
+ \param partNumber the requested part number in the stack
+ */
+ uint64_t GetStackPartStartTimeMs(int partNumber) const { return (partNumber > 0) ? GetStackPartEndTimeMs(partNumber - 1) : 0; }
+
+ /*!
+ \brief Returns the start time of the current FileItem part of a (non-ISO) stack playback
+ */
+ uint64_t GetCurrentStackPartStartTimeMs() const { return GetStackPartStartTimeMs(m_currentStackPosition); }
+
+ /*!
+ \brief Returns the total time of a (non-ISO) stack playback
+ */
+ uint64_t GetStackTotalTimeMs() const;
+
+ /*!
+ \brief Returns the stack part number corresponding to the given timestamp in a (non-ISO) stack playback
+ \param msecs the requested timestamp in the stack (in milliseconds)
+ */
+ int GetStackPartNumberAtTimeMs(uint64_t msecs);
+
+ // Stack information registration methods
+
+ /*!
+ \brief Clear all entries in the item-stack map. To be called upon playback stopped.
+ */
+ void ClearAllRegisteredStackInformation();
+
+ /*!
+ \brief Returns a smart pointer to the stack CFileItem.
+ */
+ std::shared_ptr<const CFileItem> GetRegisteredStack(const CFileItem& item) const;
+
+ /*!
+ \brief Returns true if there is a registered stack for the given CFileItem part.
+ \param item the reference to the item that is part of a stack
+ */
+ bool HasRegisteredStack(const CFileItem& item) const;
+
+ /*!
+ \brief Stores a smart pointer to the stack CFileItem in the item-stack map.
+ \param item the reference to the item that is part of a stack
+ \param stackItem the smart pointer to the stack CFileItem
+ */
+ void SetRegisteredStack(const CFileItem& item, std::shared_ptr<CFileItem> stackItem);
+
+ /*!
+ \brief Returns the part number of the part in the parameter
+ \param item the reference to the item that is part of a stack
+ */
+ int GetRegisteredStackPartNumber(const CFileItem& item);
+
+ /*!
+ \brief Stores the part number in the item-stack map.
+ \param item the reference to the item that is part of a stack
+ \param partNumber the part number of the part in other parameter
+ */
+ void SetRegisteredStackPartNumber(const CFileItem& item, int partNumber);
+
+ /*!
+ \brief Returns the start time of the part in the parameter
+ \param item the reference to the item that is part of a stack
+ */
+ uint64_t GetRegisteredStackPartStartTimeMs(const CFileItem& item) const;
+
+ /*!
+ \brief Stores the part start time in the item-stack map.
+ \param item the reference to the item that is part of a stack
+ \param startTime the start time of the part in other parameter
+ */
+ void SetRegisteredStackPartStartTimeMs(const CFileItem& item, uint64_t startTimeMs);
+
+ /*!
+ \brief Returns the total time of the stack associated to the part in the parameter
+ \param item the reference to the item that is part of a stack
+ */
+ uint64_t GetRegisteredStackTotalTimeMs(const CFileItem& item) const;
+
+ /*!
+ \brief Stores the stack's total time associated to the part in the item-stack map.
+ \param item the reference to the item that is part of a stack
+ \param totalTime the total time of the stack
+ */
+ void SetRegisteredStackTotalTimeMs(const CFileItem& item, uint64_t totalTimeMs);
+
+ CCriticalSection m_critSection;
+
+protected:
+ /*!
+ \brief Returns a FileItem part of a (non-ISO) stack playback
+ \param partNumber the requested part number in the stack
+ */
+ CFileItem& GetStackPartFileItem(int partNumber);
+ const CFileItem& GetStackPartFileItem(int partNumber) const;
+
+ class StackPartInformation
+ {
+ public:
+ StackPartInformation()
+ {
+ m_lStackPartNumber = 0;
+ m_lStackPartStartTimeMs = 0;
+ m_lStackTotalTimeMs = 0;
+ };
+ uint64_t m_lStackPartStartTimeMs;
+ uint64_t m_lStackTotalTimeMs;
+ int m_lStackPartNumber;
+ std::shared_ptr<CFileItem> m_pStack;
+ };
+
+ typedef std::shared_ptr<StackPartInformation> StackPartInformationPtr;
+ typedef std::map<std::string, StackPartInformationPtr> Stackmap;
+ Stackmap m_stackmap;
+ StackPartInformationPtr GetStackPartInformation(const std::string& key);
+ StackPartInformationPtr GetStackPartInformation(const std::string& key) const;
+
+ std::unique_ptr<CFileItemList> m_currentStack;
+ int m_currentStackPosition = 0;
+ bool m_currentStackIsDiscImageStack = false;
+};
diff --git a/xbmc/application/ApplicationVolumeHandling.cpp b/xbmc/application/ApplicationVolumeHandling.cpp
new file mode 100644
index 0000000..25229be
--- /dev/null
+++ b/xbmc/application/ApplicationVolumeHandling.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 "ApplicationVolumeHandling.h"
+
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "dialogs/GUIDialogVolumeBar.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "interfaces/AnnouncementManager.h"
+#include "peripherals/Peripherals.h"
+#include "settings/Settings.h"
+#include "settings/lib/Setting.h"
+#include "utils/Variant.h"
+#include "utils/XMLUtils.h"
+
+#include <tinyxml.h>
+
+float CApplicationVolumeHandling::GetVolumePercent() const
+{
+ // converts the hardware volume to a percentage
+ return m_volumeLevel * 100.0f;
+}
+
+float CApplicationVolumeHandling::GetVolumeRatio() const
+{
+ return m_volumeLevel;
+}
+
+void CApplicationVolumeHandling::SetHardwareVolume(float hardwareVolume)
+{
+ m_volumeLevel = std::clamp(hardwareVolume, VOLUME_MINIMUM, VOLUME_MAXIMUM);
+
+ IAE* ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ ae->SetVolume(m_volumeLevel);
+}
+
+void CApplicationVolumeHandling::VolumeChanged()
+{
+ CVariant data(CVariant::VariantTypeObject);
+ data["volume"] = static_cast<int>(std::lroundf(GetVolumePercent()));
+ data["muted"] = m_muted;
+ const auto announcementMgr = CServiceBroker::GetAnnouncementManager();
+ announcementMgr->Announce(ANNOUNCEMENT::Application, "OnVolumeChanged", data);
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ // if player has volume control, set it.
+ if (appPlayer)
+ {
+ appPlayer->SetVolume(m_volumeLevel);
+ appPlayer->SetMute(m_muted);
+ }
+}
+
+void CApplicationVolumeHandling::ShowVolumeBar(const CAction* action)
+{
+ const auto& wm = CServiceBroker::GetGUI()->GetWindowManager();
+ auto* volumeBar = wm.GetWindow<CGUIDialogVolumeBar>(WINDOW_DIALOG_VOLUME_BAR);
+ if (volumeBar != nullptr && volumeBar->IsVolumeBarEnabled())
+ {
+ volumeBar->Open();
+ if (action)
+ volumeBar->OnAction(*action);
+ }
+}
+
+bool CApplicationVolumeHandling::IsMuted() const
+{
+ if (CServiceBroker::GetPeripherals().IsMuted())
+ return true;
+ IAE* ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ return ae->IsMuted();
+ return true;
+}
+
+void CApplicationVolumeHandling::ToggleMute(void)
+{
+ if (m_muted)
+ UnMute();
+ else
+ Mute();
+}
+
+void CApplicationVolumeHandling::SetMute(bool mute)
+{
+ if (m_muted != mute)
+ {
+ ToggleMute();
+ m_muted = mute;
+ }
+}
+
+void CApplicationVolumeHandling::Mute()
+{
+ if (CServiceBroker::GetPeripherals().Mute())
+ return;
+
+ IAE* ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ ae->SetMute(true);
+ m_muted = true;
+ VolumeChanged();
+}
+
+void CApplicationVolumeHandling::UnMute()
+{
+ if (CServiceBroker::GetPeripherals().UnMute())
+ return;
+
+ IAE* ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ ae->SetMute(false);
+ m_muted = false;
+ VolumeChanged();
+}
+
+void CApplicationVolumeHandling::SetVolume(float iValue, bool isPercentage)
+{
+ float hardwareVolume = iValue;
+
+ if (isPercentage)
+ hardwareVolume /= 100.0f;
+
+ SetHardwareVolume(hardwareVolume);
+ VolumeChanged();
+}
+
+void CApplicationVolumeHandling::CacheReplayGainSettings(const CSettings& settings)
+{
+ // initialize m_replayGainSettings
+ m_replayGainSettings.iType = settings.GetInt(CSettings::SETTING_MUSICPLAYER_REPLAYGAINTYPE);
+ m_replayGainSettings.iPreAmp = settings.GetInt(CSettings::SETTING_MUSICPLAYER_REPLAYGAINPREAMP);
+ m_replayGainSettings.iNoGainPreAmp =
+ settings.GetInt(CSettings::SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP);
+ m_replayGainSettings.bAvoidClipping =
+ settings.GetBool(CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING);
+}
+
+bool CApplicationVolumeHandling::Load(const TiXmlNode* settings)
+{
+ if (!settings)
+ return false;
+
+ const TiXmlElement* audioElement = settings->FirstChildElement("audio");
+ if (audioElement)
+ {
+ XMLUtils::GetBoolean(audioElement, "mute", m_muted);
+ if (!XMLUtils::GetFloat(audioElement, "fvolumelevel", m_volumeLevel, VOLUME_MINIMUM,
+ VOLUME_MAXIMUM))
+ m_volumeLevel = VOLUME_MAXIMUM;
+ }
+
+ return true;
+}
+
+bool CApplicationVolumeHandling::Save(TiXmlNode* settings) const
+{
+ if (!settings)
+ return false;
+
+ TiXmlElement volumeNode("audio");
+ TiXmlNode* audioNode = settings->InsertEndChild(volumeNode);
+ if (!audioNode)
+ return false;
+
+ XMLUtils::SetBoolean(audioNode, "mute", m_muted);
+ XMLUtils::SetFloat(audioNode, "fvolumelevel", m_volumeLevel);
+
+ return true;
+}
+
+bool CApplicationVolumeHandling::OnSettingChanged(const CSetting& setting)
+{
+ const std::string& settingId = setting.GetId();
+
+ if (StringUtils::EqualsNoCase(settingId, CSettings::SETTING_MUSICPLAYER_REPLAYGAINTYPE))
+ m_replayGainSettings.iType = static_cast<const CSettingInt&>(setting).GetValue();
+ else if (StringUtils::EqualsNoCase(settingId, CSettings::SETTING_MUSICPLAYER_REPLAYGAINPREAMP))
+ m_replayGainSettings.iPreAmp = static_cast<const CSettingInt&>(setting).GetValue();
+ else if (StringUtils::EqualsNoCase(settingId,
+ CSettings::SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP))
+ m_replayGainSettings.iNoGainPreAmp = static_cast<const CSettingInt&>(setting).GetValue();
+ else if (StringUtils::EqualsNoCase(settingId,
+ CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING))
+ m_replayGainSettings.bAvoidClipping = static_cast<const CSettingBool&>(setting).GetValue();
+ else
+ return false;
+
+ return true;
+}
diff --git a/xbmc/application/ApplicationVolumeHandling.h b/xbmc/application/ApplicationVolumeHandling.h
new file mode 100644
index 0000000..d417227
--- /dev/null
+++ b/xbmc/application/ApplicationVolumeHandling.h
@@ -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.
+ */
+
+#pragma once
+
+#include "application/IApplicationComponent.h"
+
+class CAction;
+class CApplication;
+class CSetting;
+class CSettings;
+class TiXmlNode;
+
+/*!
+ * \brief Class handling application support for audio volume management.
+ */
+class CApplicationVolumeHandling : public IApplicationComponent
+{
+ friend class CApplication;
+
+public:
+ // replay gain settings struct for quick access by the player multiple
+ // times per second (saves doing settings lookup)
+ struct ReplayGainSettings
+ {
+ int iPreAmp;
+ int iNoGainPreAmp;
+ int iType;
+ bool bAvoidClipping;
+ };
+
+ float GetVolumePercent() const;
+ float GetVolumeRatio() const;
+ bool IsMuted() const;
+
+ void SetVolume(float iValue, bool isPercentage = true);
+ void SetMute(bool mute);
+ void ToggleMute(void);
+
+ const ReplayGainSettings& GetReplayGainSettings() const { return m_replayGainSettings; }
+
+ static constexpr float VOLUME_MINIMUM = 0.0f; // -60dB
+ static constexpr float VOLUME_MAXIMUM = 1.0f; // 0dB
+ static constexpr float VOLUME_DYNAMIC_RANGE = 90.0f; // 60dB
+
+ bool Load(const TiXmlNode* settings);
+ bool Save(TiXmlNode* settings) const;
+ bool OnSettingChanged(const CSetting& setting);
+
+protected:
+ bool IsMutedInternal() const { return m_muted; }
+ void ShowVolumeBar(const CAction* action = nullptr);
+
+ void CacheReplayGainSettings(const CSettings& settings);
+
+ void Mute();
+ void UnMute();
+
+ void SetHardwareVolume(float hardwareVolume);
+
+ void VolumeChanged();
+
+ bool m_muted = false;
+ float m_volumeLevel = VOLUME_MAXIMUM;
+ ReplayGainSettings m_replayGainSettings;
+};
diff --git a/xbmc/application/CMakeLists.txt b/xbmc/application/CMakeLists.txt
new file mode 100644
index 0000000..2fd3dbe
--- /dev/null
+++ b/xbmc/application/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(SOURCES AppEnvironment.cpp
+ AppInboundProtocol.cpp
+ Application.cpp
+ ApplicationActionListeners.cpp
+ ApplicationPlayer.cpp
+ ApplicationPlayerCallback.cpp
+ ApplicationPowerHandling.cpp
+ ApplicationSettingsHandling.cpp
+ ApplicationSkinHandling.cpp
+ ApplicationStackHelper.cpp
+ ApplicationVolumeHandling.cpp
+ AppParamParser.cpp
+ AppParams.cpp)
+
+set(HEADERS AppEnvironment.h
+ AppInboundProtocol.h
+ Application.h
+ ApplicationActionListeners.h
+ ApplicationPlayer.h
+ ApplicationPlayerCallback.h
+ ApplicationPowerHandling.h
+ ApplicationSettingsHandling.h
+ ApplicationSkinHandling.h
+ ApplicationStackHelper.h
+ ApplicationVolumeHandling.h
+ AppParamParser.h
+ AppParams.h)
+
+core_add_library(application)
diff --git a/xbmc/application/IApplicationComponent.h b/xbmc/application/IApplicationComponent.h
new file mode 100644
index 0000000..da23d3e
--- /dev/null
+++ b/xbmc/application/IApplicationComponent.h
@@ -0,0 +1,16 @@
+/*
+ * 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
+
+//! \brief Base class for application components.
+class IApplicationComponent
+{
+public:
+ virtual ~IApplicationComponent() = default;
+};