diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/interfaces/builtins | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/interfaces/builtins')
35 files changed, 5362 insertions, 0 deletions
diff --git a/xbmc/interfaces/builtins/AddonBuiltins.cpp b/xbmc/interfaces/builtins/AddonBuiltins.cpp new file mode 100644 index 0000000..056128d --- /dev/null +++ b/xbmc/interfaces/builtins/AddonBuiltins.cpp @@ -0,0 +1,522 @@ +/* + * 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 "AddonBuiltins.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "addons/AddonInstaller.h" +#include "addons/AddonManager.h" +#include "addons/AddonSystemSettings.h" +#include "addons/PluginSource.h" +#include "addons/RepositoryUpdater.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "addons/gui/GUIWindowAddonBrowser.h" +#include "application/Application.h" +#include "filesystem/PluginDirectory.h" +#include "games/tags/GameInfoTag.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "interfaces/generic/ScriptInvocationManager.h" +#include "messaging/helpers/DialogHelper.h" +#include "playlists/PlayListTypes.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include <memory> + +#if defined(TARGET_DARWIN) +#include "filesystem/SpecialProtocol.h" +#if defined(TARGET_DARWIN_OSX) +#include "platform/darwin/osx/CocoaInterface.h" +#endif +#endif + +using namespace ADDON; +using namespace KODI::MESSAGING; +using KODI::MESSAGING::HELPERS::DialogResponse; + +/*! \brief Install an addon. + * \param params The parameters. + * \details params[0] = add-on id. + */ +static int InstallAddon(const std::vector<std::string>& params) +{ + const std::string& addonid = params[0]; + + AddonPtr addon; + CAddonInstaller::GetInstance().InstallModal(addonid, addon, InstallModalPrompt::CHOICE_YES); + + return 0; +} + +/*! \brief Enable an addon. + * \param params The parameters. + * \details params[0] = add-on id. + */ +static int EnableAddon(const std::vector<std::string>& params) +{ + const std::string& addonid = params[0]; + + if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER)) + return -1; + + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(addonid, addon, OnlyEnabled::CHOICE_NO)) + return -1; + + auto response = HELPERS::ShowYesNoDialogLines(CVariant{24076}, CVariant{24135}, CVariant{addon->Name()}, CVariant{24136}); + if (response == DialogResponse::CHOICE_YES) + CServiceBroker::GetAddonMgr().EnableAddon(addonid); + + return 0; +} + +/*! \brief Run a plugin. + * \param params The parameters. + * \details params[0] = plugin:// URL to script. + */ +static int RunPlugin(const std::vector<std::string>& params) +{ + if (params.size()) + { + CFileItem item(params[0]); + if (!item.m_bIsFolder) + { + item.SetPath(params[0]); + XFILE::CPluginDirectory::RunScriptWithParams(item.GetPath(), false); + } + } + else + CLog::Log(LOGERROR, "RunPlugin called with no arguments."); + + return 0; +} + +/*! \brief Run a script, plugin or game add-on. + * \param params The parameters. + * \details params[0] = add-on id. + * params[1] is blank for no add-on parameters + * or + * params[1] = add-on parameters in url format + * or + * params[1,...] = additional parameters in format param=value. + */ +static int RunAddon(const std::vector<std::string>& params) +{ + if (params.size()) + { + const std::string& addonid = params[0]; + + AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(addonid, addon, AddonType::PLUGIN, + OnlyEnabled::CHOICE_YES)) + { + const auto plugin = std::dynamic_pointer_cast<CPluginSource>(addon); + std::string urlParameters; + std::vector<std::string> parameters; + if (params.size() == 2 && + (StringUtils::StartsWith(params[1], "/") || StringUtils::StartsWith(params[1], "?"))) + urlParameters = params[1]; + else if (params.size() > 1) + { + parameters.insert(parameters.begin(), params.begin() + 1, params.end()); + urlParameters = "?" + StringUtils::Join(parameters, "&"); + } + else + { + // Add '/' if addon is run without params (will be removed later so it's safe) + // Otherwise there are 2 entries for the same plugin in ViewModesX.db + urlParameters = "/"; + } + + std::string cmd; + if (plugin->Provides(CPluginSource::VIDEO)) + cmd = StringUtils::Format("ActivateWindow(Videos,plugin://{}{},return)", addonid, + urlParameters); + else if (plugin->Provides(CPluginSource::AUDIO)) + cmd = StringUtils::Format("ActivateWindow(Music,plugin://{}{},return)", addonid, + urlParameters); + else if (plugin->Provides(CPluginSource::EXECUTABLE)) + cmd = StringUtils::Format("ActivateWindow(Programs,plugin://{}{},return)", addonid, + urlParameters); + else if (plugin->Provides(CPluginSource::IMAGE)) + cmd = StringUtils::Format("ActivateWindow(Pictures,plugin://{}{},return)", addonid, + urlParameters); + else if (plugin->Provides(CPluginSource::GAME)) + cmd = StringUtils::Format("ActivateWindow(Games,plugin://{}{},return)", addonid, + urlParameters); + else + // Pass the script name (addonid) and all the parameters + // (params[1] ... params[x]) separated by a comma to RunPlugin + cmd = StringUtils::Format("RunPlugin({})", StringUtils::Join(params, ",")); + CBuiltins::GetInstance().Execute(cmd); + } + else if (CServiceBroker::GetAddonMgr().GetAddon(addonid, addon, AddonType::SCRIPT, + OnlyEnabled::CHOICE_YES) || + CServiceBroker::GetAddonMgr().GetAddon(addonid, addon, AddonType::SCRIPT_WEATHER, + OnlyEnabled::CHOICE_YES) || + CServiceBroker::GetAddonMgr().GetAddon(addonid, addon, AddonType::SCRIPT_LYRICS, + OnlyEnabled::CHOICE_YES) || + CServiceBroker::GetAddonMgr().GetAddon(addonid, addon, AddonType::SCRIPT_LIBRARY, + OnlyEnabled::CHOICE_YES)) + { + // Pass the script name (addonid) and all the parameters + // (params[1] ... params[x]) separated by a comma to RunScript + CBuiltins::GetInstance().Execute( + StringUtils::Format("RunScript({})", StringUtils::Join(params, ","))); + } + else if (CServiceBroker::GetAddonMgr().GetAddon(addonid, addon, AddonType::GAMEDLL, + OnlyEnabled::CHOICE_YES)) + { + CFileItem item; + + if (params.size() >= 2) + { + item = CFileItem(params[1], false); + item.GetGameInfoTag()->SetGameClient(addonid); + } + else + item = CFileItem(addon); + + if (!g_application.PlayMedia(item, "", PLAYLIST::TYPE_NONE)) + { + CLog::Log(LOGERROR, "RunAddon could not start {}", addonid); + return false; + } + } + else + CLog::Log( + LOGERROR, + "RunAddon: unknown add-on id '{}', or unexpected add-on type (not a script or plugin).", + addonid); + } + else + { + CLog::Log(LOGERROR, "RunAddon called with no arguments."); + } + + return 0; +} + +/*! \brief Run a script add-on or an apple script. + * \param params The parameters. + * \details params[0] is the URL to the apple script + * or + * params[0] is the addon-ID to the script add-on + * or + * params[0] is the URL to the python script. + * + * Set the OnlyApple template parameter to true to only attempt + * execution of applescripts. + */ +template<bool OnlyApple=false> +static int RunScript(const std::vector<std::string>& params) +{ +#if defined(TARGET_DARWIN_OSX) + std::string execute; + std::string parameter = params.size() ? params[0] : ""; + if (URIUtils::HasExtension(parameter, ".applescript|.scpt")) + { + std::string osxPath = CSpecialProtocol::TranslatePath(parameter); + Cocoa_DoAppleScriptFile(osxPath.c_str()); + } + else if (OnlyApple) + return 1; + else +#endif + { + AddonPtr addon; + std::string scriptpath; + // Test to see if the param is an addon ID + if (CServiceBroker::GetAddonMgr().GetAddon(params[0], addon, OnlyEnabled::CHOICE_YES)) + { + //Get the correct extension point to run + if (CServiceBroker::GetAddonMgr().GetAddon(params[0], addon, AddonType::SCRIPT, + OnlyEnabled::CHOICE_YES) || + CServiceBroker::GetAddonMgr().GetAddon(params[0], addon, AddonType::SCRIPT_WEATHER, + OnlyEnabled::CHOICE_YES) || + CServiceBroker::GetAddonMgr().GetAddon(params[0], addon, AddonType::SCRIPT_LYRICS, + OnlyEnabled::CHOICE_YES) || + CServiceBroker::GetAddonMgr().GetAddon(params[0], addon, AddonType::SCRIPT_LIBRARY, + OnlyEnabled::CHOICE_YES)) + { + scriptpath = addon->LibPath(); + } + else + { + // Run a random extension point (old behaviour). + if (CServiceBroker::GetAddonMgr().GetAddon(params[0], addon, OnlyEnabled::CHOICE_YES)) + { + scriptpath = addon->LibPath(); + CLog::Log(LOGWARNING, + "RunScript called for a non-script addon '{}'. This behaviour is deprecated.", + params[0]); + } + else + { + CLog::Log(LOGERROR, "{} - Could not get addon: {}", __FUNCTION__, params[0]); + } + } + } + else + scriptpath = params[0]; + + // split the path up to find the filename + std::vector<std::string> argv = params; + std::string filename = URIUtils::GetFileName(scriptpath); + if (!filename.empty()) + argv[0] = filename; + + CScriptInvocationManager::GetInstance().ExecuteAsync(scriptpath, addon, argv); + } + + return 0; +} + +/*! \brief Open the settings for the default add-on of a given type. + * \param params The parameters. + * \details params[0] = The add-on type. + */ +static int OpenDefaultSettings(const std::vector<std::string>& params) +{ + AddonPtr addon; + AddonType type = CAddonInfo::TranslateType(params[0]); + if (CAddonSystemSettings::GetInstance().GetActive(type, addon)) + { + bool changed = CGUIDialogAddonSettings::ShowForAddon(addon); + if (type == AddonType::VISUALIZATION && changed) + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_VISUALISATION_RELOAD, 0, 0); + } + + return 0; +} + +/*! \brief Set the default add-on for a given type. + * \param params The parameters. + * \details params[0] = The add-on type + */ +static int SetDefaultAddon(const std::vector<std::string>& params) +{ + std::string addonID; + AddonType type = CAddonInfo::TranslateType(params[0]); + bool allowNone = false; + if (type == AddonType::VISUALIZATION) + allowNone = true; + + if (type != AddonType::UNKNOWN && CGUIWindowAddonBrowser::SelectAddonID(type, addonID, allowNone)) + { + CAddonSystemSettings::GetInstance().SetActive(type, addonID); + if (type == AddonType::VISUALIZATION) + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_VISUALISATION_RELOAD, 0, 0); + } + + return 0; +} + +/*! \brief Open the settings for a given add-on. + * \param params The parameters. + * \details params[0] = The add-on ID. + */ +static int AddonSettings(const std::vector<std::string>& params) +{ + AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(params[0], addon, OnlyEnabled::CHOICE_YES)) + CGUIDialogAddonSettings::ShowForAddon(addon); + + return 0; +} + +/*! \brief Open the settings for a given add-on. +* \param params The parameters. +*/ +static int InstallFromZip(const std::vector<std::string>& params) +{ + CGUIWindowAddonBrowser::InstallFromZip(); + return 0; +} + +/*! \brief Stop a running script. + * \param params The parameters. + * \details params[0] = The add-on ID of the script to stop + * or + * params[0] = The URL of the script to stop. + */ +static int StopScript(const std::vector<std::string>& params) +{ + //! @todo FIXME: This does not work for addons with multiple extension points! + //! Are there any use for this? TODO: Fix hack in CScreenSaver::Destroy() and deprecate. + std::string scriptpath(params[0]); + // Test to see if the param is an addon ID + AddonPtr script; + if (CServiceBroker::GetAddonMgr().GetAddon(params[0], script, OnlyEnabled::CHOICE_YES)) + scriptpath = script->LibPath(); + CScriptInvocationManager::GetInstance().Stop(scriptpath); + + return 0; +} + +/*! \brief Check add-on repositories for updates. + * \param params (ignored) + */ +static int UpdateRepos(const std::vector<std::string>& params) +{ + CServiceBroker::GetRepositoryUpdater().CheckForUpdates(); + + return 0; +} + +/*! \brief Check local add-on directories for updates. + * \param params (ignored) + */ +static int UpdateLocals(const std::vector<std::string>& params) +{ + CServiceBroker::GetAddonMgr().FindAddons(); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions List of built-in functions +/// \section built_in_functions_1 Add-on built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`Addon.Default.OpenSettings(extensionpoint)`</b> +/// , +/// Open a settings dialog for the default addon of the given type +/// (extensionpoint) +/// @param[in] extensionpoint The add-on type +/// } +/// \table_row2_l{ +/// <b>`Addon.Default.Set(extensionpoint)`</b> +/// , +/// Open a select dialog to allow choosing the default addon of the given type +/// (extensionpoint) +/// @param[in] extensionpoint The add-on type +/// } +/// \table_row2_l{ +/// <b>`Addon.OpenSettings(id)`</b> +/// , +/// Open a settings dialog for the addon of the given id +/// @param[in] id The add-on ID +/// } +/// \table_row2_l{ +/// <b>`EnableAddon(id)`</b> +/// \anchor Builtin_EnableAddonId, +/// Enable the specified plugin/script +/// @param[in] id The add-on id +/// <p><hr> +/// @skinning_v19 **[New builtin]** \link Builtin_EnableAddonId `EnableAddon(id)`\endlink +/// <p> +/// } +/// \table_row2_l{ +/// <b>`InstallAddon(id)`</b> +/// , +/// Install the specified plugin/script +/// @param[in] id The add-on id +/// } +/// \table_row2_l{ +/// <b>`InstallFromZip`</b> +/// , +/// Opens the "Install from zip" dialog if "Unknown sources" is enabled. Prompts the warning message if not. +/// } +/// \table_row2_l{ +/// <b>`RunAddon(id[\,opt])`</b> +/// , +/// Runs the specified plugin/script +/// @param[in] id The add-on id. +/// @param[in] opt is blank for no add-on parameters\n +/// or +/// @param[in] opt Add-on parameters in url format\n +/// or +/// @param[in] opt[\,...] Additional parameters in format param=value. +/// } +/// \table_row2_l{ +/// <b>`RunAppleScript(script[\,args]*)`</b> +/// , +/// Run the specified AppleScript command +/// @param[in] script Is the URL to the apple script\n +/// or +/// @param[in] script Is the addon-ID to the script add-on\n +/// or +/// @param[in] script Is the URL to the python script. +/// +/// @note Set the OnlyApple template parameter to true to only attempt +/// execution of applescripts. +/// } +/// \table_row2_l{ +/// <b>`RunPlugin(plugin)`</b> +/// , +/// Runs the plugin. Full path must be specified. Does not work for folder +/// plugins +/// @param[in] plugin plugin:// URL to script. +/// } +/// \table_row2_l{ +/// <b>`RunScript(script[\,args]*)`</b> +/// , +/// Runs the python script. You must specify the full path to the script. If +/// the script is an add-on\, you can also execute it using its add-on id. As +/// of 2007/02/24\, all extra parameters are passed to the script as arguments +/// and can be accessed by python using sys.argv +/// @param[in] script Is the addon-ID to the script add-on\n +/// or +/// @param[in] script Is the URL to the python script. +/// } +/// \table_row2_l{ +/// <b>`StopScript(id)`</b> +/// , +/// Stop the script by ID or path\, if running +/// @param[in] id The add-on ID of the script to stop\n +/// or +/// @param[in] id The URL of the script to stop. +/// } +/// \table_row2_l{ +/// <b>`UpdateAddonRepos`</b> +/// , +/// Triggers a forced update of enabled add-on repositories. +/// } +/// \table_row2_l{ +/// <b>`UpdateLocalAddons`</b> +/// , +/// Triggers a scan of local add-on directories. +/// } +/// \table_end +/// + +CBuiltins::CommandMap CAddonBuiltins::GetOperations() const +{ + return { + {"addon.default.opensettings", {"Open a settings dialog for the default addon of the given type", 1, OpenDefaultSettings}}, + {"addon.default.set", {"Open a select dialog to allow choosing the default addon of the given type", 1, SetDefaultAddon}}, + {"addon.opensettings", {"Open a settings dialog for the addon of the given id", 1, AddonSettings}}, + {"enableaddon", {"Enables the specified plugin/script", 1, EnableAddon}}, + {"installaddon", {"Install the specified plugin/script", 1, InstallAddon}}, + {"installfromzip", { "Open the install from zip dialog", 0, InstallFromZip}}, + {"runaddon", {"Run the specified plugin/script", 1, RunAddon}}, +#ifdef TARGET_DARWIN + {"runapplescript", {"Run the specified AppleScript command", 1, RunScript<true>}}, +#endif + {"runplugin", {"Run the specified plugin", 1, RunPlugin}}, + {"runscript", {"Run the specified script", 1, RunScript}}, + {"stopscript", {"Stop the script by ID or path, if running", 1, StopScript}}, + {"updateaddonrepos", {"Check add-on repositories for updates", 0, UpdateRepos}}, + {"updatelocaladdons", {"Check for local add-on changes", 0, UpdateLocals}} + }; +} diff --git a/xbmc/interfaces/builtins/AddonBuiltins.h b/xbmc/interfaces/builtins/AddonBuiltins.h new file mode 100644 index 0000000..9418420 --- /dev/null +++ b/xbmc/interfaces/builtins/AddonBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing add-on related built-in commands. +class CAddonBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/AndroidBuiltins.cpp b/xbmc/interfaces/builtins/AndroidBuiltins.cpp new file mode 100644 index 0000000..17f4076 --- /dev/null +++ b/xbmc/interfaces/builtins/AndroidBuiltins.cpp @@ -0,0 +1,73 @@ +/* + * 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 "AndroidBuiltins.h" + +#include "ServiceBroker.h" +#include "messaging/ApplicationMessenger.h" + +/*! \brief Launch an android system activity. + * \param params The parameters. + * \details params[0] = package + * params[1] = intent (optional) + * params[2] = datatype (optional) + * params[3] = dataURI (optional) + * params[4] = flags (optional) + * params[5] = extras (optional) + * params[6] = action (optional) + * params[7] = category (optional) + * params[8] = className (optional) + */ +static int LaunchAndroidActivity(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_START_ANDROID_ACTIVITY, -1, -1, nullptr, "", + params); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_2 Android built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`StartAndroidActivity(package\,[intent\,dataType\,dataURI\,flags\,extras\,action\,category\,className])`</b> +/// , +/// Launch an Android native app with the given package name. Optional parms +/// (in order): intent\, dataType\, dataURI\, flags\, extras\, action\, +/// category\, className. +/// @param[in] package +/// @param[in] intent (optional) +/// @param[in] datatype (optional) +/// @param[in] dataURI (optional) +/// @param[in] flags (optional) +/// @param[in] extras (optional) +/// @param[in] action (optional) +/// @param[in] category (optional) +/// @param[in] className (optional) +/// <p><hr> +/// @skinning_v20 Added parameters `flags`\,`extras`\,`action`\,`category`\,`className`. +/// <p> +/// } +/// \table_end +/// + +CBuiltins::CommandMap CAndroidBuiltins::GetOperations() const +{ + return {{"startandroidactivity", + {"Launch an Android native app with the given package name. Optional parms (in order): " + "intent, dataType, dataURI, flags, extras, action, category, className.", + 1, LaunchAndroidActivity}}}; +} diff --git a/xbmc/interfaces/builtins/AndroidBuiltins.h b/xbmc/interfaces/builtins/AndroidBuiltins.h new file mode 100644 index 0000000..d4059e2 --- /dev/null +++ b/xbmc/interfaces/builtins/AndroidBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing CEC related built-in commands. +class CAndroidBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/ApplicationBuiltins.cpp b/xbmc/interfaces/builtins/ApplicationBuiltins.cpp new file mode 100644 index 0000000..dfec66d --- /dev/null +++ b/xbmc/interfaces/builtins/ApplicationBuiltins.cpp @@ -0,0 +1,222 @@ +/* + * 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 "ApplicationBuiltins.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "application/ApplicationVolumeHandling.h" +#include "filesystem/ZipManager.h" +#include "input/actions/ActionIDs.h" +#include "interfaces/AnnouncementManager.h" +#include "messaging/ApplicationMessenger.h" +#include "network/Network.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/JSONVariantParser.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <stdlib.h> + +/*! \brief Extract an archive. + * \param params The parameters + * \details params[0] = The archive URL. + * params[1] = Destination path (optional). + * If not given, extracts to folder with archive. + */ +static int Extract(const std::vector<std::string>& params) +{ + // Detects if file is zip or rar then extracts + std::string strDestDirect; + if (params.size() < 2) + strDestDirect = URIUtils::GetDirectory(params[0]); + else + strDestDirect = params[1]; + + URIUtils::AddSlashAtEnd(strDestDirect); + + if (URIUtils::IsZIP(params[0])) + g_ZipManager.ExtractArchive(params[0],strDestDirect); + else + CLog::Log(LOGERROR, "Extract, No archive given"); + + return 0; +} + +/*! \brief Mute volume. + * \param params (ignored) + */ +static int Mute(const std::vector<std::string>& params) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + appVolume->ToggleMute(); + + return 0; +} + +/*! \brief Notify all listeners on announcement bus. + * \param params The parameters. + * \details params[0] = sender. + * params[1] = data. + * params[2] = JSON with extra parameters (optional). + */ +static int NotifyAll(const std::vector<std::string>& params) +{ + CVariant data; + if (params.size() > 2) + { + if (!CJSONVariantParser::Parse(params[2], data)) + { + CLog::Log(LOGERROR, "NotifyAll failed to parse data: {}", params[2]); + return -3; + } + } + + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Other, params[0].c_str(), params[1].c_str(), data); + + return 0; +} + +/*! \brief Set volume. + * \param params the parameters. + * \details params[0] = Volume level. + * params[1] = "showVolumeBar" to show volume bar (optional). + */ +static int SetVolume(const std::vector<std::string>& params) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + float oldVolume = appVolume->GetVolumePercent(); + float volume = static_cast<float>(strtod(params[0].c_str(), nullptr)); + + appVolume->SetVolume(volume); + if (oldVolume != volume) + { + if (params.size() > 1 && StringUtils::EqualsNoCase(params[1], "showVolumeBar")) + { + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_VOLUME_SHOW, oldVolume < volume ? ACTION_VOLUME_UP : ACTION_VOLUME_DOWN); + } + } + + return 0; +} + +/*! \brief Toggle debug info. + * \param params (ignored) + */ +static int ToggleDebug(const std::vector<std::string>& params) +{ + bool debug = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_DEBUG_SHOWLOGINFO); + CServiceBroker::GetSettingsComponent()->GetSettings()->SetBool(CSettings::SETTING_DEBUG_SHOWLOGINFO, !debug); + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->SetDebugMode(!debug); + + return 0; +} + +/*! \brief Toggle DPMS state. + * \param params (ignored) + */ +static int ToggleDPMS(const std::vector<std::string>& params) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ToggleDPMS(true); + + return 0; +} + +/*! \brief Send a WOL packet to a given host. + * \param params The parameters. + * \details params[0] = The MAC of the host to wake. + */ +static int WakeOnLAN(const std::vector<std::string>& params) +{ + CServiceBroker::GetNetwork().WakeOnLan(params[0].c_str()); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_3 Application built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`Extract(url [\, dest])`</b> +/// , +/// Extracts a specified archive to an optionally specified 'absolute' path. +/// @param[in] url The archive URL. +/// @param[in] dest Destination path (optional). +/// @note If not given\, extracts to folder with archive. +/// } +/// \table_row2_l{ +/// <b>`Mute`</b> +/// , +/// Mutes (or unmutes) the volume. +/// } +/// \table_row2_l{ +/// <b>`NotifyAll(sender\, data [\, json])`</b> +/// , +/// Notify all connected clients +/// @param[in] sender Sender. +/// @param[in] data Data. +/// @param[in] json JSON with extra parameters (optional). +/// } +/// \table_row2_l{ +/// <b>`SetVolume(percent[\,showvolumebar])`</b> +/// , +/// Sets the volume to the percentage specified. Optionally\, show the Volume +/// Dialog in Kodi when setting the volume. +/// @param[in] percent Volume level. +/// @param[in] showvolumebar Add "showVolumeBar" to show volume bar (optional). +/// } +/// \table_row2_l{ +/// <b>`ToggleDebug`</b> +/// , +/// Toggles debug mode on/off +/// } +/// \table_row2_l{ +/// <b>`ToggleDPMS`</b> +/// , +/// Toggle DPMS mode manually +/// } +/// \table_row2_l{ +/// <b>`WakeOnLan(mac)`</b> +/// , +/// Sends the wake-up packet to the broadcast address for the specified MAC +/// address (Format: FF:FF:FF:FF:FF:FF or FF-FF-FF-FF-FF-FF). +/// @param[in] mac The MAC of the host to wake. +/// } +/// \table_end +/// + +CBuiltins::CommandMap CApplicationBuiltins::GetOperations() const +{ + return { + {"extract", {"Extracts the specified archive", 1, Extract}}, + {"mute", {"Mute the player", 0, Mute}}, + {"notifyall", {"Notify all connected clients", 2, NotifyAll}}, + {"setvolume", {"Set the current volume", 1, SetVolume}}, + {"toggledebug", {"Enables/disables debug mode", 0, ToggleDebug}}, + {"toggledpms", {"Toggle DPMS mode manually", 0, ToggleDPMS}}, + {"wakeonlan", {"Sends the wake-up packet to the broadcast address for the specified MAC address", 1, WakeOnLAN}} + }; +} diff --git a/xbmc/interfaces/builtins/ApplicationBuiltins.h b/xbmc/interfaces/builtins/ApplicationBuiltins.h new file mode 100644 index 0000000..f237fe4 --- /dev/null +++ b/xbmc/interfaces/builtins/ApplicationBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing application related built-in commands. +class CApplicationBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/Builtins.cpp b/xbmc/interfaces/builtins/Builtins.cpp new file mode 100644 index 0000000..624d9d1 --- /dev/null +++ b/xbmc/interfaces/builtins/Builtins.cpp @@ -0,0 +1,168 @@ +/* + * 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 "Builtins.h" + +#include "AddonBuiltins.h" +#include "ApplicationBuiltins.h" +#include "CECBuiltins.h" +#include "GUIBuiltins.h" +#include "GUIContainerBuiltins.h" +#include "GUIControlBuiltins.h" +#include "LibraryBuiltins.h" +#include "OpticalBuiltins.h" +#include "PVRBuiltins.h" +#include "PictureBuiltins.h" +#include "PlayerBuiltins.h" +#include "ProfileBuiltins.h" +#include "ServiceBroker.h" +#include "SkinBuiltins.h" +#include "SystemBuiltins.h" +#include "WeatherBuiltins.h" +#include "input/InputManager.h" +#include "powermanagement/PowerTypes.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/ExecString.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#if defined(TARGET_ANDROID) +#include "AndroidBuiltins.h" +#endif + +#if defined(TARGET_POSIX) +#include "PlatformDefs.h" +#endif + +CBuiltins::CBuiltins() +{ + RegisterCommands<CAddonBuiltins>(); + RegisterCommands<CApplicationBuiltins>(); + RegisterCommands<CGUIBuiltins>(); + RegisterCommands<CGUIContainerBuiltins>(); + RegisterCommands<CGUIControlBuiltins>(); + RegisterCommands<CLibraryBuiltins>(); + RegisterCommands<COpticalBuiltins>(); + RegisterCommands<CPictureBuiltins>(); + RegisterCommands<CPlayerBuiltins>(); + RegisterCommands<CProfileBuiltins>(); + RegisterCommands<CPVRBuiltins>(); + RegisterCommands<CSkinBuiltins>(); + RegisterCommands<CSystemBuiltins>(); + RegisterCommands<CWeatherBuiltins>(); + +#if defined(HAVE_LIBCEC) + RegisterCommands<CCECBuiltins>(); +#endif + +#if defined(TARGET_ANDROID) + RegisterCommands<CAndroidBuiltins>(); +#endif +} + +CBuiltins::~CBuiltins() = default; + +CBuiltins& CBuiltins::GetInstance() +{ + static CBuiltins sBuiltins; + return sBuiltins; +} + +bool CBuiltins::HasCommand(const std::string& execString) +{ + const CExecString exec(execString); + if (!exec.IsValid()) + return false; + + const std::string function = exec.GetFunction(); + const std::vector<std::string> parameters = exec.GetParams(); + + if (CServiceBroker::GetInputManager().HasBuiltin(function)) + return true; + + const auto& it = m_command.find(function); + if (it != m_command.end()) + { + if (it->second.parameters == 0 || it->second.parameters <= parameters.size()) + return true; + } + + return false; +} + +bool CBuiltins::IsSystemPowerdownCommand(const std::string& execString) +{ + const CExecString exec(execString); + if (!exec.IsValid()) + return false; + + const std::string execute = exec.GetFunction(); + + // Check if action is resulting in system powerdown. + if (execute == "reboot" || + execute == "restart" || + execute == "reset" || + execute == "powerdown" || + execute == "hibernate" || + execute == "suspend" ) + { + return true; + } + else if (execute == "shutdown") + { + switch (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE)) + { + case POWERSTATE_SHUTDOWN: + case POWERSTATE_SUSPEND: + case POWERSTATE_HIBERNATE: + return true; + + default: + return false; + } + } + return false; +} + +void CBuiltins::GetHelp(std::string &help) +{ + help.clear(); + + for (const auto& it : m_command) + { + help += it.first; + help += "\t"; + help += it.second.description; + help += "\n"; + } +} + +int CBuiltins::Execute(const std::string& execString) +{ + const CExecString exec(execString); + if (!exec.IsValid()) + return -1; + + const std::string execute = exec.GetFunction(); + const std::vector<std::string> params = exec.GetParams(); + + const auto& it = m_command.find(execute); + if (it != m_command.end()) + { + if (it->second.parameters == 0 || params.size() >= it->second.parameters) + return it->second.Execute(params); + else + { + CLog::Log(LOGERROR, "{0} called with invalid number of parameters (should be: {1}, is {2})", + execute, it->second.parameters, params.size()); + return -1; + } + } + else + return CServiceBroker::GetInputManager().ExecuteBuiltin(execute, params); +} diff --git a/xbmc/interfaces/builtins/Builtins.h b/xbmc/interfaces/builtins/Builtins.h new file mode 100644 index 0000000..7d0345a --- /dev/null +++ b/xbmc/interfaces/builtins/Builtins.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <string> +#include <vector> + +class CBuiltins +{ +public: + //! \brief Struct representing a command from handler classes. + struct BUILT_IN + { + std::string description; //!< Description of command (help string) + size_t parameters; //!< Number of required parameters (can be 0) + int (*Execute)(const std::vector<std::string>& params); //!< Function to handle command + }; + + //! \brief A map of commands + typedef std::map<std::string,CBuiltins::BUILT_IN> CommandMap; + + static CBuiltins& GetInstance(); + + bool HasCommand(const std::string& execString); + bool IsSystemPowerdownCommand(const std::string& execString); + void GetHelp(std::string &help); + int Execute(const std::string& execString); + +protected: + CBuiltins(); + CBuiltins(const CBuiltins&) = delete; + const CBuiltins& operator=(const CBuiltins&) = delete; + virtual ~CBuiltins(); + +private: + CommandMap m_command; //!< Map of registered commands + + + //! \brief Convenience template used to register commands from providers + template<class T> + void RegisterCommands() + { + T t; + CommandMap map = t.GetOperations(); + m_command.insert(map.begin(), map.end()); + } +}; + diff --git a/xbmc/interfaces/builtins/CECBuiltins.cpp b/xbmc/interfaces/builtins/CECBuiltins.cpp new file mode 100644 index 0000000..d381816 --- /dev/null +++ b/xbmc/interfaces/builtins/CECBuiltins.cpp @@ -0,0 +1,82 @@ +/* + * 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 "CECBuiltins.h" + +#include "ServiceBroker.h" +#include "messaging/ApplicationMessenger.h" + +/*! \brief Wake up device through CEC. + * \param params (ignored) + */ +static int ActivateSource(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_CECACTIVATESOURCE); + + return 0; +} + +/*! \brief Put device in standby through CEC. + * \param params (ignored) + */ +static int Standby(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_CECSTANDBY); + + return 0; +} + +/*! \brief Toggle device state through CEC. + * \param params (ignored) + */ +static int ToggleState(const std::vector<std::string>& params) +{ + bool result; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_CECTOGGLESTATE, 0, 0, + static_cast<void*>(&result)); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_4 CEC built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`CECActivateSource`</b> +/// , +/// Wake up playing device via a CEC peripheral +/// } +/// \table_row2_l{ +/// <b>`CECStandby`</b> +/// , +/// Put playing device on standby via a CEC peripheral +/// } +/// \table_row2_l{ +/// <b>`CECToggleState`</b> +/// , +/// Toggle state of playing device via a CEC peripheral +/// } +/// \table_end +/// + +CBuiltins::CommandMap CCECBuiltins::GetOperations() const +{ + return { + {"cectogglestate", {"Toggle state of playing device via a CEC peripheral", 0, ToggleState}}, + {"cecactivatesource", {"Wake up playing device via a CEC peripheral", 0, ActivateSource}}, + {"cecstandby", {"Put playing device on standby via a CEC peripheral", 0, Standby}} + }; +} diff --git a/xbmc/interfaces/builtins/CECBuiltins.h b/xbmc/interfaces/builtins/CECBuiltins.h new file mode 100644 index 0000000..d06c291 --- /dev/null +++ b/xbmc/interfaces/builtins/CECBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing CEC related built-in commands. +class CCECBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/CMakeLists.txt b/xbmc/interfaces/builtins/CMakeLists.txt new file mode 100644 index 0000000..27785af --- /dev/null +++ b/xbmc/interfaces/builtins/CMakeLists.txt @@ -0,0 +1,41 @@ +set(SOURCES AddonBuiltins.cpp + ApplicationBuiltins.cpp + Builtins.cpp + CECBuiltins.cpp + GUIBuiltins.cpp + GUIControlBuiltins.cpp + GUIContainerBuiltins.cpp + LibraryBuiltins.cpp + OpticalBuiltins.cpp + PictureBuiltins.cpp + PlayerBuiltins.cpp + ProfileBuiltins.cpp + PVRBuiltins.cpp + SkinBuiltins.cpp + SystemBuiltins.cpp + WeatherBuiltins.cpp) + +set(HEADERS AddonBuiltins.h + AndroidBuiltins.h + ApplicationBuiltins.h + Builtins.h + CECBuiltins.h + GUIBuiltins.h + GUIContainerBuiltins.h + GUIControlBuiltins.h + LibraryBuiltins.h + OpticalBuiltins.h + PictureBuiltins.h + PlayerBuiltins.h + ProfileBuiltins.h + PVRBuiltins.h + SkinBuiltins.h + SystemBuiltins.h + WeatherBuiltins.h) + +if(CORE_SYSTEM_NAME STREQUAL android) + list(APPEND SOURCES AndroidBuiltins.cpp) + list(APPEND HEADERS AndroidBuiltins.h) +endif() + +core_add_library(interfaces_builtins) diff --git a/xbmc/interfaces/builtins/GUIBuiltins.cpp b/xbmc/interfaces/builtins/GUIBuiltins.cpp new file mode 100644 index 0000000..f368e69 --- /dev/null +++ b/xbmc/interfaces/builtins/GUIBuiltins.cpp @@ -0,0 +1,601 @@ +/* + * 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 "GUIBuiltins.h" + +#include "ServiceBroker.h" +#include "Util.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogNumeric.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/StereoscopicsManager.h" +#include "input/WindowTranslator.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/AlarmClock.h" +#include "utils/RssManager.h" +#include "utils/Screenshot.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "windows/GUIMediaWindow.h" + +/*! \brief Execute a GUI action. + * \param params The parameters. + * \details params[0] = Action to execute. + * params[1] = Window to send action to (optional). + */ +static int Action(const std::vector<std::string>& params) +{ + // try translating the action from our ButtonTranslator + unsigned int actionID; + if (CActionTranslator::TranslateString(params[0], actionID)) + { + int windowID = params.size() == 2 ? CWindowTranslator::TranslateWindow(params[1]) : WINDOW_INVALID; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, windowID, -1, + static_cast<void*>(new CAction(actionID))); + } + + return 0; +} + +/*! \brief Activate a window. + * \param params The parameters. + * \details params[0] = The window name. + * params[1] = Window starting folder (optional). + * + * Set the Replace template parameter to true to replace current + * window in history. + */ + template<bool Replace> +static int ActivateWindow(const std::vector<std::string>& params2) +{ + std::vector<std::string> params(params2); + // get the parameters + std::string strWindow; + if (params.size()) + { + strWindow = params[0]; + params.erase(params.begin()); + } + + // confirm the window destination is valid prior to switching + int iWindow = CWindowTranslator::TranslateWindow(strWindow); + if (iWindow != WINDOW_INVALID) + { + // compare the given directory param with the current active directory + // if no directory is given, and you switch from a video window to another + // we retain history, so it makes sense to not switch to the same window in + // that case + bool bIsSameStartFolder = true; + if (!params.empty()) + { + CGUIWindow *activeWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + if (activeWindow && activeWindow->IsMediaWindow()) + bIsSameStartFolder = static_cast<CGUIMediaWindow*>(activeWindow)->IsSameStartFolder(params[0]); + } + + // activate window only if window and path differ from the current active window + if (iWindow != CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() || !bIsSameStartFolder) + { + // if the window doesn't change, make sure it knows it's gonna be replaced + // this ensures setting the start directory if we switch paths + // if we change windows, that's done anyway + if (Replace && !params.empty() && + iWindow == CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()) + params.emplace_back("replace"); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->WakeUpScreenSaverAndDPMS(); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(iWindow, params, Replace); + return 0; + } + } + else + { + CLog::Log(LOGERROR, "Activate/ReplaceWindow called with invalid destination window: {}", + strWindow); + return false; + } + + return 1; +} + +/*! \brief Activate a window and give given controls focus. + * \param params The parameters. + * \details params[0] = The window name. + * params[1,...] = Pair of (container ID, focus item). + * + * Set the Replace template parameter to true to replace current + * window in history. + */ + template<bool Replace> +static int ActivateAndFocus(const std::vector<std::string>& params) +{ + std::string strWindow = params[0]; + + // confirm the window destination is valid prior to switching + int iWindow = CWindowTranslator::TranslateWindow(strWindow); + if (iWindow != WINDOW_INVALID) + { + if (iWindow != CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()) + { + // disable the screensaver + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->WakeUpScreenSaverAndDPMS(); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(iWindow, {}, Replace); + + unsigned int iPtr = 1; + while (params.size() > iPtr + 1) + { + CGUIMessage msg(GUI_MSG_SETFOCUS, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), + atol(params[iPtr].c_str()), + (params.size() >= iPtr + 2) ? atol(params[iPtr + 1].c_str())+1 : 0); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + iPtr += 2; + } + return 0; + } + + } + else + CLog::Log(LOGERROR, "Replace/ActivateWindowAndFocus called with invalid destination window: {}", + strWindow); + + return 1; +} + +/*! \brief Start an alarm clock + * \param params The parameters. + * \details param[0] = name + * param[1] = command + * param[2] = Length in seconds (optional). + * param[3] = "silent" to suppress notifications. + * param[3] = "loop" to loop the alarm. + */ +static int AlarmClock(const std::vector<std::string>& params) +{ + // format is alarmclock(name,command[,time,true,false]); + float seconds = 0; + if (params.size() > 2) + { + if (params[2].find(':') == std::string::npos) + seconds = static_cast<float>(atoi(params[2].c_str())*60); + else + seconds = (float)StringUtils::TimeStringToSeconds(params[2]); + } + else + { // check if shutdown is specified in particular, and get the time for it + std::string strHeading; + if (StringUtils::EqualsNoCase(params[0], "shutdowntimer")) + strHeading = g_localizeStrings.Get(20145); + else + strHeading = g_localizeStrings.Get(13209); + std::string strTime; + if( CGUIDialogNumeric::ShowAndGetNumber(strTime, strHeading) ) + seconds = static_cast<float>(atoi(strTime.c_str())*60); + else + return false; + } + bool silent = false; + bool loop = false; + for (unsigned int i = 3; i < params.size() ; i++) + { + // check "true" for backward comp + if (StringUtils::EqualsNoCase(params[i], "true") || StringUtils::EqualsNoCase(params[i], "silent")) + silent = true; + else if (StringUtils::EqualsNoCase(params[i], "loop")) + loop = true; + } + + if( g_alarmClock.IsRunning() ) + g_alarmClock.Stop(params[0],silent); + // no negative times not allowed, loop must have a positive time + if (seconds < 0 || (seconds == 0 && loop)) + return false; + g_alarmClock.Start(params[0], seconds, params[1], silent, loop); + + return 0; +} + +/*! \brief Cancel an alarm clock. + * \param params The parameters. + * \details params[0] = "true" to silently cancel alarm (optional). + */ +static int CancelAlarm(const std::vector<std::string>& params) +{ + bool silent = (params.size() > 1 && + (StringUtils::EqualsNoCase(params[1], "true") || + StringUtils::EqualsNoCase(params[1], "silent"))); + g_alarmClock.Stop(params[0],silent); + + return 0; +} + +/*! \brief Clear a property in a window. + * \param params The parameters. + * \details params[0] = The property to clear. + * params[1] = The window to clear property in (optional). + */ +static int ClearProperty(const std::vector<std::string>& params) +{ + CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(params.size() > 1 ? CWindowTranslator::TranslateWindow(params[1]) : CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + if (window) + window->SetProperty(params[0],""); + + return 0; +} + +/*! \brief Close a dialog. + * \param params The parameters. + * \details params[0] = "all" to close all dialogs, or dialog name. + * params[1] = "true" to force close (skip animations) (optional). + */ +static int CloseDialog(const std::vector<std::string>& params) +{ + bool bForce = false; + if (params.size() > 1 && StringUtils::EqualsNoCase(params[1], "true")) + bForce = true; + if (StringUtils::EqualsNoCase(params[0], "all")) + { + CServiceBroker::GetGUI()->GetWindowManager().CloseDialogs(bForce); + } + else + { + int id = CWindowTranslator::TranslateWindow(params[0]); + CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(id); + if (window && window->IsDialog()) + static_cast<CGUIDialog*>(window)->Close(bForce); + } + + return 0; +} + +/*! \brief Send a notification. + * \param params The parameters. + * \details params[0] = Notification title. + * params[1] = Notification text. + * params[2] = Display time in milliseconds (optional). + * params[3] = Notification icon (optional). + */ +static int Notification(const std::vector<std::string>& params) +{ + if (params.size() < 2) + return -1; + if (params.size() == 4) + CGUIDialogKaiToast::QueueNotification(params[3],params[0],params[1],atoi(params[2].c_str())); + else if (params.size() == 3) + CGUIDialogKaiToast::QueueNotification("",params[0],params[1],atoi(params[2].c_str())); + else + CGUIDialogKaiToast::QueueNotification(params[0],params[1]); + + return 0; +} + +/*! \brief Refresh RSS feed. + * \param params (ignored) + */ +static int RefreshRSS(const std::vector<std::string>& params) +{ + CRssManager::GetInstance().Reload(); + + return 0; +} + +/*! \brief Take a screenshot. + * \param params The parameters. + * \details params[0] = URL to save file to. Blank to use default. + * params[1] = "sync" to run synchronously (optional). + */ +static int Screenshot(const std::vector<std::string>& params) +{ + if (!params.empty()) + { + // get the parameters + std::string strSaveToPath = params[0]; + bool sync = false; + if (params.size() >= 2) + sync = StringUtils::EqualsNoCase(params[1], "sync"); + + if (!strSaveToPath.empty()) + { + if (XFILE::CDirectory::Exists(strSaveToPath)) + { + std::string file = CUtil::GetNextFilename( + URIUtils::AddFileToFolder(strSaveToPath, "screenshot{:05}.png"), 65535); + + if (!file.empty()) + { + CScreenShot::TakeScreenshot(file, sync); + } + else + { + CLog::Log(LOGWARNING, "Too many screen shots or invalid folder {}", strSaveToPath); + } + } + else + CScreenShot::TakeScreenshot(strSaveToPath, sync); + } + } + else + CScreenShot::TakeScreenshot(); + + return 0; +} + +/*! \brief Set GUI language. + * \param params The parameters. + * \details params[0] = The language to use. + */ +static int SetLanguage(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SETLANGUAGE, -1, -1, nullptr, params[0]); + + return 0; +} + +/*! \brief Set a property in a window. + * \param params The parameters. + * \details params[0] = The property to set. + * params[1] = The property value. + * params[2] = The window to set property in (optional). + */ +static int SetProperty(const std::vector<std::string>& params) +{ + CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(params.size() > 2 ? CWindowTranslator::TranslateWindow(params[2]) : CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + if (window) + window->SetProperty(params[0],params[1]); + + return 0; +} + +/*! \brief Set GUI stereo mode. + * \param params The parameters. + * \details param[0] = Stereo mode identifier. + */ +static int SetStereoMode(const std::vector<std::string>& params) +{ + CAction action = CStereoscopicsManager::ConvertActionCommandToAction("SetStereoMode", params[0]); + if (action.GetID() != ACTION_NONE) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(action))); + else + { + CLog::Log(LOGERROR, "Builtin 'SetStereoMode' called with unknown parameter: {}", params[0]); + return -2; + } + + return 0; +} + +/*! \brief Toggle visualization of dirty regions. + * \param params Ignored. + */ +static int ToggleDirty(const std::vector<std::string>&) +{ + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->ToggleDirtyRegionVisualization(); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_5 GUI built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`Action(action[\,window])`</b> +/// , +/// Executes an action (same as in keymap) for the given window or the +/// active window if the parameter window is omitted. The parameter window +/// can either be the window's id\, or in the case of a standard window\, the +/// window's name. See here for a list of window names\, and their respective +/// ids. +/// @param[in] action Action to execute. +/// @param[in] window Window to send action to (optional). +/// } +/// \table_row2_l{ +/// <b>`CancelAlarm(name[\,silent])`</b> +/// , +/// Cancel a running alarm. Set silent to true to hide the alarm notification. +/// @param[in] silent Send "true" or "silent" to silently cancel alarm (optional). +/// } +/// \table_row2_l{ +/// <b>`AlarmClock(name\,command[\,time\,silent\,loop])`</b> +/// , +/// Pops up a dialog asking for the length of time for the alarm (unless the +/// parameter time is specified)\, and starts a timer. When the timer runs out\, +/// it'll execute the built-in command (the parameter command) if it is +/// specified\, otherwise it'll pop up an alarm notice. Add silent to hide the +/// alarm notification. Add loop for the alarm to execute the command each +/// time the specified time interval expires. +/// @note if using any of the last optional parameters (silent or loop)\, both must +/// be provided for any to take effect. +/// <p> +/// <b>Example:</b> +/// The following example will create an alarmclock named `mytimer` which will silently +/// fire (a single time) and set a property (timerelapsed) with value 1 in the window with +/// id 1109 after 5 seconds have passed. +/// ~~~~~~~~~~~~~ +/// AlarmClock(mytimer\,SetProperty(timerelapsed\,1\,1109)\,00:00:05\,silent\,false]) +/// ~~~~~~~~~~~~~ +/// <p> +/// @param[in] name name +/// @param[in] command command +/// @param[in] time [opt] <b>(a)</b> Length in minutes or <b>(b)</b> a timestring in the format `hh:mm:ss` or `mm min`. +/// @param[in] silent [opt] Send "silent" to suppress notifications. +/// @param[in] loop [opt] Send "loop" to loop the alarm. +/// } +/// \table_row2_l{ +/// <b>`ActivateWindow(window[\,dir\, return])`</b> +/// , +/// Opens the given window. The parameter window can either be the window's id\, +/// or in the case of a standard window\, the window's name. See \ref window_ids "here" for a list +/// of window names\, and their respective ids. +/// If\, furthermore\, the window is +/// Music\, Video\, Pictures\, or Program files\, then the optional dir parameter +/// specifies which folder Kodi should default to once the window is opened. +/// This must be a source as specified in sources.xml\, or a subfolder of a +/// valid source. For some windows (MusicLibrary and VideoLibrary)\, a third +/// parameter (return) may be specified\, which indicates that Kodi should use this +/// folder as the "root" of the level\, and thus the "parent directory" action +/// from within this folder will return the user to where they were prior to +/// the window activating. +/// @param[in] window The window name. +/// @param[in] dir Window starting folder (optional). +/// @param[in] return if dir should be used as the rootfolder of the level +/// } +/// \table_row2_l{ +/// <b>`ActivateWindowAndFocus(id1\, id2\,item1\, id3\,item2)`</b> +/// , +/// Activate window with id1\, first focus control id2 and then focus control +/// id3. if either of the controls is a container\, you can specify which +/// item to focus (else\, set it to 0). +/// @param[in] id1 The window name. +/// @param[in] params[1\,...] Pair of (container ID\, focus item). +/// } +/// \table_row2_l{ +/// <b>`ClearProperty(key[\,id])`</b> +/// , +/// Clears a window property for the current focused window/dialog(key)\, or +/// the specified window (key\,id). +/// @param[in] key The property to clear. +/// @param[in] id The window to clear property in (optional). +/// } +/// \table_row2_l{ +/// <b>`Dialog.Close(dialog[\,force])`</b> +/// , +/// Close a dialog. Set force to true to bypass animations. Use (all\,true) +/// to close all opened dialogs at once. +/// @param[in] dialog Send "all" to close all dialogs\, or dialog name. +/// @param[in] force Send "true" to force close (skip animations) (optional). +/// } +/// \table_row2_l{ +/// <b>`Notification(header\,message[\,time\,image])`</b> +/// , +/// Will display a notification dialog with the specified header and message\, +/// in addition you can set the length of time it displays in milliseconds +/// and a icon image. +/// @param[in] header Notification title. +/// @param[in] message Notification text. +/// @param[in] time Display time in milliseconds (optional). +/// @param[in] image Notification icon (optional). +/// } +/// \table_row2_l{ +/// <b>`RefreshRSS`</b> +/// , +/// Reload RSS feeds from RSSFeeds.xml +/// } +/// \table_row2_l{ +/// <b>`ReplaceWindow(window\,dir)`</b> +/// , +/// Replaces the current window with the given window. This is the same as +/// ActivateWindow() but it doesn't update the window history list\, so when +/// you go back from the new window it will not return to the previous +/// window\, rather will return to the previous window's previous window. +/// @param[in] window The window name. +/// @param[in] dir Window starting folder (optional). +/// } +/// \table_row2_l{ +/// <b>`ReplaceWindowAndFocus(id1\, id2\,item1\, id3\,item2)`</b> +/// , +/// Replace window with id1\, first focus control id2 and then focus control +/// id3. if either of the controls is a container\, you can specify which +/// item to focus (else\, set it to 0). +/// @param[in] id1 The window name. +/// @param[in] params[1\,...] Pair of (container ID\, focus item). +/// } +/// \table_row2_l{ +/// <b>`Resolution(resIdent)`</b> +/// , +/// Change Kodi's Resolution (default is 4x3). +/// param[in] resIdent A resolution identifier. +/// | | Identifiers | | +/// |:--------:|:-----------:|:--------:| +/// | pal | pal16x9 | ntsc | +/// | ntsc16x9 | 720p | 720psbs | +/// | 720ptb | 1080psbs | 1080ptb | +/// | 1080i | | | +/// } +/// \table_row2_l{ +/// <b>`SetGUILanguage(lang)`</b> +/// , +/// Set GUI Language +/// @param[in] lang The language to use. +/// } +/// \table_row2_l{ +/// <b>`SetProperty(key\,value[\,id])`</b> +/// , +/// Sets a window property for the current window (key\,value)\, or the +/// specified window (key\,value\,id). +/// @param[in] key The property to set. +/// @param[in] value The property value. +/// @param[in] id The window to set property in (optional). +/// } +/// \table_row2_l{ +/// <b>`SetStereoMode(ident)`</b> +/// , +/// Changes the stereo mode of the GUI. +/// Params can be: +/// toggle\, next\, previous\, select\, tomono or any of the supported stereomodes (off\, +/// split_vertical\, split_horizontal\, row_interleaved\, hardware_based\, anaglyph_cyan_red\, anaglyph_green_magenta\, monoscopic) +/// @param[in] ident Stereo mode identifier. +/// } +/// \table_row2_l{ +/// <b>`TakeScreenshot(url[\,sync)`</b> +/// , +/// Takes a Screenshot +/// @param[in] url URL to save file to. Blank to use default. +/// @param[in] sync Add "sync" to run synchronously (optional). +/// } +/// \table_row2_l{ +/// <b>`ToggleDirtyRegionVisualization`</b> +/// , +/// makes dirty regions visible for debugging proposes. +/// } +/// \table_end +/// + +CBuiltins::CommandMap CGUIBuiltins::GetOperations() const +{ + return { + {"action", {"Executes an action for the active window (same as in keymap)", 1, Action}}, + {"cancelalarm", {"Cancels an alarm", 1, CancelAlarm}}, + {"alarmclock", {"Prompt for a length of time and start an alarm clock", 2, AlarmClock}}, + {"activatewindow", {"Activate the specified window", 1, ActivateWindow<false>}}, + {"activatewindowandfocus", {"Activate the specified window and sets focus to the specified id", 1, ActivateAndFocus<false>}}, + {"clearproperty", {"Clears a window property for the current focused window/dialog (key,value)", 1, ClearProperty}}, + {"dialog.close", {"Close a dialog", 1, CloseDialog}}, + {"notification", {"Shows a notification on screen, specify header, then message, and optionally time in milliseconds and a icon.", 2, Notification}}, + {"refreshrss", {"Reload RSS feeds from RSSFeeds.xml", 0, RefreshRSS}}, + {"replacewindow", {"Replaces the current window with the new one", 1, ActivateWindow<true>}}, + {"replacewindowandfocus", {"Replaces the current window with the new one and sets focus to the specified id", 1, ActivateAndFocus<true>}}, + {"setguilanguage", {"Set GUI Language", 1, SetLanguage}}, + {"setproperty", {"Sets a window property for the current focused window/dialog (key,value)", 2, SetProperty}}, + {"setstereomode", {"Changes the stereo mode of the GUI. Params can be: toggle, next, previous, select, tomono or any of the supported stereomodes (off, split_vertical, split_horizontal, row_interleaved, hardware_based, anaglyph_cyan_red, anaglyph_green_magenta, anaglyph_yellow_blue, monoscopic)", 1, SetStereoMode}}, + {"takescreenshot", {"Takes a Screenshot", 0, Screenshot}}, + {"toggledirtyregionvisualization", {"Enables/disables dirty-region visualization", 0, ToggleDirty}} + }; +} diff --git a/xbmc/interfaces/builtins/GUIBuiltins.h b/xbmc/interfaces/builtins/GUIBuiltins.h new file mode 100644 index 0000000..fd8f238 --- /dev/null +++ b/xbmc/interfaces/builtins/GUIBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing GUI related built-in commands. +class CGUIBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/GUIContainerBuiltins.cpp b/xbmc/interfaces/builtins/GUIContainerBuiltins.cpp new file mode 100644 index 0000000..a97565c --- /dev/null +++ b/xbmc/interfaces/builtins/GUIContainerBuiltins.cpp @@ -0,0 +1,189 @@ +/* + * 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 "GUIContainerBuiltins.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "utils/StringUtils.h" + +/*! \brief Change sort method. + * \param params (ignored) + * + * Set the Dir template parameter to 1 to switch to next sort method + * or -1 to switch to previous sort method. + */ + template<int Dir> +static int ChangeSortMethod(const std::vector<std::string>& params) +{ + CGUIMessage message(GUI_MSG_CHANGE_SORT_METHOD, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, 0, Dir); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +/*! \brief Change view mode. + * \param params (ignored) + * + * Set the Dir template parameter to 1 to switch to next view mode + * or -1 to switch to previous view mode. + */ + template<int Dir> +static int ChangeViewMode(const std::vector<std::string>& params) +{ + CGUIMessage message(GUI_MSG_CHANGE_VIEW_MODE, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, 0, Dir); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +/*! \brief Refresh a media window. + * \param params The parameters. + * \details params[0] = The URL to refresh window at. + */ +static int Refresh(const std::vector<std::string>& params) +{ // NOTE: These messages require a media window, thus they're sent to the current activewindow. + // This shouldn't stop a dialog intercepting it though. + CGUIMessage message(GUI_MSG_NOTIFY_ALL, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, GUI_MSG_UPDATE, 1); // 1 to reset the history + message.SetStringParam(!params.empty() ? params[0] : ""); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +/*! \brief Set sort method. + * \param params The parameters. + * \details params[0] = ID of sort method. + */ +static int SetSortMethod(const std::vector<std::string>& params) +{ + CGUIMessage message(GUI_MSG_CHANGE_SORT_METHOD, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, atoi(params[0].c_str())); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +/*! \brief Set view mode. + * \param params The parameters. + * \details params[0] = ID of view mode. + */ +static int SetViewMode(const std::vector<std::string>& params) +{ + CGUIMessage message(GUI_MSG_CHANGE_VIEW_MODE, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, atoi(params[0].c_str())); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +/*! \brief Toggle sort direction. + * \param params (ignored) + */ +static int ToggleSortDirection(const std::vector<std::string>& params) +{ + CGUIMessage message(GUI_MSG_CHANGE_SORT_DIRECTION, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +/*! \brief Update a listing in a media window. + * \param params The parameters. + * \details params[0] = The URL to update listing at. + * params[1] = "replace" to reset history (optional). + */ +static int Update(const std::vector<std::string>& params) +{ + CGUIMessage message(GUI_MSG_NOTIFY_ALL, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, GUI_MSG_UPDATE, 0); + message.SetStringParam(params[0]); + if (params.size() > 1 && StringUtils::EqualsNoCase(params[1], "replace")) + message.SetParam2(1); // reset the history + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_6 GUI container built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`Container.NextSortMethod`</b> +/// , +/// Change to the next sort method. +/// } +/// \table_row2_l{ +/// <b>`Container.NextViewMode`</b> +/// , +/// Select the next view mode. +/// } +/// \table_row2_l{ +/// <b>`Container.PreviousSortMethod`</b> +/// , +/// Change to the previous sort method. +/// } +/// \table_row2_l{ +/// <b>`Container.PreviousViewMode`</b> +/// , +/// Select the previous view mode. +/// } +/// \table_row2_l{ +/// <b>`Container.Refresh(url)`</b> +/// , +/// Refresh current listing +/// @param[in] url The URL to refresh window at. +/// } +/// \table_row2_l{ +/// <b>`Container.SetSortMethod(id)`</b> +/// , +/// Change to the specified sort method. (For list of ID's \ref SortBy "see List" of sort methods below) +/// @param[in] id ID of sort method. +/// } +/// \table_row2_l{ +/// <b>`Container.SetViewMode(id)`</b> +/// , +/// Set the current view mode (list\, icons etc.) to the given container id. +/// @param[in] id ID of view mode. +/// } +/// \table_row2_l{ +/// <b>`Container.SortDirection`</b> +/// , +/// Toggle the sort direction +/// } +/// \table_row2_l{ +/// <b>`Container.Update(url\,[replace])`</b> +/// , +/// Update current listing. Send `Container.Update(path\,replace)` to reset the path history. +/// @param[in] url The URL to update listing at. +/// @param[in] replace "replace" to reset history (optional). +/// } +/// \table_end +/// + +CBuiltins::CommandMap CGUIContainerBuiltins::GetOperations() const +{ + return { + {"container.nextsortmethod", {"Change to the next sort method", 0, ChangeSortMethod<1>}}, + {"container.nextviewmode", {"Move to the next view type (and refresh the listing)", 0, ChangeViewMode<1>}}, + {"container.previoussortmethod", {"Change to the previous sort method", 0, ChangeSortMethod<-1>}}, + {"container.previousviewmode", {"Move to the previous view type (and refresh the listing)", 0, ChangeViewMode<-1>}}, + {"container.refresh", {"Refresh current listing", 0, Refresh}}, + {"container.setsortdirection", {"Toggle the sort direction", 0, ToggleSortDirection}}, + {"container.setsortmethod", {"Change to the specified sort method", 1, SetSortMethod}}, + {"container.setviewmode", {"Move to the view with the given id", 1, SetViewMode}}, + {"container.update", {"Update current listing. Send Container.Update(path,replace) to reset the path history", 1, Update}} + }; +} diff --git a/xbmc/interfaces/builtins/GUIContainerBuiltins.h b/xbmc/interfaces/builtins/GUIContainerBuiltins.h new file mode 100644 index 0000000..906afa1 --- /dev/null +++ b/xbmc/interfaces/builtins/GUIContainerBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing GUI container related built-in commands. +class CGUIContainerBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/GUIControlBuiltins.cpp b/xbmc/interfaces/builtins/GUIControlBuiltins.cpp new file mode 100644 index 0000000..d8d89fa --- /dev/null +++ b/xbmc/interfaces/builtins/GUIControlBuiltins.cpp @@ -0,0 +1,238 @@ +/* + * 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 "GUIControlBuiltins.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/WindowTranslator.h" +#include "utils/StringUtils.h" + +/*! \brief Send a move event to a GUI control. + * \param params The parameters. + * \details params[0] = ID of control. + * params[1] = Offset of move. + */ +static int ControlMove(const std::vector<std::string>& params) +{ + CGUIMessage message(GUI_MSG_MOVE_OFFSET, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), + atoi(params[0].c_str()), atoi(params[1].c_str())); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +/*! \brief Send a click event to a GUI control. + * \param params The parameters. + * \details params[0] = ID of control. + * params[1] = ID for window with control (optional). + */ +static int SendClick(const std::vector<std::string>& params) +{ + if (params.size() == 2) + { + // have a window - convert it + int windowID = CWindowTranslator::TranslateWindow(params[0]); + CGUIMessage message(GUI_MSG_CLICKED, atoi(params[1].c_str()), windowID); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + } + else + { // single param - assume you meant the focused window + CGUIMessage message(GUI_MSG_CLICKED, atoi(params[0].c_str()), CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + } + + return 0; +} + +/*! \brief Send a message to a control. + * \param params The parameters. + * \details params[0] = ID of control. + * params[1] = Action name. + * \params[2] = ID of window with control (optional). + */ +static int SendMessage(const std::vector<std::string>& params) +{ + int controlID = atoi(params[0].c_str()); + int windowID = (params.size() == 3) ? CWindowTranslator::TranslateWindow(params[2]) : CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + if (params[1] == "moveup") + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_MOVE_OFFSET, windowID, controlID, 1); + else if (params[1] == "movedown") + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_MOVE_OFFSET, windowID, controlID, -1); + else if (params[1] == "pageup") + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_PAGE_UP, windowID, controlID); + else if (params[1] == "pagedown") + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_PAGE_DOWN, windowID, controlID); + else if (params[1] == "click") + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_CLICKED, controlID, windowID); + + return 0; +} + +/*! \brief Give a control focus. + * \param params The parameters. + * \details params[0] = ID of control. + * params[1] = ID of subitem of control (optional). + * params[2] = "absolute" to focus the absolute position instead of the relative one (optional). + */ +static int SetFocus(const std::vector<std::string>& params) +{ + int controlID = atol(params[0].c_str()); + int subItem = (params.size() > 1) ? atol(params[1].c_str())+1 : 0; + int absID = 0; + if (params.size() > 2 && StringUtils::EqualsNoCase(params[2].c_str(), "absolute")) + absID = 1; + CGUIMessage msg(GUI_MSG_SETFOCUS, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), controlID, subItem, absID); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + return 0; +} + +/*! \brief Set a control to visible. + * \param params The parameters. + * \details params[0] = ID of control. + */ +static int SetVisible(const std::vector<std::string>& params) +{ + int controlID = std::stol(params[0]); + CGUIMessage msg{GUI_MSG_VISIBLE, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), + controlID}; + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + return 0; +} + +/*! \brief Set a control to hidden. + * \param params The parameters. + * \details params[0] = ID of control. + */ +static int SetHidden(const std::vector<std::string>& params) +{ + int controlID = std::stol(params[0]); + CGUIMessage msg{GUI_MSG_HIDDEN, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), + controlID}; + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + return 0; +} + +/*! \brief Shift page in a control. + * \param params The parameters. + * \details params[0] = ID of control + * + * Set Message template parameter to GUI_MSG_PAGE_DOWN/GUI_MSG_PAGE_UP. + */ + template<int Message> +static int ShiftPage(const std::vector<std::string>& params) +{ + int id = atoi(params[0].c_str()); + CGUIMessage message(Message, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), id); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_7 GUI control built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`control.message(controlId\, action[\, windowId])`</b> +/// , +/// Send a given message to a control within a given window +/// @param[in] controlId ID of control. +/// @param[in] action Action name. +/// @param[in] windowId ID of window with control (optional). +/// } +/// \table_row2_l{ +/// <b>`control.move(id\, offset)`</b> +/// , +/// Tells the specified control to 'move' to another entry specified by offset +/// @param[in] id ID of control. +/// @param[in] offset Offset of move. +/// } +/// \table_row2_l{ +/// <b>`control.setfocus(controlId[\, subitemId])`</b> +/// , +/// Change current focus to a different control id +/// @param[in] controlId ID of control. +/// @param[in] subitemId ID of subitem of control (optional). +/// @param[in] absolute "absolute" to focus the absolute position instead of the relative one (optional). +/// } +/// \table_row2_l{ +/// <b>`control.setvisible(controlId)`</b> +/// \anchor Builtin_SetVisible, +/// Set the control id to visible +/// @param[in] controlId ID of control. +/// <p><hr> +/// @skinning_v20 **[New builtin]** \link Builtin_SetVisible `SetVisible(id)`\endlink +/// <p> +/// } +/// \table_row2_l{ +/// <b>`control.sethidden(controlId)`</b> +/// \anchor Builtin_SetHidden, +/// Set the control id to hidden +/// @param[in] controlId ID of control. +/// <p><hr> +/// @skinning_v20 **[New builtin]** \link Builtin_SetHidden `SetHidden(id)`\endlink +/// <p> +/// } +/// \table_row2_l{ +/// <b>`pagedown(controlId)`</b> +/// , +/// Send a page down event to the pagecontrol with given id +/// @param[in] controlId ID of control. +/// } +/// \table_row2_l{ +/// <b>`pageup(controlId)`</b> +/// , +/// Send a page up event to the pagecontrol with given id +/// @param[in] controlId ID of control. +/// } +/// \table_row2_l{ +/// <b>`sendclick(controlId [\, windowId])`</b> +/// , +/// Send a click message from the given control to the given window +/// @param[in] controlId ID of control. +/// @param[in] windowId ID for window with control (optional). +/// } +/// \table_row2_l{ +/// <b>`setfocus`</b> +/// , +/// Change current focus to a different control id +/// @param[in] controlId ID of control. +/// @param[in] subitemId ID of subitem of control (optional). +/// @param[in] absolute "absolute" to focus the absolute position instead of the relative one (optional). +/// } +/// \table_end +/// + +CBuiltins::CommandMap CGUIControlBuiltins::GetOperations() const +{ + return { + {"control.message", {"Send a given message to a control within a given window", 2, SendMessage}}, + {"control.move", {"Tells the specified control to 'move' to another entry specified by offset", 2, ControlMove}}, + {"control.setfocus", {"Change current focus to a different control id", 1, SetFocus}}, + {"control.setvisible", {"Set the control id to visible", 1, SetVisible}}, + {"control.sethidden", {"Set the control id to Hidden", 1, SetHidden}}, + {"pagedown", {"Send a page down event to the pagecontrol with given id", 1, ShiftPage<GUI_MSG_PAGE_DOWN>}}, + {"pageup", {"Send a page up event to the pagecontrol with given id", 1, ShiftPage<GUI_MSG_PAGE_UP>}}, + {"sendclick", {"Send a click message from the given control to the given window", 1, SendClick}}, + {"setfocus", {"Change current focus to a different control id", 1, SetFocus}}, + }; +} diff --git a/xbmc/interfaces/builtins/GUIControlBuiltins.h b/xbmc/interfaces/builtins/GUIControlBuiltins.h new file mode 100644 index 0000000..70831d6 --- /dev/null +++ b/xbmc/interfaces/builtins/GUIControlBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing GUI control related built-in commands. +class CGUIControlBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/LibraryBuiltins.cpp b/xbmc/interfaces/builtins/LibraryBuiltins.cpp new file mode 100644 index 0000000..5cb678a --- /dev/null +++ b/xbmc/interfaces/builtins/LibraryBuiltins.cpp @@ -0,0 +1,416 @@ +/* + * 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 "LibraryBuiltins.h" + +#include "GUIUserMessages.h" +#include "MediaSource.h" +#include "ServiceBroker.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/helpers/DialogHelper.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicLibraryQueue.h" +#include "music/infoscanner/MusicInfoScanner.h" +#include "settings/LibExportSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoLibraryQueue.h" + +using namespace KODI::MESSAGING; + +/*! \brief Clean a library. + * \param params The parameters. + * \details params[0] = "video" or "music". + */ +static int CleanLibrary(const std::vector<std::string>& params) +{ + bool userInitiated = true; + if (params.size() > 1) + userInitiated = StringUtils::EqualsNoCase(params[1], "true"); + if (!params.size() || StringUtils::EqualsNoCase(params[0], "video") + || StringUtils::EqualsNoCase(params[0], "movies") + || StringUtils::EqualsNoCase(params[0], "tvshows") + || StringUtils::EqualsNoCase(params[0], "musicvideos")) + { + if (!CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + { + if (userInitiated && CVideoLibraryQueue::GetInstance().IsRunning()) + HELPERS::ShowOKDialogText(CVariant{700}, CVariant{703}); + else + { + const std::string content = (params.empty() || params[0] == "video") ? "" : params[0]; + const std::string directory = params.size() > 2 ? params[2] : ""; + + std::set<int> paths; + if (!content.empty() || !directory.empty()) + { + CVideoDatabase db; + std::set<std::string> contentPaths; + if (db.Open()) + { + if (!directory.empty()) + contentPaths.insert(directory); + else + db.GetPaths(contentPaths); + for (const std::string& path : contentPaths) + { + if (db.GetContentForPath(path) == content) + { + paths.insert(db.GetPathId(path)); + std::vector<std::pair<int, std::string>> sub; + if (db.GetSubPaths(path, sub)) + { + for (const auto& it : sub) + paths.insert(it.first); + } + } + } + } + if (paths.empty()) + return 0; + } + + if (userInitiated) + CVideoLibraryQueue::GetInstance().CleanLibraryModal(paths); + else + CVideoLibraryQueue::GetInstance().CleanLibrary(paths, true); + } + } + else + CLog::Log(LOGERROR, "CleanLibrary is not possible while scanning or cleaning"); + } + else if (StringUtils::EqualsNoCase(params[0], "music")) + { + if (!CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + { + if (!(userInitiated && CMusicLibraryQueue::GetInstance().IsRunning())) + CMusicLibraryQueue::GetInstance().CleanLibrary(userInitiated); + } + else + CLog::Log(LOGERROR, "CleanLibrary is not possible while scanning for media info"); + } + else + CLog::Log(LOGERROR, "Unknown content type '{}' passed to CleanLibrary, ignoring", params[0]); + + return 0; +} + +/*! \brief Export a library. + * \param params The parameters. + * \details params[0] = "video" or "music". + * params[1] = "true" to export to separate files (optional). + * params[2] = "true" to export thumbs (optional) or the file path for export to singlefile. + * params[3] = "true" to overwrite existing files (optional). + * params[4] = "true" to export actor thumbs (optional). + */ +static int ExportLibrary(const std::vector<std::string>& params) +{ + int iHeading = 647; + if (StringUtils::EqualsNoCase(params[0], "music")) + iHeading = 20196; + std::string path; + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + CServiceBroker::GetMediaManager().GetNetworkLocations(shares); + CServiceBroker::GetMediaManager().GetRemovableDrives(shares); + bool singleFile; + bool thumbs=false; + bool actorThumbs=false; + bool overwrite=false; + bool cancelled=false; + + if (params.size() > 1) + singleFile = StringUtils::EqualsNoCase(params[1], "false"); + else + { + HELPERS::DialogResponse result = HELPERS::ShowYesNoDialogText(CVariant{iHeading}, CVariant{20426}, CVariant{20428}, CVariant{20429}); + cancelled = result == HELPERS::DialogResponse::CHOICE_CANCELLED; + singleFile = result != HELPERS::DialogResponse::CHOICE_YES; + } + + if (cancelled) + return -1; + + if (!singleFile) + { + if (params.size() > 2) + thumbs = StringUtils::EqualsNoCase(params[2], "true"); + else + { + HELPERS::DialogResponse result = HELPERS::ShowYesNoDialogText(CVariant{iHeading}, CVariant{20430}); + cancelled = result == HELPERS::DialogResponse::CHOICE_CANCELLED; + thumbs = result == HELPERS::DialogResponse::CHOICE_YES; + } + } + + if (cancelled) + return -1; + + if (thumbs && !singleFile && StringUtils::EqualsNoCase(params[0], "video")) + { + std::string movieSetsInfoPath = CServiceBroker::GetSettingsComponent()->GetSettings()-> + GetString(CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER); + if (movieSetsInfoPath.empty()) + { + auto result = HELPERS::ShowYesNoDialogText(CVariant{iHeading}, CVariant{36301}); + cancelled = result != HELPERS::DialogResponse::CHOICE_YES; + } + } + + if (cancelled) + return -1; + + if (thumbs && StringUtils::EqualsNoCase(params[0], "video")) + { + if (params.size() > 4) + actorThumbs = StringUtils::EqualsNoCase(params[4], "true"); + else + { + HELPERS::DialogResponse result = HELPERS::ShowYesNoDialogText(CVariant{iHeading}, CVariant{20436}); + cancelled = result == HELPERS::DialogResponse::CHOICE_CANCELLED; + actorThumbs = result == HELPERS::DialogResponse::CHOICE_YES; + } + } + + if (cancelled) + return -1; + + if (!singleFile) + { + if (params.size() > 3) + overwrite = StringUtils::EqualsNoCase(params[3], "true"); + else + { + HELPERS::DialogResponse result = HELPERS::ShowYesNoDialogText(CVariant{iHeading}, CVariant{20431}); + cancelled = result == HELPERS::DialogResponse::CHOICE_CANCELLED; + overwrite = result == HELPERS::DialogResponse::CHOICE_YES; + } + } + + if (cancelled) + return -1; + + if (params.size() > 2) + path=params[2]; + if (!singleFile || !path.empty() || + CGUIDialogFileBrowser::ShowAndGetDirectory(shares, g_localizeStrings.Get(661), + path, true)) + { + if (StringUtils::EqualsNoCase(params[0], "video")) + { + CVideoDatabase videodatabase; + videodatabase.Open(); + videodatabase.ExportToXML(path, singleFile, thumbs, actorThumbs, overwrite); + videodatabase.Close(); + } + else + { + CLibExportSettings settings; + // ELIBEXPORT_SINGLEFILE, ELIBEXPORT_ALBUMS + ELIBEXPORT_ALBUMARTISTS by default + settings.m_strPath = path; + if (!singleFile) + settings.SetExportType(ELIBEXPORT_TOLIBRARYFOLDER); + settings.m_artwork = thumbs; + settings.m_overwrite = overwrite; + // Export music library (not showing progress dialog) + CMusicLibraryQueue::GetInstance().ExportLibrary(settings, false); + } + } + + return 0; +} + +/*! \brief Export a library with extended parameters +Avoiding breaking change to original ExportLibrary routine parameters +* \param params The parameters. +* \details params[0] = "video" or "music". +* params[1] = export type "singlefile", "separate", or "library". +* params[2] = path of destination folder. +* params[3,...] = "unscraped" to include unscraped items +* params[3,...] = "overwrite" to overwrite existing files. +* params[3,...] = "artwork" to include images such as thumbs and fanart. +* params[3,...] = "skipnfo" to not include nfo files (just art). +* params[3,...] = "ablums" to include albums. +* params[3,...] = "albumartists" to include album artists. +* params[3,...] = "songartists" to include song artists. +* params[3,...] = "otherartists" to include other artists. +*/ +static int ExportLibrary2(const std::vector<std::string>& params) +{ + CLibExportSettings settings; + if (params.size() < 3) + return -1; + settings.m_strPath = params[2]; + settings.SetExportType(ELIBEXPORT_SINGLEFILE); + if (StringUtils::EqualsNoCase(params[1], "separate")) + settings.SetExportType(ELIBEXPORT_SEPARATEFILES); + else if (StringUtils::EqualsNoCase(params[1], "library")) + { + settings.SetExportType(ELIBEXPORT_TOLIBRARYFOLDER); + settings.m_strPath.clear(); + } + settings.ClearItems(); + + for (unsigned int i = 2; i < params.size(); i++) + { + if (StringUtils::EqualsNoCase(params[i], "artwork")) + settings.m_artwork = true; + else if (StringUtils::EqualsNoCase(params[i], "overwrite")) + settings.m_overwrite = true; + else if (StringUtils::EqualsNoCase(params[i], "unscraped")) + settings.m_unscraped = true; + else if (StringUtils::EqualsNoCase(params[i], "skipnfo")) + settings.m_skipnfo = true; + else if (StringUtils::EqualsNoCase(params[i], "albums")) + settings.AddItem(ELIBEXPORT_ALBUMS); + else if (StringUtils::EqualsNoCase(params[i], "albumartists")) + settings.AddItem(ELIBEXPORT_ALBUMARTISTS); + else if (StringUtils::EqualsNoCase(params[i], "songartists")) + settings.AddItem(ELIBEXPORT_SONGARTISTS); + else if (StringUtils::EqualsNoCase(params[i], "otherartists")) + settings.AddItem(ELIBEXPORT_OTHERARTISTS); + else if (StringUtils::EqualsNoCase(params[i], "actorthumbs")) + settings.AddItem(ELIBEXPORT_ACTORTHUMBS); + } + if (StringUtils::EqualsNoCase(params[0], "music")) + { + // Export music library (not showing progress dialog) + CMusicLibraryQueue::GetInstance().ExportLibrary(settings, false); + } + else + { + CVideoDatabase videodatabase; + videodatabase.Open(); + videodatabase.ExportToXML(settings.m_strPath, settings.IsSingleFile(), + settings.m_artwork, settings.IsItemExported(ELIBEXPORT_ACTORTHUMBS), settings.m_overwrite); + videodatabase.Close(); + } + return 0; +} + + +/*! \brief Update a library. + * \param params The parameters. + * \details params[0] = "video" or "music". + * params[1] = "true" to suppress dialogs (optional). + */ +static int UpdateLibrary(const std::vector<std::string>& params) +{ + bool userInitiated = true; + if (params.size() > 2) + userInitiated = StringUtils::EqualsNoCase(params[2], "true"); + if (StringUtils::EqualsNoCase(params[0], "music")) + { + if (CMusicLibraryQueue::GetInstance().IsScanningLibrary()) + CMusicLibraryQueue::GetInstance().StopLibraryScanning(); + else + CMusicLibraryQueue::GetInstance().ScanLibrary(params.size() > 1 ? params[1] : "", + MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL, + userInitiated); + } + else if (StringUtils::EqualsNoCase(params[0], "video")) + { + if (CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + CVideoLibraryQueue::GetInstance().StopLibraryScanning(); + else + CVideoLibraryQueue::GetInstance().ScanLibrary(params.size() > 1 ? params[1] : "", false, + userInitiated); + } + + return 0; +} + +/*! \brief Open a video library search. + * \param params (ignored) + */ +static int SearchVideoLibrary(const std::vector<std::string>& params) +{ + CGUIMessage msg(GUI_MSG_SEARCH, 0, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg, WINDOW_VIDEO_NAV); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_8 Library built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`cleanlibrary(type)`</b> +/// , +/// Clean the video/music library +/// @param[in] type "video"\, "movies"\, "tvshows"\, "musicvideos" or "music". +/// } +/// \table_row2_l{ +/// <b>`exportlibrary(type [\, exportSingeFile\, exportThumbs\, overwrite\, exportActorThumbs])`</b> +/// , +/// Export the video/music library +/// @param[in] type "video" or "music". +/// @param[in] exportSingleFile Add "true" to export to separate files (optional). +/// @param[in] exportThumbs Add "true" to export thumbs (optional). +/// @param[in] overwrite Add "true" to overwrite existing files (optional). +/// @param[in] exportActorThumbs Add "true" to export actor thumbs (optional). +/// } +/// \table_row2_l{ +/// <b>`exportlibrary2(library\, exportFiletype\, path [\, unscraped][\, overwrite][\, artwork][\, skipnfo] +/// [\, albums][\, albumartists][\, songartists][\, otherartists][\, actorthumbs])`</b> +/// , +/// Export the video/music library with extended parameters +/// @param[in] library "video" or "music". +/// @param[in] exportFiletype "singlefile"\, "separate" or "library". +/// @param[in] path Path to destination folder. +/// @param[in] unscraped Add "unscraped" to include unscraped items. +/// @param[in] overwrite Add "overwrite" to overwrite existing files. +/// @param[in] artwork Add "artwork" to include images such as thumbs and fanart. +/// @param[in] skipnfo Add "skipnfo" to not include nfo files(just art). +/// @param[in] albums Add "ablums" to include albums. +/// @param[in] albumartists Add "albumartists" to include album artists. +/// @param[in] songartists Add "songartists" to include song artists. +/// @param[in] otherartists Add "otherartists" to include other artists. +/// @param[in] actorthumbs Add "actorthumbs" to include other actor thumbs. +/// } +/// \table_row2_l{ +/// <b>`updatelibrary([type\, suppressDialogs])`</b> +/// , +/// Update the selected library (music or video) +/// @param[in] type "video" or "music". +/// @param[in] suppressDialogs Add "true" to suppress dialogs (optional). +/// } +/// \table_row2_l{ +/// <b>`videolibrary.search`</b> +/// , +/// Brings up a search dialog which will search the library +/// } +/// \table_end +/// + +CBuiltins::CommandMap CLibraryBuiltins::GetOperations() const +{ + return { + {"cleanlibrary", {"Clean the video/music library", 1, CleanLibrary}}, + {"exportlibrary", {"Export the video/music library", 1, ExportLibrary}}, + {"exportlibrary2", {"Export the video/music library", 1, ExportLibrary2}}, + {"updatelibrary", {"Update the selected library (music or video)", 1, UpdateLibrary}}, + {"videolibrary.search", {"Brings up a search dialog which will search the library", 0, SearchVideoLibrary}} + }; +} diff --git a/xbmc/interfaces/builtins/LibraryBuiltins.h b/xbmc/interfaces/builtins/LibraryBuiltins.h new file mode 100644 index 0000000..1bc8744 --- /dev/null +++ b/xbmc/interfaces/builtins/LibraryBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing library related built-in commands. +class CLibraryBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/OpticalBuiltins.cpp b/xbmc/interfaces/builtins/OpticalBuiltins.cpp new file mode 100644 index 0000000..4a0ec5d --- /dev/null +++ b/xbmc/interfaces/builtins/OpticalBuiltins.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "OpticalBuiltins.h" + +#include "ServiceBroker.h" + +#ifdef HAS_DVD_DRIVE +#include "storage/MediaManager.h" +#endif + +#ifdef HAS_CDDA_RIPPER +#include "cdrip/CDDARipper.h" +#endif + +/*! \brief Eject the tray of an optical drive. + * \param params (ignored) + */ +static int Eject(const std::vector<std::string>& params) +{ +#ifdef HAS_DVD_DRIVE + CServiceBroker::GetMediaManager().ToggleTray(); +#endif + + return 0; +} + +/*! \brief Rip currently inserted CD. + * \param params (ignored) + */ +static int RipCD(const std::vector<std::string>& params) +{ +#ifdef HAS_CDDA_RIPPER + KODI::CDRIP::CCDDARipper::GetInstance().RipCD(); +#endif + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_9 Optical container built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`EjectTray`</b> +/// , +/// Either opens or closes the DVD tray\, depending on its current state. +/// } +/// \table_row2_l{ +/// <b>`RipCD`</b> +/// , +/// Will rip the inserted CD from the DVD-ROM drive. +/// } +/// \table_end +/// + +CBuiltins::CommandMap COpticalBuiltins::GetOperations() const +{ + return { + {"ejecttray", {"Close or open the DVD tray", 0, Eject}}, + {"ripcd", {"Rip the currently inserted audio CD", 0, RipCD}} + }; +} diff --git a/xbmc/interfaces/builtins/OpticalBuiltins.h b/xbmc/interfaces/builtins/OpticalBuiltins.h new file mode 100644 index 0000000..c1b218e --- /dev/null +++ b/xbmc/interfaces/builtins/OpticalBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing optical media related built-in commands. +class COpticalBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/PVRBuiltins.cpp b/xbmc/interfaces/builtins/PVRBuiltins.cpp new file mode 100644 index 0000000..f87d0d6 --- /dev/null +++ b/xbmc/interfaces/builtins/PVRBuiltins.cpp @@ -0,0 +1,224 @@ +/* + * 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 "PVRBuiltins.h" + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/guiinfo/GUIInfoLabels.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsTimers.h" +#include "pvr/windows/GUIWindowPVRGuide.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <cstdlib> +#include <regex> + +using namespace PVR; + +/*! \brief Search for missing channel icons + * \param params (ignored) + */ +static int SearchMissingIcons(const std::vector<std::string>& params) +{ + CServiceBroker::GetPVRManager().TriggerSearchMissingChannelIcons(); + return 0; +} + +/*! \brief will toggle recording of playing channel, if any. + * \param params (ignored) + */ +static int ToggleRecordPlayingChannel(const std::vector<std::string>& params) +{ + CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().ToggleRecordingOnPlayingChannel(); + return 0; +} + +/*! \brief seeks to the given percentage in timeshift buffer, if timeshifting is supported. + * \param params The parameters + * \details params[0] = percentage to seek to in the timeshift buffer. + */ +static int SeekPercentage(const std::vector<std::string>& params) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + if (params.empty()) + { + CLog::Log(LOGERROR,"PVR.SeekPercentage(n) - No argument given"); + } + else + { + const float fTimeshiftPercentage = static_cast<float>(std::atof(params.front().c_str())); + if (fTimeshiftPercentage < 0 || fTimeshiftPercentage > 100) + { + CLog::Log(LOGERROR, "PVR.SeekPercentage(n) - Invalid argument ({:f}), must be in range 0-100", + fTimeshiftPercentage); + } + else if (appPlayer->IsPlaying()) + { + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + + int iTimeshiftProgressDuration = 0; + infoMgr.GetInt(iTimeshiftProgressDuration, PVR_TIMESHIFT_PROGRESS_DURATION, + INFO::DEFAULT_CONTEXT); + + int iTimeshiftBufferStart = 0; + infoMgr.GetInt(iTimeshiftBufferStart, PVR_TIMESHIFT_PROGRESS_BUFFER_START, + INFO::DEFAULT_CONTEXT); + + float fPlayerPercentage = static_cast<float>(iTimeshiftProgressDuration) / + static_cast<float>(g_application.GetTotalTime()) * + (fTimeshiftPercentage - static_cast<float>(iTimeshiftBufferStart)); + fPlayerPercentage = std::max(0.0f, std::min(fPlayerPercentage, 100.0f)); + + g_application.SeekPercentage(fPlayerPercentage); + } + } + return 0; +} + +namespace +{ +/*! \brief Control PVR Guide window's EPG grid. + * \param params The parameters + * \details params[0] = Control to execute. + */ +int EpgGridControl(const std::vector<std::string>& params) +{ + if (params.empty()) + { + CLog::Log(LOGERROR, "EpgGridControl(n) - No argument given"); + return 0; + } + + int activeWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + if (activeWindow != WINDOW_TV_GUIDE && activeWindow != WINDOW_RADIO_GUIDE) + { + CLog::Log(LOGERROR, "EpgGridControl(n) - Guide window not active"); + return 0; + } + + CGUIWindowPVRGuideBase* guideWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowPVRGuideBase>(activeWindow); + if (!guideWindow) + { + CLog::Log(LOGERROR, "EpgGridControl(n) - Unable to get Guide window instance"); + return 0; + } + + std::string param(params[0]); + StringUtils::ToLower(param); + + if (param == "firstprogramme") + { + guideWindow->GotoBegin(); + } + else if (param == "lastprogramme") + { + guideWindow->GotoEnd(); + } + else if (param == "currentprogramme") + { + guideWindow->GotoCurrentProgramme(); + } + else if (param == "selectdate") + { + guideWindow->OpenDateSelectionDialog(); + } + else if (StringUtils::StartsWithNoCase(param, "+") || StringUtils::StartsWithNoCase(param, "-")) + { + // jump back/forward n hours + if (std::regex_match(param, std::regex("[(-|+)|][0-9]+"))) + { + guideWindow->GotoDate(std::atoi(param.c_str())); + } + else + { + CLog::Log(LOGERROR, "EpgGridControl(n) - invalid argument"); + } + } + else if (param == "firstchannel") + { + guideWindow->GotoFirstChannel(); + } + else if (param == "playingchannel") + { + guideWindow->GotoPlayingChannel(); + } + else if (param == "lastchannel") + { + guideWindow->GotoLastChannel(); + } + else if (param == "previousgroup") + { + guideWindow->ActivatePreviousChannelGroup(); + } + else if (param == "nextgroup") + { + guideWindow->ActivateNextChannelGroup(); + } + else if (param == "selectgroup") + { + guideWindow->OpenChannelGroupSelectionDialog(); + } + + return 0; +} + +} // unnamed namespace + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_10 PVR built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`PVR.SearchMissingChannelIcons`</b> +/// , +/// Will start a search for missing channel icons +/// } +/// \table_row2_l{ +/// <b>`PVR.ToggleRecordPlayingChannel`</b> +/// , +/// Will toggle recording on playing channel\, if any +/// } +/// \table_row2_l{ +/// <b>`PVR.SeekPercentage`</b> +/// , +/// Performs a seek to the given percentage in timeshift buffer\, if timeshifting is supported +/// } +/// \table_row2_l{ +/// <b>`PVR.EpgGridControl`</b> +/// , +/// Control PVR Guide window's EPG grid +/// } +/// \table_end +/// + +CBuiltins::CommandMap CPVRBuiltins::GetOperations() const +{ + return { + {"pvr.searchmissingchannelicons", {"Search for missing channel icons", 0, SearchMissingIcons}}, + {"pvr.togglerecordplayingchannel", {"Toggle recording on playing channel", 0, ToggleRecordPlayingChannel}}, + {"pvr.seekpercentage", {"Performs a seek to the given percentage in timeshift buffer", 1, SeekPercentage}}, + {"pvr.epggridcontrol", {"Control PVR Guide window's EPG grid", 1, EpgGridControl}}, + }; +} diff --git a/xbmc/interfaces/builtins/PVRBuiltins.h b/xbmc/interfaces/builtins/PVRBuiltins.h new file mode 100644 index 0000000..a22259b --- /dev/null +++ b/xbmc/interfaces/builtins/PVRBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing PVR related built-in commands. +class CPVRBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/PictureBuiltins.cpp b/xbmc/interfaces/builtins/PictureBuiltins.cpp new file mode 100644 index 0000000..15cfe0a --- /dev/null +++ b/xbmc/interfaces/builtins/PictureBuiltins.cpp @@ -0,0 +1,135 @@ +/* + * 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 "PictureBuiltins.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "utils/StringUtils.h" + +/*! \brief Show a picture. + * \param params The parameters. + * \details params[0] = URL of picture. + */ +static int Show(const std::vector<std::string>& params) +{ + CGUIMessage msg(GUI_MSG_SHOW_PICTURE, 0, 0); + msg.SetStringParam(params[0]); + CGUIWindow *pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_SLIDESHOW); + if (pWindow) + pWindow->OnMessage(msg); + + return 0; +} + +/*! \brief Start a slideshow. + * \param params The parameters. + * \details params[0] = Path to run slideshow for. + * params[1,..] = "recursive" to run a recursive slideshow. + * params[1,...] = "random" to randomize slideshow. + * params[1,...] = "notrandom" to not randomize slideshow. + * params[1,...] = "pause" to start slideshow paused. + * params[1,...] = "beginslide=<number>" to start at a given slide. + * + * Set the template parameter Recursive to true to run a recursive slideshow. + */ + template<bool Recursive> +static int Slideshow(const std::vector<std::string>& params) +{ + std::string beginSlidePath; + // leave RecursiveSlideShow command as-is + unsigned int flags = 0; + if (Recursive) + flags |= 1; + + // SlideShow(dir[,recursive][,[not]random][,pause][,beginslide="/path/to/start/slide.jpg"]) + // the beginslide value need be escaped (for '"' or '\' in it, by backslash) + // and then quoted, or not. See CUtil::SplitParams() + else + { + for (unsigned int i = 1 ; i < params.size() ; i++) + { + if (StringUtils::EqualsNoCase(params[i], "recursive")) + flags |= 1; + else if (StringUtils::EqualsNoCase(params[i], "random")) // set fullscreen or windowed + flags |= 2; + else if (StringUtils::EqualsNoCase(params[i], "notrandom")) + flags |= 4; + else if (StringUtils::EqualsNoCase(params[i], "pause")) + flags |= 8; + else if (StringUtils::StartsWithNoCase(params[i], "beginslide=")) + beginSlidePath = params[i].substr(11); + } + } + + CGUIMessage msg(GUI_MSG_START_SLIDESHOW, 0, 0, flags); + std::vector<std::string> strParams; + strParams.push_back(params[0]); + strParams.push_back(beginSlidePath); + msg.SetStringParams(strParams); + CGUIWindow *pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_SLIDESHOW); + if (pWindow) + pWindow->OnMessage(msg); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_11 Picture built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`RecursiveSlideShow(dir)`</b> +/// , +/// Run a slideshow from the specified directory\, including all subdirs. +/// @param[in] dir Path to run slideshow for. +/// @param[in] random Add "random" to randomize slideshow (optional). +/// @param[in] notrandom Add "notrandom" to not randomize slideshow (optional). +/// @param[in] pause Add "pause" to start slideshow paused (optional). +/// @param[in] beginslide Add "beginslide=<number>" to start at a given slide (optional). +/// } +/// \table_row2_l{ +/// <b>`ShowPicture(picture)`</b> +/// , +/// Display a picture by file path. +/// @param[in] url URL of picture. +/// } +/// \table_row2_l{ +/// <b>`SlideShow(dir [\,recursive\, [not]random])`</b> +/// , +/// Starts a slideshow of pictures in the folder dir. Optional parameters are +/// <b>recursive</b>\, and **random** or **notrandom** slideshow\, adding images +/// from sub-folders. The **random** and **notrandom** parameters override +/// the Randomize setting found in the pictures media window. +/// @param[in] dir Path to run slideshow for. +/// @param[in] recursive Add "recursive" to run a recursive slideshow (optional). +/// @param[in] random Add "random" to randomize slideshow (optional). +/// @param[in] notrandom Add "notrandom" to not randomize slideshow (optional). +/// @param[in] pause Add "pause" to start slideshow paused (optional). +/// @param[in] beginslide Add "beginslide=<number>" to start at a given slide (optional). +/// } +/// \table_end +/// + +CBuiltins::CommandMap CPictureBuiltins::GetOperations() const +{ + return { + {"recursiveslideshow", {"Run a slideshow from the specified directory, including all subdirs", 1, Slideshow<true>}}, + {"showpicture", {"Display a picture by file path", 1, Show}}, + {"slideshow", {"Run a slideshow from the specified directory", 1, Slideshow<false>}} + }; +} diff --git a/xbmc/interfaces/builtins/PictureBuiltins.h b/xbmc/interfaces/builtins/PictureBuiltins.h new file mode 100644 index 0000000..4dd396b --- /dev/null +++ b/xbmc/interfaces/builtins/PictureBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing picture related built-in commands. +class CPictureBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp new file mode 100644 index 0000000..ca69d69 --- /dev/null +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -0,0 +1,842 @@ +/* + * 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 "PlayerBuiltins.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "SeekHandler.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationPowerHandling.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "music/MusicUtils.h" +#include "playlists/PlayList.h" +#include "pvr/PVRManager.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/recordings/PVRRecording.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "video/PlayerController.h" +#include "video/VideoUtils.h" +#include "video/windows/GUIWindowVideoBase.h" + +#include <math.h> + +#ifdef HAS_DVD_DRIVE +#include "Autorun.h" +#endif + +/*! \brief Clear current playlist + * \param params (ignored) + */ +static int ClearPlaylist(const std::vector<std::string>& params) +{ + CServiceBroker::GetPlaylistPlayer().Clear(); + + return 0; +} + +/*! \brief Start a playlist from a given offset. + * \param params The parameters. + * \details params[0] = Position in playlist or playlist type. + * params[1] = Position in playlist if params[0] is playlist type (optional). + */ +static int PlayOffset(const std::vector<std::string>& params) +{ + // playlist.playoffset(offset) + // playlist.playoffset(music|video,offset) + std::string strPos = params[0]; + std::string paramlow(params[0]); + StringUtils::ToLower(paramlow); + if (params.size() > 1) + { + // ignore any other parameters if present + std::string strPlaylist = params[0]; + strPos = params[1]; + + PLAYLIST::Id playlistId = PLAYLIST::TYPE_NONE; + if (paramlow == "music") + playlistId = PLAYLIST::TYPE_MUSIC; + else if (paramlow == "video") + playlistId = PLAYLIST::TYPE_VIDEO; + + // unknown playlist + if (playlistId == PLAYLIST::TYPE_NONE) + { + CLog::Log(LOGERROR, "Playlist.PlayOffset called with unknown playlist: {}", strPlaylist); + return false; + } + + // user wants to play the 'other' playlist + if (playlistId != CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist()) + { + g_application.StopPlaying(); + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId); + } + } + // play the desired offset + int pos = atol(strPos.c_str()); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + // playlist is already playing + if (appPlayer->IsPlaying()) + CServiceBroker::GetPlaylistPlayer().PlayNext(pos); + // we start playing the 'other' playlist so we need to use play to initialize the player state + else + CServiceBroker::GetPlaylistPlayer().Play(pos, ""); + + return 0; +} + +/*! \brief Control player. + * \param params The parameters + * \details params[0] = Control to execute. + * params[1] = "notify" to notify user (optional, certain controls). + */ +static int PlayerControl(const std::vector<std::string>& params) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + + std::string paramlow(params[0]); + StringUtils::ToLower(paramlow); + + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + if (paramlow == "play") + { // play/pause + // either resume playing, or pause + if (appPlayer->IsPlaying()) + { + if (appPlayer->GetPlaySpeed() != 1) + appPlayer->SetPlaySpeed(1); + else + appPlayer->Pause(); + } + } + else if (paramlow == "stop") + { + g_application.StopPlaying(); + } + else if (StringUtils::StartsWithNoCase(params[0], "frameadvance")) + { + std::string strFrames; + if (params[0].size() == 12) + CLog::Log(LOGERROR, "PlayerControl(frameadvance(n)) called with no argument"); + else if (params[0].size() < 15) // arg must be at least "(N)" + CLog::Log(LOGERROR, "PlayerControl(frameadvance(n)) called with invalid argument: \"{}\"", + params[0].substr(13)); + else + + strFrames = params[0].substr(13); + StringUtils::TrimRight(strFrames, ")"); + float frames = (float) atof(strFrames.c_str()); + appPlayer->FrameAdvance(frames); + } + else if (paramlow =="rewind" || paramlow == "forward") + { + if (appPlayer->IsPlaying() && !appPlayer->IsPaused()) + { + float playSpeed = appPlayer->GetPlaySpeed(); + + if (paramlow == "rewind" && playSpeed == 1) // Enables Rewinding + playSpeed *= -2; + else if (paramlow == "rewind" && playSpeed > 1) //goes down a notch if you're FFing + playSpeed /= 2; + else if (paramlow == "forward" && playSpeed < 1) //goes up a notch if you're RWing + { + playSpeed /= 2; + if (playSpeed == -1) + playSpeed = 1; + } + else + playSpeed *= 2; + + if (playSpeed > 32 || playSpeed < -32) + playSpeed = 1; + + appPlayer->SetPlaySpeed(playSpeed); + } + } + else if (paramlow =="tempoup" || paramlow == "tempodown") + { + if (appPlayer->SupportsTempo() && appPlayer->IsPlaying() && !appPlayer->IsPaused()) + { + float playTempo = appPlayer->GetPlayTempo(); + if (paramlow == "tempodown") + playTempo -= 0.1f; + else if (paramlow == "tempoup") + playTempo += 0.1f; + + appPlayer->SetTempo(playTempo); + } + } + else if (StringUtils::StartsWithNoCase(params[0], "tempo")) + { + if (params[0].size() == 5) + CLog::Log(LOGERROR, "PlayerControl(tempo(n)) called with no argument"); + else if (params[0].size() < 8) // arg must be at least "(N)" + CLog::Log(LOGERROR, "PlayerControl(tempo(n)) called with invalid argument: \"{}\"", + params[0].substr(6)); + else + { + if (appPlayer->SupportsTempo() && appPlayer->IsPlaying() && !appPlayer->IsPaused()) + { + std::string strTempo = params[0].substr(6); + StringUtils::TrimRight(strTempo, ")"); + float playTempo = strtof(strTempo.c_str(), nullptr); + + appPlayer->SetTempo(playTempo); + } + } + } + else if (paramlow == "next") + { + g_application.OnAction(CAction(ACTION_NEXT_ITEM)); + } + else if (paramlow == "previous") + { + g_application.OnAction(CAction(ACTION_PREV_ITEM)); + } + else if (paramlow == "bigskipbackward") + { + if (appPlayer->IsPlaying()) + appPlayer->Seek(false, true); + } + else if (paramlow == "bigskipforward") + { + if (appPlayer->IsPlaying()) + appPlayer->Seek(true, true); + } + else if (paramlow == "smallskipbackward") + { + if (appPlayer->IsPlaying()) + appPlayer->Seek(false, false); + } + else if (paramlow == "smallskipforward") + { + if (appPlayer->IsPlaying()) + appPlayer->Seek(true, false); + } + else if (StringUtils::StartsWithNoCase(params[0], "seekpercentage")) + { + std::string offset; + if (params[0].size() == 14) + CLog::Log(LOGERROR,"PlayerControl(seekpercentage(n)) called with no argument"); + else if (params[0].size() < 17) // arg must be at least "(N)" + CLog::Log(LOGERROR, "PlayerControl(seekpercentage(n)) called with invalid argument: \"{}\"", + params[0].substr(14)); + else + { + // Don't bother checking the argument: an invalid arg will do seek(0) + offset = params[0].substr(15); + StringUtils::TrimRight(offset, ")"); + float offsetpercent = (float) atof(offset.c_str()); + if (offsetpercent < 0 || offsetpercent > 100) + CLog::Log(LOGERROR, "PlayerControl(seekpercentage(n)) argument, {:f}, must be 0-100", + offsetpercent); + else if (appPlayer->IsPlaying()) + g_application.SeekPercentage(offsetpercent); + } + } + else if (paramlow == "showvideomenu") + { + if (appPlayer->IsPlaying()) + appPlayer->OnAction(CAction(ACTION_SHOW_VIDEOMENU)); + } + else if (StringUtils::StartsWithNoCase(params[0], "partymode")) + { + std::string strXspPath; + //empty param=music, "music"=music, "video"=video, else xsp path + PartyModeContext context = PARTYMODECONTEXT_MUSIC; + if (params[0].size() > 9) + { + if (params[0].size() == 16 && StringUtils::EndsWithNoCase(params[0], "video)")) + context = PARTYMODECONTEXT_VIDEO; + else if (params[0].size() != 16 || !StringUtils::EndsWithNoCase(params[0], "music)")) + { + strXspPath = params[0].substr(10); + StringUtils::TrimRight(strXspPath, ")"); + context = PARTYMODECONTEXT_UNKNOWN; + } + } + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Disable(); + else + g_partyModeManager.Enable(context, strXspPath); + } + else if (paramlow == "random" || paramlow == "randomoff" || paramlow == "randomon") + { + // get current playlist + PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + + // reverse the current setting + bool shuffled = CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId); + if ((shuffled && paramlow == "randomon") || (!shuffled && paramlow == "randomoff")) + return 0; + + // check to see if we should notify the user + bool notify = (params.size() == 2 && StringUtils::EqualsNoCase(params[1], "notify")); + CServiceBroker::GetPlaylistPlayer().SetShuffle(playlistId, !shuffled, notify); + + // save settings for now playing windows + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + CMediaSettings::GetInstance().SetMusicPlaylistShuffled( + CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId)); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + break; + case PLAYLIST::TYPE_VIDEO: + CMediaSettings::GetInstance().SetVideoPlaylistShuffled( + CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId)); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + default: + break; + } + + // send message + CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_RANDOM, 0, 0, playlistId, + CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId)); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + } + else if (StringUtils::StartsWithNoCase(params[0], "repeat")) + { + // get current playlist + PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + PLAYLIST::RepeatState prevRepeatState = + CServiceBroker::GetPlaylistPlayer().GetRepeat(playlistId); + + std::string paramlow(params[0]); + StringUtils::ToLower(paramlow); + + PLAYLIST::RepeatState repeatState; + if (paramlow == "repeatall") + repeatState = PLAYLIST::RepeatState::ALL; + else if (paramlow == "repeatone") + repeatState = PLAYLIST::RepeatState::ONE; + else if (paramlow == "repeatoff") + repeatState = PLAYLIST::RepeatState::NONE; + else if (prevRepeatState == PLAYLIST::RepeatState::NONE) + repeatState = PLAYLIST::RepeatState::ALL; + else if (prevRepeatState == PLAYLIST::RepeatState::ALL) + repeatState = PLAYLIST::RepeatState::ONE; + else + repeatState = PLAYLIST::RepeatState::NONE; + + if (repeatState == prevRepeatState) + return 0; + + // check to see if we should notify the user + bool notify = (params.size() == 2 && StringUtils::EqualsNoCase(params[1], "notify")); + CServiceBroker::GetPlaylistPlayer().SetRepeat(playlistId, repeatState, notify); + + // save settings for now playing windows + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + CMediaSettings::GetInstance().SetMusicPlaylistRepeat(repeatState == + PLAYLIST::RepeatState::ALL); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + break; + case PLAYLIST::TYPE_VIDEO: + CMediaSettings::GetInstance().SetVideoPlaylistRepeat(repeatState == + PLAYLIST::RepeatState::ALL); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + } + + // send messages so now playing window can get updated + CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_REPEAT, 0, 0, playlistId, static_cast<int>(repeatState)); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + } + else if (StringUtils::StartsWithNoCase(params[0], "resumelivetv")) + { + CFileItem& fileItem(g_application.CurrentFileItem()); + std::shared_ptr<PVR::CPVRChannel> channel = fileItem.HasPVRRecordingInfoTag() ? fileItem.GetPVRRecordingInfoTag()->Channel() : std::shared_ptr<PVR::CPVRChannel>(); + + if (channel) + { + const std::shared_ptr<PVR::CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(channel); + if (!groupMember) + { + CLog::Log(LOGERROR, "ResumeLiveTv could not obtain channel group member for channel: {}", + channel->ChannelName()); + return false; + } + + CFileItem playItem(groupMember); + if (!g_application.PlayMedia( + playItem, "", channel->IsRadio() ? PLAYLIST::TYPE_MUSIC : PLAYLIST::TYPE_VIDEO)) + { + CLog::Log(LOGERROR, "ResumeLiveTv could not play channel: {}", channel->ChannelName()); + return false; + } + } + } + else if (paramlow == "reset") + { + g_application.OnAction(CAction(ACTION_PLAYER_RESET)); + } + + return 0; +} + +/*! \brief Play currently inserted DVD. + * \param params The parameters. + * \details params[0] = "restart" to restart from resume point (optional). + */ +static int PlayDVD(const std::vector<std::string>& params) +{ +#ifdef HAS_DVD_DRIVE + bool restart = false; + if (!params.empty() && StringUtils::EqualsNoCase(params[0], "restart")) + restart = true; + MEDIA_DETECT::CAutorun::PlayDisc(CServiceBroker::GetMediaManager().GetDiscPath(), true, restart); +#endif + + return 0; +} + +namespace +{ +void GetItemsForPlayList(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems) +{ + if (VIDEO_UTILS::IsItemPlayable(*item)) + VIDEO_UTILS::GetItemsForPlayList(item, queuedItems); + else if (MUSIC_UTILS::IsItemPlayable(*item)) + MUSIC_UTILS::GetItemsForPlayList(item, queuedItems); + else + CLog::LogF(LOGERROR, "Unable to get playlist items for {}", item->GetPath()); +} + +int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) +{ + // restore to previous window if needed + if( CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW || + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO || + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME || + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION ) + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + + // reset screensaver + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + + CFileItem item(params[0], URIUtils::HasSlashAtEnd(params[0], true)); + + // at this point the item instance has only the path and the folder flag set. We + // need some extended item properties to process resume successfully. Load them. + item.LoadDetails(); + + // ask if we need to check guisettings to resume + bool askToResume = true; + int playOffset = 0; + bool hasPlayOffset = false; + bool playNext = true; + for (unsigned int i = 1 ; i < params.size() ; i++) + { + if (StringUtils::EqualsNoCase(params[i], "isdir")) + item.m_bIsFolder = true; + else if (params[i] == "1") // set fullscreen or windowed + CMediaSettings::GetInstance().SetMediaStartWindowed(true); + else if (StringUtils::EqualsNoCase(params[i], "resume")) + { + // force the item to resume (if applicable) + if (VIDEO_UTILS::GetItemResumeInformation(item).isResumable) + item.SetStartOffset(STARTOFFSET_RESUME); + else + item.SetStartOffset(0); + + askToResume = false; + } + else if (StringUtils::EqualsNoCase(params[i], "noresume")) + { + // force the item to start at the beginning + item.SetStartOffset(0); + askToResume = false; + } + else if (StringUtils::StartsWithNoCase(params[i], "playoffset=")) + { + playOffset = atoi(params[i].substr(11).c_str()) - 1; + item.SetProperty("playlist_starting_track", playOffset); + hasPlayOffset = true; + } + else if (StringUtils::StartsWithNoCase(params[i], "playlist_type_hint=")) + { + // Set the playlist type for the playlist file (e.g. STRM) + int playlistTypeHint = std::stoi(params[i].substr(19)); + item.SetProperty("playlist_type_hint", playlistTypeHint); + } + else if (StringUtils::EqualsNoCase(params[i], "playnext")) + { + // If app player is currently playing, the queued media shall be played next. + playNext = true; + } + } + + if (!item.m_bIsFolder && item.IsPlugin()) + item.SetProperty("IsPlayable", true); + + if (askToResume == true) + { + if (CGUIWindowVideoBase::ShowResumeMenu(item) == false) + return false; + item.SetProperty("check_resume", false); + } + + if (item.m_bIsFolder || item.IsPlayList()) + { + CFileItemList items; + GetItemsForPlayList(std::make_shared<CFileItem>(item), items); + if (!items.IsEmpty()) // fall through on non expandable playlist + { + bool containsMusic = false; + bool containsVideo = false; + for (const auto& i : items) + { + const bool isVideo = i->IsVideo(); + containsMusic |= !isVideo; + containsVideo |= isVideo; + + if (containsMusic && containsVideo) + break; + } + + PLAYLIST::Id playlistId = containsVideo ? PLAYLIST::TYPE_VIDEO : PLAYLIST::TYPE_MUSIC; + // Mixed playlist item played by music player, mixed content folder has music removed + if (containsMusic && containsVideo) + { + if (item.IsPlayList()) + playlistId = PLAYLIST::TYPE_MUSIC; + else + { + for (int i = items.Size() - 1; i >= 0; i--) //remove music entries + { + if (!items[i]->IsVideo()) + items.Remove(i); + } + } + } + + auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer(); + + // Play vs. Queue (+Play) + if (forcePlay) + { + playlistPlayer.ClearPlaylist(playlistId); + playlistPlayer.Reset(); + playlistPlayer.Add(playlistId, items); + playlistPlayer.SetCurrentPlaylist(playlistId); + playlistPlayer.Play(playOffset, ""); + } + else + { + const int oldSize = playlistPlayer.GetPlaylist(playlistId).size(); + + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (playNext) + { + if (appPlayer->IsPlaying()) + playlistPlayer.Insert(playlistId, items, playlistPlayer.GetCurrentSong() + 1); + else + playlistPlayer.Add(playlistId, items); + } + else + { + playlistPlayer.Add(playlistId, items); + } + + if (items.Size() && !appPlayer->IsPlaying()) + { + playlistPlayer.SetCurrentPlaylist(playlistId); + + if (containsMusic) + { + // video does not auto play on queue like music + playlistPlayer.Play(hasPlayOffset ? playOffset : oldSize, ""); + } + } + } + + return 0; + } + } + + if (forcePlay) + { + if (item.HasVideoInfoTag() && item.GetStartOffset() == STARTOFFSET_RESUME) + { + const CBookmark bookmark = item.GetVideoInfoTag()->GetResumePoint(); + if (bookmark.IsSet()) + item.SetStartOffset(CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds)); + } + + if ((item.IsAudio() || item.IsVideo()) && !item.IsSmartPlayList()) + { + if (!item.HasProperty("playlist_type_hint")) + item.SetProperty("playlist_type_hint", PLAYLIST::TYPE_MUSIC); + + CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(item), ""); + } + else + { + g_application.PlayMedia(item, "", PLAYLIST::TYPE_NONE); + } + } + + return 0; +} + +/*! \brief Start playback of media. + * \param params The parameters. + * \details params[0] = URL to media to play (optional). + * params[1,...] = "isdir" if media is a directory (optional). + * params[1,...] = "1" to start playback in fullscreen (optional). + * params[1,...] = "resume" to force resuming (optional). + * params[1,...] = "noresume" to force not resuming (optional). + * params[1,...] = "playoffset=<offset>" to start playback from a given position in a playlist (optional). + * params[1,...] = "playlist_type_hint=<id>" to set the playlist type if a playlist file (e.g. STRM) is played (optional), + * for <id> value refer to PLAYLIST::TYPE_MUSIC / PLAYLIST::TYPE_VIDEO values, if not set will fallback to music playlist. + */ +int PlayMedia(const std::vector<std::string>& params) +{ + return PlayOrQueueMedia(params, true); +} + +/*! \brief Queue media in the video or music playlist, according to type of media items. If both audio and video items are contained, queue to video + * playlist. Start playback at requested position if player is not playing. + * \param params The parameters. + * \details params[0] = URL of media to queue. + * params[1,...] = "isdir" if media is a directory (optional). + * params[1,...] = "1" to start playback in fullscreen (optional). + * params[1,...] = "resume" to force resuming (optional). + * params[1,...] = "noresume" to force not resuming (optional). + * params[1,...] = "playoffset=<offset>" to start playback from a given position in a playlist (optional). + * params[1,...] = "playlist_type_hint=<id>" to set the playlist type if a playlist file (e.g. STRM) is played (optional), + * for <id> value refer to PLAYLIST::TYPE_MUSIC / PLAYLIST::TYPE_VIDEO values, if not set will fallback to music playlist. + * params[1,...] = "playnext" if player is currently playing, to play the media right after the currently playing item. If player is not + * playing, append media to current playlist (optional). + */ +int QueueMedia(const std::vector<std::string>& params) +{ + return PlayOrQueueMedia(params, false); +} + +} // unnamed namespace + +/*! \brief Start playback with a given playback core. + * \param params The parameters. + * \details params[0] = Name of playback core. + */ +static int PlayWith(const std::vector<std::string>& params) +{ + g_application.OnAction(CAction(ACTION_PLAYER_PLAY, params[0])); + + return 0; +} + +/*! \brief Seek in currently playing media. + * \param params The parameters. + * \details params[0] = Number of seconds to seek. + */ +static int Seek(const std::vector<std::string>& params) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlaying()) + appPlayer->GetSeekHandler().SeekSeconds(atoi(params[0].c_str())); + + return 0; +} + +static int SubtitleShiftUp(const std::vector<std::string>& params) +{ + CAction action{ACTION_SUBTITLE_VSHIFT_UP}; + if (!params.empty() && params[0] == "save") + action.SetText("save"); + CPlayerController::GetInstance().OnAction(action); + return 0; +} + +static int SubtitleShiftDown(const std::vector<std::string>& params) +{ + CAction action{ACTION_SUBTITLE_VSHIFT_DOWN}; + if (!params.empty() && params[0] == "save") + action.SetText("save"); + CPlayerController::GetInstance().OnAction(action); + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_12 Player built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`PlaysDisc(parm)`</b>\n +/// <b>`PlayDVD(param)`</b>(deprecated) +/// , +/// Plays the inserted disc\, like CD\, DVD or Blu-ray\, in the disc drive. +/// @param[in] param "restart" to restart from resume point (optional) +/// } +/// \table_row2_l{ +/// <b>`PlayerControl(control[\,param])`</b> +/// , +/// Allows control of music and videos. <br> +/// <br> +/// | Control | Video playback behaviour | Audio playback behaviour | Added in | +/// |:------------------------|:---------------------------------------|:----------------------------|:------------| +/// | Play | Play/Pause | Play/Pause | | +/// | Stop | Stop | Stop | | +/// | Forward | Fast Forward | Fast Forward | | +/// | Rewind | Rewind | Rewind | | +/// | Next | Next chapter or movie in playlists | Next track | | +/// | Previous | Previous chapter or movie in playlists | Previous track | | +/// | TempoUp | Increases playback speed | none | Kodi v18 | +/// | TempoDown | Decreases playback speed | none | Kodi v18 | +/// | Tempo(n) | Sets playback speed to given value | none | Kodi v19 | +/// | BigSkipForward | Big Skip Forward | Big Skip Forward | Kodi v15 | +/// | BigSkipBackward | Big Skip Backward | Big Skip Backward | Kodi v15 | +/// | SmallSkipForward | Small Skip Forward | Small Skip Forward | Kodi v15 | +/// | SmallSkipBackward | Small Skip Backward | Small Skip Backward | Kodi v15 | +/// | SeekPercentage(n) | Seeks to given percentage | Seeks to given percentage | | +/// | Random * | Toggle Random Playback | Toggle Random Playback | | +/// | RandomOn | Sets 'Random' to 'on' | Sets 'Random' to 'on' | | +/// | RandomOff | Sets 'Random' to 'off' | Sets 'Random' to 'off' | | +/// | Repeat * | Cycles through repeat modes | Cycles through repeat modes | | +/// | RepeatOne | Repeats a single video | Repeats a single track | | +/// | RepeatAll | Repeat all videos in a list | Repeats all tracks in a list| | +/// | RepeatOff | Sets 'Repeat' to 'off' | Sets 'Repeat' to 'off' | | +/// | Partymode(music) ** | none | Toggles music partymode | | +/// | Partymode(video) ** | Toggles video partymode | none | | +/// | Partymode(path to .xsp) | Partymode for *.xsp-file | Partymode for *.xsp-file | | +/// | ShowVideoMenu | Shows the DVD/BR menu if available | none | | +/// | FrameAdvance(n) *** | Advance video by _n_ frames | none | Kodi v18 | +/// | SubtitleShiftUp(save) | Shift up the subtitle position, add "save" to save the change permanently | none | Kodi v20 | +/// | SubtitleShiftDown(save) | Shift down the subtitle position, add "save" to save the change permanently | none | Kodi v20 | +/// <br> +/// '*' = For these controls\, the PlayerControl built-in function can make use of the 'notify'-parameter. For example: PlayerControl(random\, notify) +/// <br> +/// '**' = If no argument is given for 'partymode'\, the control will default to music. +/// <br> +/// '***' = This only works if the player is paused. +/// <br> +/// @param[in] control Control to execute. +/// @param[in] param "notify" to notify user (optional\, certain controls). +/// +/// @note 'TempoUp' or 'TempoDown' only works if "Sync playback to display" is enabled. +/// @note 'Next' will behave differently while using video playlists. In those\, chapters will be ignored and the next movie will be played. +/// } +/// \table_row2_l{ +/// <b>`Playlist.Clear`</b> +/// , +/// Clear the current playlist +/// @param[in] (ignored) +/// } +/// \table_row2_l{ +/// <b>`Playlist.PlayOffset(positionType[\,position])`</b> +/// , +/// Start playing from a particular offset in the playlist +/// @param[in] positionType Position in playlist or playlist type. +/// @param[in] position Position in playlist if params[0] is playlist type (optional). +/// } +/// \table_row2_l{ +/// <b>`PlayMedia(media[\,isdir][\,1]\,[playoffset=xx])`</b> +/// , +/// Plays the media. This can be a playlist\, music\, or video file\, directory\, +/// plugin or an Url. The optional parameter "\,isdir" can be used for playing +/// a directory. "\,1" will start the media without switching to fullscreen. +/// If media is a playlist\, you can use playoffset=xx where xx is +/// the position to start playback from. +/// @param[in] media URL to media to play (optional). +/// @param[in] isdir Set "isdir" if media is a directory (optional). +/// @param[in] windowed Set "1" to start playback without switching to fullscreen (optional). +/// @param[in] resume Set "resume" to force resuming (optional). +/// @param[in] noresume Set "noresume" to force not resuming (optional). +/// @param[in] playeroffset Set "playoffset=<offset>" to start playback from a given position in a playlist (optional). +/// } +/// \table_row2_l{ +/// <b>`PlayWith(core)`</b> +/// , +/// Play the selected item with the specified player core. +/// @param[in] core Name of playback core. +/// } +/// \table_row2_l{ +/// <b>`Seek(seconds)`</b> +/// , +/// Seeks to the specified relative amount of seconds within the current +/// playing media. A negative value will seek backward and a positive value forward. +/// @param[in] seconds Number of seconds to seek. +/// } +/// \table_row2_l{ +/// <b>`QueueMedia(media[\,isdir][\,1][\,playnext]\,[playoffset=xx])`</b> +/// \anchor Builtin_QueueMedia, +/// Queues the given media. This can be a playlist\, music\, or video file\, directory\, +/// plugin or an Url. The optional parameter "\,isdir" can be used for playing +/// a directory. "\,1" will start the media without switching to fullscreen. +/// If media is a playlist\, you can use playoffset=xx where xx is +/// the position to start playback from. +/// @param[in] media URL of media to queue. +/// @param[in] isdir Set "isdir" if media is a directory (optional). +/// @param[in] 1 Set "1" to start playback without switching to fullscreen (optional). +/// @param[in] resume Set "resume" to force resuming (optional). +/// @param[in] noresume Set "noresume" to force not resuming (optional). +/// @param[in] playeroffset Set "playoffset=<offset>" to start playback from a given position in a playlist (optional). +/// @param[in] playnext Set "playnext" to play the media right after the currently playing item, if player is currently +/// playing. If player is not playing, append media to current playlist (optional). +/// <p><hr> +/// @skinning_v20 **[New builtin]** \link Builtin_QueueMedia `QueueMedia(media[\,isdir][\,1][\,playnext]\,[playoffset=xx])`\endlink +/// <p> +/// } +/// \table_end +/// + +// clang-format off +CBuiltins::CommandMap CPlayerBuiltins::GetOperations() const +{ + return { + {"playdisc", {"Plays the inserted disc, like CD, DVD or Blu-ray, in the disc drive.", 0, PlayDVD}}, + {"playdvd", {"Plays the inserted disc, like CD, DVD or Blu-ray, in the disc drive.", 0, PlayDVD}}, + {"playlist.clear", {"Clear the current playlist", 0, ClearPlaylist}}, + {"playlist.playoffset", {"Start playing from a particular offset in the playlist", 1, PlayOffset}}, + {"playercontrol", {"Control the music or video player", 1, PlayerControl}}, + {"playmedia", {"Play the specified media file (or playlist)", 1, PlayMedia}}, + {"queuemedia", {"Queue the specified media in video or music playlist", 1, QueueMedia}}, + {"playwith", {"Play the selected item with the specified core", 1, PlayWith}}, + {"seek", {"Performs a seek in seconds on the current playing media file", 1, Seek}}, + {"subtitleshiftup", {"Shift up the subtitle position", 0, SubtitleShiftUp}}, + {"subtitleshiftdown", {"Shift down the subtitle position", 0, SubtitleShiftDown}}, + }; +} +// clang-format on diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.h b/xbmc/interfaces/builtins/PlayerBuiltins.h new file mode 100644 index 0000000..efa9474 --- /dev/null +++ b/xbmc/interfaces/builtins/PlayerBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing player related built-in commands. +class CPlayerBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/ProfileBuiltins.cpp b/xbmc/interfaces/builtins/ProfileBuiltins.cpp new file mode 100644 index 0000000..448f821 --- /dev/null +++ b/xbmc/interfaces/builtins/ProfileBuiltins.cpp @@ -0,0 +1,133 @@ +/* + * 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 "ProfileBuiltins.h" + +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "favourites/FavouritesService.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "profiles/ProfileManager.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" + +/*! \brief Load a profile. + * \param params The parameters. + * \details params[0] = The profile name. + * params[1] = "prompt" to allow unlocking dialogs (optional) + */ +static int LoadProfile(const std::vector<std::string>& params) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + int index = profileManager->GetProfileIndex(params[0]); + bool prompt = (params.size() == 2 && StringUtils::EqualsNoCase(params[1], "prompt")); + bool bCanceled; + if (index >= 0 + && (profileManager->GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE + || g_passwordManager.IsProfileLockUnlocked(index,bCanceled,prompt))) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_LOADPROFILE, index); + } + + return 0; +} + +/*! \brief Log off currently signed in profile. + * \param params (ignored) + */ +static int LogOff(const std::vector<std::string>& params) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + profileManager->LogOff(); + + return 0; +} + +/*! \brief Toggle master mode. + * \param params (ignored) + */ +static int MasterMode(const std::vector<std::string>& params) +{ + if (g_passwordManager.bMasterUser) + { + g_passwordManager.bMasterUser = false; + g_passwordManager.LockSources(true); + + // master mode turned OFF => refresh favourites due to possible visibility changes + CServiceBroker::GetFavouritesService().RefreshFavourites(); + + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(20052),g_localizeStrings.Get(20053)); + } + else if (g_passwordManager.IsMasterLockUnlocked(true)) // prompt user for code + { + g_passwordManager.LockSources(false); + g_passwordManager.bMasterUser = true; + + // master mode turned ON => refresh favourites due to possible visibility changes + CServiceBroker::GetFavouritesService().RefreshFavourites(); + + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(20052),g_localizeStrings.Get(20054)); + } + + CUtil::DeleteVideoDatabaseDirectoryCache(); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + return 0; +} + + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_13 Profile built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`LoadProfile(profilename\,[prompt])`</b> +/// , +/// Load the specified profile. If prompt is not specified\, and a password +/// would be required for the requested profile\, this command will silently +/// fail. If prompt is specified and a password is required\, a password +/// dialog will be shown. +/// @param[in] profilename The profile name. +/// @param[in] prompt Add "prompt" to allow unlocking dialogs (optional) +/// } +/// \table_row2_l{ +/// <b>`Mastermode`</b> +/// , +/// Runs Kodi in master mode +/// } +/// \table_row2_l{ +/// <b>`System.LogOff`</b> +/// , +/// Log off current user. +/// } +/// \table_end +/// + +CBuiltins::CommandMap CProfileBuiltins::GetOperations() const +{ + return { + {"loadprofile", {"Load the specified profile (note; if locks are active it won't work)", 1, LoadProfile}}, + {"mastermode", {"Control master mode", 0, MasterMode}}, + {"system.logoff", {"Log off current user", 0, LogOff}} + }; +} diff --git a/xbmc/interfaces/builtins/ProfileBuiltins.h b/xbmc/interfaces/builtins/ProfileBuiltins.h new file mode 100644 index 0000000..e30c1ee --- /dev/null +++ b/xbmc/interfaces/builtins/ProfileBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing profile related built-in commands. +class CProfileBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/SkinBuiltins.cpp b/xbmc/interfaces/builtins/SkinBuiltins.cpp new file mode 100644 index 0000000..5b2b4c2 --- /dev/null +++ b/xbmc/interfaces/builtins/SkinBuiltins.cpp @@ -0,0 +1,687 @@ +/* + * 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 "SkinBuiltins.h" + +#include "MediaSource.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIWindowAddonBrowser.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationSkinHandling.h" +#include "dialogs/GUIDialogColorPicker.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogNumeric.h" +#include "dialogs/GUIDialogSelect.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/SkinSettings.h" +#include "storage/MediaManager.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +using namespace ADDON; + +/*! \brief Reload current skin. + * \param params The parameters. + * \details params[0] = "confirm" to show a confirmation dialog (optional). + */ +static int ReloadSkin(const std::vector<std::string>& params) +{ + // Reload the skin + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent<CApplicationSkinHandling>(); + appSkin->ReloadSkin(!params.empty() && StringUtils::EqualsNoCase(params[0], "confirm")); + + return 0; +} + +/*! \brief Unload current skin. + * \param params (ignored) + */ +static int UnloadSkin(const std::vector<std::string>& params) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent<CApplicationSkinHandling>(); + appSkin->UnloadSkin(); + + return 0; +} + +/*! \brief Toggle a skin bool setting. + * \param params The parameters. + * \details params[0] = Skin setting to toggle. + */ +static int ToggleSetting(const std::vector<std::string>& params) +{ + int setting = CSkinSettings::GetInstance().TranslateBool(params[0]); + CSkinSettings::GetInstance().SetBool(setting, !CSkinSettings::GetInstance().GetBool(setting)); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return 0; +} + +/*! \brief Set an add-on type skin setting. + * \param params The parameters. + * \details params[0] = Skin setting to store result in. + * params[1,...] = Add-on types to allow selecting. + */ +static int SetAddon(const std::vector<std::string>& params) +{ + int string = CSkinSettings::GetInstance().TranslateString(params[0]); + std::vector<ADDON::AddonType> types; + for (unsigned int i = 1 ; i < params.size() ; i++) + { + ADDON::AddonType type = CAddonInfo::TranslateType(params[i]); + if (type != AddonType::UNKNOWN) + types.push_back(type); + } + std::string result; + if (!types.empty() && CGUIWindowAddonBrowser::SelectAddonID(types, result, true) == 1) + { + CSkinSettings::GetInstance().SetString(string, result); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + } + + return 0; +} + +/*! \brief Select and set a skin bool setting. + * \param params The parameters. + * \details params[0] = Names of skin settings. + */ +static int SelectBool(const std::vector<std::string>& params) +{ + std::vector<std::pair<std::string, std::string>> settings; + + CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + pDlgSelect->Reset(); + pDlgSelect->SetHeading(CVariant{g_localizeStrings.Get(atoi(params[0].c_str()))}); + + for (unsigned int i = 1 ; i < params.size() ; i++) + { + if (params[i].find('|') != std::string::npos) + { + std::vector<std::string> values = StringUtils::Split(params[i], '|'); + std::string label = g_localizeStrings.Get(atoi(values[0].c_str())); + settings.emplace_back(label, values[1].c_str()); + pDlgSelect->Add(label); + } + } + + pDlgSelect->Open(); + + if(pDlgSelect->IsConfirmed()) + { + unsigned int iItem = pDlgSelect->GetSelectedItem(); + + for (unsigned int i = 0 ; i < settings.size() ; i++) + { + std::string item = settings[i].second; + int setting = CSkinSettings::GetInstance().TranslateBool(item); + if (i == iItem) + CSkinSettings::GetInstance().SetBool(setting, true); + else + CSkinSettings::GetInstance().SetBool(setting, false); + } + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + } + + return 0; +} + +/*! \brief Set a skin bool setting. + * \param params The parameters. + * \details params[0] = Name of skin setting. + * params[1] = Value to set ("false", or "true") (optional). + */ +static int SetBool(const std::vector<std::string>& params) +{ + if (params.size() > 1) + { + int string = CSkinSettings::GetInstance().TranslateBool(params[0]); + CSkinSettings::GetInstance().SetBool(string, StringUtils::EqualsNoCase(params[1], "true")); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + return 0; + } + // default is to set it to true + int setting = CSkinSettings::GetInstance().TranslateBool(params[0]); + CSkinSettings::GetInstance().SetBool(setting, true); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return 0; +} + +/*! \brief Set a numeric skin setting. + * \param params The parameters. + * \details params[0] = Name of skin setting. + */ +static int SetNumeric(const std::vector<std::string>& params) +{ + int string = CSkinSettings::GetInstance().TranslateString(params[0]); + std::string value = CSkinSettings::GetInstance().GetString(string); + if (CGUIDialogNumeric::ShowAndGetNumber(value, g_localizeStrings.Get(611))) + CSkinSettings::GetInstance().SetString(string, value); + + return 0; +} + +/*! \brief Set a path skin setting. + * \param params The parameters. + * \details params[0] = Name of skin setting. + * params[1] = Extra URL to allow selection from (optional). + */ +static int SetPath(const std::vector<std::string>& params) +{ + int string = CSkinSettings::GetInstance().TranslateString(params[0]); + std::string value = CSkinSettings::GetInstance().GetString(string); + VECSOURCES localShares; + CServiceBroker::GetMediaManager().GetLocalDrives(localShares); + CServiceBroker::GetMediaManager().GetNetworkLocations(localShares); + if (params.size() > 1) + { + value = params[1]; + URIUtils::AddSlashAtEnd(value); + bool bIsSource; + if (CUtil::GetMatchingSource(value,localShares,bIsSource) < 0) // path is outside shares - add it as a separate one + { + CMediaSource share; + share.strName = g_localizeStrings.Get(13278); + share.strPath = value; + localShares.push_back(share); + } + } + + if (CGUIDialogFileBrowser::ShowAndGetDirectory(localShares, g_localizeStrings.Get(657), value)) + CSkinSettings::GetInstance().SetString(string, value); + + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return 0; +} + +/*! \brief Set a skin file setting. + * \param params The parameters. + * \details params[0] = Name of skin setting. + * params[1] = File mask or add-on type (optional). + * params[2] = Extra URL to allow selection from or + * content type if mask is an addon-on type (optional). + */ +static int SetFile(const std::vector<std::string>& params) +{ + int string = CSkinSettings::GetInstance().TranslateString(params[0]); + std::string value = CSkinSettings::GetInstance().GetString(string); + VECSOURCES localShares; + CServiceBroker::GetMediaManager().GetLocalDrives(localShares); + + // Note. can only browse one addon type from here + // if browsing for addons, required param[1] is addontype string, with optional param[2] + // as contenttype string see IAddon.h & ADDON::TranslateXX + std::string strMask = (params.size() > 1) ? params[1] : ""; + StringUtils::ToLower(strMask); + ADDON::AddonType type; + if ((type = CAddonInfo::TranslateType(strMask)) != AddonType::UNKNOWN) + { + CURL url; + url.SetProtocol("addons"); + url.SetHostName("enabled"); + url.SetFileName(strMask+"/"); + localShares.clear(); + std::string content = (params.size() > 2) ? params[2] : ""; + StringUtils::ToLower(content); + url.SetPassword(content); + std::string strMask; + if (type == AddonType::SCRIPT) + strMask = ".py"; + std::string replace; + if (CGUIDialogFileBrowser::ShowAndGetFile(url.Get(), strMask, CAddonInfo::TranslateType(type, true), replace, true, true, true)) + { + if (StringUtils::StartsWithNoCase(replace, "addons://")) + CSkinSettings::GetInstance().SetString(string, URIUtils::GetFileName(replace)); + else + CSkinSettings::GetInstance().SetString(string, replace); + } + } + else + { + if (params.size() > 2) + { + value = params[2]; + URIUtils::AddSlashAtEnd(value); + bool bIsSource; + if (CUtil::GetMatchingSource(value,localShares,bIsSource) < 0) // path is outside shares - add it as a separate one + { + CMediaSource share; + share.strName = g_localizeStrings.Get(13278); + share.strPath = value; + localShares.push_back(share); + } + } + if (CGUIDialogFileBrowser::ShowAndGetFile(localShares, strMask, g_localizeStrings.Get(1033), value)) + CSkinSettings::GetInstance().SetString(string, value); + } + + return 0; +} + +/*! \brief Set a skin image setting. + * \param params The parameters. + * \details params[0] = Name of skin setting. + * params[1] = Extra URL to allow selection from (optional). + */ +static int SetImage(const std::vector<std::string>& params) +{ + int string = CSkinSettings::GetInstance().TranslateString(params[0]); + std::string value = CSkinSettings::GetInstance().GetString(string); + VECSOURCES localShares; + CServiceBroker::GetMediaManager().GetLocalDrives(localShares); + if (params.size() > 1) + { + value = params[1]; + URIUtils::AddSlashAtEnd(value); + bool bIsSource; + if (CUtil::GetMatchingSource(value,localShares,bIsSource) < 0) // path is outside shares - add it as a separate one + { + CMediaSource share; + share.strName = g_localizeStrings.Get(13278); + share.strPath = value; + localShares.push_back(share); + } + } + if (CGUIDialogFileBrowser::ShowAndGetImage(localShares, g_localizeStrings.Get(1030), value)) + CSkinSettings::GetInstance().SetString(string, value); + + return 0; +} + +/*! \brief Set a skin color setting. + * \param params The parameters. + * \details params[0] = Name of skin setting. + * params[1] = Dialog header text. + * params[2] = Hex value of the preselected color (optional). + * params[3] = XML file containing color definitions (optional). + */ +static int SetColor(const std::vector<std::string>& params) +{ + int string = CSkinSettings::GetInstance().TranslateString(params[0]); + std::string value = CSkinSettings::GetInstance().GetString(string); + + if (value.empty() && params.size() > 2) + { + value = params[2]; + } + + CGUIDialogColorPicker* pDlgColorPicker = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogColorPicker>( + WINDOW_DIALOG_COLOR_PICKER); + pDlgColorPicker->Reset(); + pDlgColorPicker->SetHeading(CVariant{g_localizeStrings.Get(atoi(params[1].c_str()))}); + + if (params.size() > 3) + { + pDlgColorPicker->LoadColors(params[3]); + } + else + { + pDlgColorPicker->LoadColors(); + } + + pDlgColorPicker->SetSelectedColor(value); + + pDlgColorPicker->Open(); + + if (pDlgColorPicker->IsConfirmed()) + { + value = pDlgColorPicker->GetSelectedColor(); + CSkinSettings::GetInstance().SetString(string, value); + } + + return 0; +} + +/*! \brief Set a string skin setting. + * \param params The parameters. + * \details params[0] = Name of skin setting. + * params[1] = Value of skin setting (optional). + */ +static int SetString(const std::vector<std::string>& params) +{ + // break the parameter up if necessary + int string = 0; + if (params.size() > 1) + { + string = CSkinSettings::GetInstance().TranslateString(params[0]); + CSkinSettings::GetInstance().SetString(string, params[1]); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + return 0; + } + else + string = CSkinSettings::GetInstance().TranslateString(params[0]); + + std::string value = CSkinSettings::GetInstance().GetString(string); + if (CGUIKeyboardFactory::ShowAndGetInput(value, CVariant{g_localizeStrings.Get(1029)}, true)) + CSkinSettings::GetInstance().SetString(string, value); + + return 0; +} + +/*! \brief Select skin theme. + * \param params The parameters. + * \details params[0] = 0 or 1 to increase theme, -1 to decrease. + */ +static int SetTheme(const std::vector<std::string>& params) +{ + // enumerate themes + std::vector<std::string> vecTheme; + CUtil::GetSkinThemes(vecTheme); + + int iTheme = -1; + + // find current theme + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + const std::string strTheme = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKINTHEME); + if (!StringUtils::EqualsNoCase(strTheme, "SKINDEFAULT")) + { + for (size_t i=0;i<vecTheme.size();++i) + { + std::string strTmpTheme(strTheme); + URIUtils::RemoveExtension(strTmpTheme); + if (StringUtils::EqualsNoCase(vecTheme[i], strTmpTheme)) + { + iTheme=i; + break; + } + } + } + + int iParam = atoi(params[0].c_str()); + if (iParam == 0 || iParam == 1) + iTheme++; + else if (iParam == -1) + iTheme--; + if (iTheme > (int)vecTheme.size()-1) + iTheme = -1; + if (iTheme < -1) + iTheme = vecTheme.size()-1; + + std::string strSkinTheme = "SKINDEFAULT"; + if (iTheme != -1 && iTheme < (int)vecTheme.size()) + strSkinTheme = vecTheme[iTheme]; + + settings->SetString(CSettings::SETTING_LOOKANDFEEL_SKINTHEME, strSkinTheme); + // also set the default color theme + std::string colorTheme(URIUtils::ReplaceExtension(strSkinTheme, ".xml")); + if (StringUtils::EqualsNoCase(colorTheme, "Textures.xml")) + colorTheme = "defaults.xml"; + settings->SetString(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS, colorTheme); + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent<CApplicationSkinHandling>(); + appSkin->ReloadSkin(); + + return 0; +} + +/*! \brief Reset a skin setting. + * \param params The parameters. + * \details params[0] = Name of setting to reset. + */ +static int SkinReset(const std::vector<std::string>& params) +{ + CSkinSettings::GetInstance().Reset(params[0]); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return 0; +} + +/*! \brief Reset all skin settings. + * \param params (ignored) + */ +static int SkinResetAll(const std::vector<std::string>& params) +{ + CSkinSettings::GetInstance().Reset(); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return 0; +} + +/*! \brief Toggle skin debug. + * \param params (ignored) + */ +static int SkinDebug(const std::vector<std::string>& params) +{ + g_SkinInfo->ToggleDebug(); + + return 0; +} + +/*! \brief Starts a given skin timer + * \param params The parameters. + * \details params[0] = Name of the timer. + * \return -1 in case of error, 0 in case of success + */ +static int SkinTimerStart(const std::vector<std::string>& params) +{ + if (params.empty()) + { + return -1; + } + + g_SkinInfo->TimerStart(params[0]); + return 0; +} + +/*! \brief Stops a given skin timer + * \param params The parameters. + * \details params[0] = Name of the timer. + * \return -1 in case of error, 0 in case of success + */ +static int SkinTimerStop(const std::vector<std::string>& params) +{ + if (params.empty()) + { + return -1; + } + + g_SkinInfo->TimerStop(params[0]); + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_14 Skin built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`ReloadSkin(reload)`</b> +/// , +/// Reloads the current skin – useful for Skinners to use after they upload +/// modified skin files (saves power cycling) +/// @param[in] reload <b>"confirm"</b> to show a confirmation dialog (optional). +/// } +/// \table_row2_l{ +/// <b>`UnloadSkin()`</b> +/// , +/// Unloads the current skin +/// } +/// \table_row2_l{ +/// <b>`Skin.Reset(setting)`</b> +/// , +/// Resets the skin `setting`. If `setting` is a bool setting (i.e. set via +/// `SetBool` or `ToggleSetting`) then the setting is reset to false. If +/// `setting` is a string (Set via <c>SetString</c>\, <c>SetImage</c> or +/// <c>SetPath</c>) then it is set to empty. +/// @param[in] setting Name of setting to reset. +/// } +/// \table_row2_l{ +/// <b>`Skin.ResetSettings()`</b> +/// , +/// Resets all the above skin settings to their defaults (toggles all set to +/// false\, strings all set to empty.) +/// } +/// \table_row2_l{ +/// <b>`Skin.SetAddon(string\,type)`</b> +/// , +/// Pops up a select dialog and allows the user to select an add-on of the +/// given type to be used elsewhere in the skin via the info tag +/// `Skin.String(string)`. The most common types are xbmc.addon.video\, +/// xbmc.addon.audio\, xbmc.addon.image and xbmc.addon.executable. +/// @param[in] string[0] Skin setting to store result in. +/// @param[in] type[1\,...] Add-on types to allow selecting. +/// } +/// \table_row2_l{ +/// <b>`Skin.SetBool(setting[\,value])`</b> +/// \anchor Skin_SetBool, +/// Sets the skin `setting` to true\, for use with the conditional visibility +/// tags containing \link Skin_HasSetting `Skin.HasSetting(setting)`\endlink. The settings are saved +/// per-skin in settings.xml just like all the other Kodi settings. +/// @param[in] setting Name of skin setting. +/// @param[in] value Value to set ("false"\, or "true") (optional). +/// } +/// \table_row2_l{ +/// <b>`Skin.SetFile(string\,mask\,folderpath)`</b> +/// , +/// \" minus quotes. If the folderpath parameter is set the file browser will +/// start in that folder. +/// @param[in] string Name of skin setting. +/// @param[in] mask File mask or add-on type (optional). +/// @param[in] folderpath Extra URL to allow selection from or. +/// content type if mask is an addon-on type (optional). +/// } +/// \table_row2_l{ +/// <b>`Skin.SetImage(string[\,url])`</b> +/// , +/// Pops up a file browser and allows the user to select an image file to be +/// used in an image control elsewhere in the skin via the info tag +/// `Skin.String(string)`. If the value parameter is specified\, then the +/// file browser dialog does not pop up\, and the image path is set directly. +/// The path option allows you to open the file browser in the specified +/// folder. +/// @param[in] string Name of skin setting. +/// @param[in] url Extra URL to allow selection from (optional). +/// } +/// \table_row2_l{ +/// <b>`Skin.SetColor(string\,header[\,colorfile\,selectedcolor])`</b> +/// \anchor Builtin_SetColor, +/// Pops up a color selection dialog and allows the user to select a color to be +/// used to define the color of a label control or as a colordiffuse value for a texture +/// elsewhere in the skin via the info tag `Skin.String(string)`. +/// Skinners can optionally set the color that needs to be preselected in the +/// dialog by specifying the hex value of this color. +/// Also optionally\, skinners can include their own color definition file. If not specified\, +/// the default colorfile included with Kodi will be used. +/// @param[in] string Name of skin setting. +/// @param[in] string Dialog header text. +/// @param[in] string Hex value of the color to preselect (optional)\, +/// example: FF00FF00. +/// @param[in] string Filepath of the color definition file (optional). +/// <p><hr> +/// @skinning_v20 **[New builtin]** \link Builtin_SetColor `SetColor(string\,header[\,colorfile\,selectedcolor])`\endlink +/// <p> +/// } +/// \table_row2_l{ +/// <b>`Skin.SetNumeric(numeric[\,value])`</b> +/// \anchor Skin_SetNumeric, +/// Pops up a keyboard dialog and allows the user to input a numerical. +/// @param[in] numeric Name of skin setting. +/// @param[in] value Value of skin setting (optional). +/// } +/// \table_row2_l{ +/// <b>`Skin.SetPath(string[\,value])`</b> +/// , +/// Pops up a folder browser and allows the user to select a folder of images +/// to be used in a multi image control else where in the skin via the info +/// tag `Skin.String(string)`. If the value parameter is specified\, then the +/// file browser dialog does not pop up\, and the path is set directly. +/// @param[in] string Name of skin setting. +/// @param[in] value Extra URL to allow selection from (optional). +/// } +/// \table_row2_l{ +/// <b>`Skin.SetString(string[\,value])`</b> +/// \anchor Skin_SetString, +/// Pops up a keyboard dialog and allows the user to input a string which can +/// be used in a label control elsewhere in the skin via the info tag +/// \link Skin_StringValue `Skin.String(string)`\endlink. The value of the setting +/// can also be compared to another value using the info bool \link Skin_StringCompare `Skin.String(string\, value)`\endlink. +/// If the value parameter is specified\, then the +/// keyboard dialog does not pop up\, and the string is set directly. +/// @param[in] string Name of skin setting. +/// @param[in] value Value of skin setting (optional). +/// } +/// \table_row2_l{ +/// <b>`Skin.Theme(cycle)`</b> +/// \anchor Skin_CycleTheme, +/// Cycles the skin theme. Skin.theme(-1) will go backwards. +/// @param[in] cycle 0 or 1 to increase theme\, -1 to decrease. +/// } +/// \table_row2_l{ +/// <b>`Skin.ToggleDebug`</b> +/// , +/// Toggles skin debug info on/off +/// } +/// \table_row2_l{ +/// <b>`Skin.ToggleSetting(setting)`</b> +/// , +/// Toggles the skin `setting` for use with conditional visibility tags +/// containing `Skin.HasSetting(setting)`. +/// @param[in] setting Skin setting to toggle +/// } +/// \table_row2_l{ +/// <b>`Skin.TimerStart(timer)`</b> +/// \anchor Builtin_SkinStartTimer, +/// Starts the timer with name `timer` +/// @param[in] timer The name of the timer +/// <p><hr> +/// @skinning_v20 **[New builtin]** \link Builtin_SkinStartTimer `Skin.TimerStart(timer)`\endlink +/// <p> +/// } +/// \table_row2_l{ +/// <b>`Skin.TimerStop(timer)`</b> +/// \anchor Builtin_SkinStopTimer, +/// Stops the timer with name `timer` +/// @param[in] timer The name of the timer +/// <p><hr> +/// @skinning_v20 **[New builtin]** \link Builtin_SkinStopTimer `Skin.TimerStop(timer)`\endlink +/// <p> +/// } +/// \table_end +/// + +CBuiltins::CommandMap CSkinBuiltins::GetOperations() const +{ + return {{"reloadskin", {"Reload Kodi's skin", 0, ReloadSkin}}, + {"unloadskin", {"Unload Kodi's skin", 0, UnloadSkin}}, + {"skin.reset", {"Resets a skin setting to default", 1, SkinReset}}, + {"skin.resetsettings", {"Resets all skin settings", 0, SkinResetAll}}, + {"skin.setaddon", {"Prompts and set an addon", 2, SetAddon}}, + {"skin.selectbool", {"Prompts and set a skin setting", 2, SelectBool}}, + {"skin.setbool", {"Sets a skin setting on", 1, SetBool}}, + {"skin.setfile", {"Prompts and sets a file", 1, SetFile}}, + {"skin.setimage", {"Prompts and sets a skin image", 1, SetImage}}, + {"skin.setcolor", {"Prompts and sets a skin color", 1, SetColor}}, + {"skin.setnumeric", {"Prompts and sets numeric input", 1, SetNumeric}}, + {"skin.setpath", {"Prompts and sets a skin path", 1, SetPath}}, + {"skin.setstring", {"Prompts and sets skin string", 1, SetString}}, + {"skin.theme", {"Control skin theme", 1, SetTheme}}, + {"skin.toggledebug", {"Toggle skin debug", 0, SkinDebug}}, + {"skin.togglesetting", {"Toggles a skin setting on or off", 1, ToggleSetting}}, + {"skin.timerstart", {"Starts a given skin timer", 1, SkinTimerStart}}, + {"skin.timerstop", {"Stops a given skin timer", 1, SkinTimerStop}}}; +} diff --git a/xbmc/interfaces/builtins/SkinBuiltins.h b/xbmc/interfaces/builtins/SkinBuiltins.h new file mode 100644 index 0000000..48a4306 --- /dev/null +++ b/xbmc/interfaces/builtins/SkinBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing skin related built-in commands. +class CSkinBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/SystemBuiltins.cpp b/xbmc/interfaces/builtins/SystemBuiltins.cpp new file mode 100644 index 0000000..3a41a5f --- /dev/null +++ b/xbmc/interfaces/builtins/SystemBuiltins.cpp @@ -0,0 +1,267 @@ +/* + * 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 "SystemBuiltins.h" + +#include "ServiceBroker.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/StringUtils.h" + +/*! \brief Execute a system executable. + * \param params The parameters. + * \details params[0] = The path to the executable. + * + * Set the template parameter Wait to true to wait for execution exit. + */ + template<int Wait=0> +static int Exec(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_OS, Wait, -1, nullptr, params[0]); + + return 0; +} + +/*! \brief Hibernate system. + * \param params (ignored) + */ +static int Hibernate(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_HIBERNATE); + + return 0; +} + +/*! \brief Inhibit idle shutdown timer. + * \param params The parameters. + * \details params[0] = "true" to inhibit shutdown timer (optional). + */ +static int InhibitIdle(const std::vector<std::string>& params) +{ + bool inhibit = (params.size() == 1 && StringUtils::EqualsNoCase(params[0], "true")); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_INHIBITIDLESHUTDOWN, inhibit); + + return 0; +} + +/*! \brief Minimize application. + * \param params (ignored) + */ +static int Minimize(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE); + + return 0; +} + +/*! \brief Powerdown system. + * \param params (ignored) + */ +static int Powerdown(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_POWERDOWN); + + return 0; +} + +/*! \brief Quit application. + * \param params (ignored) + */ +static int Quit(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + + return 0; +} + +/*! \brief Reboot system. + * \param params (ignored) + */ +static int Reboot(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RESTART); + + return 0; +} + +/*! \brief Restart application. + * \param params (ignored) + */ +static int RestartApp(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RESTARTAPP); + + return 0; +} + +/*! \brief Activate screensaver. + * \param params (ignored) + */ +static int ActivateScreensaver(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_ACTIVATESCREENSAVER); + + return 0; +} + +/*! \brief Reset screensaver. + * \param params (ignored) + */ +static int ResetScreensaver(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RESETSCREENSAVER); + + return 0; +} + +/*! \brief Inhibit screensaver. + * \param params The parameters. + * \details params[0] = "true" to inhibit screensaver (optional). + */ +static int InhibitScreenSaver(const std::vector<std::string>& params) +{ + bool inhibit = (params.size() == 1 && StringUtils::EqualsNoCase(params[0], "true")); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_INHIBITSCREENSAVER, inhibit); + + return 0; +} + +/*! \brief Shutdown system. + * \param params (ignored) + */ +static int Shutdown(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SHUTDOWN); + + return 0; +} + +/*! \brief Suspend system. + * \param params (ignored) + */ +static int Suspend(const std::vector<std::string>& params) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SUSPEND); + + return 0; +} + + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_15 System built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`ActivateScreensaver`</b> +/// , +/// Starts the screensaver +/// } +/// \table_row2_l{ +/// <b>`InhibitScreensaver(yesNo)`</b> +/// , +/// Inhibit the screensaver +/// @param[in] yesNo value with "true" or "false" to inhibit or allow screensaver (leaving empty defaults to false) +/// } +/// \table_row2_l{ +/// <b>`Hibernate`</b> +/// , +/// Hibernate (S4) the System +/// } +/// \table_row2_l{ +/// <b>`InhibitIdleShutdown(true/false)`</b> +/// , +/// Prevent the system to shutdown on idle. +/// @param[in] value "true" to inhibit shutdown timer (optional). +/// } +/// \table_row2_l{ +/// <b>`Minimize`</b> +/// , +/// Minimizes Kodi +/// } +/// \table_row2_l{ +/// <b>`Powerdown`</b> +/// , +/// Powerdown system +/// } +/// \table_row2_l{ +/// <b>`Quit`</b> +/// , +/// Quits Kodi +/// } +/// \table_row2_l{ +/// <b>`Reboot`</b> +/// , +/// Cold reboots the system (power cycle) +/// } +/// \table_row2_l{ +/// <b>`Reset`</b> +/// , +/// Reset the system (same as reboot) +/// } +/// \table_row2_l{ +/// <b>`Restart`</b> +/// , +/// Restart the system (same as reboot) +/// } +/// \table_row2_l{ +/// <b>`RestartApp`</b> +/// , +/// Restarts Kodi (only implemented under Windows and Linux) +/// } +/// \table_row2_l{ +/// <b>`ShutDown`</b> +/// , +/// Trigger default Shutdown action defined in System Settings +/// } +/// \table_row2_l{ +/// <b>`Suspend`</b> +/// , +/// Suspends (S3 / S1 depending on bios setting) the System +/// } +/// \table_row2_l{ +/// <b>`System.Exec(exec)`</b> +/// , +/// Execute shell commands +/// @param[in] exec The path to the executable +/// } +/// \table_row2_l{ +/// <b>`System.ExecWait(exec)`</b> +/// , +/// Execute shell commands and freezes Kodi until shell is closed +/// @param[in] exec The path to the executable +/// } +/// \table_end +/// + +CBuiltins::CommandMap CSystemBuiltins::GetOperations() const +{ + return {{"activatescreensaver", {"Activate Screensaver", 0, ActivateScreensaver}}, + {"resetscreensaver", {"Reset Screensaver", 0, ResetScreensaver}}, + {"hibernate", {"Hibernates the system", 0, Hibernate}}, + {"inhibitidleshutdown", {"Inhibit idle shutdown", 0, InhibitIdle}}, + {"inhibitscreensaver", {"Inhibit Screensaver", 0, InhibitScreenSaver}}, + {"minimize", {"Minimize Kodi", 0, Minimize}}, + {"powerdown", {"Powerdown system", 0, Powerdown}}, + {"quit", {"Quit Kodi", 0, Quit}}, + {"reboot", {"Reboot the system", 0, Reboot}}, + {"reset", {"Reset the system (same as reboot)", 0, Reboot}}, + {"restart", {"Restart the system (same as reboot)", 0, Reboot}}, + {"restartapp", {"Restart Kodi", 0, RestartApp}}, + {"shutdown", {"Shutdown the system", 0, Shutdown}}, + {"suspend", {"Suspends the system", 0, Suspend}}, + {"system.exec", {"Execute shell commands", 1, Exec<0>}}, + {"system.execwait", + {"Execute shell commands and freezes Kodi until shell is closed", 1, Exec<1>}}}; +} diff --git a/xbmc/interfaces/builtins/SystemBuiltins.h b/xbmc/interfaces/builtins/SystemBuiltins.h new file mode 100644 index 0000000..ad5c16c --- /dev/null +++ b/xbmc/interfaces/builtins/SystemBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing system related built-in commands. +class CSystemBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; diff --git a/xbmc/interfaces/builtins/WeatherBuiltins.cpp b/xbmc/interfaces/builtins/WeatherBuiltins.cpp new file mode 100644 index 0000000..8769a88 --- /dev/null +++ b/xbmc/interfaces/builtins/WeatherBuiltins.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WeatherBuiltins.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" + +#include <stdlib.h> + +/*! \brief Switch to a given weather location. + * \param params The parameters. + * \details params[0] = 1, 2 or 3. + */ +static int SetLocation(const std::vector<std::string>& params) +{ + int loc = atoi(params[0].c_str()); + CGUIMessage msg(GUI_MSG_ITEM_SELECT, 0, 0, loc); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg, WINDOW_WEATHER); + + return 0; +} + +/*! \brief Switch weather location or refresh current. + * \param params (ignored) + * + * The Direction template parameter can be -1 for previous location, + * 1 for next location or 0 to refresh current location. + */ + template<int Direction> +static int SwitchLocation(const std::vector<std::string>& params) +{ + CGUIMessage msg(GUI_MSG_MOVE_OFFSET, 0, 0, Direction); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg, WINDOW_WEATHER); + + return 0; +} + +// Note: For new Texts with comma add a "\" before!!! Is used for table text. +// +/// \page page_List_of_built_in_functions +/// \section built_in_functions_16 Weather built-in's +/// +/// ----------------------------------------------------------------------------- +/// +/// \table_start +/// \table_h2_l{ +/// Function, +/// Description } +/// \table_row2_l{ +/// <b>`Weather.Refresh`</b> +/// , +/// Force weather data refresh. +/// } +/// \table_row2_l{ +/// <b>`Weather.LocationNext`</b> +/// , +/// Switch to next weather location +/// } +/// \table_row2_l{ +/// <b>`Weather.LocationPrevious`</b> +/// , +/// Switch to previous weather location +/// } +/// \table_row2_l{ +/// <b>`Weather.LocationSet`</b> +/// , +/// Switch to given weather location (parameter can be 1-3). +/// @param[in] parameter 1-3 +/// } +/// \table_end +/// + +CBuiltins::CommandMap CWeatherBuiltins::GetOperations() const +{ + return { + {"weather.refresh", {"Force weather data refresh", 0, SwitchLocation<0>}}, + {"weather.locationnext", {"Switch to next weather location", 0, SwitchLocation<1>}}, + {"weather.locationprevious", {"Switch to previous weather location", 0, SwitchLocation<-1>}}, + {"weather.locationset", {"Switch to given weather location (parameter can be 1-3)", 1, SetLocation}} + }; +} diff --git a/xbmc/interfaces/builtins/WeatherBuiltins.h b/xbmc/interfaces/builtins/WeatherBuiltins.h new file mode 100644 index 0000000..6827cfc --- /dev/null +++ b/xbmc/interfaces/builtins/WeatherBuiltins.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Builtins.h" + +//! \brief Class providing weather related built-in commands. +class CWeatherBuiltins +{ +public: + //! \brief Returns the map of operations. + CBuiltins::CommandMap GetOperations() const; +}; |